/, '')
+ results[:messageText].gsub!(/(\.)? /, '. ')
results[:messageText].strip!
end
@@ -316,7 +401,7 @@ def recurring_parse(data)
end
def commit(params, use_profile_api = false)
- post(post_data(params,use_profile_api),use_profile_api)
+ post(post_data(params, use_profile_api), use_profile_api)
end
def recurring_commit(params)
@@ -327,10 +412,10 @@ def post(data, use_profile_api=nil)
response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : self.live_url), data))
response[:customer_vault_id] = response[:customerCode] if response[:customerCode]
build_response(success?(response), message_from(response), response,
- :test => test? || response[:authCode] == "TEST",
+ :test => test? || response[:authCode] == 'TEST',
:authorization => authorization_from(response),
:cvv_result => CVD_CODES[response[:cvdId]],
- :avs_result => { :code => (AVS_CODES.include? response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] }
+ :avs_result => { :code => AVS_CODES.include?(response[:avsId]) ? AVS_CODES[response[:avsId]] : response[:avsId] }
)
end
@@ -351,10 +436,6 @@ def recurring_message_from(response)
response[:message]
end
- def success?(response)
- response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1'
- end
-
def recurring_success?(response)
response[:code] == '1'
end
@@ -363,7 +444,7 @@ def add_source(post, source)
if source.is_a?(String) or source.is_a?(Integer)
post[:customerCode] = source
else
- card_brand(source) == "check" ? add_check(post, source) : add_credit_card(post, source)
+ card_brand(source) == 'check' ? add_check(post, source) : add_credit_card(post, source)
end
end
@@ -380,14 +461,13 @@ def post_data(params, use_profile_api)
params[:username] = @options[:user] if @options[:user]
params[:password] = @options[:password] if @options[:password]
params[:merchant_id] = @options[:login]
+ params[:passcode] = @options[:api_key]
end
params[:vbvEnabled] = '0'
params[:scEnabled] = '0'
- params.reject{|k, v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ params.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
-
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/beanstream_interac.rb b/lib/active_merchant/billing/gateways/beanstream_interac.rb
index 2caa3b7a761..37ca7595a31 100644
--- a/lib/active_merchant/billing/gateways/beanstream_interac.rb
+++ b/lib/active_merchant/billing/gateways/beanstream_interac.rb
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/beanstream/beanstream_core'
+require 'active_merchant/billing/gateways/beanstream/beanstream_core'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -7,21 +7,21 @@ def redirect
params['pageContents']
end
end
-
+
class BeanstreamInteracGateway < Gateway
include BeanstreamCore
# Confirm a transaction posted back from the bank to Beanstream.
# Confirming a transaction does not require any credentials,
# and in an application with many merchants sharing a funded
- # URL the application may not yet know which merchant the
+ # URL the application may not yet know which merchant the
# post back is for until the response of the confirmation is
# received, which contains the order number.
def self.confirm(transaction)
gateway = new(:login => '')
gateway.confirm(transaction)
end
-
+
def purchase(money, options = {})
post = {}
add_amount(post, money)
@@ -31,24 +31,27 @@ def purchase(money, options = {})
add_transaction_type(post, :purchase)
commit(post)
end
-
+
+ def success?(response)
+ response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1'
+ end
+
# Confirm a transaction posted back from the bank to Beanstream.
def confirm(transaction)
post(transaction)
end
-
+
private
-
+
def add_interac_details(post, options)
address = options[:billing_address] || options[:address] || {}
post[:trnCardOwner] = address[:name]
post[:paymentMethod] = 'IO'
end
-
+
def build_response(*args)
BeanstreamInteracResponse.new(*args)
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/blue_pay.rb b/lib/active_merchant/billing/gateways/blue_pay.rb
index 1a0b999c7f5..e275b649dfe 100644
--- a/lib/active_merchant/billing/gateways/blue_pay.rb
+++ b/lib/active_merchant/billing/gateways/blue_pay.rb
@@ -1,501 +1,522 @@
-require 'digest/md5'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class BluePayGateway < Gateway
- class_attribute :rebilling_url, :ignore_http_status
-
- self.live_url = 'https://secure.bluepay.com/interfaces/bp20post'
- self.rebilling_url = 'https://secure.bluepay.com/interfaces/bp20rebadmin'
-
- self.ignore_http_status = true
-
- CARD_CODE_ERRORS = %w( N S )
- AVS_ERRORS = %w( A E N R W Z )
- AVS_REASON_CODES = %w(27 45)
-
- FRAUD_REVIEW_STATUSES = %w( E 0 )
-
- FIELD_MAP = {
- 'TRANS_ID' => :transaction_id,
- 'STATUS' => :response_code,
- 'AVS' => :avs_result_code,
- 'CVV2'=> :card_code,
- 'AUTH_CODE' => :authorization,
- 'MESSAGE' => :message,
- 'REBID' => :rebid,
- 'TRANS_TYPE' => :trans_type,
- 'PAYMENT_ACCOUNT_MASK' => :acct_mask,
- 'CARD_TYPE' => :card_type,
- }
-
- REBILL_FIELD_MAP = {
- 'REBILL_ID' => :rebill_id,
- 'ACCOUNT_ID'=> :account_id,
- 'USER_ID' => :user_id,
- 'TEMPLATE_ID' => :template_id,
- 'STATUS' => :status,
- 'CREATION_DATE' => :creation_date,
- 'NEXT_DATE' => :next_date,
- 'LAST_DATE' => :last_date,
- 'SCHED_EXPR' => :schedule,
- 'CYCLES_REMAIN' => :cycles_remain,
- 'REB_AMOUNT' => :rebill_amount,
- 'NEXT_AMOUNT' => :next_amount,
- 'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc.
- }
-
- self.supported_countries = ['US']
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
- self.homepage_url = 'http://www.bluepay.com/'
- self.display_name = 'BluePay'
- self.money_format = :dollars
-
- # Creates a new BluepayGateway
- #
- # The gateway requires that a valid Account ID and Secret Key be passed
- # in the +options+ hash.
- #
- # ==== Options
- #
- # * :account_id -- The BluePay gateway Account ID (REQUIRED)
- # * :secret_key -- The BluePay gateway Secret Key (REQUIRED)
- # * :test -- set to true for TEST mode or false for LIVE mode
- def initialize(options = {})
- requires!(options, :login, :password)
- super
- end
-
- # Performs an authorization, which reserves the funds on the customer's credit card. This does not actually take funds from the customer
- # This is referred to an AUTH transaction in BluePay
- #
- # ==== Parameters
- #
- # * money -- The amount to be authorized as an Integer value in cents.
- # * payment_object -- This can either be one of three things:
- # A CreditCard object,
- # A Check object,
- # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
- # * options -- A hash of optional parameters.
- def authorize(money, payment_object, options = {})
- post = {}
- add_payment_method(post, payment_object)
- add_invoice(post, options)
- add_address(post, options)
- add_customer_data(post, options)
- add_rebill(post, options) if options[:rebill]
- add_duplicate_override(post, options)
- post[:TRANS_TYPE] = 'AUTH'
- commit('AUTH_ONLY', money, post)
- end
-
- # Perform a purchase, which is essentially an authorization and capture in a single operation.
- # This is referred to a SALE transaction in BluePay
- #
- # ==== Parameters
- #
- # * money -- The amount to be purchased as an Integer value in cents.
- # * payment_object -- This can either be one of three things:
- # A CreditCard object,
- # A Check object,
- # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
- # * options -- A hash of optional parameters.,
- def purchase(money, payment_object, options = {})
- post = {}
- add_payment_method(post, payment_object)
- add_invoice(post, options)
- add_address(post, options)
- add_customer_data(post, options)
- add_rebill(post, options) if options[:rebill]
- add_duplicate_override(post, options)
- post[:TRANS_TYPE] = 'SALE'
- commit('AUTH_CAPTURE', money, post)
- end
-
- # Captures the funds from an authorize transaction.
- # This is referred to a CAPTURE transaction in BluePay
- #
- # ==== Parameters
- #
- # * money -- The amount to be captured as an Integer value in cents.
- # * identification -- The Master ID, or token, returned from the previous authorize transaction.
- def capture(money, identification, options = {})
- post = {}
- add_address(post, options)
- add_customer_data(post, options)
- post[:MASTER_ID] = identification
- post[:TRANS_TYPE] = 'CAPTURE'
- commit('PRIOR_AUTH_CAPTURE', money, post)
- end
-
- # Void a previous transaction
- # This is referred to a VOID transaction in BluePay
- #
- # ==== Parameters
- #
- # * identification - The Master ID, or token, returned from a previous authorize transaction.
- def void(identification, options = {})
- post = {}
- post[:MASTER_ID] = identification
- post[:TRANS_TYPE] = 'VOID'
- commit('VOID', nil, post)
- end
-
- # Performs a credit.
- #
- # This transaction indicates that money should flow from the merchant to the customer.
- #
- # ==== Parameters
- #
- # * money -- The amount to be credited to the customer as an Integer value in cents.
- # * payment_object -- This can either be one of three things:
- # A CreditCard object,
- # A Check object,
- # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
- # If the payment_object is a token, then the transaction type will reverse a previous capture or purchase transaction, returning the funds to the customer. If the amount is nil, a full credit will be processed. This is referred to a REFUND transaction in BluePay.
- # If the payment_object is either a CreditCard or Check object, then the transaction type will be an unmatched credit placing funds in the specified account. This is referred to a CREDIT transaction in BluePay.
- # * options -- A hash of parameters.
- def refund(money, identification, options = {})
- if(identification && !identification.kind_of?(String))
- deprecated "refund should only be used to refund a referenced transaction"
- return credit(money, identification, options)
- end
-
- post = {}
- post[:PAYMENT_ACCOUNT] = ''
- post[:MASTER_ID] = identification
- post[:TRANS_TYPE] = 'REFUND'
- post[:NAME1] = (options[:first_name] ? options[:first_name] : "")
- post[:NAME2] = options[:last_name] if options[:last_name]
- post[:ZIP] = options[:zip] if options[:zip]
- add_invoice(post, options)
- add_address(post, options)
- add_customer_data(post, options)
- commit('CREDIT', money, post)
- end
-
- def credit(money, payment_object, options = {})
- if(payment_object && payment_object.kind_of?(String))
- deprecated "credit should only be used to credit a payment method"
- return refund(money, payment_object, options)
- end
-
- post = {}
- post[:PAYMENT_ACCOUNT] = ''
- add_payment_method(post, payment_object)
- post[:TRANS_TYPE] = 'CREDIT'
-
- post[:NAME1] = (options[:first_name] ? options[:first_name] : "")
- post[:NAME2] = options[:last_name] if options[:last_name]
- post[:ZIP] = options[:zip] if options[:zip]
- add_invoice(post, options)
- add_address(post, options)
- add_customer_data(post, options)
- commit('CREDIT', money, post)
- end
-
- # Create a new recurring payment.
- #
- # ==== Parameters
- #
- # * money -- The amount to charge the customer at the time of the recurring payment setup, in cents. Set to zero if you do not want the customer to be charged at this time.
- # * payment_object -- This can either be one of three things:
- # A CreditCard object,
- # A Check object,
- # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
- # * options -- A hash of optional parameters.,
-
- # ==== Options
- #
- # * :rebill_start_date is a string that tells the gateway when to start the rebill. (REQUIRED)
- # Has two valid formats:
- # "YYYY-MM-DD HH:MM:SS" Hours, minutes, and seconds are optional.
- # "XX UNITS" Relative date as explained below. Marked from the time of the
- # transaction (i.e.: 10 DAYS, 1 MONTH, 1 YEAR)
- # * :rebill_expression is the period of time in-between rebillings. (REQUIRED)
- # It uses the same "XX UNITS" format as rebill_start_date, explained above.
- # Optional parameters include:
- # * rebill_cycles: Number of times to rebill. Don't send or set to nil for infinite rebillings (or
- # until canceled).
- # * rebill_amount: Amount to rebill. Defaults to amount of transaction for rebillings.
- #
- # For example, to charge the customer $19.95 now and then charge $39.95 in 60 days every 3 months for 5 times, the options hash would be as follows:
- # :rebill_start_date => '60 DAYS',
- # :rebill_expression => '3 MONTHS',
- # :rebill_cycles => '5',
- # :rebill_amount => '39.95'
- # A money object of 1995 cents would be passed into the 'money' parameter.
- def recurring(money, payment_object, options = {})
- requires!(options, :rebill_start_date, :rebill_expression)
- options[:rebill] = true
- if money
- purchase(money, payment_object, options)
- else
- authorize(money, payment_object, options)
- end
- end
-
- # View a recurring payment
- #
- # This will pull data associated with a current recurring billing
- #
- # ==== Parameters
- #
- # * rebill_id -- A string containing the rebill_id of the recurring billing that is already active (REQUIRED)
- def status_recurring(rebill_id)
- post = {}
- requires!(rebill_id)
- post[:REBILL_ID] = rebill_id
- post[:TRANS_TYPE] = 'GET'
- commit('rebill', 'nil', post)
- end
-
- # Update a recurring payment's details.
- #
- # This transaction updates an existing recurring billing
- #
- # ==== Options
- #
- # * :rebill_id -- The 12 digit rebill ID used to update a particular rebilling cycle. (REQUIRED)
- # * :rebill_amount -- A string containing the new rebilling amount.
- # * :rebill_next_date -- A string containing the new rebilling next date.
- # * :rebill_expression -- A string containing the new rebilling expression.
- # * :rebill_cycles -- A string containing the new rebilling cycles.
- # * :rebill_next_amount -- A string containing the next rebilling amount to charge the customer. This ONLY affects the next scheduled charge; all other rebillings will continue at the regular (rebill_amount) amount.
- # Take a look above at the recurring_payment method for similar examples on how to use.
- def update_recurring(options = {})
- post = {}
- requires!(options, :rebill_id)
- post[:REBILL_ID] = options[:rebill_id]
- post[:TRANS_TYPE] = 'SET'
- post[:REB_AMOUNT] = amount(options[:rebill_amount]) if options[:rebill_amount]
- post[:NEXT_DATE] = options[:rebill_next_date]
- post[:REB_EXPR] = options[:rebill_expression]
- post[:REB_CYCLES] = options[:rebill_cycles]
- post[:NEXT_AMOUNT] = options[:rebill_next_amount]
- commit('rebill', 'nil', post)
- end
-
- # Cancel a recurring payment.
- #
- # This transaction cancels an existing recurring billing.
- #
- # ==== Parameters
- #
- # * rebill_id -- A string containing the rebill_id of the recurring billing that you wish to cancel/stop (REQUIRED)
- def cancel_recurring(rebill_id)
- post = {}
- requires!(rebill_id)
- post[:REBILL_ID] = rebill_id
- post[:TRANS_TYPE] = 'SET'
- post[:STATUS] = 'stopped'
- commit('rebill', 'nil', post)
- end
-
- private
-
- def commit(action, money, fields)
- fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill')
- fields[:MODE] = (test? ? 'TEST' : 'LIVE')
- fields[:ACCOUNT_ID] = @options[:login]
-
- if action == 'rebill'
- url = rebilling_url
- fields[:TAMPER_PROOF_SEAL] = calc_rebill_tps(fields)
- else
- url = live_url
- fields[:TAMPER_PROOF_SEAL] = calc_tps(amount(money), fields)
- end
- parse(ssl_post(url, post_data(action, fields)))
- end
-
- def parse_recurring(response_fields, opts={}) # expected status?
- parsed = {}
- response_fields.each do |k,v|
- mapped_key = REBILL_FIELD_MAP.include?(k) ? REBILL_FIELD_MAP[k] : k
- parsed[mapped_key] = v
- end
-
- success = parsed[:status] != 'error'
- message = parsed[:status]
-
- Response.new(success, message, parsed,
- :test => test?,
- :authorization => parsed[:rebill_id])
- end
-
- def parse(body)
- # The bp20api has max one value per form field.
- response_fields = Hash[CGI::parse(body).map{|k,v| [k.upcase,v.first]}]
-
- if response_fields.include? "REBILL_ID"
- return parse_recurring(response_fields)
- end
-
- parsed = {}
- response_fields.each do |k,v|
- mapped_key = FIELD_MAP.include?(k) ? FIELD_MAP[k] : k
- parsed[mapped_key] = v
- end
-
- # normalize message
- message = message_from(parsed)
- success = parsed[:response_code] == '1'
- Response.new(success, message, parsed,
- :test => test?,
- :authorization => (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]),
- :fraud_review => FRAUD_REVIEW_STATUSES.include?(parsed[:response_code]),
- :avs_result => { :code => parsed[:avs_result_code] },
- :cvv_result => parsed[:card_code]
- )
- end
-
- def message_from(parsed)
- message = parsed[:message]
- if(parsed[:response_code].to_i == 2)
- if CARD_CODE_ERRORS.include?(parsed[:card_code])
- message = CVVResult.messages[parsed[:card_code]]
- elsif AVS_ERRORS.include?(parsed[:avs_result_code])
- message = AVSResult.messages[ parsed[:avs_result_code] ]
- else
- message = message.chomp('.')
- end
- elsif message == "Missing ACCOUNT_ID"
- message = "The merchant login ID or password is invalid"
- elsif message =~ /Approved/
- message = "This transaction has been approved"
- elsif message =~ /Expired/
- message = "The credit card has expired"
- end
- message
- end
-
- def add_invoice(post, options)
- post[:ORDER_ID] = options[:order_id]
- post[:INVOICE_ID] = options[:invoice]
- post[:invoice_num] = options[:order_id]
- post[:MEMO] = options[:description]
- post[:description] = options[:description]
- end
-
- def add_payment_method(post, payment_object)
- post[:MASTER_ID] = ''
- case payment_object
- when String
- post[:MASTER_ID] = payment_object
- when Check
- add_check(post, payment_object)
- else
- add_creditcard(post, payment_object)
- end
- end
-
- def add_creditcard(post, creditcard)
- post[:PAYMENT_TYPE] = 'CREDIT'
- post[:PAYMENT_ACCOUNT] = creditcard.number
- post[:CARD_CVV2] = creditcard.verification_value
- post[:CARD_EXPIRE] = expdate(creditcard)
- post[:NAME1] = creditcard.first_name
- post[:NAME2] = creditcard.last_name
- end
-
- CHECK_ACCOUNT_TYPES = {
- "checking" => "C",
- "savings" => "S"
- }
-
- def add_check(post, check)
- post[:PAYMENT_TYPE] = 'ACH'
- post[:PAYMENT_ACCOUNT] = [CHECK_ACCOUNT_TYPES[check.account_type], check.routing_number, check.account_number].join(":")
- post[:NAME1] = check.first_name
- post[:NAME2] = check.last_name
- end
-
- def add_customer_data(post, options)
- post[:EMAIL] = options[:email]
- post[:CUSTOM_ID] = options[:customer]
- end
-
- def add_duplicate_override(post, options)
- post[:DUPLICATE_OVERRIDE] = options[:duplicate_override]
- end
-
- def add_address(post, options)
- if address = (options[:shipping_address] || options[:billing_address] || options[:address])
- post[:ADDR1] = address[:address1]
- post[:ADDR2] = address[:address2]
- post[:COMPANY_NAME] = address[:company]
- post[:PHONE] = address[:phone]
- post[:CITY] = address[:city]
- post[:STATE] = (address[:state].blank? ? 'n/a' : address[:state])
- post[:ZIP] = address[:zip]
- post[:COUNTRY] = address[:country]
- end
- end
-
- def add_rebill(post, options)
- post[:DO_REBILL] = '1'
- post[:REB_AMOUNT] = amount(options[:rebill_amount])
- post[:REB_FIRST_DATE] = options[:rebill_start_date]
- post[:REB_EXPR] = options[:rebill_expression]
- post[:REB_CYCLES] = options[:rebill_cycles]
- end
-
- def post_data(action, parameters = {})
- post = {}
- post[:version] = '1'
- post[:login] = ''
- post[:tran_key] = ''
- post[:relay_response] = "FALSE"
- post[:type] = action
- post[:delim_data] = "TRUE"
- post[:delim_char] = ","
- post[:encap_char] = "$"
- post[:card_num] = '4111111111111111'
- post[:exp_date] = '1212'
- post[:solution_ID] = application_id if(application_id && application_id != "ActiveMerchant")
- post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
- end
-
- def expdate(creditcard)
- year = format(creditcard.year, :two_digits)
- month = format(creditcard.month, :two_digits)
-
- "#{month}#{year}"
- end
-
- def calc_tps(amount, post)
- post[:NAME1] ||= ''
- Digest::MD5.hexdigest(
- [
- @options[:password],
- @options[:login],
- post[:TRANS_TYPE],
- amount,
- post[:MASTER_ID],
- post[:NAME1],
- post[:PAYMENT_ACCOUNT]
- ].join("")
- )
- end
-
- def calc_rebill_tps(post)
- Digest::MD5.hexdigest(
- [
- @options[:password],
- @options[:login],
- post[:TRANS_TYPE],
- post[:REBILL_ID]
- ].join("")
- )
- end
-
- def handle_response(response)
- if ignore_http_status || (200...300).include?(response.code.to_i)
- return response.body
- end
- raise ResponseError.new(response)
- end
- end
- end
-end
+require 'digest/md5'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class BluePayGateway < Gateway
+ class_attribute :rebilling_url, :ignore_http_status
+
+ self.live_url = 'https://secure.bluepay.com/interfaces/bp20post'
+ self.rebilling_url = 'https://secure.bluepay.com/interfaces/bp20rebadmin'
+
+ self.ignore_http_status = true
+
+ CARD_CODE_ERRORS = %w( N S )
+ AVS_ERRORS = %w( A E N R W Z )
+ AVS_REASON_CODES = %w(27 45)
+
+ FIELD_MAP = {
+ 'TRANS_ID' => :transaction_id,
+ 'STATUS' => :response_code,
+ 'AVS' => :avs_result_code,
+ 'CVV2'=> :card_code,
+ 'AUTH_CODE' => :authorization,
+ 'MESSAGE' => :message,
+ 'REBID' => :rebid,
+ 'TRANS_TYPE' => :trans_type,
+ 'PAYMENT_ACCOUNT_MASK' => :acct_mask,
+ 'CARD_TYPE' => :card_type
+ }
+
+ REBILL_FIELD_MAP = {
+ 'REBILL_ID' => :rebill_id,
+ 'ACCOUNT_ID'=> :account_id,
+ 'USER_ID' => :user_id,
+ 'TEMPLATE_ID' => :template_id,
+ 'STATUS' => :status,
+ 'CREATION_DATE' => :creation_date,
+ 'NEXT_DATE' => :next_date,
+ 'LAST_DATE' => :last_date,
+ 'SCHED_EXPR' => :schedule,
+ 'CYCLES_REMAIN' => :cycles_remain,
+ 'REB_AMOUNT' => :rebill_amount,
+ 'NEXT_AMOUNT' => :next_amount,
+ 'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc.
+ 'CUST_TOKEN' => :cust_token
+ }
+
+ self.supported_countries = ['US', 'CA']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+ self.homepage_url = 'http://www.bluepay.com/'
+ self.display_name = 'BluePay'
+ self.money_format = :dollars
+
+ # Creates a new BluepayGateway
+ #
+ # The gateway requires that a valid Account ID and Secret Key be passed
+ # in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * :account_id -- The BluePay gateway Account ID (REQUIRED)
+ # * :secret_key -- The BluePay gateway Secret Key (REQUIRED)
+ # * :test -- set to true for TEST mode or false for LIVE mode
+ def initialize(options = {})
+ requires!(options, :login, :password)
+ super
+ end
+
+ # Performs an authorization, which reserves the funds on the customer's credit card. This does not actually take funds from the customer
+ # This is referred to an AUTH transaction in BluePay
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be authorized as an Integer value in cents.
+ # * payment_object -- This can either be one of three things:
+ # A CreditCard object,
+ # A Check object,
+ # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
+ # * options -- A hash of optional parameters.
+ def authorize(money, payment_object, options = {})
+ post = {}
+ add_payment_method(post, payment_object)
+ add_invoice(post, options)
+ add_address(post, options)
+ add_customer_data(post, options)
+ add_rebill(post, options) if options[:rebill]
+ add_duplicate_override(post, options)
+ post[:TRANS_TYPE] = 'AUTH'
+ commit('AUTH_ONLY', money, post, options)
+ end
+
+ # Perform a purchase, which is essentially an authorization and capture in a single operation.
+ # This is referred to a SALE transaction in BluePay
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be purchased as an Integer value in cents.
+ # * payment_object -- This can either be one of three things:
+ # A CreditCard object,
+ # A Check object,
+ # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
+ # * options -- A hash of optional parameters.,
+ def purchase(money, payment_object, options = {})
+ post = {}
+ add_payment_method(post, payment_object)
+ add_invoice(post, options)
+ add_address(post, options)
+ add_customer_data(post, options)
+ add_rebill(post, options) if options[:rebill]
+ add_duplicate_override(post, options)
+ post[:TRANS_TYPE] = 'SALE'
+ commit('AUTH_CAPTURE', money, post, options)
+ end
+
+ # Captures the funds from an authorize transaction.
+ # This is referred to a CAPTURE transaction in BluePay
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be captured as an Integer value in cents.
+ # * identification -- The Master ID, or token, returned from the previous authorize transaction.
+ def capture(money, identification, options = {})
+ post = {}
+ add_address(post, options)
+ add_customer_data(post, options)
+ post[:MASTER_ID] = identification
+ post[:TRANS_TYPE] = 'CAPTURE'
+ commit('PRIOR_AUTH_CAPTURE', money, post, options)
+ end
+
+ # Void a previous transaction
+ # This is referred to a VOID transaction in BluePay
+ #
+ # ==== Parameters
+ #
+ # * identification - The Master ID, or token, returned from a previous authorize transaction.
+ def void(identification, options = {})
+ post = {}
+ post[:MASTER_ID] = identification
+ post[:TRANS_TYPE] = 'VOID'
+ commit('VOID', nil, post, options)
+ end
+
+ # Performs a credit.
+ #
+ # This transaction indicates that money should flow from the merchant to the customer.
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be credited to the customer as an Integer value in cents.
+ # * payment_object -- This can either be one of three things:
+ # A CreditCard object,
+ # A Check object,
+ # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
+ # If the payment_object is a token, then the transaction type will reverse a previous capture or purchase transaction, returning the funds to the customer. If the amount is nil, a full credit will be processed. This is referred to a REFUND transaction in BluePay.
+ # If the payment_object is either a CreditCard or Check object, then the transaction type will be an unmatched credit placing funds in the specified account. This is referred to a CREDIT transaction in BluePay.
+ # * options -- A hash of parameters.
+ def refund(money, identification, options = {})
+ if(identification && !identification.kind_of?(String))
+ ActiveMerchant.deprecated 'refund should only be used to refund a referenced transaction'
+ return credit(money, identification, options)
+ end
+
+ post = {}
+ post[:PAYMENT_ACCOUNT] = ''
+ post[:MASTER_ID] = identification
+ post[:TRANS_TYPE] = 'REFUND'
+ post[:NAME1] = options[:first_name] || ''
+ post[:NAME2] = options[:last_name] if options[:last_name]
+ post[:ZIP] = options[:zip] if options[:zip]
+ add_invoice(post, options)
+ add_address(post, options)
+ add_customer_data(post, options)
+ commit('CREDIT', money, post, options)
+ end
+
+ def credit(money, payment_object, options = {})
+ if payment_object&.kind_of?(String)
+ ActiveMerchant.deprecated 'credit should only be used to credit a payment method'
+ return refund(money, payment_object, options)
+ end
+
+ post = {}
+ post[:PAYMENT_ACCOUNT] = ''
+ add_payment_method(post, payment_object)
+ post[:TRANS_TYPE] = 'CREDIT'
+
+ post[:NAME1] = options[:first_name] || ''
+ post[:NAME2] = options[:last_name] if options[:last_name]
+ post[:ZIP] = options[:zip] if options[:zip]
+ add_invoice(post, options)
+ add_address(post, options)
+ add_customer_data(post, options)
+ commit('CREDIT', money, post, options)
+ end
+
+ # Create a new recurring payment.
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to charge the customer at the time of the recurring payment setup, in cents. Set to zero if you do not want the customer to be charged at this time.
+ # * payment_object -- This can either be one of three things:
+ # A CreditCard object,
+ # A Check object,
+ # or a token. The token is called the Master ID. This is a unique transaction ID returned from a previous transaction. This token associates all the stored information for a previous transaction.
+ # * options -- A hash of optional parameters.,
+
+ # ==== Options
+ #
+ # * :rebill_start_date is a string that tells the gateway when to start the rebill. (REQUIRED)
+ # Has two valid formats:
+ # "YYYY-MM-DD HH:MM:SS" Hours, minutes, and seconds are optional.
+ # "XX UNITS" Relative date as explained below. Marked from the time of the
+ # transaction (i.e.: 10 DAYS, 1 MONTH, 1 YEAR)
+ # * :rebill_expression is the period of time in-between rebillings. (REQUIRED)
+ # It uses the same "XX UNITS" format as rebill_start_date, explained above.
+ # Optional parameters include:
+ # * rebill_cycles: Number of times to rebill. Don't send or set to nil for infinite rebillings (or
+ # until canceled).
+ # * rebill_amount: Amount to rebill. Defaults to amount of transaction for rebillings.
+ #
+ # For example, to charge the customer $19.95 now and then charge $39.95 in 60 days every 3 months for 5 times, the options hash would be as follows:
+ # :rebill_start_date => '60 DAYS',
+ # :rebill_expression => '3 MONTHS',
+ # :rebill_cycles => '5',
+ # :rebill_amount => '39.95'
+ # A money object of 1995 cents would be passed into the 'money' parameter.
+ def recurring(money, payment_object, options = {})
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
+ requires!(options, :rebill_start_date, :rebill_expression)
+ options[:rebill] = true
+ if money
+ purchase(money, payment_object, options)
+ else
+ authorize(money, payment_object, options)
+ end
+ end
+
+ # View a recurring payment
+ #
+ # This will pull data associated with a current recurring billing
+ #
+ # ==== Parameters
+ #
+ # * rebill_id -- A string containing the rebill_id of the recurring billing that is already active (REQUIRED)
+ def status_recurring(rebill_id)
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
+ post = {}
+ requires!(rebill_id)
+ post[:REBILL_ID] = rebill_id
+ post[:TRANS_TYPE] = 'GET'
+ commit('rebill', 'nil', post)
+ end
+
+ # Update a recurring payment's details.
+ #
+ # This transaction updates an existing recurring billing
+ #
+ # ==== Options
+ #
+ # * :rebill_id -- The 12 digit rebill ID used to update a particular rebilling cycle. (REQUIRED)
+ # * :rebill_amount -- A string containing the new rebilling amount.
+ # * :rebill_next_date -- A string containing the new rebilling next date.
+ # * :rebill_expression -- A string containing the new rebilling expression.
+ # * :rebill_cycles -- A string containing the new rebilling cycles.
+ # * :rebill_next_amount -- A string containing the next rebilling amount to charge the customer. This ONLY affects the next scheduled charge; all other rebillings will continue at the regular (rebill_amount) amount.
+ # Take a look above at the recurring_payment method for similar examples on how to use.
+ def update_recurring(options = {})
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
+ post = {}
+ requires!(options, :rebill_id)
+ post[:REBILL_ID] = options[:rebill_id]
+ post[:TRANS_TYPE] = 'SET'
+ post[:REB_AMOUNT] = amount(options[:rebill_amount]) if options[:rebill_amount]
+ post[:NEXT_DATE] = options[:rebill_next_date]
+ post[:REB_EXPR] = options[:rebill_expression]
+ post[:REB_CYCLES] = options[:rebill_cycles]
+ post[:NEXT_AMOUNT] = options[:rebill_next_amount]
+ commit('rebill', 'nil', post)
+ end
+
+ # Cancel a recurring payment.
+ #
+ # This transaction cancels an existing recurring billing.
+ #
+ # ==== Parameters
+ #
+ # * rebill_id -- A string containing the rebill_id of the recurring billing that you wish to cancel/stop (REQUIRED)
+ def cancel_recurring(rebill_id)
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
+ post = {}
+ requires!(rebill_id)
+ post[:REBILL_ID] = rebill_id
+ post[:TRANS_TYPE] = 'SET'
+ post[:STATUS] = 'stopped'
+ commit('rebill', 'nil', post)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?card_num=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?CARD_CVV2=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?PAYMENT_ACCOUNT=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?TAMPER_PROOF_SEAL=)[^&"]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def commit(action, money, fields, options = {})
+ fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill')
+ fields[:MODE] = (test? ? 'TEST' : 'LIVE')
+ fields[:ACCOUNT_ID] = @options[:login]
+ fields[:CUSTOMER_IP] = options[:ip] if options[:ip]
+
+ if action == 'rebill'
+ url = rebilling_url
+ fields[:TAMPER_PROOF_SEAL] = calc_rebill_tps(fields)
+ else
+ url = live_url
+ fields[:TAMPER_PROOF_SEAL] = calc_tps(amount(money), fields)
+ end
+ parse(ssl_post(url, post_data(action, fields)))
+ end
+
+ def parse_recurring(response_fields, opts={}) # expected status?
+ parsed = {}
+ response_fields.each do |k, v|
+ mapped_key = REBILL_FIELD_MAP.include?(k) ? REBILL_FIELD_MAP[k] : k
+ parsed[mapped_key] = v
+ end
+
+ success = parsed[:status] != 'error'
+ message = parsed[:status]
+
+ Response.new(success, message, parsed,
+ :test => test?,
+ :authorization => parsed[:rebill_id])
+ end
+
+ def parse(body)
+ # The bp20api has max one value per form field.
+ response_fields = Hash[CGI::parse(body).map { |k, v| [k.upcase, v.first] }]
+
+ if response_fields.include? 'REBILL_ID'
+ return parse_recurring(response_fields)
+ end
+
+ parsed = {}
+ response_fields.each do |k, v|
+ mapped_key = FIELD_MAP.include?(k) ? FIELD_MAP[k] : k
+ parsed[mapped_key] = v
+ end
+
+ # normalize message
+ message = message_from(parsed)
+ success = parsed[:response_code] == '1'
+ Response.new(success, message, parsed,
+ :test => test?,
+ :authorization => (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]),
+ :avs_result => { :code => parsed[:avs_result_code] },
+ :cvv_result => parsed[:card_code]
+ )
+ end
+
+ def message_from(parsed)
+ message = parsed[:message]
+ if(parsed[:response_code].to_i == 2)
+ if CARD_CODE_ERRORS.include?(parsed[:card_code])
+ message = CVVResult.messages[parsed[:card_code]]
+ elsif AVS_ERRORS.include?(parsed[:avs_result_code])
+ message = AVSResult.messages[parsed[:avs_result_code]]
+ else
+ message = message.chomp('.')
+ end
+ elsif message == 'Missing ACCOUNT_ID'
+ message = 'The merchant login ID or password is invalid'
+ elsif message =~ /Approved/
+ message = 'This transaction has been approved'
+ elsif message =~ /Expired/
+ message = 'The credit card has expired'
+ end
+ message
+ end
+
+ def add_invoice(post, options)
+ post[:ORDER_ID] = options[:order_id]
+ post[:INVOICE_ID] = options[:invoice]
+ post[:invoice_num] = options[:order_id]
+ post[:MEMO] = options[:description]
+ post[:description] = options[:description]
+ end
+
+ def add_payment_method(post, payment_object)
+ post[:MASTER_ID] = ''
+ case payment_object
+ when String
+ post[:MASTER_ID] = payment_object
+ when Check
+ add_check(post, payment_object)
+ else
+ add_creditcard(post, payment_object)
+ end
+ end
+
+ def add_creditcard(post, creditcard)
+ post[:PAYMENT_TYPE] = 'CREDIT'
+ post[:PAYMENT_ACCOUNT] = creditcard.number
+ post[:CARD_CVV2] = creditcard.verification_value
+ post[:CARD_EXPIRE] = expdate(creditcard)
+ post[:NAME1] = creditcard.first_name
+ post[:NAME2] = creditcard.last_name
+ end
+
+ CHECK_ACCOUNT_TYPES = {
+ 'checking' => 'C',
+ 'savings' => 'S'
+ }
+
+ def add_check(post, check)
+ post[:PAYMENT_TYPE] = 'ACH'
+ post[:PAYMENT_ACCOUNT] = [CHECK_ACCOUNT_TYPES[check.account_type], check.routing_number, check.account_number].join(':')
+ post[:NAME1] = check.first_name
+ post[:NAME2] = check.last_name
+ end
+
+ def add_customer_data(post, options)
+ post[:EMAIL] = options[:email]
+ post[:CUSTOM_ID] = options[:customer]
+ post[:CUSTOM_ID2] = options[:custom_id2]
+ end
+
+ def add_duplicate_override(post, options)
+ post[:DUPLICATE_OVERRIDE] = options[:duplicate_override]
+ end
+
+ def add_address(post, options)
+ if address = (options[:shipping_address] || options[:billing_address] || options[:address])
+ post[:ADDR1] = address[:address1]
+ post[:ADDR2] = address[:address2]
+ post[:COMPANY_NAME] = address[:company]
+ post[:PHONE] = address[:phone]
+ post[:CITY] = address[:city]
+ post[:STATE] = (address[:state].blank? ? 'n/a' : address[:state])
+ post[:ZIP] = address[:zip]
+ post[:COUNTRY] = address[:country]
+ end
+ end
+
+ def add_rebill(post, options)
+ post[:DO_REBILL] = '1'
+ post[:REB_AMOUNT] = amount(options[:rebill_amount])
+ post[:REB_FIRST_DATE] = options[:rebill_start_date]
+ post[:REB_EXPR] = options[:rebill_expression]
+ post[:REB_CYCLES] = options[:rebill_cycles]
+ end
+
+ def post_data(action, parameters = {})
+ post = {}
+ post[:version] = '1'
+ post[:login] = ''
+ post[:tran_key] = ''
+ post[:relay_response] = 'FALSE'
+ post[:type] = action
+ post[:delim_data] = 'TRUE'
+ post[:delim_char] = ','
+ post[:encap_char] = '$'
+ post[:card_num] = '4111111111111111'
+ post[:exp_date] = '1212'
+ post[:solution_ID] = application_id if application_id
+ post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ def expdate(creditcard)
+ year = format(creditcard.year, :two_digits)
+ month = format(creditcard.month, :two_digits)
+
+ "#{month}#{year}"
+ end
+
+ def calc_tps(amount, post)
+ post[:NAME1] ||= ''
+ Digest::MD5.hexdigest(
+ [
+ @options[:password],
+ @options[:login],
+ post[:TRANS_TYPE],
+ amount,
+ post[:MASTER_ID],
+ post[:NAME1],
+ post[:PAYMENT_ACCOUNT]
+ ].join('')
+ )
+ end
+
+ def calc_rebill_tps(post)
+ Digest::MD5.hexdigest(
+ [
+ @options[:password],
+ @options[:login],
+ post[:TRANS_TYPE],
+ post[:REBILL_ID]
+ ].join('')
+ )
+ end
+
+ def handle_response(response)
+ if ignore_http_status || (200...300).cover?(response.code.to_i)
+ return response.body
+ end
+ raise ResponseError.new(response)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/blue_snap.rb b/lib/active_merchant/billing/gateways/blue_snap.rb
new file mode 100644
index 00000000000..b344749b2d4
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/blue_snap.rb
@@ -0,0 +1,522 @@
+require 'nokogiri'
+
+module ActiveMerchant
+ module Billing
+ class BlueSnapGateway < Gateway
+ self.test_url = 'https://sandbox.bluesnap.com/services/2'
+ self.live_url = 'https://ws.bluesnap.com/services/2'
+ self.supported_countries = %w(US CA GB AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE AR BO BR BZ CL CO CR DO EC GF GP GT HN HT MF MQ MX NI PA PE PR PY SV UY VE)
+
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja, :cabal]
+
+ self.homepage_url = 'https://home.bluesnap.com/'
+ self.display_name = 'BlueSnap'
+
+ TRANSACTIONS = {
+ purchase: 'AUTH_CAPTURE',
+ authorize: 'AUTH_ONLY',
+ capture: 'CAPTURE',
+ void: 'AUTH_REVERSAL',
+ refund: 'REFUND'
+ }
+
+ CVC_CODE_TRANSLATOR = {
+ 'MA' => 'M',
+ 'NC' => 'U',
+ 'ND' => 'P',
+ 'NM' => 'N',
+ 'NP' => 'S'
+ }
+
+ AVS_CODE_TRANSLATOR = {
+ 'line1: U, zip: U, name: U' => 'I',
+ 'line1: U, zip: U, name: M' => 'I',
+ 'line1: U, zip: U, name: N' => 'I',
+ 'line1: U, zip: M, name: U' => 'P',
+ 'line1: U, zip: M, name: M' => 'P',
+ 'line1: U, zip: M, name: N' => 'F',
+ 'line1: U, zip: N, name: U' => 'O',
+ 'line1: U, zip: N, name: M' => 'O',
+ 'line1: U, zip: N, name: N' => 'O',
+ 'line1: M, zip: U, name: U' => 'B',
+ 'line1: M, zip: U, name: M' => 'B',
+ 'line1: M, zip: U, name: N' => 'T',
+ 'line1: M, zip: M, name: U' => 'M',
+ 'line1: M, zip: M, name: M' => 'V',
+ 'line1: M, zip: M, name: N' => 'H',
+ 'line1: M, zip: N, name: U' => 'A',
+ 'line1: M, zip: N, name: M' => 'O',
+ 'line1: M, zip: N, name: N' => 'A',
+ 'line1: N, zip: U, name: U' => 'C',
+ 'line1: N, zip: U, name: M' => 'C',
+ 'line1: N, zip: U, name: N' => 'C',
+ 'line1: N, zip: M, name: U' => 'W',
+ 'line1: N, zip: M, name: M' => 'L',
+ 'line1: N, zip: M, name: N' => 'W',
+ 'line1: N, zip: N, name: U' => 'N',
+ 'line1: N, zip: N, name: M' => 'K',
+ 'line1: N, zip: N, name: N' => 'N',
+ }
+
+ BANK_ACCOUNT_TYPE_MAPPING = {
+ 'personal_checking' => 'CONSUMER_CHECKING',
+ 'personal_savings' => 'CONSUMER_SAVINGS',
+ 'business_checking' => 'CORPORATE_CHECKING',
+ 'business_savings' => 'CORPORATE_SAVINGS'
+ }
+
+ STATE_CODE_COUNTRIES = %w(US CA)
+
+ def initialize(options={})
+ requires!(options, :api_username, :api_password)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ payment_method_details = PaymentMethodDetails.new(payment_method)
+
+ commit(:purchase, :post, payment_method_details) do |doc|
+ if payment_method_details.alt_transaction?
+ add_alt_transaction_purchase(doc, money, payment_method_details, options)
+ else
+ add_auth_purchase(doc, money, payment_method, options)
+ end
+ end
+ end
+
+ def authorize(money, payment_method, options={})
+ commit(:authorize) do |doc|
+ add_auth_purchase(doc, money, payment_method, options)
+ end
+ end
+
+ def capture(money, authorization, options={})
+ commit(:capture, :put) do |doc|
+ add_authorization(doc, authorization)
+ add_order(doc, options)
+ add_amount(doc, money, options) if options[:include_capture_amount] == true
+ end
+ end
+
+ def refund(money, authorization, options={})
+ commit(:refund, :put) do |doc|
+ add_authorization(doc, authorization)
+ add_amount(doc, money, options)
+ add_order(doc, options)
+ end
+ end
+
+ def void(authorization, options={})
+ commit(:void, :put) do |doc|
+ add_authorization(doc, authorization)
+ add_order(doc, options)
+ end
+ end
+
+ def verify(payment_method, options={})
+ authorize(0, payment_method, options)
+ end
+
+ def store(payment_method, options = {})
+ payment_method_details = PaymentMethodDetails.new(payment_method)
+
+ commit(:store, :post, payment_method_details) do |doc|
+ add_personal_info(doc, payment_method, options)
+ add_echeck_company(doc, payment_method) if payment_method_details.check?
+ doc.send('payment-sources') do
+ payment_method_details.check? ? store_echeck(doc, payment_method) : store_credit_card(doc, payment_method)
+ end
+ add_order(doc, options)
+ end
+ end
+
+ def store_credit_card(doc, payment_method)
+ doc.send('credit-card-info') do
+ add_credit_card(doc, payment_method)
+ end
+ end
+
+ def store_echeck(doc, payment_method)
+ doc.send('ecp-info') do
+ doc.send('ecp') do
+ add_echeck(doc, payment_method)
+ end
+ end
+ end
+
+ def verify_credentials
+ begin
+ ssl_get(url.to_s, headers)
+ rescue ResponseError => e
+ return false if e.response.code.to_i == 401
+ end
+
+ true
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r((<(?:public-)?account-number>).+((?:public-)?account-number>)), '\1[FILTERED]\2').
+ gsub(%r((<(?:public-)?routing-number>).+((?:public-)?routing-number>)), '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_auth_purchase(doc, money, payment_method, options)
+ doc.send('recurring-transaction', options[:recurring] ? 'RECURRING' : 'ECOMMERCE')
+ add_order(doc, options)
+ doc.send('store-card', options[:store_card] || false)
+ add_amount(doc, money, options)
+ add_fraud_info(doc, options)
+
+ if payment_method.is_a?(String)
+ doc.send('vaulted-shopper-id', payment_method)
+ else
+ doc.send('card-holder-info') do
+ add_personal_info(doc, payment_method, options)
+ end
+ add_credit_card(doc, payment_method)
+ end
+ end
+
+ def add_amount(doc, money, options)
+ doc.amount(amount(money))
+ doc.currency(options[:currency] || currency(money))
+ end
+
+ def add_personal_info(doc, payment_method, options)
+ doc.send('first-name', payment_method.first_name)
+ doc.send('last-name', payment_method.last_name)
+ doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number]
+ doc.email(options[:email]) if options[:email]
+ add_address(doc, options)
+ end
+
+ def add_credit_card(doc, card)
+ doc.send('credit-card') do
+ doc.send('card-number', card.number)
+ doc.send('security-code', card.verification_value)
+ doc.send('expiration-month', card.month)
+ doc.send('expiration-year', card.year)
+ end
+ end
+
+ def add_description(doc, description)
+ doc.send('transaction-meta-data') do
+ doc.send('meta-data') do
+ doc.send('meta-key', 'description')
+ doc.send('meta-value', truncate(description, 500))
+ doc.send('meta-description', 'Description')
+ end
+ end
+ end
+
+ def add_order(doc, options)
+ doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
+ doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
+ add_description(doc, options[:description]) if options[:description]
+ add_3ds(doc, options[:three_d_secure]) if options[:three_d_secure]
+ add_level_3_data(doc, options)
+ end
+
+ def add_address(doc, options)
+ address = options[:billing_address]
+ return unless address
+
+ doc.country(address[:country]) if address[:country]
+ doc.state(address[:state]) if address[:state] && STATE_CODE_COUNTRIES.include?(address[:country])
+ doc.address(address[:address]) if address[:address]
+ doc.city(address[:city]) if address[:city]
+ doc.zip(address[:zip]) if address[:zip]
+ end
+
+ def add_3ds(doc, three_d_secure_options)
+ eci = three_d_secure_options[:eci]
+ cavv = three_d_secure_options[:cavv]
+ xid = three_d_secure_options[:xid]
+ ds_transaction_id = three_d_secure_options[:ds_transaction_id]
+ version = three_d_secure_options[:version]
+
+ doc.send('three-d-secure') do
+ doc.eci(eci) if eci
+ doc.cavv(cavv) if cavv
+ doc.xid(xid) if xid
+ doc.send('three-d-secure-version', version) if version
+ doc.send('ds-transaction-id', ds_transaction_id) if ds_transaction_id
+ end
+ end
+
+ def add_level_3_data(doc, options)
+ return unless options[:customer_reference_number]
+ doc.send('level-3-data') do
+ send_when_present(doc, :customer_reference_number, options)
+ send_when_present(doc, :sales_tax_amount, options)
+ send_when_present(doc, :freight_amount, options)
+ send_when_present(doc, :duty_amount, options)
+ send_when_present(doc, :destination_zip_code, options)
+ send_when_present(doc, :destination_country_code, options)
+ send_when_present(doc, :ship_from_zip_code, options)
+ send_when_present(doc, :discount_amount, options)
+ send_when_present(doc, :tax_amount, options)
+ send_when_present(doc, :tax_rate, options)
+ add_level_3_data_items(doc, options[:level_3_data_items]) if options[:level_3_data_items]
+ end
+ end
+
+ def send_when_present(doc, options_key, options, xml_element_name = nil)
+ return unless options[options_key]
+ xml_element_name ||= options_key.to_s
+
+ doc.send(xml_element_name.dasherize, options[options_key])
+ end
+
+ def add_level_3_data_items(doc, items)
+ items.each do |item|
+ doc.send('level-3-data-item') do
+ item.each do |key, value|
+ key = key.to_s.dasherize
+ doc.send(key, value)
+ end
+ end
+ end
+ end
+
+ def add_authorization(doc, authorization)
+ doc.send('transaction-id', authorization)
+ end
+
+ def add_fraud_info(doc, options)
+ doc.send('transaction-fraud-info') do
+ doc.send('shopper-ip-address', options[:ip]) if options[:ip]
+ end
+ end
+
+ def add_alt_transaction_purchase(doc, money, payment_method_details, options)
+ doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
+ doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
+ add_amount(doc, money, options)
+
+ vaulted_shopper_id = payment_method_details.vaulted_shopper_id
+ doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id
+
+ if payment_method_details.check?
+ add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?)
+ end
+
+ add_fraud_info(doc, options)
+ add_description(doc, options)
+ end
+
+ def add_echeck_transaction(doc, check, options, vaulted_shopper)
+ unless vaulted_shopper
+ doc.send('payer-info') do
+ add_personal_info(doc, check, options)
+ add_echeck_company(doc, check)
+ end
+ end
+
+ doc.send('ecp-transaction') do
+ add_echeck(doc, check) unless vaulted_shopper
+ end
+
+ doc.send('authorized-by-shopper', options[:authorized_by_shopper])
+ end
+
+ def add_echeck_company(doc, check)
+ doc.send('company-name', truncate(check.name, 50)) if check.account_holder_type = 'business'
+ end
+
+ def add_echeck(doc, check)
+ doc.send('account-number', check.account_number)
+ doc.send('routing-number', check.routing_number)
+ doc.send('account-type', BANK_ACCOUNT_TYPE_MAPPING["#{check.account_holder_type}_#{check.account_type}"])
+ end
+
+ def parse(response)
+ return bad_authentication_response if response.code.to_i == 401
+ return forbidden_response(response.body) if response.code.to_i == 403
+
+ parsed = {}
+ doc = Nokogiri::XML(response.body)
+ doc.root.xpath('*').each do |node|
+ if node.elements.empty?
+ parsed[node.name.downcase] = node.text
+ else
+ node.elements.each do |childnode|
+ parse_element(parsed, childnode)
+ end
+ end
+ end
+
+ parsed['content-location-header'] = response['content-location']
+ parsed
+ end
+
+ def parse_element(parsed, node)
+ if !node.elements.empty?
+ node.elements.each { |e| parse_element(parsed, e) }
+ else
+ parsed[node.name.downcase] = node.text
+ end
+ end
+
+ def api_request(action, request, verb, payment_method_details)
+ ssl_request(verb, url(action, payment_method_details), request, headers)
+ rescue ResponseError => e
+ e.response
+ end
+
+ def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new())
+ request = build_xml_request(action, payment_method_details) { |doc| yield(doc) }
+ response = api_request(action, request, verb, payment_method_details)
+ parsed = parse(response)
+
+ succeeded = success_from(action, response)
+ Response.new(
+ succeeded,
+ message_from(succeeded, parsed),
+ parsed,
+ authorization: authorization_from(action, parsed, payment_method_details),
+ avs_result: avs_result(parsed),
+ cvv_result: cvv_result(parsed),
+ error_code: error_code_from(parsed),
+ test: test?
+ )
+ end
+
+ def url(action = nil, payment_method_details = PaymentMethodDetails.new())
+ base = test? ? test_url : live_url
+ resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url
+ "#{base}/#{resource}"
+ end
+
+ def cvv_result(parsed)
+ CVVResult.new(CVC_CODE_TRANSLATOR[parsed['cvv-response-code']])
+ end
+
+ def avs_result(parsed)
+ AVSResult.new(code: AVS_CODE_TRANSLATOR[avs_lookup_key(parsed)])
+ end
+
+ def avs_lookup_key(p)
+ "line1: #{p['avs-response-code-address']}, zip: #{p['avs-response-code-zip']}, name: #{p['avs-response-code-name']}"
+ end
+
+ def success_from(action, response)
+ (200...300).cover?(response.code.to_i)
+ end
+
+ def message_from(succeeded, parsed_response)
+ return 'Success' if succeeded
+ parsed_response['description']
+ end
+
+ def authorization_from(action, parsed_response, payment_method_details)
+ action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id']
+ end
+
+ def vaulted_shopper_id(parsed_response, payment_method_details)
+ return nil unless parsed_response['content-location-header']
+ vaulted_shopper_id = parsed_response['content-location-header'].split('/').last
+ vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction?
+ vaulted_shopper_id
+ end
+
+ def error_code_from(parsed_response)
+ parsed_response['code']
+ end
+
+ def root_attributes
+ {
+ xmlns: 'http://ws.plimus.com'
+ }
+ end
+
+ def root_element(action, payment_method_details)
+ action == :store ? 'vaulted-shopper' : payment_method_details.root_element
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/xml',
+ 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip),
+ }
+ end
+
+ def build_xml_request(action, payment_method_details)
+ builder = Nokogiri::XML::Builder.new
+ builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc|
+ doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction?
+ yield(doc)
+ end
+ builder.doc.root.to_xml
+ end
+
+ def handle_response(response)
+ case response.code.to_i
+ when 200...300
+ response
+ else
+ raise ResponseError.new(response)
+ end
+ end
+
+ def bad_authentication_response
+ { 'description' => 'Unable to authenticate. Please check your credentials.' }
+ end
+
+ def forbidden_response(body)
+ { 'description' => body }
+ end
+ end
+
+ class PaymentMethodDetails
+ attr_reader :payment_method, :vaulted_shopper_id, :payment_method_type
+
+ def initialize(payment_method = nil)
+ @payment_method = payment_method
+ @payment_method_type = nil
+ parse(payment_method)
+ end
+
+ def check?
+ @payment_method.is_a?(Check) || @payment_method_type == 'check'
+ end
+
+ def alt_transaction?
+ check?
+ end
+
+ def root_element
+ alt_transaction? ? 'alt-transaction' : 'card-transaction'
+ end
+
+ def resource_url
+ alt_transaction? ? 'alt-transactions' : 'transactions'
+ end
+
+ private
+
+ def parse(payment_method)
+ return unless payment_method
+
+ if payment_method.is_a?(String)
+ @vaulted_shopper_id, payment_method_type = payment_method.split('|')
+ @payment_method_type = payment_method_type if payment_method_type.present?
+ elsif payment_method.is_a?(Check)
+ @payment_method_type = payment_method.type
+ else
+ @payment_method_type = 'credit_card'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/bogus.rb b/lib/active_merchant/billing/gateways/bogus.rb
index 3a23de6e8b0..8cafd0eeba5 100644
--- a/lib/active_merchant/billing/gateways/bogus.rb
+++ b/lib/active_merchant/billing/gateways/bogus.rb
@@ -4,70 +4,54 @@ module Billing #:nodoc:
class BogusGateway < Gateway
AUTHORIZATION = '53433'
- SUCCESS_MESSAGE = "Bogus Gateway: Forced success"
- FAILURE_MESSAGE = "Bogus Gateway: Forced failure"
- ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error"
- CREDIT_ERROR_MESSAGE = "Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error"
- UNSTORE_ERROR_MESSAGE = "Bogus Gateway: Use trans_id ending in 1 for success, 2 for exception and anything else for error"
- CAPTURE_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success"
- VOID_ERROR_MESSAGE = "Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success"
- REFUND_ERROR_MESSAGE = "Bogus Gateway: Use trans_id number ending in 1 for exception, 2 for error and anything else for success"
-
- self.supported_countries = ['US']
+ AUTHORIZATION_EMV_SUCCESS = '8A023030'
+ AUTHORIZATION_EMV_DECLINE = '8A023035'
+
+ SUCCESS_MESSAGE = 'Bogus Gateway: Forced success'
+ FAILURE_MESSAGE = 'Bogus Gateway: Forced failure'
+ NUMBER_ERROR_MESSAGE = 'Bogus Gateway: Use CreditCard number ending in 1 for success, 2 for exception and anything else for error'
+ AMOUNT_ERROR_MESSAGE = 'Bogus Gateway: Use amount ending in 00 for success, 05 for failure and anything else for exception'
+ UNSTORE_ERROR_MESSAGE = 'Bogus Gateway: Use trans_id ending in 1 for success, 2 for exception and anything else for error'
+ CAPTURE_ERROR_MESSAGE = 'Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success'
+ VOID_ERROR_MESSAGE = 'Bogus Gateway: Use authorization number ending in 1 for exception, 2 for error and anything else for success'
+ REFUND_ERROR_MESSAGE = 'Bogus Gateway: Use trans_id number ending in 1 for exception, 2 for error and anything else for success'
+ CHECK_ERROR_MESSAGE = 'Bogus Gateway: Use bank account number ending in 1 for success, 2 for exception and anything else for error'
+
+ self.supported_countries = []
self.supported_cardtypes = [:bogus]
self.homepage_url = 'http://example.com'
self.display_name = 'Bogus'
- def authorize(money, credit_card_or_reference, options = {})
- money = amount(money)
- case normalize(credit_card_or_reference)
- when /1$/
- Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION )
- when /2$/
- Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true)
+ def authorize(money, paysource, options = {})
+ if paysource.respond_to?(:emv?) && paysource.emv?
+ authorize_emv(money, paysource, options)
else
- raise Error, ERROR_MESSAGE
+ authorize_swipe(money, paysource, options)
end
end
- def purchase(money, credit_card_or_reference, options = {})
- money = amount(money)
- case normalize(credit_card_or_reference)
- when /1$/, AUTHORIZATION
- Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION)
- when /2$/
- Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true)
+ def purchase(money, paysource, options = {})
+ if paysource.respond_to?(:emv?) && paysource.emv?
+ purchase_emv(money, paysource, options)
else
- raise Error, ERROR_MESSAGE
+ purchase_swipe(money, paysource, options)
end
end
- def recurring(money, credit_card_or_reference, options = {})
- money = amount(money)
- case normalize(credit_card_or_reference)
- when /1$/
- Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true)
- when /2$/
- Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE },:test => true)
- else
- raise Error, ERROR_MESSAGE
- end
- end
-
- def credit(money, credit_card_or_reference, options = {})
- if credit_card_or_reference.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
- return refund(money, credit_card_or_reference, options)
+ def credit(money, paysource, options = {})
+ if paysource.is_a?(String)
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
+ return refund(money, paysource, options)
end
money = amount(money)
- case normalize(credit_card_or_reference)
+ case normalize(paysource)
when /1$/
- Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true )
+ Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true)
when /2$/
- Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true)
+ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
else
- raise Error, CREDIT_ERROR_MESSAGE
+ raise Error, error_message(paysource)
end
end
@@ -77,7 +61,7 @@ def refund(money, reference, options = {})
when /1$/
raise Error, REFUND_ERROR_MESSAGE
when /2$/
- Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true)
+ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
else
Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true)
end
@@ -89,7 +73,7 @@ def capture(money, reference, options = {})
when /1$/
raise Error, CAPTURE_ERROR_MESSAGE
when /2$/
- Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true)
+ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
else
Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true)
end
@@ -100,20 +84,20 @@ def void(reference, options = {})
when /1$/
raise Error, VOID_ERROR_MESSAGE
when /2$/
- Response.new(false, FAILURE_MESSAGE, {:authorization => reference, :error => FAILURE_MESSAGE }, :test => true)
+ Response.new(false, FAILURE_MESSAGE, {:authorization => reference, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
else
Response.new(true, SUCCESS_MESSAGE, {:authorization => reference}, :test => true)
end
end
- def store(credit_card_or_reference, options = {})
- case normalize(credit_card_or_reference)
+ def store(paysource, options = {})
+ case normalize(paysource)
when /1$/
Response.new(true, SUCCESS_MESSAGE, {:billingid => '1'}, :test => true, :authorization => AUTHORIZATION)
when /2$/
- Response.new(false, FAILURE_MESSAGE, {:billingid => nil, :error => FAILURE_MESSAGE }, :test => true)
+ Response.new(false, FAILURE_MESSAGE, {:billingid => nil, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
else
- raise Error, ERROR_MESSAGE
+ raise Error, error_message(paysource)
end
end
@@ -122,7 +106,7 @@ def unstore(reference, options = {})
when /1$/
Response.new(true, SUCCESS_MESSAGE, {}, :test => true)
when /2$/
- Response.new(false, FAILURE_MESSAGE, {:error => FAILURE_MESSAGE },:test => true)
+ Response.new(false, FAILURE_MESSAGE, {:error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
else
raise Error, UNSTORE_ERROR_MESSAGE
end
@@ -130,11 +114,71 @@ def unstore(reference, options = {})
private
- def normalize(credit_card_or_reference)
- if credit_card_or_reference.respond_to?(:number)
- credit_card_or_reference.number
+ def authorize_emv(money, paysource, options = {})
+ money = amount(money)
+ case money
+ when /00$/
+ Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION, :emv_authorization => AUTHORIZATION_EMV_SUCCESS)
+ when /05$/
+ Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error], :emv_authorization => AUTHORIZATION_EMV_DECLINE)
+ else
+ raise Error, error_message(paysource)
+ end
+ end
+
+ def authorize_swipe(money, paysource, options = {})
+ money = amount(money)
+ case normalize(paysource)
+ when /1$/, AUTHORIZATION
+ Response.new(true, SUCCESS_MESSAGE, {:authorized_amount => money}, :test => true, :authorization => AUTHORIZATION)
+ when /2$/
+ Response.new(false, FAILURE_MESSAGE, {:authorized_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
+ else
+ raise Error, error_message(paysource)
+ end
+ end
+
+ def purchase_emv(money, paysource, options = {})
+ money = amount(money)
+ case money
+ when /00$/
+ Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION, :emv_authorization => AUTHORIZATION_EMV_SUCCESS)
+ when /05$/
+ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error], :emv_authorization => AUTHORIZATION_EMV_DECLINE)
else
- credit_card_or_reference.to_s
+ raise Error, error_message(paysource)
+ end
+ end
+
+ def purchase_swipe(money, paysource, options = {})
+ money = amount(money)
+ case normalize(paysource)
+ when /1$/, AUTHORIZATION
+ Response.new(true, SUCCESS_MESSAGE, {:paid_amount => money}, :test => true, :authorization => AUTHORIZATION)
+ when /2$/
+ Response.new(false, FAILURE_MESSAGE, {:paid_amount => money, :error => FAILURE_MESSAGE }, :test => true, :error_code => STANDARD_ERROR_CODE[:processing_error])
+ else
+ raise Error, error_message(paysource)
+ end
+ end
+
+ def normalize(paysource)
+ if paysource.respond_to?(:account_number) && (paysource.try(:number).blank? || paysource.number.blank?)
+ paysource.account_number
+ elsif paysource.respond_to?(:number)
+ paysource.number
+ else
+ paysource.to_s
+ end
+ end
+
+ def error_message(paysource)
+ if paysource.respond_to?(:emv?) && paysource.emv?
+ AMOUNT_ERROR_MESSAGE
+ elsif paysource.respond_to?(:account_number)
+ CHECK_ERROR_MESSAGE
+ elsif paysource.respond_to?(:number)
+ NUMBER_ERROR_MESSAGE
end
end
end
diff --git a/lib/active_merchant/billing/gateways/borgun.rb b/lib/active_merchant/billing/gateways/borgun.rb
new file mode 100644
index 00000000000..b949144fcfb
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/borgun.rb
@@ -0,0 +1,220 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class BorgunGateway < Gateway
+ self.display_name = 'Borgun'
+ self.homepage_url = 'http://www.borgun.com'
+
+ self.test_url = 'https://gatewaytest.borgun.is/ws/Heimir.pub.ws:Authorization'
+ self.live_url = 'https://gateway01.borgun.is/ws/Heimir.pub.ws:Authorization'
+
+ self.supported_countries = ['IS', 'GB', 'HU', 'CZ', 'DE', 'DK', 'SE' ]
+ self.default_currency = 'ISK'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb]
+
+ self.homepage_url = 'https://www.borgun.is/'
+
+ def initialize(options={})
+ requires!(options, :processor, :merchant_id, :username, :password)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+ post[:TransType] = '1'
+ add_invoice(post, money, options)
+ add_payment_method(post, payment)
+ commit('sale', post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ post[:TransType] = '5'
+ add_invoice(post, money, options)
+ add_payment_method(post, payment)
+ commit('authonly', post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ post[:TransType] = '1'
+ add_invoice(post, money, options)
+ add_reference(post, authorization)
+ commit('capture', post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ post[:TransType] = '3'
+ add_invoice(post, money, options)
+ add_reference(post, authorization)
+ commit('refund', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ # TransType, TrAmount, and currency must match original values from auth or purchase.
+ _, _, _, _, _, transtype, tramount, currency = split_authorization(authorization)
+ post[:TransType] = transtype
+ options[:currency] = options[:currency] || CURRENCY_CODES.key(currency)
+ add_invoice(post, tramount.to_i, options)
+ add_reference(post, authorization)
+ commit('void', post)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.gsub(%r((<PAN>)[^&]*(</PAN>))i, '\1[FILTERED]\2').
+ gsub(%r((<CVC2>)[^&]*(</CVC2>))i, '\1[FILTERED]\2').
+ gsub(%r(((?:\r\n)?Authorization: Basic )[^\r\n]+(\r\n)?), '\1[FILTERED]\2')
+ end
+
+ private
+
+ CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") }
+ CURRENCY_CODES['ISK'] = '352'
+ CURRENCY_CODES['EUR'] = '978'
+ CURRENCY_CODES['USD'] = '840'
+
+ def add_invoice(post, money, options)
+ post[:TrAmount] = amount(money)
+ post[:TrCurrency] = CURRENCY_CODES[options[:currency] || currency(money)]
+ post[:TerminalID] = options[:terminal_id] || '1'
+ end
+
+ def add_payment_method(post, payment_method)
+ post[:PAN] = payment_method.number
+ post[:ExpDate] = format(payment_method.year, :two_digits) + format(payment_method.month, :two_digits)
+ post[:CVC2] = payment_method.verification_value
+ post[:DateAndTime] = Time.now.strftime('%y%m%d%H%M%S')
+ post[:RRN] = 'AMRCNT' + six_random_digits
+ end
+
+ def add_reference(post, authorization)
+ dateandtime, _batch, transaction, rrn, authcode, _, _, _ = split_authorization(authorization)
+ post[:DateAndTime] = dateandtime
+ post[:Transaction] = transaction
+ post[:RRN] = rrn
+ post[:AuthCode] = authcode
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(CGI.unescapeHTML(xml))
+ body = doc.xpath('//getAuthorizationReply')
+ body = doc.xpath('//cancelAuthorizationReply') if body.length == 0
+ body.children.each do |node|
+ if node.text?
+ next
+ elsif node.elements.size == 0
+ response[node.name.downcase.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name.downcase}_#{childnode.name.downcase}"
+ response[name.to_sym] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ def commit(action, post)
+ post[:Version] = '1000'
+ post[:Processor] = @options[:processor]
+ post[:MerchantID] = @options[:merchant_id]
+
+ request = build_request(action, post)
+ raw = ssl_post(url(action), request, headers)
+ pairs = parse(raw)
+ success = success_from(pairs)
+
+ Response.new(
+ success,
+ message_from(success, pairs),
+ pairs,
+ authorization: authorization_from(pairs),
+ test: test?
+ )
+ end
+
+ def success_from(response)
+ (response[:actioncode] == '000')
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response[:message] || "Error with ActionCode=#{response[:actioncode]}"
+ end
+ end
+
+ def authorization_from(response)
+ [
+ response[:dateandtime],
+ response[:batch],
+ response[:transaction],
+ response[:rrn],
+ response[:authcode],
+ response[:transtype],
+ response[:tramount],
+ response[:trcurrency]
+ ].join('|')
+ end
+
+ def split_authorization(authorization)
+ dateandtime, batch, transaction, rrn, authcode, transtype, tramount, currency = authorization.split('|')
+ [dateandtime, batch, transaction, rrn, authcode, transtype, tramount, currency]
+ end
+
+ def headers
+ {
+ 'Authorization' => 'Basic ' + Base64.strict_encode64(@options[:username].to_s + ':' + @options[:password].to_s),
+ }
+ end
+
+ def build_request(action, post)
+ mode = action == 'void' ? 'cancel' : 'get'
+ xml = Builder::XmlMarkup.new :indent => 18
+ xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
+ xml.tag!("#{mode}Authorization") do
+ post.each do |field, value|
+ xml.tag!(field, value)
+ end
+ end
+ inner = CGI.escapeHTML(xml.target!)
+ envelope(mode).sub(/{{ :body }}/, inner)
+ end
+
+ def envelope(mode)
+ <<-EOS
+
+
+
+
+ <#{mode}AuthReqXml>
+ {{ :body }}
+ #{mode}AuthReqXml>
+
+
+
+ EOS
+ end
+
+ def url(action)
+ (test? ? test_url : live_url)
+ end
+
+ def six_random_digits
+ (0...6).map { rand(48..57).chr }.join
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/bpoint.rb b/lib/active_merchant/billing/gateways/bpoint.rb
new file mode 100644
index 00000000000..320e024a03b
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/bpoint.rb
@@ -0,0 +1,277 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class BpointGateway < Gateway
+ self.live_url = 'https://www.bpoint.com.au/evolve/service_1_4_4.asmx'
+
+ self.supported_countries = ['AU']
+ self.default_currency = 'AUD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
+
+ self.homepage_url = 'https://www.bpoint.com.au/bpoint'
+ self.display_name = 'BPoint'
+
+ def initialize(options={})
+ requires!(options, :username, :password, :merchant_number)
+ super
+ end
+
+ def store(credit_card, options={})
+ options[:crn1] ||= 'DEFAULT'
+ request_body = soap_request do |xml|
+ add_token(xml, credit_card, options)
+ end
+ commit(request_body)
+ end
+
+ def purchase(amount, credit_card, options={})
+ request_body = soap_request do |xml|
+ process_payment(xml) do |payment_xml|
+ add_purchase(payment_xml, amount, credit_card, options)
+ end
+ end
+ commit(request_body)
+ end
+
+ def authorize(amount, credit_card, options={})
+ request_body = soap_request do |xml|
+ process_payment(xml) do |payment_xml|
+ add_authorize(payment_xml, amount, credit_card, options)
+ end
+ end
+ commit(request_body)
+ end
+
+ def capture(amount, authorization, options={})
+ request_body = soap_request do |xml|
+ process_payment(xml) do |payment_xml|
+ add_capture(payment_xml, amount, authorization, options)
+ end
+ end
+ commit(request_body)
+ end
+
+ def refund(amount, authorization, options={})
+ request_body = soap_request do |xml|
+ process_payment(xml) do |payment_xml|
+ add_refund(payment_xml, amount, authorization, options)
+ end
+ end
+ commit(request_body)
+ end
+
+ def void(amount, authorization, options={})
+ request_body = soap_request do |xml|
+ process_payment(xml) do |payment_xml|
+ add_void(payment_xml, amount, authorization, options)
+ end
+ end
+ commit(request_body)
+ 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(100, 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 soap_request
+ Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml|
+ xml.send('soap12:Envelope', soap_envelope_attributes) {
+ xml.send('soap12:Body') {
+ yield(xml) if block_given?
+ }
+ }
+ end.to_xml
+ end
+
+ def soap_envelope_attributes
+ { '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' }
+ end
+
+ def process_payment(xml)
+ xml.send('ProcessPayment', { 'xmlns' => 'urn:Eve_1_4_4' }) {
+ credentials_xml(xml)
+ xml.send('txnReq') {
+ yield(xml) if block_given?
+ }
+ }
+ end
+
+ def add_token(xml, credit_card, options)
+ xml.send('AddToken', { 'xmlns' => 'urn:Eve_1_4_4' }) {
+ credentials_xml(xml)
+ xml.send('tokenRequest') {
+ xml.send('CRN1', options[:crn1])
+ xml.send('CRN2', '')
+ xml.send('CRN3', '')
+ xml.send('CardNumber', credit_card.number)
+ xml.send('ExpiryDate', expdate(credit_card))
+ }
+ }
+ end
+
+ def credentials_xml(xml)
+ xml.send('username', @options[:username])
+ xml.send('password', @options[:password])
+ xml.send('merchantNumber', @options[:merchant_number])
+ end
+
+ def add_purchase(xml, amount, credit_card, options)
+ payment_xml(xml, 'PAYMENT', amount, options)
+ credit_card_xml(xml, credit_card)
+ end
+
+ def add_authorize(xml, amount, credit_card, options)
+ payment_xml(xml, 'PREAUTH', amount, options)
+ credit_card_xml(xml, credit_card)
+ end
+
+ def add_capture(xml, amount, transaction_number, options)
+ payment_xml(xml, 'CAPTURE', amount, options)
+ transaction_number_xml(xml, transaction_number)
+ end
+
+ def add_refund(xml, amount, transaction_number, options)
+ payment_xml(xml, 'REFUND', amount, options)
+ transaction_number_xml(xml, transaction_number)
+ end
+
+ def add_void(xml, amount, transaction_number, options)
+ payment_xml(xml, 'REVERSAL', amount, options)
+ transaction_number_xml(xml, transaction_number)
+ end
+
+ def payment_xml(xml, payment_type, amount, options)
+ xml.send('PaymentType', payment_type)
+ xml.send('TxnType', 'WEB_SHOP')
+ xml.send('BillerCode', options.fetch(:biller_code, ''))
+ xml.send('MerchantReference', options[:order_id]) if options[:order_id]
+ xml.send('CRN1', options[:crn1]) if options[:crn1]
+ xml.send('CRN2', options[:crn2]) if options[:crn2]
+ xml.send('CRN3', options[:crn3]) if options[:crn3]
+ xml.send('Amount', amount)
+ end
+
+ def credit_card_xml(xml, credit_card)
+ xml.send('CardNumber', credit_card.number)
+ xml.send('ExpiryDate', expdate(credit_card))
+ xml.send('CVC', credit_card.verification_value)
+ end
+
+ def transaction_number_xml(xml, transaction_number)
+ xml.send('OriginalTransactionNumber', transaction_number)
+ end
+
+ def commit(request_body)
+ parse(ssl_post(live_url, request_body, request_headers))
+ end
+
+ def request_headers
+ { 'Content-Type' => 'application/soap+xml; charset=utf-8' }
+ end
+
+ def parse(body)
+ response_for(Nokogiri::XML(body).remove_namespaces!)
+ end
+
+ def response_for(xml_doc)
+ if xml_doc.xpath('//ProcessPaymentResponse').any?
+ ProcessPaymentResponse.new(xml_doc, self).to_response
+ elsif xml_doc.xpath('//AddTokenResponse').any?
+ AddTokenResponse.new(xml_doc, self).to_response
+ end
+ end
+
+ class BPointResponse
+ attr_reader :xml_doc, :gateway, :params
+
+ def initialize(xml_doc, gateway)
+ @xml_doc = xml_doc
+ @gateway = gateway
+ @params = init_params
+ end
+
+ def to_response
+ Response.new(success?, message, params, options)
+ end
+
+ private
+
+ def init_params
+ {}.tap do |h|
+ xml_doc.xpath(response_node).each do |node|
+ if node.elements.empty?
+ h[node.name.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name}_#{childnode.name}"
+ h[name.to_sym] = childnode.text
+ end
+ end
+ end
+ end
+ end
+
+ def response_node
+ "//#{self.class.name.split('::').last}/*"
+ end
+
+ def options
+ { authorization: params[authorization_key], test: gateway.test? }
+ end
+ end
+
+ class ProcessPaymentResponse < BPointResponse
+
+ private
+
+ def authorization_key
+ :ProcessPaymentResult_TransactionNumber
+ end
+
+ def success?
+ params[:ProcessPaymentResult_ResponseCode] == '0'
+ end
+
+ def message
+ params[:ProcessPaymentResult_AuthorisationResult] || params[:response_ResponseMessage]
+ end
+ end
+
+ class AddTokenResponse < BPointResponse
+
+ private
+
+ def authorization_key
+ :AddTokenResult_Token
+ end
+
+ def success?
+ params[:response_ResponseCode] == 'SUCCESS'
+ end
+
+ def message
+ params[:response_ResponseCode].capitalize
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/braintree.rb b/lib/active_merchant/billing/gateways/braintree.rb
index 6167d6be6de..bfe682c31db 100644
--- a/lib/active_merchant/billing/gateways/braintree.rb
+++ b/lib/active_merchant/billing/gateways/braintree.rb
@@ -1,10 +1,10 @@
-require File.dirname(__FILE__) + '/braintree/braintree_common'
+require 'active_merchant/billing/gateways/braintree/braintree_common'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class BraintreeGateway < Gateway
include BraintreeCommon
-
+
self.abstract_class = true
def self.new(options={})
diff --git a/lib/active_merchant/billing/gateways/braintree/braintree_common.rb b/lib/active_merchant/billing/gateways/braintree/braintree_common.rb
index 87e980f71a2..7343584f7aa 100644
--- a/lib/active_merchant/billing/gateways/braintree/braintree_common.rb
+++ b/lib/active_merchant/billing/gateways/braintree/braintree_common.rb
@@ -1,9 +1,22 @@
module BraintreeCommon
def self.included(base)
- base.supported_countries = %w(US CA AU AD AT BE BG CY CZ DK EE FI FR GI DE GR HU IS IM IE IT LV LI LT LU MT MC NL NO PL PT RO SM SK SI ES SE CH TR GB)
- base.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
+ base.supported_countries = %w(US CA AD AT BE BG HR CY CZ DK EE FI FR GI DE GR GG HU IS IM IE IT JE LV LI LT LU MT MC NL NO PL PT RO SM SK SI ES SE CH TR GB SG HK MY AU NZ)
+ base.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
base.homepage_url = 'http://www.braintreepaymentsolutions.com'
base.display_name = 'Braintree'
base.default_currency = 'USD'
+ base.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW LAK PYG RWF UGX VND VUV XAF XOF XPF)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ return '' if transcript.blank?
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?ccnumber=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2')
end
end
diff --git a/lib/active_merchant/billing/gateways/braintree_blue.rb b/lib/active_merchant/billing/gateways/braintree_blue.rb
index a400a02dcbd..463c7ac2b5f 100644
--- a/lib/active_merchant/billing/gateways/braintree_blue.rb
+++ b/lib/active_merchant/billing/gateways/braintree_blue.rb
@@ -1,12 +1,14 @@
-require File.dirname(__FILE__) + '/braintree/braintree_common'
+require 'active_merchant/billing/gateways/braintree/braintree_common'
begin
- require "braintree"
+ require 'braintree'
rescue LoadError
- raise "Could not load the braintree gem. Use `gem install braintree` to install it."
+ raise 'Could not load the braintree gem. Use `gem install braintree` to install it.'
end
-raise "Need braintree gem 2.x.y. Run `gem install braintree --version '~>2.0'` to get the correct version." unless Braintree::Version::Major == 2
+unless Braintree::Version::Major == 2 && Braintree::Version::Minor >= 78
+ raise "Need braintree gem >= 2.78.0. Run `gem install braintree --version '~>2.78'` to get the correct version."
+end
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -23,56 +25,52 @@ module Billing #:nodoc:
# Setting an ActiveMerchant +wiredump_device+ will automatically
# configure the Braintree logger (via the Braintree gem's
# configuration) when the BraintreeBlueGateway is instantiated.
- # Additionally, the log level will be set to +DEBUG+. Therefore,
- # all you have to do is set the +wiredump_device+ and you'll
- # get your debug output from your HTTP interactions with the
- # remote gateway. (Don't enable this in production.)
- #
- # For example:
- #
- # ActiveMerchant::Billing::BraintreeBlueGateway.wiredump_device = Logger.new(STDOUT)
- # # => #
- #
- # Braintree::Configuration.logger
- # # => (some other logger, created by default by the gem)
- #
- # Braintree::Configuration.logger.level
- # # => 1 (INFO)
- #
- # ActiveMerchant::Billing::BraintreeBlueGateway.new(:merchant_id => 'x', :public_key => 'x', :private_key => 'x')
+ # Additionally, the log level will be set to +DEBUG+. Therefore,
+ # all you have to do is set the +wiredump_device+ and you'll get
+ # your debug output from your HTTP interactions with the remote
+ # gateway. (Don't enable this in production.) The ActiveMerchant
+ # implementation doesn't mess with the Braintree::Configuration
+ # globals at all, so there won't be any side effects outside
+ # Active Merchant.
#
- # Braintree::Configuration.logger
- # # => #
+ # If no +wiredump_device+ is set, the logger in
+ # +Braintree::Configuration.logger+ will be cloned and the log
+ # level set to +WARN+.
#
- # Braintree::Configuration.logger.level
- # # => 0 (DEBUG)
- #
- # Alternatively, you can avoid setting the +wiredump_device+
- # and set +Braintree::Configuration.logger+ and/or
- # +Braintree::Configuration.logger.level+ directly.
class BraintreeBlueGateway < Gateway
include BraintreeCommon
+ include Empty
self.display_name = 'Braintree (Blue Platform)'
+ ERROR_CODES = {
+ cannot_refund_if_unsettled: 91506
+ }
+
def initialize(options = {})
requires!(options, :merchant_id, :public_key, :private_key)
@merchant_account_id = options[:merchant_account_id]
super
- Braintree::Configuration.merchant_id = options[:merchant_id]
- Braintree::Configuration.public_key = options[:public_key]
- Braintree::Configuration.private_key = options[:private_key]
- Braintree::Configuration.environment = (options[:environment] || (test? ? :sandbox : :production)).to_sym
- Braintree::Configuration.custom_user_agent = "ActiveMerchant #{ActiveMerchant::VERSION}"
-
- if wiredump_device
- Braintree::Configuration.logger = ((Logger === wiredump_device) ? wiredump_device : Logger.new(wiredump_device))
- Braintree::Configuration.logger.level = Logger::DEBUG
+ if wiredump_device.present?
+ logger = (Logger === wiredump_device ? wiredump_device : Logger.new(wiredump_device))
+ logger.level = Logger::DEBUG
else
- Braintree::Configuration.logger.level = Logger::WARN
+ logger = Braintree::Configuration.logger.clone
+ logger.level = Logger::WARN
end
+
+ @configuration = Braintree::Configuration.new(
+ :merchant_id => options[:merchant_id],
+ :public_key => options[:public_key],
+ :private_key => options[:private_key],
+ :environment => (options[:environment] || (test? ? :sandbox : :production)).to_sym,
+ :custom_user_agent => "ActiveMerchant #{ActiveMerchant::VERSION}",
+ :logger => options[:logger] || logger
+ )
+
+ @braintree_gateway = Braintree::Gateway.new(@configuration)
end
def authorize(money, credit_card_or_vault_id, options = {})
@@ -81,8 +79,8 @@ def authorize(money, credit_card_or_vault_id, options = {})
def capture(money, authorization, options = {})
commit do
- result = Braintree::Transaction.submit_for_settlement(authorization, amount(money).to_s)
- Response.new(result.success?, message_from_result(result))
+ result = @braintree_gateway.transaction.submit_for_settlement(authorization, localized_amount(money, options[:currency] || default_currency).to_s)
+ response_from_result(result)
end
end
@@ -97,76 +95,76 @@ def credit(money, credit_card_or_vault_id, options = {})
def refund(*args)
# legacy signature: #refund(transaction_id, options = {})
# new signature: #refund(money, transaction_id, options = {})
- money, transaction_id, _ = extract_refund_args(args)
- money = amount(money).to_s if money
+ money, transaction_id, options = extract_refund_args(args)
+ money = localized_amount(money, options[:currency] || default_currency).to_s if money
commit do
- result = Braintree::Transaction.refund(transaction_id, money)
- Response.new(result.success?, message_from_result(result),
- {:braintree_transaction => (transaction_hash(result.transaction) if result.success?)},
- {:authorization => (result.transaction.id if result.success?)}
- )
+ response = response_from_result(@braintree_gateway.transaction.refund(transaction_id, money))
+ return response if response.success?
+ return response unless options[:force_full_refund_if_unsettled]
+
+ void(transaction_id) if response.message =~ /#{ERROR_CODES[:cannot_refund_if_unsettled]}/
end
end
def void(authorization, options = {})
commit do
- result = Braintree::Transaction.void(authorization)
- Response.new(result.success?, message_from_result(result),
- {:braintree_transaction => (transaction_hash(result.transaction) if result.success?)},
- {:authorization => (result.transaction.id if result.success?)}
- )
+ response_from_result(@braintree_gateway.transaction.void(authorization))
+ end
+ 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(creditcard, options = {})
- commit do
- parameters = {
- :first_name => creditcard.first_name,
- :last_name => creditcard.last_name,
- :email => options[:email],
- :credit_card => {
- :number => creditcard.number,
- :cvv => creditcard.verification_value,
- :expiration_month => creditcard.month.to_s.rjust(2, "0"),
- :expiration_year => creditcard.year.to_s
- }
- }
- result = Braintree::Customer.create(merge_credit_card_options(parameters, options))
- Response.new(result.success?, message_from_result(result),
- {
- :braintree_customer => (customer_hash(result.customer) if result.success?),
- :customer_vault_id => (result.customer.id if result.success?)
- },
- :authorization => (result.customer.id if result.success?)
- )
+ if options[:customer].present?
+ MultiResponse.new.tap do |r|
+ customer_exists_response = nil
+ r.process { customer_exists_response = check_customer_exists(options[:customer]) }
+ r.process do
+ if customer_exists_response.params['exists']
+ add_credit_card_to_customer(creditcard, options)
+ else
+ add_customer_with_credit_card(creditcard, options)
+ end
+ end
+ end
+ else
+ add_customer_with_credit_card(creditcard, options)
end
end
def update(vault_id, creditcard, options = {})
braintree_credit_card = nil
commit do
- braintree_credit_card = Braintree::Customer.find(vault_id).credit_cards.detect { |cc| cc.default? }
+ braintree_credit_card = @braintree_gateway.customer.find(vault_id).credit_cards.detect(&:default?)
return Response.new(false, 'Braintree::NotFoundError') if braintree_credit_card.nil?
- options.merge!(:update_existing_token => braintree_credit_card.token)
+ options[:update_existing_token] = braintree_credit_card.token
credit_card_params = merge_credit_card_options({
:credit_card => {
+ :cardholder_name => creditcard.name,
:number => creditcard.number,
:cvv => creditcard.verification_value,
- :expiration_month => creditcard.month.to_s.rjust(2, "0"),
+ :expiration_month => creditcard.month.to_s.rjust(2, '0'),
:expiration_year => creditcard.year.to_s
}
}, options)[:credit_card]
- result = Braintree::Customer.update(vault_id,
+ result = @braintree_gateway.customer.update(vault_id,
:first_name => creditcard.first_name,
:last_name => creditcard.last_name,
- :email => options[:email],
+ :email => scrub_email(options[:email]),
+ :phone => options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
+ options[:billing_address][:phone]),
:credit_card => credit_card_params
)
Response.new(result.success?, message_from_result(result),
- :braintree_customer => (customer_hash(Braintree::Customer.find(vault_id)) if result.success?),
+ :braintree_customer => (customer_hash(@braintree_gateway.customer.find(vault_id), :include_credit_cards) if result.success?),
:customer_vault_id => (result.customer.id if result.success?)
)
end
@@ -174,45 +172,167 @@ def update(vault_id, creditcard, options = {})
def unstore(customer_vault_id, options = {})
commit do
- Braintree::Customer.delete(customer_vault_id)
- Response.new(true, "OK")
+ if(!customer_vault_id && options[:credit_card_token])
+ @braintree_gateway.credit_card.delete(options[:credit_card_token])
+ else
+ @braintree_gateway.customer.delete(customer_vault_id)
+ end
+ Response.new(true, 'OK')
end
end
alias_method :delete, :unstore
+ def supports_network_tokenization?
+ true
+ end
+
+ def verify_credentials
+ begin
+ @braintree_gateway.transaction.find('non_existent_token')
+ rescue Braintree::AuthenticationError
+ return false
+ rescue Braintree::NotFoundError
+ return true
+ end
+
+ true
+ end
+
private
+ def check_customer_exists(customer_vault_id)
+ commit do
+ begin
+ @braintree_gateway.customer.find(customer_vault_id)
+ ActiveMerchant::Billing::Response.new(true, 'Customer found', {exists: true}, authorization: customer_vault_id)
+ rescue Braintree::NotFoundError
+ ActiveMerchant::Billing::Response.new(true, 'Customer not found', {exists: false})
+ end
+ end
+ end
+
+ def add_customer_with_credit_card(creditcard, options)
+ commit do
+ if options[:payment_method_nonce]
+ credit_card_params = { payment_method_nonce: options[:payment_method_nonce] }
+ else
+ credit_card_params = {
+ :credit_card => {
+ :cardholder_name => creditcard.name,
+ :number => creditcard.number,
+ :cvv => creditcard.verification_value,
+ :expiration_month => creditcard.month.to_s.rjust(2, '0'),
+ :expiration_year => creditcard.year.to_s,
+ :token => options[:credit_card_token]
+ }
+ }
+ end
+ parameters = {
+ :first_name => creditcard.first_name,
+ :last_name => creditcard.last_name,
+ :email => scrub_email(options[:email]),
+ :phone => options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
+ options[:billing_address][:phone]),
+ :id => options[:customer],
+ :device_data => options[:device_data],
+ }.merge credit_card_params
+ result = @braintree_gateway.customer.create(merge_credit_card_options(parameters, options))
+ Response.new(result.success?, message_from_result(result),
+ {
+ :braintree_customer => (customer_hash(result.customer, :include_credit_cards) if result.success?),
+ :customer_vault_id => (result.customer.id if result.success?),
+ :credit_card_token => (result.customer.credit_cards[0].token if result.success?)
+ },
+ :authorization => (result.customer.id if result.success?)
+ )
+ end
+ end
+
+ def add_credit_card_to_customer(credit_card, options)
+ commit do
+ parameters = {
+ customer_id: options[:customer],
+ token: options[:credit_card_token],
+ cardholder_name: credit_card.name,
+ number: credit_card.number,
+ cvv: credit_card.verification_value,
+ expiration_month: credit_card.month.to_s.rjust(2, '0'),
+ expiration_year: credit_card.year.to_s,
+ device_data: options[:device_data],
+ }
+ if options[:billing_address]
+ address = map_address(options[:billing_address])
+ parameters[:credit_card][:billing_address] = address unless address.all? { |_k, v| empty?(v) }
+ end
+
+ result = @braintree_gateway.credit_card.create(parameters)
+ ActiveMerchant::Billing::Response.new(
+ result.success?,
+ message_from_result(result),
+ {
+ customer_vault_id: (result.credit_card.customer_id if result.success?),
+ credit_card_token: (result.credit_card.token if result.success?)
+ },
+ authorization: (result.credit_card.customer_id if result.success?)
+ )
+ end
+ end
+
+ def scrub_email(email)
+ return nil unless email.present?
+ return nil if
+ email !~ /^.+@[^\.]+(\.[^\.]+)+[a-z]$/i ||
+ email =~ /\.(con|met)$/i
+
+ email
+ end
+
+ def scrub_zip(zip)
+ return nil unless zip.present?
+ return nil if(
+ zip.gsub(/[^a-z0-9]/i, '').length > 9 ||
+ zip =~ /[^a-z0-9\- ]/i
+ )
+ zip
+ end
+
def merge_credit_card_options(parameters, options)
valid_options = {}
options.each do |key, value|
valid_options[key] = value if [:update_existing_token, :verify_card, :verification_merchant_account_id].include?(key)
end
+ if valid_options.include?(:verify_card) && @merchant_account_id
+ valid_options[:verification_merchant_account_id] ||= @merchant_account_id
+ end
+
parameters[:credit_card] ||= {}
- parameters[:credit_card].merge!(:options => valid_options)
- parameters[:credit_card][:billing_address] = map_address(options[:billing_address]) if options[:billing_address]
+ parameters[:credit_card][:options] = valid_options
+ if options[:billing_address]
+ address = map_address(options[:billing_address])
+ parameters[:credit_card][:billing_address] = address unless address.all? { |_k, v| empty?(v) }
+ end
parameters
end
def map_address(address)
- return {} if address.nil?
mapped = {
:street_address => address[:address1],
:extended_address => address[:address2],
:company => address[:company],
:locality => address[:city],
:region => address[:state],
- :postal_code => address[:zip],
+ :postal_code => scrub_zip(address[:zip]),
}
- if(address[:country] || address[:country_code_alpha2])
- mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2])
- elsif address[:country_name]
- mapped[:country_name] = address[:country_name]
- elsif address[:country_code_alpha3]
- mapped[:country_code_alpha3] = address[:country_code_alpha3]
- elsif address[:country_code_numeric]
- mapped[:country_code_numeric] = address[:country_code_numeric]
+
+ mapped[:country_code_alpha2] = (address[:country] || address[:country_code_alpha2]) if address[:country] || address[:country_code_alpha2]
+ mapped[:country_name] = address[:country_name] if address[:country_name]
+ mapped[:country_code_alpha3] = address[:country_code_alpha3] if address[:country_code_alpha3]
+ unless address[:country].blank?
+ mapped[:country_code_alpha3] ||= Country.find(address[:country]).code(:alpha3).value
end
+ mapped[:country_code_numeric] = address[:country_code_numeric] if address[:country_code_numeric]
+
mapped
end
@@ -224,41 +344,112 @@ def commit(&block)
def message_from_result(result)
if result.success?
- "OK"
- elsif result.errors.size == 0 && result.credit_card_verification
+ 'OK'
+ elsif result.errors.any?
+ result.errors.map { |e| "#{e.message} (#{e.code})" }.join(' ')
+ elsif result.credit_card_verification
"Processor declined: #{result.credit_card_verification.processor_response_text} (#{result.credit_card_verification.processor_response_code})"
else
- result.errors.map { |e| "#{e.message} (#{e.code})" }.join(" ")
+ result.message.to_s
+ end
+ end
+
+ def response_from_result(result)
+ response_hash = { braintree_transaction: transaction_hash(result) }
+
+ Response.new(
+ result.success?,
+ message_from_result(result),
+ response_hash,
+ authorization: result.transaction&.id,
+ test: test?
+ )
+ end
+
+ def response_params(result)
+ params = {}
+ params[:customer_vault_id] = result.transaction.customer_details.id if result.success?
+ params[:braintree_transaction] = transaction_hash(result)
+ params
+ end
+
+ def response_options(result)
+ options = {}
+ if result.transaction
+ options[:authorization] = result.transaction.id
+ options[:avs_result] = { code: avs_code_from(result.transaction) }
+ options[:cvv_result] = result.transaction.cvv_response_code
+ end
+ options[:test] = test?
+ options
+ end
+
+ def avs_code_from(transaction)
+ transaction.avs_error_response_code ||
+ avs_mapping["street: #{transaction.avs_street_address_response_code}, zip: #{transaction.avs_postal_code_response_code}"]
+ end
+
+ def avs_mapping
+ {
+ 'street: M, zip: M' => 'M',
+ 'street: M, zip: N' => 'A',
+ 'street: M, zip: U' => 'B',
+ 'street: M, zip: I' => 'B',
+ 'street: M, zip: A' => 'B',
+
+ 'street: N, zip: M' => 'Z',
+ 'street: N, zip: N' => 'C',
+ 'street: N, zip: U' => 'C',
+ 'street: N, zip: I' => 'C',
+ 'street: N, zip: A' => 'C',
+
+ 'street: U, zip: M' => 'P',
+ 'street: U, zip: N' => 'N',
+ 'street: U, zip: U' => 'I',
+ 'street: U, zip: I' => 'I',
+ 'street: U, zip: A' => 'I',
+
+ 'street: I, zip: M' => 'P',
+ 'street: I, zip: N' => 'C',
+ 'street: I, zip: U' => 'I',
+ 'street: I, zip: I' => 'I',
+ 'street: I, zip: A' => 'I',
+
+ 'street: A, zip: M' => 'P',
+ 'street: A, zip: N' => 'C',
+ 'street: A, zip: U' => 'I',
+ 'street: A, zip: I' => 'I',
+ 'street: A, zip: A' => 'I',
+
+ 'street: B, zip: B' => 'B'
+ }
+ end
+
+ def message_from_transaction_result(result)
+ if result.transaction && result.transaction.status == 'gateway_rejected'
+ 'Transaction declined - gateway rejected'
+ elsif result.transaction
+ "#{result.transaction.processor_response_code} #{result.transaction.processor_response_text}"
+ else
+ message_from_result(result)
+ end
+ end
+
+ def response_code_from_result(result)
+ if result.transaction
+ result.transaction.processor_response_code
+ elsif result.errors.size == 0 && result.credit_card_verification
+ result.credit_card_verification.processor_response_code
+ elsif result.errors.size > 0
+ result.errors.first.code
end
end
def create_transaction(transaction_type, money, credit_card_or_vault_id, options)
transaction_params = create_transaction_parameters(money, credit_card_or_vault_id, options)
-
commit do
- result = Braintree::Transaction.send(transaction_type, transaction_params)
- response_params, response_options, avs_result, cvv_result = {}, {}, {}, {}
- if result.success?
- response_params[:braintree_transaction] = transaction_hash(result.transaction)
- response_params[:customer_vault_id] = result.transaction.customer_details.id
- response_options[:authorization] = result.transaction.id
- end
- if result.transaction
- response_options[:avs_result] = {
- :code => nil, :message => nil,
- :street_match => result.transaction.avs_street_address_response_code,
- :postal_match => result.transaction.avs_postal_code_response_code
- }
- response_options[:cvv_result] = result.transaction.cvv_response_code
- if result.transaction.status == "gateway_rejected"
- message = "Transaction declined - gateway rejected"
- else
- message = "#{result.transaction.processor_response_code} #{result.transaction.processor_response_text}"
- end
- else
- message = message_from_result(result)
- end
- response = Response.new(result.success?, message, response_params, response_options)
+ result = @braintree_gateway.transaction.send(transaction_type, transaction_params)
+ response = Response.new(result.success?, message_from_transaction_result(result), response_params(result), response_options(result))
response.cvv_result['message'] = ''
response
end
@@ -277,34 +468,43 @@ def extract_refund_args(args)
end
end
- def customer_hash(customer)
- credit_cards = customer.credit_cards.map do |cc|
- {
- "bin" => cc.bin,
- "expiration_date" => cc.expiration_date,
- "token" => cc.token,
- "last_4" => cc.last_4,
- "card_type" => cc.card_type,
- "masked_number" => cc.masked_number
- }
+ def customer_hash(customer, include_credit_cards=false)
+ hash = {
+ 'email' => customer.email,
+ 'phone' => customer.phone,
+ 'first_name' => customer.first_name,
+ 'last_name' => customer.last_name,
+ 'id' => customer.id
+ }
+
+ if include_credit_cards
+ hash['credit_cards'] = customer.credit_cards.map do |cc|
+ {
+ 'bin' => cc.bin,
+ 'expiration_date' => cc.expiration_date,
+ 'token' => cc.token,
+ 'last_4' => cc.last_4,
+ 'card_type' => cc.card_type,
+ 'masked_number' => cc.masked_number
+ }
+ end
end
- {
- "email" => customer.email,
- "first_name" => customer.first_name,
- "last_name" => customer.last_name,
- "credit_cards" => credit_cards,
- "id" => customer.id
- }
+ hash
end
- def transaction_hash(transaction)
+ def transaction_hash(result)
+ unless result.success?
+ return { 'processor_response_code' => response_code_from_result(result) }
+ end
+
+ transaction = result.transaction
if transaction.vault_customer
vault_customer = {
}
- vault_customer["credit_cards"] = transaction.vault_customer.credit_cards.map do |cc|
+ vault_customer['credit_cards'] = transaction.vault_customer.credit_cards.map do |cc|
{
- "bin" => cc.bin
+ 'bin' => cc.bin
}
end
else
@@ -312,90 +512,229 @@ def transaction_hash(transaction)
end
customer_details = {
- "id" => transaction.customer_details.id,
- "email" => transaction.customer_details.email
+ 'id' => transaction.customer_details.id,
+ 'email' => transaction.customer_details.email,
+ 'phone' => transaction.customer_details.phone,
}
billing_details = {
- "street_address" => transaction.billing_details.street_address,
- "extended_address" => transaction.billing_details.extended_address,
- "company" => transaction.billing_details.company,
- "locality" => transaction.billing_details.locality,
- "region" => transaction.billing_details.region,
- "postal_code" => transaction.billing_details.postal_code,
- "country_name" => transaction.billing_details.country_name,
+ 'street_address' => transaction.billing_details.street_address,
+ 'extended_address' => transaction.billing_details.extended_address,
+ 'company' => transaction.billing_details.company,
+ 'locality' => transaction.billing_details.locality,
+ 'region' => transaction.billing_details.region,
+ 'postal_code' => transaction.billing_details.postal_code,
+ 'country_name' => transaction.billing_details.country_name,
}
shipping_details = {
- "street_address" => transaction.shipping_details.street_address,
- "extended_address" => transaction.shipping_details.extended_address,
- "company" => transaction.shipping_details.company,
- "locality" => transaction.shipping_details.locality,
- "region" => transaction.shipping_details.region,
- "postal_code" => transaction.shipping_details.postal_code,
- "country_name" => transaction.shipping_details.country_name,
+ 'street_address' => transaction.shipping_details.street_address,
+ 'extended_address' => transaction.shipping_details.extended_address,
+ 'company' => transaction.shipping_details.company,
+ 'locality' => transaction.shipping_details.locality,
+ 'region' => transaction.shipping_details.region,
+ 'postal_code' => transaction.shipping_details.postal_code,
+ 'country_name' => transaction.shipping_details.country_name,
}
credit_card_details = {
- "masked_number" => transaction.credit_card_details.masked_number,
- "bin" => transaction.credit_card_details.bin,
- "last_4" => transaction.credit_card_details.last_4,
- "card_type" => transaction.credit_card_details.card_type,
- "token" => transaction.credit_card_details.token
+ 'masked_number' => transaction.credit_card_details.masked_number,
+ 'bin' => transaction.credit_card_details.bin,
+ 'last_4' => transaction.credit_card_details.last_4,
+ 'card_type' => transaction.credit_card_details.card_type,
+ 'token' => transaction.credit_card_details.token
}
+ if transaction.risk_data
+ risk_data = {
+ 'id' => transaction.risk_data.id,
+ 'decision' => transaction.risk_data.decision,
+ 'device_data_captured' => transaction.risk_data.device_data_captured,
+ 'fraud_service_provider' => transaction.risk_data.fraud_service_provider
+ }
+ else
+ risk_data = nil
+ end
+
{
- "order_id" => transaction.order_id,
- "status" => transaction.status,
- "credit_card_details" => credit_card_details,
- "customer_details" => customer_details,
- "billing_details" => billing_details,
- "shipping_details" => shipping_details,
- "vault_customer" => vault_customer,
- "merchant_account_id" => transaction.merchant_account_id
+ 'order_id' => transaction.order_id,
+ 'amount' => transaction.amount.to_s,
+ 'status' => transaction.status,
+ 'credit_card_details' => credit_card_details,
+ 'customer_details' => customer_details,
+ 'billing_details' => billing_details,
+ 'shipping_details' => shipping_details,
+ 'vault_customer' => vault_customer,
+ 'merchant_account_id' => transaction.merchant_account_id,
+ 'risk_data' => risk_data,
+ 'network_transaction_id' => transaction.network_transaction_id || nil,
+ 'processor_response_code' => response_code_from_result(result)
}
end
def create_transaction_parameters(money, credit_card_or_vault_id, options)
parameters = {
- :amount => amount(money).to_s,
+ :amount => localized_amount(money, options[:currency] || default_currency).to_s,
:order_id => options[:order_id],
:customer => {
- :id => options[:store] == true ? "" : options[:store],
- :email => options[:email]
+ :id => options[:store] == true ? '' : options[:store],
+ :email => scrub_email(options[:email]),
+ :phone => options[:phone] || (options[:billing_address][:phone] if options[:billing_address] &&
+ options[:billing_address][:phone])
},
:options => {
:store_in_vault => options[:store] ? true : false,
- :submit_for_settlement => options[:submit_for_settlement]
+ :submit_for_settlement => options[:submit_for_settlement],
+ :hold_in_escrow => options[:hold_in_escrow],
}
}
+ if options[:skip_advanced_fraud_checking]
+ parameters[:options][:skip_advanced_fraud_checking] = options[:skip_advanced_fraud_checking]
+ end
+
+ if options[:skip_avs]
+ parameters[:options][:skip_avs] = options[:skip_avs]
+ end
+
+ if options[:skip_cvv]
+ parameters[:options][:skip_cvv] = options[:skip_cvv]
+ end
+
+ parameters[:custom_fields] = options[:custom_fields]
+ parameters[:device_data] = options[:device_data] if options[:device_data]
+ parameters[:service_fee_amount] = options[:service_fee_amount] if options[:service_fee_amount]
if merchant_account_id = (options[:merchant_account_id] || @merchant_account_id)
parameters[:merchant_account_id] = merchant_account_id
end
- if options[:recurring]
+ if options[:transaction_source]
+ parameters[:transaction_source] = options[:transaction_source]
+ elsif options[:recurring]
parameters[:recurring] = true
end
+ add_payment_method(parameters, credit_card_or_vault_id, options)
+ add_stored_credential_data(parameters, credit_card_or_vault_id, options)
+
+ parameters[:billing] = map_address(options[:billing_address]) if options[:billing_address]
+ parameters[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address]
+
+ channel = @options[:channel] || application_id
+ parameters[:channel] = channel if channel
+
+ if options[:descriptor_name] || options[:descriptor_phone] || options[:descriptor_url]
+ parameters[:descriptor] = {
+ name: options[:descriptor_name],
+ phone: options[:descriptor_phone],
+ url: options[:descriptor_url]
+ }
+ end
+
+ add_3ds_info(parameters, options[:three_d_secure])
+
+ parameters[:tax_amount] = options[:tax_amount] if options[:tax_amount]
+ parameters[:tax_exempt] = options[:tax_exempt] if options[:tax_exempt]
+ parameters[:purchase_order_number] = options[:purchase_order_number] if options[:purchase_order_number]
+
+ parameters[:shipping_amount] = options[:shipping_amount] if options[:shipping_amount]
+ parameters[:discount_amount] = options[:discount_amount] if options[:discount_amount]
+ parameters[:ships_from_postal_code] = options[:ships_from_postal_code] if options[:ships_from_postal_code]
+
+ parameters[:line_items] = options[:line_items] if options[:line_items]
+
+ parameters
+ end
+
+ def add_3ds_info(parameters, three_d_secure_opts)
+ return if empty?(three_d_secure_opts)
+ pass_thru = {}
+
+ pass_thru[:three_d_secure_version] = three_d_secure_opts[:version] if three_d_secure_opts[:version]
+ pass_thru[:eci_flag] = three_d_secure_opts[:eci] if three_d_secure_opts[:eci]
+ pass_thru[:cavv_algorithm] = three_d_secure_opts[:cavv_algorithm] if three_d_secure_opts[:cavv_algorithm]
+ pass_thru[:cavv] = three_d_secure_opts[:cavv] if three_d_secure_opts[:cavv]
+ pass_thru[:directory_response] = three_d_secure_opts[:directory_response_status] if three_d_secure_opts[:directory_response_status]
+ pass_thru[:authentication_response] = three_d_secure_opts[:authentication_response_status] if three_d_secure_opts[:authentication_response_status]
+
+ parameters[:three_d_secure_pass_thru] = pass_thru.merge(xid_or_ds_trans_id(three_d_secure_opts))
+ end
+
+ def xid_or_ds_trans_id(three_d_secure_opts)
+ if three_d_secure_opts[:version].to_f >= 2
+ { ds_transaction_id: three_d_secure_opts[:ds_transaction_id] }
+ else
+ { xid: three_d_secure_opts[:xid] }
+ end
+ end
+
+ def add_stored_credential_data(parameters, credit_card_or_vault_id, options)
+ return unless (stored_credential = options[:stored_credential])
+ parameters[:external_vault] = {}
+ if stored_credential[:initial_transaction]
+ parameters[:external_vault][:status] = 'will_vault'
+ else
+ parameters[:external_vault][:status] = 'vaulted'
+ parameters[:external_vault][:previous_network_transaction_id] = stored_credential[:network_transaction_id]
+ end
+ if stored_credential[:initiator] == 'merchant'
+ if stored_credential[:reason_type] == 'installment'
+ parameters[:transaction_source] = 'recurring'
+ else
+ parameters[:transaction_source] = stored_credential[:reason_type]
+ end
+ else
+ parameters[:transaction_source] = ''
+ end
+ end
+
+ def add_payment_method(parameters, credit_card_or_vault_id, options)
if credit_card_or_vault_id.is_a?(String) || credit_card_or_vault_id.is_a?(Integer)
- parameters[:customer_id] = credit_card_or_vault_id
+ if options[:payment_method_token]
+ parameters[:payment_method_token] = credit_card_or_vault_id
+ options.delete(:billing_address)
+ elsif options[:payment_method_nonce]
+ parameters[:payment_method_nonce] = credit_card_or_vault_id
+ else
+ parameters[:customer_id] = credit_card_or_vault_id
+ end
else
parameters[:customer].merge!(
:first_name => credit_card_or_vault_id.first_name,
:last_name => credit_card_or_vault_id.last_name
)
- parameters[:credit_card] = {
- :number => credit_card_or_vault_id.number,
- :cvv => credit_card_or_vault_id.verification_value,
- :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, "0"),
- :expiration_year => credit_card_or_vault_id.year.to_s
- }
+ if credit_card_or_vault_id.is_a?(NetworkTokenizationCreditCard)
+ if credit_card_or_vault_id.source == :apple_pay
+ parameters[:apple_pay_card] = {
+ :number => credit_card_or_vault_id.number,
+ :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, '0'),
+ :expiration_year => credit_card_or_vault_id.year.to_s,
+ :cardholder_name => credit_card_or_vault_id.name,
+ :cryptogram => credit_card_or_vault_id.payment_cryptogram,
+ :eci_indicator => credit_card_or_vault_id.eci
+ }
+ elsif credit_card_or_vault_id.source == :android_pay || credit_card_or_vault_id.source == :google_pay
+ parameters[:android_pay_card] = {
+ :number => credit_card_or_vault_id.number,
+ :cryptogram => credit_card_or_vault_id.payment_cryptogram,
+ :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, '0'),
+ :expiration_year => credit_card_or_vault_id.year.to_s,
+ :google_transaction_id => credit_card_or_vault_id.transaction_id,
+ :source_card_type => credit_card_or_vault_id.brand,
+ :source_card_last_four => credit_card_or_vault_id.last_digits,
+ :eci_indicator => credit_card_or_vault_id.eci
+ }
+ end
+ else
+ parameters[:credit_card] = {
+ :number => credit_card_or_vault_id.number,
+ :cvv => credit_card_or_vault_id.verification_value,
+ :expiration_month => credit_card_or_vault_id.month.to_s.rjust(2, '0'),
+ :expiration_year => credit_card_or_vault_id.year.to_s,
+ :cardholder_name => credit_card_or_vault_id.name
+ }
+ end
end
- parameters[:billing] = map_address(options[:billing_address]) if options[:billing_address]
- parameters[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address]
- parameters
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/braintree_orange.rb b/lib/active_merchant/billing/gateways/braintree_orange.rb
index 16e24beea42..f56502eb7a0 100644
--- a/lib/active_merchant/billing/gateways/braintree_orange.rb
+++ b/lib/active_merchant/billing/gateways/braintree_orange.rb
@@ -1,5 +1,5 @@
-require File.dirname(__FILE__) + '/smart_ps.rb'
-require File.dirname(__FILE__) + '/braintree/braintree_common'
+require 'active_merchant/billing/gateways/smart_ps.rb'
+require 'active_merchant/billing/gateways/braintree/braintree_common'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -7,7 +7,8 @@ class BraintreeOrangeGateway < SmartPs
include BraintreeCommon
self.display_name = 'Braintree (Orange Platform)'
-
+ self.supported_countries = ['US']
+
self.live_url = self.test_url = 'https://secure.braintreepaymentgateway.com/api/transact.php'
def add_processor(post, options)
@@ -16,4 +17,3 @@ def add_processor(post, options)
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/bridge_pay.rb b/lib/active_merchant/billing/gateways/bridge_pay.rb
new file mode 100644
index 00000000000..d8cf6218265
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/bridge_pay.rb
@@ -0,0 +1,244 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class BridgePayGateway < Gateway
+ self.display_name = 'BridgePay'
+ self.homepage_url = 'http://www.bridgepaynetwork.com/'
+
+ self.test_url = 'https://gatewaystage.itstgate.com/SmartPayments/transact3.asmx'
+ self.live_url = 'https://gateway.itstgate.com/SmartPayments/transact3.asmx'
+
+ self.supported_countries = ['CA', 'US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+
+ def initialize(options={})
+ requires!(options, :user_name, :password)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = initialize_required_fields('Sale')
+
+ # Allow the same amount in multiple transactions.
+ post[:ExtData] = 'T'
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit(post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = initialize_required_fields('Auth')
+
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit(post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = initialize_required_fields('Force')
+
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit(post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = initialize_required_fields('Return')
+
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+
+ commit(post)
+ end
+
+ def void(authorization, options={})
+ post = initialize_required_fields('Void')
+
+ add_reference(post, authorization)
+
+ commit(post)
+ end
+
+ def verify(creditcard, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def store(creditcard, options={})
+ post = initialize_required_fields('')
+ post[:transaction] = 'Create'
+ post[:CardNumber] = creditcard.number
+ post[:CustomerPaymentInfoKey] = ''
+ post[:token] = ''
+ add_payment_method(post, creditcard)
+
+ commit(post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?CardNum=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?CVNum=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?Password=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?TransitNum=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?AccountNum=)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_payment_method(post, payment_method)
+ if payment_method.respond_to? :brand
+ post[:NameOnCard] = payment_method.name if payment_method.name
+ post[:ExpDate] = expdate(payment_method)
+ post[:CardNum] = payment_method.number
+ post[:CVNum] = payment_method.verification_value
+ elsif payment_method.is_a?(String)
+ add_token(post, payment_method)
+ else
+ post[:CheckNum] = payment_method.number
+ post[:TransitNum] = payment_method.routing_number
+ post[:AccountNum] = payment_method.account_number
+ post[:NameOnCheck] = payment_method.name
+ post[:ExtData] = "#{payment_method.account_type.capitalize}" if payment_method.account_type
+ end
+ end
+
+ def add_token(post, payment_method)
+ payment_method = payment_method.split('|')
+ post[:ExtData] = "TRead#{payment_method[1]}#{payment_method[0]}#{payment_method[2]}"
+ end
+
+ def initialize_required_fields(transaction_type)
+ post = {}
+ post[:TransType] = transaction_type
+ post[:Amount] = ''
+ post[:PNRef] = ''
+ post[:InvNum] = ''
+ post[:CardNum] = ''
+ post[:ExpDate] = ''
+ post[:MagData] = ''
+ post[:NameOnCard] = ''
+ post[:Zip] = ''
+ post[:Street] = ''
+ post[:CVNum] = ''
+ post[:MagData] = ''
+ post[:ExtData] = ''
+ post[:MICR] = ''
+ post[:DL] = ''
+ post[:SS] = ''
+ post[:DOB] = ''
+ post[:StateCode] = ''
+ post[:CheckType] = ''
+ post
+ end
+
+ def add_customer_data(post, options)
+ if(billing_address = (options[:billing_address] || options[:address]))
+ post[:Street] = billing_address[:address1]
+ post[:Zip] = billing_address[:zip]
+ end
+ end
+
+ def add_invoice(post, amount, options)
+ post[:Amount] = amount(amount)
+ post[:InvNum] = options[:order_id]
+ end
+
+ def expdate(creditcard)
+ "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :two_digits)}"
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.root&.xpath('*')&.each do |node|
+ if node.elements.size == 0
+ response[node.name.downcase.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name.downcase}_#{childnode.name.downcase}"
+ response[name.to_sym] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ def commit(parameters)
+ data = post_data(parameters)
+ raw = parse(ssl_post(url(parameters), data))
+
+ Response.new(
+ success_from(raw),
+ message_from(raw),
+ raw,
+ authorization: authorization_from(raw),
+ test: test?
+ )
+ end
+
+ def url(params)
+ if params[:transaction]
+ "#{base_url}/ManageCardVault"
+ else
+ action = params[:TransitNum] ? 'ProcessCheck' : 'ProcessCreditCard'
+ "#{base_url}/#{action}"
+ end
+ end
+
+ def base_url
+ test? ? test_url : live_url
+ end
+
+ def success_from(response)
+ response[:result] == '0'
+ end
+
+ def message_from(response)
+ response[:respmsg] || response[:message]
+ end
+
+ def authorization_from(response)
+ if response[:token]
+ [response[:token], response[:customerpaymentinfokey], response[:expdate]].join('|')
+ else
+ [response[:authcode], response[:pnref]].join('|')
+ end
+ end
+
+ def split_authorization(authorization)
+ authcode, pnref = authorization.split('|')
+ [authcode, pnref]
+ end
+
+ def add_reference(post, authorization)
+ authcode, pnref = split_authorization(authorization)
+ post[:AuthCode] = authcode
+ post[:PNRef] = pnref
+ end
+
+ def post_data(post)
+ {
+ :UserName => @options[:user_name],
+ :Password => @options[:password]
+ }.merge(post).collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cams.rb b/lib/active_merchant/billing/gateways/cams.rb
new file mode 100644
index 00000000000..4fd30dd0489
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cams.rb
@@ -0,0 +1,230 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CamsGateway < Gateway
+ self.live_url = 'https://secure.centralams.com/gw/api/transact.php'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'https://www.centralams.com/'
+ self.display_name = 'CAMS: Central Account Management System'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '200' => STANDARD_ERROR_CODE[:card_declined],
+ '201' => STANDARD_ERROR_CODE[:card_declined],
+ '202' => STANDARD_ERROR_CODE[:card_declined],
+ '203' => STANDARD_ERROR_CODE[:card_declined],
+ '204' => STANDARD_ERROR_CODE[:card_declined],
+ '220' => STANDARD_ERROR_CODE[:card_declined],
+ '221' => STANDARD_ERROR_CODE[:card_declined],
+ '222' => STANDARD_ERROR_CODE[:incorrect_number],
+ '223' => STANDARD_ERROR_CODE[:expired_card],
+ '224' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '225' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '240' => STANDARD_ERROR_CODE[:call_issuer],
+ '250' => STANDARD_ERROR_CODE[:pickup_card],
+ '251' => STANDARD_ERROR_CODE[:pickup_card],
+ '252' => STANDARD_ERROR_CODE[:pickup_card],
+ '253' => STANDARD_ERROR_CODE[:pickup_card],
+ '260' => STANDARD_ERROR_CODE[:card_declined],
+ '261' => STANDARD_ERROR_CODE[:card_declined],
+ '262' => STANDARD_ERROR_CODE[:card_declined],
+ '263' => STANDARD_ERROR_CODE[:processing_error],
+ '264' => STANDARD_ERROR_CODE[:card_declined],
+ '300' => STANDARD_ERROR_CODE[:card_declined],
+ '400' => STANDARD_ERROR_CODE[:processing_error],
+ '410' => STANDARD_ERROR_CODE[:processing_error],
+ '411' => STANDARD_ERROR_CODE[:processing_error],
+ '420' => STANDARD_ERROR_CODE[:processing_error],
+ '421' => STANDARD_ERROR_CODE[:processing_error],
+ '430' => STANDARD_ERROR_CODE[:processing_error],
+ '440' => STANDARD_ERROR_CODE[:processing_error],
+ '441' => STANDARD_ERROR_CODE[:processing_error],
+ '460' => STANDARD_ERROR_CODE[:invalid_number],
+ '461' => STANDARD_ERROR_CODE[:processing_error],
+ '801' => STANDARD_ERROR_CODE[:processing_error],
+ '811' => STANDARD_ERROR_CODE[:processing_error],
+ '812' => STANDARD_ERROR_CODE[:processing_error],
+ '813' => STANDARD_ERROR_CODE[:processing_error],
+ '814' => STANDARD_ERROR_CODE[:processing_error],
+ '815' => STANDARD_ERROR_CODE[:processing_error],
+ '823' => STANDARD_ERROR_CODE[:processing_error],
+ '824' => STANDARD_ERROR_CODE[:processing_error],
+ '881' => STANDARD_ERROR_CODE[:processing_error],
+ '882' => STANDARD_ERROR_CODE[:processing_error],
+ '883' => STANDARD_ERROR_CODE[:processing_error],
+ '884' => STANDARD_ERROR_CODE[:card_declined],
+ '885' => STANDARD_ERROR_CODE[:card_declined],
+ '886' => STANDARD_ERROR_CODE[:card_declined],
+ '887' => STANDARD_ERROR_CODE[:processing_error],
+ '888' => STANDARD_ERROR_CODE[:processing_error],
+ '889' => STANDARD_ERROR_CODE[:processing_error],
+ '890' => STANDARD_ERROR_CODE[:processing_error],
+ '891' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '892' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '893' => STANDARD_ERROR_CODE[:processing_error],
+ '894' => STANDARD_ERROR_CODE[:processing_error]
+ }
+
+ def initialize(options={})
+ requires!(options, :username, :password)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+
+ if payment.respond_to?(:number)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ elsif payment.kind_of?(String)
+ post[:transactionid] = split_authorization(payment)[0]
+ end
+
+ commit('sale', post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+
+ commit('auth', post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_invoice(post, money, options)
+
+ commit('capture', post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_invoice(post, money, options)
+ commit('refund', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ commit('void', post)
+ end
+
+ def verify(credit_card, options={})
+ post = {}
+ add_invoice(post, 0, options)
+ add_payment(post, credit_card)
+ add_address(post, credit_card, options)
+ commit('verify', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ %w(ccnumber cvv password).each do |field|
+ transcript = transcript.gsub(%r((#{field}=)[^&]+), '\1[FILTERED]\2')
+ end
+
+ transcript
+ end
+
+ private
+
+ def add_address(post, creditcard, options={})
+ post[:firstname] = creditcard.first_name
+ post[:lastname] = creditcard.last_name
+
+ return unless options[:billing_address]
+
+ address = options[:billing_address]
+ post[:address1] = address[:address1]
+ post[:address2] = address[:address2]
+ post[:city] = address[:city]
+ post[:state] = address[:state]
+ post[:zip] = address[:zip]
+ post[:country] = address[:country]
+ post[:phone] = address[:phone]
+ end
+
+ def add_reference(post, authorization)
+ transaction_id, authcode = split_authorization(authorization)
+ post['transactionid'] = transaction_id
+ post['authcode'] = authcode
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:currency] = (options[:currency] || default_currency)
+ end
+
+ def add_payment(post, payment)
+ post[:ccnumber] = payment.number
+ post[:ccexp] = "#{payment.month.to_s.rjust(2, "0")}#{payment.year.to_s[-2..-1]}"
+ post[:cvv] = payment.verification_value
+ end
+
+ def parse(body)
+ kvs = body.split('&')
+
+ kvs.inject({}) { |h, kv|
+ k, v = kv.split('=')
+ h[k] = v
+ h
+ }
+ end
+
+ def commit(action, parameters)
+ url = live_url
+ parameters[:type] = action
+
+ response_body = ssl_post(url, post_data(parameters))
+ response = parse(response_body)
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def success_from(response)
+ response['response_code'] == '100'
+ end
+
+ def message_from(response)
+ response['responsetext']
+ end
+
+ def authorization_from(response)
+ [response['transactionid'], response['authcode']].join('#')
+ end
+
+ def split_authorization(authorization)
+ transaction_id, authcode = authorization.split('#')
+ [transaction_id, authcode]
+ end
+
+ def post_data(parameters = {})
+ parameters[:password] = @options[:password]
+ parameters[:username] = @options[:username]
+
+ parameters.collect { |k, v| "#{k}=#{v}" }.join('&')
+ end
+
+ def error_code_from(response)
+ STANDARD_ERROR_CODE_MAPPING[response['response_code']]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/card_connect.rb b/lib/active_merchant/billing/gateways/card_connect.rb
new file mode 100644
index 00000000000..4d953f0a6dd
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/card_connect.rb
@@ -0,0 +1,316 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CardConnectGateway < Gateway
+ self.test_url = 'https://fts.cardconnect.com:6443/cardconnect/rest/'
+ self.live_url = 'https://fts.cardconnect.com:8443/cardconnect/rest/'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'https://cardconnect.com/'
+ self.display_name = 'Card Connect'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '11' => STANDARD_ERROR_CODE[:card_declined],
+ '12' => STANDARD_ERROR_CODE[:incorrect_number],
+ '13' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '14' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '15' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '16' => STANDARD_ERROR_CODE[:expired_card],
+ '17' => STANDARD_ERROR_CODE[:incorrect_zip],
+ '21' => STANDARD_ERROR_CODE[:config_error],
+ '22' => STANDARD_ERROR_CODE[:config_error],
+ '23' => STANDARD_ERROR_CODE[:config_error],
+ '24' => STANDARD_ERROR_CODE[:processing_error],
+ '25' => STANDARD_ERROR_CODE[:processing_error],
+ '27' => STANDARD_ERROR_CODE[:processing_error],
+ '28' => STANDARD_ERROR_CODE[:processing_error],
+ '29' => STANDARD_ERROR_CODE[:processing_error],
+ '31' => STANDARD_ERROR_CODE[:processing_error],
+ '32' => STANDARD_ERROR_CODE[:processing_error],
+ '33' => STANDARD_ERROR_CODE[:card_declined],
+ '34' => STANDARD_ERROR_CODE[:card_declined],
+ '35' => STANDARD_ERROR_CODE[:incorrect_zip],
+ '36' => STANDARD_ERROR_CODE[:processing_error],
+ '37' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '41' => STANDARD_ERROR_CODE[:processing_error],
+ '42' => STANDARD_ERROR_CODE[:processing_error],
+ '43' => STANDARD_ERROR_CODE[:processing_error],
+ '44' => STANDARD_ERROR_CODE[:config_error],
+ '61' => STANDARD_ERROR_CODE[:processing_error],
+ '62' => STANDARD_ERROR_CODE[:processing_error],
+ '63' => STANDARD_ERROR_CODE[:processing_error],
+ '64' => STANDARD_ERROR_CODE[:config_error],
+ '65' => STANDARD_ERROR_CODE[:processing_error],
+ '66' => STANDARD_ERROR_CODE[:processing_error],
+ '91' => STANDARD_ERROR_CODE[:processing_error],
+ '92' => STANDARD_ERROR_CODE[:processing_error],
+ '93' => STANDARD_ERROR_CODE[:processing_error],
+ '94' => STANDARD_ERROR_CODE[:processing_error],
+ '95' => STANDARD_ERROR_CODE[:config_error],
+ '96' => STANDARD_ERROR_CODE[:processing_error],
+ 'NU' => STANDARD_ERROR_CODE[:card_declined],
+ 'N3' => STANDARD_ERROR_CODE[:card_declined],
+ 'NJ' => STANDARD_ERROR_CODE[:card_declined],
+ '51' => STANDARD_ERROR_CODE[:card_declined],
+ 'C2' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '54' => STANDARD_ERROR_CODE[:expired_card],
+ '05' => STANDARD_ERROR_CODE[:card_declined],
+ '03' => STANDARD_ERROR_CODE[:config_error],
+ '60' => STANDARD_ERROR_CODE[:pickup_card]
+ }
+
+ def initialize(options = {})
+ requires!(options, :merchant_id, :username, :password)
+ require_valid_domain!(options, :domain)
+ super
+ end
+
+ def require_valid_domain!(options, param)
+ if options[param]
+ raise ArgumentError.new('not a valid cardconnect domain') unless /\Dcardconnect.com:\d{1,}\D/ =~ options[param]
+ end
+ end
+
+ def purchase(money, payment, options = {})
+ if options[:po_number]
+ MultiResponse.run do |r|
+ r.process { authorize(money, payment, options) }
+ r.process { capture(money, r.authorization, options) }
+ end
+ else
+ post = {}
+ add_invoice(post, options)
+ add_money(post, money)
+ add_payment(post, payment)
+ add_currency(post, money, options)
+ add_address(post, options)
+ add_customer_data(post, options)
+ add_3DS(post, options)
+ post[:capture] = 'Y'
+ commit('auth', post)
+ end
+ end
+
+ def authorize(money, payment, options = {})
+ post = {}
+ add_money(post, money)
+ add_currency(post, money, options)
+ add_invoice(post, options)
+ add_payment(post, payment)
+ add_address(post, options)
+ add_customer_data(post, options)
+ add_3DS(post, options)
+ commit('auth', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_money(post, money)
+ add_reference(post, authorization)
+ add_additional_data(post, options)
+ commit('capture', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ add_money(post, money)
+ add_reference(post, authorization)
+ commit('refund', post)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ add_reference(post, authorization)
+ commit('void', post)
+ end
+
+ def verify(credit_card, options = {})
+ authorize(0, credit_card, options)
+ end
+
+ def store(payment, options = {})
+ post = {}
+ add_payment(post, payment)
+ add_address(post, options)
+ add_customer_data(post, options)
+ commit('profile', post)
+ end
+
+ def unstore(authorization, options = {})
+ account_id, profile_id = authorization.split('|')
+ commit('profile', {},
+ verb: :delete,
+ path: "/#{profile_id}/#{account_id}/#{@options[:merchant_id]}")
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(("cvv2\\":\\")\d*), '\1[FILTERED]').
+ gsub(%r(("merchid\\":\\")\d*), '\1[FILTERED]').
+ gsub(%r((&?"account\\":\\")\d*), '\1[FILTERED]').
+ gsub(%r((&?"token\\":\\")\d*), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_customer_data(post, options)
+ post[:email] = options[:email] if options[:email]
+ end
+
+ def add_address(post, options)
+ if address = options[:billing_address] || options[:address]
+ post[:address] = address[:address1] if address[:address1]
+ post[:address].concat(" #{address[:address2]}") if address[:address2]
+ post[:city] = address[:city] if address[:city]
+ post[:region] = address[:state] if address[:state]
+ post[:country] = address[:country] if address[:country]
+ post[:postal] = address[:zip] if address[:zip]
+ post[:phone] = address[:phone] if address[:phone]
+ end
+ end
+
+ def add_money(post, money)
+ post[:amount] = amount(money)
+ end
+
+ def add_currency(post, money, options)
+ post[:currency] = (options[:currency] || currency(money))
+ end
+
+ def add_invoice(post, options)
+ post[:orderid] = options[:order_id]
+ post[:ecomind] = (options[:recurring] ? 'R' : 'E')
+ end
+
+ def add_payment(post, payment)
+ if payment.is_a?(String)
+ account_id, profile_id = payment.split('|')
+ post[:profile] = profile_id
+ post[:acctid] = account_id
+ else
+ post[:name] = payment.name
+ if card_brand(payment) == 'check'
+ add_echeck(post, payment)
+ else
+ post[:account] = payment.number
+ post[:expiry] = expdate(payment)
+ post[:cvv2] = payment.verification_value
+ end
+ end
+ end
+
+ def add_echeck(post, payment)
+ post[:accttype] = 'ECHK'
+ post[:account] = payment.account_number
+ post[:bankaba] = payment.routing_number
+ end
+
+ def add_reference(post, authorization)
+ post[:retref] = authorization
+ end
+
+ def add_additional_data(post, options)
+ post[:ponumber] = options[:po_number]
+ post[:taxamnt] = options[:tax_amount] if options[:tax_amount]
+ post[:frtamnt] = options[:freight_amount] if options[:freight_amount]
+ post[:dutyamnt] = options[:duty_amount] if options[:duty_amount]
+ post[:orderdate] = options[:order_date] if options[:order_date]
+ post[:shipfromzip] = options[:ship_from_zip] if options[:ship_from_zip]
+ if (shipping_address = options[:shipping_address])
+ post[:shiptozip] = shipping_address[:zip]
+ post[:shiptocountry] = shipping_address[:country]
+ end
+ if options[:items]
+ post[:items] = options[:items].map do |item|
+ updated = {}
+ item.each_pair do |k, v|
+ updated.merge!(k.to_s.gsub(/_/, '') => v)
+ end
+ updated
+ end
+ end
+ end
+
+ def add_3DS(post, options)
+ post[:secureflag] = options[:secure_flag] if options[:secure_flag]
+ post[:securevalue] = options[:secure_value] if options[:secure_value]
+ post[:securexid] = options[:secure_xid] if options[:secure_xid]
+ end
+
+ def headers
+ {
+ 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:username]}:#{@options[:password]}"),
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def expdate(credit_card)
+ "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def url(action, path)
+ if test?
+ test_url + action + path
+ else
+ (@options[:domain] || live_url) + action + path
+ end
+ end
+
+ def commit(action, parameters, verb: :put, path: '')
+ parameters[:frontendid] = application_id
+ parameters[:merchid] = @options[:merchant_id]
+ url = url(action, path)
+ response = parse(ssl_request(verb, url, post_data(parameters), headers))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['avsresp']),
+ cvv_result: CVVResult.new(response['cvvresp']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ rescue ResponseError => e
+ return Response.new(false, 'Unable to authenticate. Please check your credentials.', {}, :test => test?) if e.response.code == '401'
+ raise
+ end
+
+ def success_from(response)
+ response['respstat'] == 'A'
+ end
+
+ def message_from(response)
+ response['setlstat'] ? "#{response['resptext']} #{response['setlstat']}" : response['resptext']
+ end
+
+ def authorization_from(response)
+ if response['profileid']
+ "#{response['acctid']}|#{response['profileid']}"
+ else
+ response['retref']
+ end
+ end
+
+ def post_data(parameters = {})
+ parameters.to_json
+ end
+
+ def error_code_from(response)
+ STANDARD_ERROR_CODE_MAPPING[response['respcode']] unless success_from(response)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/card_save.rb b/lib/active_merchant/billing/gateways/card_save.rb
index 7bd9ee8e4d2..7d5920be05b 100644
--- a/lib/active_merchant/billing/gateways/card_save.rb
+++ b/lib/active_merchant/billing/gateways/card_save.rb
@@ -1,23 +1,22 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class CardSaveGateway < IridiumGateway
- #CardSave lets you handle failovers on payments by providing 3 gateways in case one happens to be down
- #URLS = ['https://gw1.cardsaveonlinepayments.com:4430/','https://gw2.cardsaveonlinepayments.com:4430/','https://gw3.cardsaveonlinepayments.com:4430/']
-
+ # CardSave lets you handle failovers on payments by providing 3 gateways in case one happens to be down
+ # URLS = ['https://gw1.cardsaveonlinepayments.com:4430/','https://gw2.cardsaveonlinepayments.com:4430/','https://gw3.cardsaveonlinepayments.com:4430/']
+
self.money_format = :cents
self.default_currency = 'GBP'
- self.supported_cardtypes = [ :visa, :switch, :maestro, :master, :solo, :american_express, :jcb ]
+ self.supported_cardtypes = [ :visa, :maestro, :master, :american_express, :jcb ]
self.supported_countries = [ 'GB' ]
self.homepage_url = 'http://www.cardsave.net/'
self.display_name = 'CardSave'
-
+
def initialize(options={})
super
@test_url = 'https://gw1.cardsaveonlinepayments.com:4430/'
@live_url = 'https://gw1.cardsaveonlinepayments.com:4430/'
end
-
+
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/card_stream.rb b/lib/active_merchant/billing/gateways/card_stream.rb
index 30dc8897f38..f5ac54fdb83 100644
--- a/lib/active_merchant/billing/gateways/card_stream.rb
+++ b/lib/active_merchant/billing/gateways/card_stream.rb
@@ -1,55 +1,108 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- #
- # CardStream supports the following credit cards, which are auto-detected by
- # the gateway based on the card number used:
- # * AM American Express
- # * Diners Club
- # * Electron
- # * JCB
- # * UK Maestro
- # * Maestro International
- # * Mastercard
- # * Solo
- # * Style
- # * Switch
- # * Visa Credit
- # * Visa Debit
- # * Visa Purchasing
- #
class CardStreamGateway < Gateway
- self.live_url = self.test_url = 'https://gateway.cardstream.com/process.ashx'
+ THREEDSECURE_REQUIRED_DEPRECATION_MESSAGE = 'Specifying the :threeDSRequired initialization option is deprecated. Please use the `:threeds_required => true` *transaction* option instead.'
+
+ self.test_url = self.live_url = 'https://gateway.cardstream.com/direct/'
self.money_format = :cents
self.default_currency = 'GBP'
- self.supported_countries = ['GB']
- self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :solo, :switch]
+ self.supported_countries = ['GB', 'US', 'CH', 'SE', 'SG', 'NO', 'JP', 'IS', 'HK', 'NL', 'CZ', 'CA', 'AU']
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro]
self.homepage_url = 'http://www.cardstream.com/'
self.display_name = 'CardStream'
- APPROVED = '00'
-
CURRENCY_CODES = {
- "AUD"=> '036',
- "CAD"=> '124',
- "CZK"=> '203',
- "DKK"=> '208',
- "HKD"=> '344',
- "ICK"=> '352',
- "JPY"=> '392',
- "NOK"=> '578',
- "SGD"=> '702',
- "SEK"=> '752',
- "CHF"=> '756',
- "GBP"=> '826',
- "USD"=> '840',
- "EUR"=> '978'
- }
-
- TRANSACTIONS = {
- :purchase => 'ESALE_KEYED',
- :refund => 'EREFUND_KEYED',
- :authorization => 'ESALE_KEYED'
+ 'AED' => '784',
+ 'ALL' => '008',
+ 'AMD' => '051',
+ 'ANG' => '532',
+ 'ARS' => '032',
+ 'AUD' => '036',
+ 'AWG' => '533',
+ 'BAM' => '977',
+ 'BBD' => '052',
+ 'BGN' => '975',
+ 'BMD' => '060',
+ 'BOB' => '068',
+ 'BRL' => '986',
+ 'BSD' => '044',
+ 'BWP' => '072',
+ 'BZD' => '084',
+ 'CAD' => '124',
+ 'CHF' => '756',
+ 'CLP' => '152',
+ 'CNY' => '156',
+ 'COP' => '170',
+ 'CRC' => '188',
+ 'CZK' => '203',
+ 'DKK' => '208',
+ 'DOP' => '214',
+ 'EGP' => '818',
+ 'EUR' => '978',
+ 'GBP' => '826',
+ 'GEL' => '981',
+ 'GIP' => '292',
+ 'GTQ' => '320',
+ 'GYD' => '328',
+ 'HKD' => '344',
+ 'HNL' => '340',
+ 'HRK' => '191',
+ 'HUF' => '348',
+ 'ISK' => '352',
+ 'IDR' => '360',
+ 'ILS' => '376',
+ 'INR' => '356',
+ 'JPY' => '392',
+ 'JMD' => '388',
+ 'KES' => '404',
+ 'KRW' => '410',
+ 'KYD' => '136',
+ 'LBP' => '422',
+ 'LKR' => '144',
+ 'MAD' => '504',
+ 'MVR' => '462',
+ 'MWK' => '454',
+ 'MXN' => '484',
+ 'MYR' => '458',
+ 'NAD' => '516',
+ 'NGN' => '566',
+ 'NIO' => '558',
+ 'NOK' => '578',
+ 'NPR' => '524',
+ 'NZD' => '554',
+ 'PAB' => '590',
+ 'PEN' => '604',
+ 'PGK' => '598',
+ 'PHP' => '608',
+ 'PKR' => '586',
+ 'PLN' => '985',
+ 'PYG' => '600',
+ 'QAR' => '634',
+ 'RON' => '946',
+ 'RSD' => '941',
+ 'RUB' => '643',
+ 'RWF' => '646',
+ 'SAR' => '682',
+ 'SEK' => '752',
+ 'SGD' => '702',
+ 'SRD' => '968',
+ 'THB' => '764',
+ 'TND' => '788',
+ 'TRY' => '949',
+ 'TTD' => '780',
+ 'TWD' => '901',
+ 'TZS' => '834',
+ 'UAH' => '980',
+ 'UGX' => '800',
+ 'USD' => '840',
+ 'UYU' => '858',
+ 'VND' => '704',
+ 'WST' => '882',
+ 'XAF' => '950',
+ 'XCD' => '951',
+ 'XOF' => '952',
+ 'ZAR' => '710'
}
CVV_CODE = {
@@ -65,11 +118,11 @@ class CardStreamGateway < Gateway
# 4 - Postcode not matched.
# 8 - Postcode partially matched.
AVS_POSTAL_MATCH = {
- "0" => nil,
- "1" => nil,
- "2" => "Y",
- "4" => "N",
- "8" => "N"
+ '0' => nil,
+ '1' => nil,
+ '2' => 'Y',
+ '4' => 'N',
+ '8' => 'N'
}
# 0 - No additional information available.
@@ -78,148 +131,237 @@ class CardStreamGateway < Gateway
# 4 - Address numeric not matched.
# 8 - Address numeric partially matched.
AVS_STREET_MATCH = {
- "0" => nil,
- "1" => nil,
- "2" => "Y",
- "4" => "N",
- "8" => "N"
+ '0' => nil,
+ '1' => nil,
+ '2' => 'Y',
+ '4' => 'N',
+ '8' => 'N'
}
def initialize(options = {})
- requires!(options, :login, :password)
+ requires!(options, :login, :shared_secret)
+ @threeds_required = false
+ if options[:threeDSRequired]
+ ActiveMerchant.deprecated(THREEDSECURE_REQUIRED_DEPRECATION_MESSAGE)
+ @threeds_required = options[:threeDSRequired]
+ end
super
end
- def purchase(money, credit_card, options = {})
- requires!(options, :order_id)
-
+ def authorize(money, credit_card_or_reference, options = {})
post = {}
+ add_pair(post, :captureDelay, -1)
+ add_amount(post, money, options)
+ add_invoice(post, credit_card_or_reference, money, options)
+ add_credit_card_or_reference(post, credit_card_or_reference)
+ add_customer_data(post, options)
+ add_remote_address(post, options)
+ commit('SALE', post)
+ end
+ def purchase(money, credit_card_or_reference, options = {})
+ post = {}
+ add_pair(post, :captureDelay, 0)
add_amount(post, money, options)
- add_invoice(post, money, credit_card, options)
- add_credit_card(post, credit_card)
- add_address(post, options)
+ add_invoice(post, credit_card_or_reference, money, options)
+ add_credit_card_or_reference(post, credit_card_or_reference)
add_customer_data(post, options)
+ add_remote_address(post, options)
+ commit('SALE', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_pair(post, :xref, authorization)
+ add_pair(post, :amount, amount(money), :required => true)
+ add_remote_address(post, options)
- commit(:purchase, post)
+ commit('CAPTURE', post)
end
- private
+ def refund(money, authorization, options = {})
+ post = {}
+ add_pair(post, :xref, authorization)
+ add_amount(post, money, options)
+ add_remote_address(post, options)
+ response = commit('REFUND_SALE', post)
- def add_amount(post, money, options)
- add_pair(post, :Amount, amount(money), :required => true)
- add_pair(post, :CurrencyCode, currency_code(options[:currency] || currency(money)), :required => true)
+ return response if response.success?
+ return response unless options[:force_full_refund_if_unsettled]
+
+ if response.params['responseCode'] == '65541'
+ void(authorization, options)
+ else
+ response
+ end
end
- def add_customer_data(post, options)
- add_pair(post, :BillingEmail, options[:email])
- add_pair(post, :BillingPhoneNumber, options[:phone])
+ def void(authorization, options = {})
+ post = {}
+ add_pair(post, :xref, authorization)
+ add_remote_address(post, options)
+ commit('CANCEL', post)
end
- def add_address(post, options)
- address = options[:billing_address] || options[:address]
+ def verify(creditcard, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
- return if address.nil?
+ def supports_scrubbing?
+ true
+ end
- add_pair(post, :BillingStreet, address[:address1])
- add_pair(post, :BillingHouseNumber, address[:address2])
- add_pair(post, :BillingCity, address[:city])
- add_pair(post, :BillingState, address[:state])
- add_pair(post, :BillingPostCode, address[:zip])
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((cardNumber=)\d+), '\1[FILTERED]').
+ gsub(%r((CVV=)\d+), '\1[FILTERED]')
end
- def add_invoice(post, money, credit_card, options)
- add_pair(post, :TransactionUnique, options[:order_id], :required => true)
- add_pair(post, :OrderDesc, options[:description] || options[:order_id], :required => true)
+ private
- if [ 'american_express', 'diners_club' ].include?(card_brand(credit_card).to_s)
- add_pair(post, :AEIT1Quantity, 1)
- add_pair(post, :AEIT1Description, (options[:description] || options[:order_id]).slice(0, 15))
- add_pair(post, :AEIT1GrossValue, amount(money))
- end
+ def add_amount(post, money, options)
+ add_pair(post, :amount, amount(money), :required => true)
+ add_pair(post, :currencyCode, currency_code(options[:currency] || currency(money)))
end
- def add_credit_card(post, credit_card)
- add_pair(post, :CardName, credit_card.name, :required => true)
- add_pair(post, :CardNumber, credit_card.number, :required => true)
+ def add_customer_data(post, options)
+ add_pair(post, :customerEmail, options[:email])
+ if (address = options[:billing_address] || options[:address])
+ add_pair(post, :customerAddress, "#{address[:address1]} #{address[:address2]}".strip)
+ add_pair(post, :customerPostCode, address[:zip])
+ add_pair(post, :customerPhone, options[:phone])
+ add_pair(post, :customerCountryCode, address[:country] || 'GB')
+ else
+ add_pair(post, :customerCountryCode, 'GB')
+ end
+ end
- add_pair(post, :ExpiryDateMM, format(credit_card.month, :two_digits), :required => true)
- add_pair(post, :ExpiryDateYY, format(credit_card.year, :two_digits), :required => true)
+ def add_invoice(post, credit_card_or_reference, money, options)
+ add_pair(post, :transactionUnique, options[:order_id], :required => true)
+ add_pair(post, :orderRef, options[:description] || options[:order_id], :required => true)
+ add_pair(post, :statementNarrative1, options[:merchant_name]) if options[:merchant_name]
+ add_pair(post, :statementNarrative2, options[:dynamic_descriptor]) if options[:dynamic_descriptor]
+ if credit_card_or_reference.respond_to?(:number)
+ if ['american_express', 'diners_club'].include?(card_brand(credit_card_or_reference).to_s)
+ add_pair(post, :item1Quantity, 1)
+ add_pair(post, :item1Description, (options[:description] || options[:order_id]).slice(0, 15))
+ add_pair(post, :item1GrossValue, amount(money))
+ end
+ end
- if requires_start_date_or_issue_number?(credit_card)
- add_pair(post, :StartDateMM, format(credit_card.start_month, :two_digits))
- add_pair(post, :StartDateYY, format(credit_card.start_year, :two_digits))
+ add_pair(post, :type, options[:type] || '1')
+ add_threeds_required(post, options)
+ end
- add_pair(post, :IssueNumber, credit_card.issue_number)
+ def add_credit_card_or_reference(post, credit_card_or_reference)
+ if credit_card_or_reference.respond_to?(:number)
+ add_credit_card(post, credit_card_or_reference)
+ else
+ add_reference(post, credit_card_or_reference.to_s)
end
+ end
- add_pair(post, :CV2, credit_card.verification_value)
+ def add_reference(post, reference)
+ add_pair(post, :xref, reference, :required => true)
end
- def commit(action, parameters)
- response = parse( ssl_post(self.live_url, post_data(action, parameters)) )
+ def add_credit_card(post, credit_card)
+ add_pair(post, :customerName, credit_card.name, :required => true)
+ add_pair(post, :cardNumber, credit_card.number, :required => true)
+ add_pair(post, :cardExpiryMonth, format(credit_card.month, :two_digits), :required => true)
+ add_pair(post, :cardExpiryYear, format(credit_card.year, :two_digits), :required => true)
+ add_pair(post, :cardCVV, credit_card.verification_value)
+ end
- Response.new(response[:response_code] == APPROVED, message_from(response), response,
- :test => test?,
- :authorization => response[:cross_reference],
- :cvv_result => CVV_CODE[ response[:avscv2_response_code].to_s[0, 1] ],
- :avs_result => {
- :street_match => AVS_STREET_MATCH[ response[:avscv2_response_code].to_s[2, 1] ],
- :postal_match => AVS_POSTAL_MATCH[ response[:avscv2_response_code].to_s[1, 1] ]
- }
- )
+ def add_threeds_required(post, options)
+ add_pair(post, :threeDSRequired, options[:threeds_required] || @threeds_required ? 'Y' : 'N')
end
- def message_from(results)
- results[:response_code] == APPROVED ? "APPROVED" : results[:message]
+ def add_remote_address(post, options={})
+ add_pair(post, :remoteAddress, options[:ip] || '1.1.1.1')
end
- def post_data(action, parameters = {})
- parameters.update(
- :MerchantPassword => @options[:password],
- :MerchantID => @options[:login],
- :MessageType => TRANSACTIONS[action],
- :CallBack => "disable",
- :DuplicateDelay => "0",
- :EchoCardType => "YES",
- :EchoAmount => "YES",
- :EchoAVSCV2ResponseCode => "YES",
- :ReturnAVSCV2Message => "YES",
- :CountryCode => '826' # 826 for UK based merchant
- )
+ def normalize_line_endings(str)
+ str.gsub(/%0D%0A|%0A%0D|%0D/, '%0A')
+ end
- add_pair(parameters, :Dispatch, action == :authorization ? "LATER" : "NOW")
+ def add_hmac(post)
+ result = post.sort.collect { |key, value| "#{key}=#{normalize_line_endings(CGI.escape(value.to_s))}" }.join('&')
+ result = Digest::SHA512.hexdigest("#{result}#{@options[:shared_secret]}")
- parameters.collect { |key, value| "VP#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ add_pair(post, :signature, result)
end
- # VPCrossReference
- # The value in VPCrossReference on a success transaction will contain
- # a unique reference that you may use to run future transactions.
- # Please note that cross reference transactions must come a static IP
- # addressed that has been pre-registered with Cardstream. To
- # register an IP address please send it to support@cardstream.com
- # with your Cardstream issued merchant ID and it will be added to
- # your account.
def parse(body)
result = {}
- pairs = body.split("&")
+ pairs = body.split('&')
pairs.each do |pair|
- a = pair.split("=")
- result[a[0].gsub(/^VP/,'').underscore.to_sym] = a[1]
+ a = pair.split('=')
+ # because some value pairs don't have a value
+ result[a[0].to_sym] = a[1] == nil ? '' : CGI.unescape(a[1])
end
-
result
end
+ def commit(action, parameters)
+ parameters.update(:countryCode => self.supported_countries[0]) unless ['CAPTURE', 'CANCEL'].include?(action)
+ parameters.update(
+ :merchantID => @options[:login],
+ :action => action
+ )
+ # adds a signature to the post hash/array
+ add_hmac(parameters)
+
+ response = parse(ssl_post(self.live_url, post_data(action, parameters)))
+
+ Response.new(
+ response[:responseCode] == '0',
+ response[:responseCode] == '0' ? 'APPROVED' : response[:responseMessage],
+ response,
+ :test => test?,
+ :authorization => response[:xref],
+ :cvv_result => CVV_CODE[response[:avscv2ResponseCode].to_s[0, 1]],
+ :avs_result => avs_from(response)
+ )
+ end
+
+ def avs_from(response)
+ postal_match = AVS_POSTAL_MATCH[response[:avscv2ResponseCode].to_s[1, 1]]
+ street_match = AVS_STREET_MATCH[response[:avscv2ResponseCode].to_s[2, 1]]
+
+ code = if postal_match == 'Y' && street_match == 'Y'
+ 'M'
+ elsif postal_match == 'Y'
+ 'P'
+ elsif street_match == 'Y'
+ 'A'
+ else
+ 'I'
+ end
+
+ AVSResult.new({
+ :code => code,
+ :postal_match => postal_match,
+ :street_match => street_match
+ })
+ end
+
def currency_code(currency)
CURRENCY_CODES[currency]
end
+ def post_data(action, parameters = {})
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
def add_pair(post, key, value, options = {})
post[key] = value if !value.blank? || options[:required]
end
+
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/card_stream_modern.rb b/lib/active_merchant/billing/gateways/card_stream_modern.rb
deleted file mode 100644
index 1d3393fa22d..00000000000
--- a/lib/active_merchant/billing/gateways/card_stream_modern.rb
+++ /dev/null
@@ -1,155 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class CardStreamModernGateway < Gateway
- self.test_url = self.live_url = 'https://gateway.cardstream.com/direct/'
- self.money_format = :cents
- self.default_currency = 'GBP'
- self.supported_countries = ['GB']
- self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :solo, :switch]
- self.homepage_url = 'http://www.cardstream.com/'
- self.display_name = 'CardStream'
-
- def initialize(options = {})
- requires!(options, :login)
- if(options[:threeDSRequired])
- @threeDSRequired = options[:threeDSRequired]
- else
- @threeDSRequired = 'N'
- end
- super
- end
-
- def authorize(money, creditcard, options = {})
- post = {}
- add_amount(post, money, options)
- add_invoice(post, creditcard, money, options)
- add_creditcard(post, creditcard)
- add_address(post, creditcard, options)
- add_customer_data(post, options)
- commit('PREAUTH', post)
- end
-
- def purchase(money, creditcard, options = {})
- post = {}
- add_amount(post, money, options)
- add_invoice(post, creditcard, money, options)
- add_creditcard(post, creditcard)
- add_address(post, creditcard, options)
- add_customer_data(post, options)
- commit('SALE', post)
- end
-
- def capture(money, authorization, options = {})
- post = {}
- add_pair(post, :xref, authorization)
- add_amount(post, money, options)
- commit('SALE', post)
- end
-
- def refund(money, authorization, options = {})
- post = {}
- add_pair(post, :xref, authorization)
- add_amount(post, money, options)
- commit('REFUND', post)
- end
-
- def void(authorization, options = {})
- post = {}
- add_pair(post, :xref, authorization)
- commit('REFUND', post)
- end
-
- private
-
- def add_amount(post, money, options)
- add_pair(post, :amount, amount(money), :required => true)
- add_pair(post, :currencyCode, options[:currency] || self.default_currency)
- end
-
- def add_customer_data(post, options)
- address = options[:billing_address] || options[:address]
- add_pair(post, :customerPostCode, address[:zip])
- add_pair(post, :customerEmail, options[:email])
- add_pair(post, :customerPhone, options[:phone])
- end
-
- def add_address(post, creditcard, options)
- address = options[:billing_address] || options[:address]
-
- return if address.nil?
-
- add_pair(post, :customerAddress, address[:address1] + " " + (address[:address2].nil? ? "" : address[:address2]) )
- add_pair(post, :customerPostCode, address[:zip])
- end
-
- def add_invoice(post, credit_card, money, options)
- add_pair(post, :transactionUnique, options[:order_id], :required => true)
- add_pair(post, :orderRef, options[:description] || options[:order_id], :required => true)
- if [ 'american_express', 'diners_club' ].include?(card_brand(credit_card).to_s)
- add_pair(post, :item1Quantity, 1)
- add_pair(post, :item1Description, (options[:description] || options[:order_id]).slice(0, 15))
- add_pair(post, :item1GrossValue, amount(money))
- end
- end
-
- def add_creditcard(post, credit_card)
- add_pair(post, :customerName, credit_card.name, :required => true)
- add_pair(post, :cardNumber, credit_card.number, :required => true)
-
- add_pair(post, :cardExpiryMonth, format(credit_card.month, :two_digits), :required => true)
- add_pair(post, :cardExpiryYear, format(credit_card.year, :two_digits), :required => true)
-
- if requires_start_date_or_issue_number?(credit_card)
- add_pair(post, :cardStartMonth, format(credit_card.start_month, :two_digits))
- add_pair(post, :cardStartYear, format(credit_card.start_year, :two_digits))
-
- add_pair(post, :cardIssueNumber, credit_card.issue_number)
- end
-
- add_pair(post, :cardCVV, credit_card.verification_value)
- end
-
- def parse(body)
- result = {}
- pairs = body.split("&")
- pairs.each do |pair|
- a = pair.split("=")
- result[a[0].to_sym] = CGI.unescape(a[1])
- end
- result
- end
-
- def commit(action, parameters)
- response = parse( ssl_post(self.live_url, post_data(action, parameters)) )
-
- Response.new(response[:responseCode] == "0",
- response[:responseCode] == "0" ? "APPROVED" : response[:responseMessage],
- response,
- :test => test?,
- :authorization => response[:xref],
- :avs_result => {
- :street_match => response[:addressCheck],
- :postal_match => response[:postcodeCheck],
- },
- :cvv_result => response[:cv2Check]
- )
- end
-
- def post_data(action, parameters = {})
- parameters.update(
- :merchantID => @options[:login],
- :action => action,
- :type => '1', #Ecommerce
- :countryCode => self.supported_countries[0],
- :threeDSRequired => @threeDSRequired #Disable 3d secure by default
- )
- parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
- end
-
- def add_pair(post, key, value, options = {})
- post[key] = value if !value.blank? || options[:required]
- end
- end
- end
-end
-
diff --git a/lib/active_merchant/billing/gateways/cardknox.rb b/lib/active_merchant/billing/gateways/cardknox.rb
new file mode 100644
index 00000000000..938e6ade478
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cardknox.rb
@@ -0,0 +1,327 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CardknoxGateway < Gateway
+ self.live_url = 'https://x1.cardknox.com/gateway'
+
+ self.supported_countries = ['US', 'CA', 'GB']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+
+ self.homepage_url = 'https://www.cardknox.com/'
+ self.display_name = 'Cardknox'
+
+ COMMANDS = {
+ credit_card: {
+ purchase: 'cc:sale',
+ authorization: 'cc:authonly',
+ capture: 'cc:capture',
+ refund: 'cc:refund',
+ void: 'cc:void',
+ save: 'cc:save'
+ },
+ check: {
+ purchase: 'check:sale',
+ refund: 'check:refund',
+ void: 'check:void',
+ save: 'check:save'
+ }
+ }
+
+ def initialize(options={})
+ requires!(options, :api_key)
+ super
+ end
+
+ # There are three sources for doing a purchase transation:
+ # - credit card
+ # - check
+ # - cardknox token, which is returned in the the authorization string "ref_num;token;command"
+
+ def purchase(amount, source, options={})
+ post = {}
+ add_amount(post, amount, options)
+ add_invoice(post, options)
+ add_source(post, source)
+ add_address(post, source, options)
+ add_customer_data(post, options)
+ add_custom_fields(post, options)
+ commit(:purchase, source_type(source), post)
+ end
+
+ def authorize(amount, source, options={})
+ post = {}
+ add_amount(post, amount)
+ add_invoice(post, options)
+ add_source(post, source)
+ add_address(post, source, options)
+ add_customer_data(post, options)
+ add_custom_fields(post, options)
+ commit(:authorization, source_type(source), post)
+ end
+
+ def capture(amount, authorization, options = {})
+ post = {}
+ add_reference(post, authorization)
+ add_amount(post, amount)
+ commit(:capture, source_type(authorization), post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_amount(post, amount)
+ commit(:refund, source_type(authorization), post)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ add_reference(post, authorization)
+ commit(:void, source_type(authorization), 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(source, options = {})
+ post = {}
+ add_source(post, source)
+ add_address(post, source, options)
+ add_invoice(post, options)
+ add_customer_data(post, options)
+ add_custom_fields(post, options)
+ commit(:save, source_type(source), post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((xCardNum=)\d+), '\1[FILTERED]').
+ gsub(%r((xCVV=)\d+), '\1[FILTERED]').
+ gsub(%r((xAccount=)\d+), '\1[FILTERED]').
+ gsub(%r((xRouting=)\d+), '\1[FILTERED]').
+ gsub(%r((xKey=)\w+), '\1[FILTERED]')
+ end
+
+ private
+
+ def split_authorization(authorization)
+ authorization.split(';')
+ end
+
+ def add_reference(post, reference)
+ reference, _, _ = split_authorization(reference)
+ post[:Refnum] = reference
+ end
+
+ def source_type(source)
+ if source.respond_to?(:brand)
+ :credit_card
+ elsif source.respond_to?(:routing_number)
+ :check
+ elsif source.kind_of?(String)
+ source_type_from(source)
+ else
+ raise ArgumentError, "Unknown source #{source.inspect}"
+ end
+ end
+
+ def source_type_from(authorization)
+ _, _, source_type = split_authorization(authorization)
+ (source_type || 'credit_card').to_sym
+ end
+
+ def add_source(post, source)
+ if source.respond_to?(:brand)
+ add_credit_card(post, source)
+ elsif source.respond_to?(:routing_number)
+ add_check(post, source)
+ elsif source.kind_of?(String)
+ add_cardknox_token(post, source)
+ else
+ raise ArgumentError, "Invalid payment source #{source.inspect}"
+ end
+ end
+
+ # Subtotal + Tax + Tip = Amount.
+
+ def add_amount(post, money, options = {})
+ post[:Tip] = amount(options[:tip])
+ post[:Amount] = amount(money)
+ end
+
+ def expdate(credit_card)
+ year = format(credit_card.year, :two_digits)
+ month = format(credit_card.month, :two_digits)
+ "#{month}#{year}"
+ end
+
+ def add_customer_data(post, options)
+ address = options[:billing_address] || {}
+ post[:Street] = address[:address1]
+ post[:Zip] = address[:zip]
+ post[:PONum] = options[:po_number]
+ post[:Fax] = options[:fax]
+ post[:Email] = options[:email]
+ post[:IP] = options[:ip]
+ end
+
+ def add_address(post, source, options)
+ add_address_for_type(:billing, post, source, options[:billing_address]) if options[:billing_address]
+ add_address_for_type(:shipping, post, source, options[:shipping_address]) if options[:shipping_address]
+ end
+
+ def add_address_for_type(type, post, source, address)
+ prefix = address_key_prefix(type)
+ if source.respond_to?(:first_name)
+ post[address_key(prefix, 'FirstName')] = source.first_name
+ post[address_key(prefix, 'LastName')] = source.last_name
+ else
+ post[address_key(prefix, 'FirstName')] = address[:first_name]
+ post[address_key(prefix, 'LastName')] = address[:last_name]
+ end
+ post[address_key(prefix, 'MiddleName')] = address[:middle_name]
+
+ post[address_key(prefix, 'Company')] = address[:company]
+ post[address_key(prefix, 'Street')] = address[:address1]
+ post[address_key(prefix, 'Street2')] = address[:address2]
+ post[address_key(prefix, 'City')] = address[:city]
+ post[address_key(prefix, 'State')] = address[:state]
+ post[address_key(prefix, 'Zip')] = address[:zip]
+ post[address_key(prefix, 'Country')] = address[:country]
+ post[address_key(prefix, 'Phone')] = address[:phone]
+ post[address_key(prefix, 'Mobile')] = address[:mobile]
+ end
+
+ def address_key_prefix(type)
+ case type
+ when :shipping then 'Ship'
+ when :billing then 'Bill'
+ else
+ raise ArgumentError, "Unknown address key prefix: #{type}"
+ end
+ end
+
+ def address_key(prefix, key)
+ "#{prefix}#{key}".to_sym
+ end
+
+ def add_invoice(post, options)
+ post[:Invoice] = options[:invoice]
+ post[:OrderID] = options[:order_id]
+ post[:Comments] = options[:comments]
+ post[:Description] = options[:description]
+ post[:Tax] = amount(options[:tax])
+ end
+
+ def add_custom_fields(post, options)
+ options.keys.grep(/^custom(?:[01]\d|20)$/) do |key|
+ post[key.to_s.capitalize] = options[key]
+ end
+ end
+
+ def add_credit_card(post, credit_card)
+ if credit_card.track_data.present?
+ post[:Magstripe] = credit_card.track_data
+ post[:Cardpresent] = true
+ else
+ post[:CardNum] = credit_card.number
+ post[:CVV] = credit_card.verification_value
+ post[:Exp] = expdate(credit_card)
+ post[:Name] = credit_card.name
+ post[:CardPresent] = true if credit_card.manual_entry
+ end
+ end
+
+ def add_check(post, check)
+ post[:Routing] = check.routing_number
+ post[:Account] = check.account_number
+ post[:Name] = check.name
+ post[:CheckNum] = check.number
+ end
+
+ def add_cardknox_token(post, authorization)
+ _, token, _ = split_authorization(authorization)
+
+ post[:Token] = token
+ end
+
+ def parse(body)
+ fields = {}
+ for line in body.split('&')
+ key, value = *line.scan(%r{^(\w+)\=(.*)$}).flatten
+ fields[key] = CGI.unescape(value.to_s)
+ end
+
+ {
+ result: fields['xResult'],
+ status: fields['xStatus'],
+ error: fields['xError'],
+ auth_code: fields['xAuthCode'],
+ ref_num: fields['xRefNum'],
+ current_ref_num: fields['xRefNumCurrent'],
+ token: fields['xToken'],
+ batch: fields['xBatch'],
+ avs_result: fields['xAvsResult'],
+ avs_result_code: fields['xAvsResultCode'],
+ cvv_result: fields['xCvvResult'],
+ cvv_result_code: fields['xCvvResultCode'],
+ remaining_balance: fields['xRemainingBalance'],
+ amount: fields['xAuthAmount'],
+ masked_card_num: fields['xMaskedCardNumber'],
+ masked_account_number: fields['MaskedAccountNumber']
+ }.delete_if { |k, v| v.nil? }
+ end
+
+ def commit(action, source_type, parameters)
+ response = parse(ssl_post(live_url, post_data(COMMANDS[source_type][action], parameters)))
+
+ Response.new(
+ (response[:status] == 'Approved'),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, source_type),
+ avs_result: { code: response[:avs_result_code] },
+ cvv_result: response[:cvv_result_code]
+ )
+ end
+
+ def message_from(response)
+ if response[:status] == 'Approved'
+ 'Success'
+ elsif response[:error].blank?
+ 'Unspecified error'
+ else
+ response[:error]
+ end
+ end
+
+ def authorization_from(response, source_type)
+ "#{response[:ref_num]};#{response[:token]};#{source_type}"
+ end
+
+ def post_data(command, parameters = {})
+ initial_parameters = {
+ Key: @options[:api_key],
+ Version: '4.5.4',
+ SoftwareName: 'Active Merchant',
+ SoftwareVersion: ActiveMerchant::VERSION.to_s,
+ Command: command,
+ }
+
+ seed = SecureRandom.hex(32).upcase
+ hash = Digest::SHA1.hexdigest("#{initial_parameters[:command]}:#{@options[:pin]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}")
+ initial_parameters[:Hash] = "s/#{seed}/#{hash}/n" unless @options[:pin].blank?
+ parameters = initial_parameters.merge(parameters)
+
+ parameters.reject { |k, v| v.blank? }.collect { |key, value| "x#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cardprocess.rb b/lib/active_merchant/billing/gateways/cardprocess.rb
new file mode 100644
index 00000000000..c91121c0641
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cardprocess.rb
@@ -0,0 +1,254 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CardprocessGateway < Gateway
+ self.test_url = 'https://test.vr-pay-ecommerce.de/v1/payments'
+ self.live_url = 'https://vr-pay-ecommerce.de/v1/payments'
+
+ self.supported_countries = %w[ BE BG CZ DK DE EE IE ES FR HR IT CY LV LT LU
+ MT HU NL AT PL PT RO SI SK FI SE GB IS LI NO
+ CH ME MK AL RS TR BA ]
+ self.default_currency = 'EUR'
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
+
+ self.homepage_url = 'https://vr-pay-ecommerce.docs.oppwa.com/'
+ self.display_name = 'CardProcess VR-Pay'
+ self.money_format = :dollars
+
+ STANDARD_ERROR_CODE_MAPPING = {}
+
+ # Creates a new CardProcess Gateway
+ #
+ # The gateway requires a valid login, password, and entity ID
+ # to be passed in the +options+ hash.
+ #
+ # === Options
+ #
+ # * :user_id -- The CardProcess user ID
+ # * :password -- The CardProcess password
+ # * :entity_id -- The CardProcess channel or entity ID for any transactions
+ def initialize(options={})
+ requires!(options, :user_id, :password, :entity_id)
+ super
+ # This variable exists purely to allow remote tests to force error codes;
+ # the lack of a setter despite its usage is intentional.
+ @test_options = {}
+ end
+
+ def purchase(money, payment, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ commit('DB', post)
+ end
+
+ def authorize(money, payment, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ commit('PA', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {
+ id: authorization
+ }
+ add_invoice(post, money, options)
+ commit('CP', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {
+ id: authorization
+ }
+ add_invoice(post, money, options)
+ commit('RF', post)
+ end
+
+ def credit(money, payment, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ commit('CD', post)
+ end
+
+ def void(authorization, _options = {})
+ post = {
+ id: authorization
+ }
+ commit('RV', post)
+ end
+
+ def verify(credit_card, options = {})
+ MultiResponse.run do |r|
+ r.process { authorize(100, credit_card, options) }
+ r.process { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r{(authentication\.[^=]+=)[^&]+}, '\1[FILTERED]').
+ gsub(%r{(card\.number=)\d+}, '\1[FILTERED]').
+ gsub(%r{(cvv=)\d{3,4}}, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_customer_data(post, options)
+ post['customer.ip'] = options[:ip] if options[:ip]
+ end
+
+ def add_address(post, _card, options)
+ if (address = options[:billing_address] || options[:address])
+ post[:billing] = hashify_address(address)
+ end
+
+ if (shipping = options[:shipping_address])
+ post[:shipping] = hashify_address(shipping)
+ end
+ end
+
+ def add_invoice(post, money, options)
+ return if money.nil?
+ post[:amount] = amount(money)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:merchantInvoiceId] = options[:merchant_invoice_id] if options[:merchant_invoice_id]
+ post[:merchantTransactionId] = options[:merchant_transaction_id] if options[:merchant_transaction_id]
+ post[:transactionCategory] = options[:transaction_category] if options[:transaction_category]
+ end
+
+ def add_payment(post, payment)
+ return if payment.is_a?(String)
+ post[:paymentBrand] = payment.brand.upcase if payment.brand
+ post[:card] ||= {}
+ post[:card][:number] = payment.number
+ post[:card][:holder] = payment.name
+ post[:card][:expiryMonth] = sprintf('%02d', payment.month)
+ post[:card][:expiryYear] = sprintf('%02d', payment.year)
+ post[:card][:cvv] = payment.verification_value
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, parameters)
+ url = (test? ? test_url : live_url)
+ if (id = parameters.delete(:id))
+ url += "/#{id}"
+ end
+
+ begin
+ raw_response = ssl_post(url, post_data(action, parameters.merge(@test_options)))
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
+ response = parse(raw_response)
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['result']['avsResponse']),
+ cvv_result: CVVResult.new(response['result']['cvvResponse']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def success_from(response)
+ !(response['result']['code'] =~ /^(000\.000\.|000\.100\.1|000\.[36])/).nil?
+ end
+
+ def message_from(response)
+ response['result']['description']
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def post_data(action, parameters = {})
+ post = parameters.clone
+ post[:authentication] ||= {}
+ post[:authentication][:userId] = @options[:user_id]
+ post[:authentication][:password] = @options[:password]
+ post[:authentication][:entityId] = @options[:entity_id]
+ post[:paymentType] = action
+ dot_flatten_hash(post).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ def error_code_from(response)
+ unless success_from(response)
+ case response['result']['code']
+ when '100.100.101'
+ STANDARD_ERROR_CODE[:incorrect_number]
+ when '100.100.303'
+ STANDARD_ERROR_CODE[:expired_card]
+ when /100\.100\.(201|301|305)/
+ STANDARD_ERROR_CODE[:invalid_expiry_date]
+ when /100.100.60[01]/
+ STANDARD_ERROR_CODE[:invalid_cvc]
+ when '800.100.151'
+ STANDARD_ERROR_CODE[:invalid_number]
+ when '800.100.153'
+ STANDARD_ERROR_CODE[:incorrect_cvc]
+ when /800.800.(102|302)/
+ STANDARD_ERROR_CODE[:incorrect_address]
+ when '800.800.202'
+ STANDARD_ERROR_CODE[:invalid_zip]
+ when '800.100.166'
+ STANDARD_ERROR_CODE[:incorrect_pin]
+ when '800.100.171'
+ STANDARD_ERROR_CODE[:pickup_card]
+ when /^(200|700)\./
+ STANDARD_ERROR_CODE[:config_error]
+ when /^(800\.[17]00|800\.800\.[123])/
+ STANDARD_ERROR_CODE[:card_declined]
+ when /^(900\.[1234]00)/
+ STANDARD_ERROR_CODE[:processing_error]
+ else
+ STANDARD_ERROR_CODE[:processing_error]
+ end
+ end
+ end
+
+ def hashify_address(address)
+ hash = {}
+ hash[:street1] = address[:address1] if address[:address1]
+ hash[:street2] = address[:address2] if address[:address2]
+ hash[:city] = address[:city] if address[:city]
+ hash[:state] = address[:state] if address[:state]
+ hash[:postcode] = address[:zip] if address[:zip]
+ hash[:country] = address[:country] if address[:country]
+ hash
+ end
+
+ def dot_flatten_hash(hash, prefix = '')
+ h = {}
+ hash.each_pair do |k, v|
+ if v.is_a?(Hash)
+ h.merge!(dot_flatten_hash(v, prefix + k.to_s + '.'))
+ else
+ h[prefix + k.to_s] = v
+ end
+ end
+ h
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cashnet.rb b/lib/active_merchant/billing/gateways/cashnet.rb
new file mode 100644
index 00000000000..48a7d1308c5
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cashnet.rb
@@ -0,0 +1,219 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CashnetGateway < Gateway
+ include Empty
+
+ self.live_url = 'https://commerce.cashnet.com/'
+ self.test_url = 'https://train.cashnet.com/'
+
+ self.supported_countries = ['US']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+ self.homepage_url = 'http://www.higherone.com/'
+ self.display_name = 'Cashnet'
+ self.money_format = :dollars
+ self.max_retries = 0
+
+ # Creates a new CashnetGateway
+ #
+ # ==== Options
+ #
+ # * :merchant -- Gateway Merchant (REQUIRED)
+ # * :operator -- Operator (REQUIRED)
+ # * :password -- Password (REQUIRED)
+ # * :merchant_gateway_name -- Site name (REQUIRED)
+ # * :station -- Station (defaults to "WEB")
+ # * :custcode -- Customer code (defaults to
+ # "ActiveMerchant/#{ActiveMerchant::VERSION}")
+ # * :default_item_code -- Default item code (defaults to "FEE",
+ # can be overridden on a per-transaction basis with options[:item_code])
+ def initialize(options = {})
+ requires!(
+ options,
+ :merchant,
+ :operator,
+ :password,
+ :merchant_gateway_name
+ )
+ options[:default_item_code] ||= 'FEE'
+ super
+ end
+
+ def purchase(money, payment_object, options = {})
+ post = {}
+ add_creditcard(post, payment_object)
+ add_invoice(post, options)
+ add_address(post, options)
+ add_customer_data(post, options)
+ commit('SALE', money, post)
+ end
+
+ def refund(money, identification, options = {})
+ post = {}
+ post[:origtx] = identification
+ add_invoice(post, options)
+ add_customer_data(post, options)
+ commit('REFUND', money, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r{(password=)[^&]+}, '\1[FILTERED]').
+ gsub(%r{(cardno=)[^&]+}, '\1[FILTERED]').
+ gsub(%r{(cid=)[^&]+}, '\1[FILTERED]')
+ end
+
+ private
+
+ def commit(action, money, fields)
+ fields[:amount] = amount(money)
+ url = (test? ? test_url : live_url) + CGI.escape(@options[:merchant_gateway_name])
+ raw_response = ssl_post(url, post_data(action, fields))
+ parsed_response = parse(raw_response)
+
+ return unparsable_response(raw_response) unless parsed_response
+
+ success = (parsed_response[:result] == '0')
+ Response.new(
+ success,
+ CASHNET_CODES[parsed_response[:result]],
+ parsed_response,
+ test: test?,
+ authorization: (success ? parsed_response[:tx] : '')
+ )
+ end
+
+ def post_data(action, parameters = {})
+ post = {}
+ post[:command] = action
+ post[:merchant] = @options[:merchant]
+ post[:operator] = @options[:operator]
+ post[:password] = @options[:password]
+ post[:station] = (@options[:station] || 'WEB')
+ post[:custcode] = (@options[:custcode] || "ActiveMerchant/#{ActiveMerchant::VERSION}")
+ post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ def add_creditcard(post, creditcard)
+ post[:cardno] = creditcard.number
+ post[:cid] = creditcard.verification_value
+ post[:expdate] = expdate(creditcard)
+ post[:card_name_g] = creditcard.name
+ post[:fname] = creditcard.first_name
+ post[:lname] = creditcard.last_name
+ end
+
+ def add_invoice(post, options)
+ post[:order_number] = options[:order_id] if options[:order_id].present?
+ post[:itemcode] = (options[:item_code] || @options[:default_item_code])
+ end
+
+ def add_address(post, options)
+ if address = (options[:shipping_address] || options[:billing_address] || options[:address])
+ post[:addr_g] = String(address[:address1]) + ',' + String(address[:address2])
+ post[:city_g] = address[:city]
+ post[:state_g] = address[:state]
+ post[:zip_g] = address[:zip]
+ end
+ end
+
+ def add_customer_data(post, options)
+ post[:email_g] = options[:email]
+ post[:custcode] = options[:custcode] unless empty?(options[:custcode])
+ end
+
+ def expdate(creditcard)
+ year = format(creditcard.year, :two_digits)
+ month = format(creditcard.month, :two_digits)
+
+ "#{month}#{year}"
+ end
+
+ def parse(body)
+ match = body.match(/(.*)<\/cngateway>/)
+ return nil unless match
+
+ Hash[CGI::parse(match[1]).map { |k, v| [k.to_sym, v.first] }]
+ end
+
+ def handle_response(response)
+ if (200...300).cover?(response.code.to_i)
+ return response.body
+ elsif response.code.to_i == 302
+ return ssl_get(URI.parse(response['location']))
+ end
+ raise ResponseError.new(response)
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Unparsable response received from Cashnet. Please contact Cashnet if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+
+ CASHNET_CODES = {
+ '0' => 'Success',
+ '1' => 'Invalid customer code, no customer code specified',
+ '2' => 'Invalid operator code, no operator specified',
+ '3' => 'Invalid workstation code, no station specified',
+ '4' => 'Invalid item code, no code specified',
+ '5' => 'Negative amount is not allowed',
+ '6' => 'Invalid credit card number, no credit card number provided',
+ '7' => 'Invalid expiration date, no expiration date provided',
+ '8' => 'Please only provide either ACH or credit card information',
+ '9' => 'Invalid ACH account number, no account number provided',
+ '10' => 'Invalid routing/transit number, no routing/transit number provided',
+ '11' => 'Invalid account type, no account type provided',
+ '12' => 'Invalid check digit for routing/transit number',
+ '13' => 'No ACH merchant account set up for the location of the station being used',
+ '21' => 'Invalid merchant code, no merchant code provided',
+ '22' => 'Invalid client code, no client code provided',
+ '23' => 'Invalid password, no password provided',
+ '24' => 'Invalid transaction type, no transaction type provided',
+ '25' => 'Invalid amount, amount not provided',
+ '26' => 'Invalid payment code provided',
+ '27' => 'Invalid version number, version not found',
+ '31' => 'Application amount exceeds account balance',
+ '150' => 'Invalid payment information, no payment information provided',
+ '200' => 'Invalid command',
+ '201' => 'Customer not on file',
+ '205' => 'Invalid operator or password',
+ '206' => 'Operator is not authorized for this function',
+ '208' => 'Customer/PIN authentication unsuccessful',
+ '209' => 'Credit card error',
+ '211' => 'Credit card error',
+ '212' => 'Customer/PIN not on file',
+ '213' => 'Customer information not on file',
+ '215' => 'Old PIN does not validate ',
+ '221' => 'Invalid credit card processor type specified in location or payment code',
+ '222' => 'Credit card processor error',
+ '280' => 'SmartPay transaction not posted',
+ '301' => 'Original transaction not found for this customer',
+ '302' => 'Amount to refund exceeds original payment amount or is missing',
+ '304' => 'Original credit card payment not found or corrupted',
+ '305' => 'Refund amounts should be expressed as positive amounts',
+ '306' => 'Original ACH payment not found',
+ '307' => 'Original electronic payment not found',
+ '308' => 'Invalid original transaction number, original transaction number not found',
+ '310' => 'Refund amount exceeds amount still available for a refund',
+ '321' => 'Store has not been implemented',
+ '501' => 'Unable to roll over batch',
+ '502' => 'Batch not found',
+ '503' => 'Batch information not available',
+ '650' => 'Invalid quick code',
+ '651' => 'Transaction amount does not match amount specified in quick code',
+ '652' => 'Invalid item code in the detail of the quick code',
+ '701' => 'This website has been disabled. Please contact the system administrator.',
+ '702' => 'Improper merchant code. Please contact the system administrator.',
+ '703' => 'This site is temporarily down for maintenance. We regret the inconvenience. Please try again later.',
+ '704' => 'Duplicate item violation. Please contact the system administrator.',
+ '705' => 'An invalid reference type has been passed into the system. Please contact the system administrator',
+ '706' => 'Items violating unique selection have been passed in. Please contact the system administrator.',
+ '999' => 'An unexpected error has occurred. Please consult the event log.'
+ }
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cc5.rb b/lib/active_merchant/billing/gateways/cc5.rb
index 4d315243c4b..3d25ec7d3f1 100644
--- a/lib/active_merchant/billing/gateways/cc5.rb
+++ b/lib/active_merchant/billing/gateways/cc5.rb
@@ -33,6 +33,18 @@ def capture(money, authorization, options = {})
commit(build_capture_request(money, authorization, options))
end
+ def void(authorization, options = {})
+ commit(build_void_request(authorization, options))
+ end
+
+ def refund(money, authorization, options = {})
+ commit(build_authorization_credit_request(money, authorization, options))
+ end
+
+ def credit(money, creditcard, options = {})
+ commit(build_creditcard_credit_request(money, creditcard, options))
+ end
+
protected
def build_sale_request(type, money, creditcard, options = {})
@@ -58,7 +70,6 @@ def build_sale_request(type, money, creditcard, options = {})
add_address(xml, address)
end
end
-
end
xml.target!
@@ -75,6 +86,39 @@ def build_capture_request(money, authorization, options = {})
end
end
+ def build_void_request(authorization, options = {})
+ xml = Builder::XmlMarkup.new :indent => 2
+
+ xml.tag! 'CC5Request' do
+ add_login_tags(xml)
+ xml.tag! 'OrderId', authorization
+ xml.tag! 'Type', 'Void'
+ end
+ end
+
+ def build_authorization_credit_request(money, authorization, options = {})
+ xml = Builder::XmlMarkup.new :indent => 2
+
+ xml.tag! 'CC5Request' do
+ add_login_tags(xml)
+ xml.tag! 'OrderId', authorization
+ xml.tag! 'Type', 'Credit'
+ add_amount_tags(money, options, xml)
+ end
+ end
+
+ def build_creditcard_credit_request(money, creditcard, options = {})
+ xml = Builder::XmlMarkup.new :indent => 2
+
+ xml.tag! 'CC5Request' do
+ add_login_tags(xml)
+ xml.tag! 'Type', 'Credit'
+ xml.tag! 'Number', creditcard.number
+
+ add_amount_tags(money, options, xml)
+ end
+ end
+
def add_address(xml, address)
xml.tag! 'Name', normalize(address[:name])
xml.tag! 'Street1', normalize(address[:address1])
@@ -103,7 +147,7 @@ def currency_code(currency)
end
def commit(request)
- raw_response = ssl_post((test? ? self.test_url : self.live_url), "DATA=" + request)
+ raw_response = ssl_post((test? ? self.test_url : self.live_url), 'DATA=' + request)
response = parse(raw_response)
@@ -130,25 +174,23 @@ def parse(body)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|element| parse_element(response, element) }
+ node.elements.each { |element| parse_element(response, element) }
else
response[node.name.underscore.to_sym] = node.text
end
end
def success?(response)
- (response[:response] == "Approved")
+ (response[:response] == 'Approved')
end
def normalize(text)
return unless text
if ActiveSupport::Inflector.method(:transliterate).arity == -2
- ActiveSupport::Inflector.transliterate(text,'')
- elsif RUBY_VERSION >= '1.9'
- text.gsub(/[^\x00-\x7F]+/, '')
+ ActiveSupport::Inflector.transliterate(text, '')
else
- ActiveSupport::Inflector.transliterate(text).to_s
+ text.gsub(/[^\x00-\x7F]+/, '')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/cecabank.rb b/lib/active_merchant/billing/gateways/cecabank.rb
new file mode 100644
index 00000000000..b09ed92c37a
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cecabank.rb
@@ -0,0 +1,249 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CecabankGateway < Gateway
+ self.test_url = 'https://tpv.ceca.es'
+ self.live_url = 'https://pgw.ceca.es'
+
+ self.supported_countries = ['ES']
+ self.supported_cardtypes = [:visa, :master, :american_express]
+ self.homepage_url = 'http://www.ceca.es/es/'
+ self.display_name = 'Cecabank'
+ self.default_currency = 'EUR'
+ self.money_format = :cents
+
+ #### CECA's MAGIC NUMBERS
+ CECA_NOTIFICATIONS_URL = 'NONE'
+ CECA_ENCRIPTION = 'SHA2'
+ CECA_DECIMALS = '2'
+ CECA_MODE = 'SSL'
+ CECA_UI_LESS_LANGUAGE = 'XML'
+ CECA_UI_LESS_LANGUAGE_REFUND = '1'
+ CECA_UI_LESS_REFUND_PAGE = 'anulacion_xml'
+ CECA_ACTION_REFUND = 'anulaciones/anularParcial' # use partial refund's URL to avoid time frame limitations and decision logic on client side
+ CECA_ACTION_PURCHASE = 'tpv/compra'
+ CECA_CURRENCIES_DICTIONARY = {'EUR' => 978, 'USD' => 840, 'GBP' => 826}
+
+ # Creates a new CecabankGateway
+ #
+ # The gateway requires four values for connection to be passed
+ # in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * :merchant_id -- Cecabank's merchant_id (REQUIRED)
+ # * :acquirer_bin -- Cecabank's acquirer_bin (REQUIRED)
+ # * :terminal_id -- Cecabank's terminal_id (REQUIRED)
+ # * :key -- Cecabank's cypher key (REQUIRED)
+ # * :test -- +true+ or +false+. If true, perform transactions against the test server.
+ # Otherwise, perform transactions against the production server.
+ def initialize(options = {})
+ requires!(options, :merchant_id, :acquirer_bin, :terminal_id, :key)
+ super
+ end
+
+ # Perform a purchase, which is essentially an authorization and capture in a single operation.
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be purchased as an Integer value in cents.
+ # * creditcard -- The CreditCard details for the transaction.
+ # * options -- A hash of optional parameters.
+ #
+ # ==== Options
+ #
+ # * :order_id -- order_id passed used purchase. (REQUIRED)
+ # * :currency -- currency. Supported: EUR, USD, GBP.
+ # * :description -- description to be pased to the gateway.
+ def purchase(money, creditcard, options = {})
+ requires!(options, :order_id)
+
+ post = {'Descripcion' => options[:description],
+ 'Num_operacion' => options[:order_id],
+ 'Idioma' => CECA_UI_LESS_LANGUAGE,
+ 'Pago_soportado' => CECA_MODE,
+ 'URL_OK' => CECA_NOTIFICATIONS_URL,
+ 'URL_NOK' => CECA_NOTIFICATIONS_URL,
+ 'Importe' => amount(money),
+ 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)]}
+
+ add_creditcard(post, creditcard)
+
+ commit(CECA_ACTION_PURCHASE, post)
+ end
+
+ # Refund a transaction.
+ #
+ # This transaction indicates to the gateway that
+ # money should flow from the merchant to the customer.
+ #
+ # ==== Parameters
+ #
+ # * money -- The amount to be credited to the customer as an Integer value in cents.
+ # * identification -- The reference given from the gateway on purchase (reference, not operation).
+ # * options -- A hash of parameters.
+ def refund(money, identification, options = {})
+ reference, order_id = split_authorization(identification)
+
+ post = {'Referencia' => reference,
+ 'Num_operacion' => order_id,
+ 'Idioma' => CECA_UI_LESS_LANGUAGE_REFUND,
+ 'Pagina' => CECA_UI_LESS_REFUND_PAGE,
+ 'Importe' => amount(money),
+ 'TipoMoneda' => CECA_CURRENCIES_DICTIONARY[options[:currency] || currency(money)]}
+
+ commit(CECA_ACTION_REFUND, post)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?pan=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_creditcard(post, creditcard)
+ post['PAN'] = creditcard.number
+ post['Caducidad'] = expdate(creditcard)
+ post['CVV2'] = creditcard.verification_value
+ post['Pago_elegido'] = CECA_MODE
+ end
+
+ def expdate(creditcard)
+ "#{format(creditcard.year, :four_digits)}#{format(creditcard.month, :two_digits)}"
+ end
+
+ def parse(body)
+ response = {}
+
+ root = REXML::Document.new(body).root
+
+ response[:success] = (root.attributes['valor'] == 'OK')
+ response[:date] = root.attributes['fecha']
+ response[:operation_number] = root.attributes['numeroOperacion']
+ response[:message] = root.attributes['valor']
+
+ if root.elements['OPERACION']
+ response[:operation_type] = root.elements['OPERACION'].attributes['tipo']
+ response[:amount] = root.elements['OPERACION/importe'].text.strip
+ end
+
+ response[:description] = root.elements['OPERACION/descripcion'].text if root.elements['OPERACION/descripcion']
+ response[:authorization_number] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
+ response[:reference] = root.elements['OPERACION/referencia'].text if root.elements['OPERACION/referencia']
+ response[:pan] = root.elements['OPERACION/pan'].text if root.elements['OPERACION/pan']
+
+ if root.elements['ERROR']
+ response[:error_code] = root.elements['ERROR/codigo'].text
+ response[:error_message] = root.elements['ERROR/descripcion'].text
+ else
+ if root.elements['OPERACION'].attributes['numeroOperacion'] == '000'
+ if(root.elements['OPERACION/numeroAutorizacion'])
+ response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text
+ end
+ else
+ response[:authorization] = root.attributes['numeroOperacion']
+ end
+ end
+
+ return response
+ rescue REXML::ParseException => e
+ response[:success] = false
+ response[:message] = 'Unable to parse the response.'
+ response[:error_message] = e.message
+ response
+ end
+
+ def commit(action, parameters)
+ parameters.merge!(
+ 'Cifrado' => CECA_ENCRIPTION,
+ 'Firma' => generate_signature(action, parameters),
+ 'Exponente' => CECA_DECIMALS,
+ 'MerchantID' => options[:merchant_id],
+ 'AcquirerBIN' => options[:acquirer_bin],
+ 'TerminalID' => options[:terminal_id]
+ )
+ url = (test? ? self.test_url : self.live_url) + "/tpvweb/#{action}.action"
+ xml = ssl_post("#{url}?", post_data(parameters))
+ response = parse(xml)
+ Response.new(
+ response[:success],
+ message_from(response),
+ response,
+ :test => test?,
+ :authorization => build_authorization(response),
+ :error_code => response[:error_code]
+ )
+ end
+
+ def message_from(response)
+ if response[:message] == 'ERROR' && response[:error_message]
+ response[:error_message]
+ elsif response[:error_message]
+ "#{response[:message]} #{response[:error_message]}"
+ else
+ response[:message]
+ end
+ end
+
+ def post_data(params)
+ return nil unless params
+
+ params.map do |key, value|
+ next if value.blank?
+ if value.is_a?(Hash)
+ h = {}
+ value.each do |k, v|
+ h["#{key}.#{k}"] = v unless v.blank?
+ end
+ post_data(h)
+ else
+ "#{key}=#{CGI.escape(value.to_s)}"
+ end
+ end.compact.join('&')
+ end
+
+ def build_authorization(response)
+ [response[:reference], response[:authorization]].join('|')
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def generate_signature(action, parameters)
+ signature_fields = case action
+ when CECA_ACTION_REFUND
+ options[:key].to_s +
+ options[:merchant_id].to_s +
+ options[:acquirer_bin].to_s +
+ options[:terminal_id].to_s +
+ parameters['Num_operacion'].to_s +
+ parameters['Importe'].to_s +
+ parameters['TipoMoneda'].to_s +
+ CECA_DECIMALS +
+ parameters['Referencia'].to_s +
+ CECA_ENCRIPTION
+ else
+ options[:key].to_s +
+ options[:merchant_id].to_s +
+ options[:acquirer_bin].to_s +
+ options[:terminal_id].to_s +
+ parameters['Num_operacion'].to_s +
+ parameters['Importe'].to_s +
+ parameters['TipoMoneda'].to_s +
+ CECA_DECIMALS +
+ CECA_ENCRIPTION +
+ CECA_NOTIFICATIONS_URL +
+ CECA_NOTIFICATIONS_URL
+ end
+ Digest::SHA2.hexdigest(signature_fields)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cenpos.rb b/lib/active_merchant/billing/gateways/cenpos.rb
new file mode 100644
index 00000000000..9ba37a7c00b
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/cenpos.rb
@@ -0,0 +1,327 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CenposGateway < Gateway
+ self.display_name = 'CenPOS'
+ self.homepage_url = 'https://www.cenpos.com/'
+
+ self.live_url = 'https://ww3.cenpos.net/6/transact.asmx'
+
+ self.supported_countries = %w(AD AI AG AR AU AT BS BB BE BZ BM BR BN BG CA HR CY CZ DK DM EE FI FR DE GR GD GY HK HU IS IL IT JP LV LI LT LU MY MT MX MC MS NL PA PL PT KN LC MF VC SM SG SK SI ZA ES SR SE CH TR GB US UY)
+ self.default_currency = 'USD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ def initialize(options={})
+ requires!(options, :merchant_id, :password, :user_id)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit('Sale', post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit('Auth', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit('SpecialForce', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_void_required_elements(post)
+ add_reference(post, authorization)
+ add_remembered_amount(post, authorization)
+ add_tax(post, options)
+ add_order_id(post, options)
+
+ commit('Void', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit('SpecialReturn', post)
+ end
+
+ def credit(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+
+ commit('Credit', 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_invoice(post, money, options)
+ post[:Amount] = amount(money)
+ post[:CurrencyCode] = options[:currency] || currency(money)
+ post[:InvoiceDetail] = options[:invoice_detail] if options[:invoice_detail]
+ post[:CustomerCode] = options[:customer_code] if options[:customer_code]
+ add_order_id(post, options)
+ add_tax(post, options)
+ end
+
+ def add_payment_method(post, payment_method)
+ post[:NameOnCard] = payment_method.name
+ post[:CardNumber] = payment_method.number
+ post[:CardVerificationNumber] = payment_method.verification_value
+ post[:CardExpirationDate] = format(payment_method.month, :two_digits) + format(payment_method.year, :two_digits)
+ post[:CardLastFourDigits] = payment_method.last_digits
+ post[:MagneticData] = payment_method.track_data if payment_method.track_data
+ end
+
+ def add_customer_data(post, options)
+ if(billing_address = (options[:billing_address] || options[:address]))
+ post[:CustomerEmailAddress] = billing_address[:email]
+ post[:CustomerPhone] = billing_address[:phone]
+ post[:CustomerBillingAddress] = billing_address[:address1]
+ post[:CustomerCity] = billing_address[:city]
+ post[:CustomerState] = billing_address[:state]
+ post[:CustomerZipCode] = billing_address[:zip]
+ end
+ end
+
+ def add_void_required_elements(post)
+ post[:GeoLocationInformation] = nil
+ post[:IMEI] = nil
+ end
+
+ def add_order_id(post, options)
+ post[:InvoiceNumber] = options[:order_id]
+ end
+
+ def add_tax(post, options)
+ post[:TaxAmount] = amount(options[:tax] || 0)
+ end
+
+ def add_reference(post, authorization)
+ reference_number, last_four_digits = split_authorization(authorization)
+ post[:ReferenceNumber] = reference_number
+ post[:CardLastFourDigits] = last_four_digits
+ end
+
+ def add_remembered_amount(post, authorization)
+ post[:Amount] = split_authorization(authorization).last
+ end
+
+ def commit(action, post)
+ post[:MerchantId] = @options[:merchant_id]
+ post[:Password] = @options[:password]
+ post[:UserId] = @options[:user_id]
+ post[:TransactionType] = action
+
+ data = build_request(post)
+ begin
+ xml = ssl_post(self.live_url, data, headers)
+ raw = parse(xml)
+ rescue ActiveMerchant::ResponseError => e
+ if(e.response.code == '500' && e.response.body.start_with?(' 'identity',
+ 'Content-Type' => 'text/xml;charset=UTF-8',
+ 'SOAPAction' => 'http://tempuri.org/Transactional/ProcessCreditCard'
+ }
+ end
+
+ def build_request(post)
+ xml = Builder::XmlMarkup.new :indent => 8
+ xml.tag!('acr:MerchantId', post.delete(:MerchantId))
+ xml.tag!('acr:Password', post.delete(:Password))
+ xml.tag!('acr:UserId', post.delete(:UserId))
+ post.sort.each do |field, value|
+ xml.tag!("acr1:#{field}", value)
+ end
+ envelope(xml.target!)
+ end
+
+ def envelope(body)
+ <<-EOS
+
+
+
+
+
+ #{body}
+
+
+
+
+ EOS
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.remove_namespaces!
+ body = doc.xpath('//ProcessCreditCardResult')
+ body.children.each do |node|
+ if node.elements.size == 0
+ response[node.name.underscore.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name.underscore}_#{childnode.name.underscore}"
+ response[name.to_sym] = childnode.text
+ end
+ end
+ end unless doc.root.nil?
+
+ response
+ end
+
+ def success_from(response)
+ response == '0'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response[:message] || 'Unable to read error message'
+ end
+ end
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '211' => STANDARD_ERROR_CODE[:invalid_number],
+ '252' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '257' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '333' => STANDARD_ERROR_CODE[:expired_card],
+ '1' => STANDARD_ERROR_CODE[:card_declined],
+ '99' => STANDARD_ERROR_CODE[:processing_error],
+ }
+
+ def authorization_from(request, response)
+ [ response[:reference_number], request[:CardLastFourDigits], request[:Amount] ].join('|')
+ end
+
+ def split_authorization(authorization)
+ reference_number, last_four_digits, original_amount = authorization.split('|')
+ [reference_number, last_four_digits, original_amount]
+ end
+
+ def error_code_from(succeeded, response)
+ succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response[:result]]
+ end
+
+ def cvv_result_from_xml(xml)
+ ActiveMerchant::Billing::CVVResult.new(cvv_result_code(xml))
+ end
+
+ def avs_result_from_xml(xml)
+ ActiveMerchant::Billing::AVSResult.new(code: avs_result_code(xml))
+ end
+
+ def cvv_result_code(xml)
+ cvv = validation_result_element(xml, 'CVV')
+ return nil unless cvv
+ validation_result_matches?(*validation_result_element_text(cvv.parent)) ? 'M' : 'N'
+ end
+
+ def avs_result_code(xml)
+ billing_address_elem = validation_result_element(xml, 'Billing Address')
+ zip_code_elem = validation_result_element(xml, 'Zip Code')
+
+ return nil unless billing_address_elem && zip_code_elem
+
+ billing_matches = avs_result_matches(billing_address_elem)
+ zip_matches = avs_result_matches(zip_code_elem)
+
+ if billing_matches && zip_matches
+ 'D'
+ elsif !billing_matches && zip_matches
+ 'P'
+ elsif billing_matches && !zip_matches
+ 'B'
+ else
+ 'C'
+ end
+ end
+
+ def avs_result_matches(elem)
+ validation_result_matches?(*validation_result_element_text(elem.parent))
+ end
+
+ def validation_result_element(xml, name)
+ doc = Nokogiri::XML(xml)
+ doc.remove_namespaces!
+ doc.at_xpath("//ParameterValidationResultList//ParameterValidationResult//Name[text() = '#{name}']")
+ end
+
+ def validation_result_element_text(element)
+ result_text = element.elements.detect { |elem|
+ elem.name == 'Result'
+ }.children.detect(&:text).text
+
+ result_text.split(';').collect(&:strip)
+ end
+
+ def validation_result_matches?(present, match)
+ present.downcase.start_with?('present') &&
+ match.downcase.start_with?('match')
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/certo_direct.rb b/lib/active_merchant/billing/gateways/certo_direct.rb
deleted file mode 100644
index a77fc780ab0..00000000000
--- a/lib/active_merchant/billing/gateways/certo_direct.rb
+++ /dev/null
@@ -1,277 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class CertoDirectGateway < Gateway
- self.live_url = self.test_url = "https://secure.certodirect.com/gateway/process/v2"
-
- self.supported_countries = [
- "BE", "BG", "CZ", "DK", "DE", "EE", "IE", "EL", "ES", "FR",
- "IT", "CY", "LV", "LT", "LU", "HU", "MT", "NL", "AT", "PL",
- "PT", "RO", "SI", "SK", "FI", "SE", "GB"
- ]
-
- self.supported_cardtypes = [:visa, :master, :american_express, :discover]
- self.homepage_url = 'http://www.certodirect.com/'
- self.display_name = 'CertoDirect'
-
- # Creates a new CertoDirectGateway
- #
- # The gateway requires that a valid login and password be passed
- # in the +options+ hash.
- #
- # ==== Options
- #
- # * :login -- The CertoDirect Shop ID (REQUIRED)
- # * :password -- The CertoDirect Shop Password. (REQUIRED)
- # * :test -- +true+ or +false+. If true, perform transactions against the test server.
- # Otherwise, perform transactions against the production server.
- def initialize(options = {})
- requires!(options, :login, :password)
- super
- end
-
- # Perform a purchase, which is essentially an authorization and capture in a single operation.
- #
- # ==== Parameters
- #
- # * money -- The amount to be purchased as an Integer value in cents.
- # * credit_card -- The CreditCard details for the transaction.
- # * options -- A hash of optional parameters.
- def purchase(money, credit_card, options = {})
- requires!(options, :email, :currency, :ip, :description)
-
- commit(build_sale_request(money, credit_card, options))
- end
-
- # Refund a transaction.
- #
- # This transaction indicates to the gateway that
- # money should flow from the merchant to the customer.
- #
- # ==== Parameters
- #
- # * money -- The amount to be credited to the customer as an Integer value in cents.
- # * identification -- The ID of the original order against which the refund is being issued.
- # * options -- A hash of parameters.
- def refund(money, identification, options = {})
- requires!(options, :reason)
-
- commit(build_refund_request(money, identification, options))
- end
-
- # Performs an authorization, which reserves the funds on the customer's credit card, but does not
- # charge the card.
- #
- # ==== Parameters
- #
- # * money -- The amount to be authorized as an Integer value in cents.
- # * credit_card -- The CreditCard details for the transaction.
- # * options -- A hash of optional parameters.
- def authorize(money, credit_card, options = {})
- requires!(options, :email, :currency, :ip, :description)
-
- commit(build_authorize_request(money, credit_card, options))
- end
-
- # Captures the funds from an authorized transaction.
- #
- # ==== Parameters
- #
- # * money -- The amount to be captured as an Integer value in cents.
- # * identification -- The authorization returned from the previous authorize request.
- def capture(money, identification, options = {})
- commit(build_capture_request(money, identification))
- end
-
- # Void a previous transaction
- #
- # ==== Parameters
- #
- # * money -- The amount to be captured as an Integer value in cents.
- # * identification - The authorization returned from the previous authorize request.
- def void(money, identification, options = {})
- commit(build_void_request(money, identification))
- end
-
- # Create a recurring payment.
- #
- # ==== Parameters
- #
- # * options -- A hash of parameters.
- #
- # ==== Options
- #
- def recurring(identification, options={})
- commit(build_recurring_request(identification, options))
- end
-
-
- private
-
- def commit(request_xml)
- begin
- response = Hash.from_xml(ssl_post(self.live_url, request_xml, headers))
- Response.new(success?(response),
- message(response),
- response,
- :test => test?,
- :authorization => authorization(response))
- rescue ResponseError => e
- raise e unless e.response.code == '403'
- response = Hash.from_xml(e.response.body)['response']
- Response.new(false, message(response), {}, :test => test?)
- end
- end
-
- def build_sale_request(money, credit_card, options)
- build_request_xml('Sale') do |xml|
- add_order(xml, money, credit_card, options)
- end
- end
-
- def build_authorize_request(money, credit_card, options)
- build_request_xml('Authorize') do |xml|
- add_order(xml, money, credit_card, options)
- end
- end
-
- def build_refund_request(money, identification, options)
- build_request_xml('Refund') do |xml|
- add_reference_info(xml, money, identification, options)
- xml.tag! 'reason', options[:reason]
- end
- end
-
- def build_capture_request(money, identification)
- build_request_xml('Capture') do |xml|
- add_reference_info(xml, money, identification, options)
- end
- end
-
- def build_void_request(money, identification)
- build_request_xml('Void') do |xml|
- add_reference_info(xml, money, identification, options)
- end
- end
-
- def build_recurring_request(identification, options)
- build_request_xml('Sale') do |xml|
- xml.tag! 'order' do |xml|
- xml.tag!('test', 'true') if test?
- xml.tag! 'initial_order_id', identification, :type => 'integer'
-
- add_order_details(xml, options[:amount], options) if has_any_order_details_key?(options)
- add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address]
- add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address]
- end
- end
- end
-
- def build_request_xml(type, &block)
- xml = Builder::XmlMarkup.new(:indent => 2)
- xml.tag! 'transaction' do
- xml.tag! 'type', type
- yield(xml)
- end
- xml.target!
- end
-
- def add_order(xml, money, credit_card, options)
- xml.tag! 'order' do
- xml.tag!('test', 'true') if test?
-
- xml.tag!('return_url', options[:return_url]) if options[:return_url]
- xml.tag!('cancel_url', options[:cancel_url]) if options[:cancel_url]
-
- xml.tag! 'payment_method_type', 'CreditCard'
- xml.tag! 'payment_method' do
- xml.tag! 'number', credit_card.number
- xml.tag! 'exp_month', "%02i" % credit_card.month
- xml.tag! 'exp_year', credit_card.year
- xml.tag! 'holder', credit_card.name
- xml.tag! 'verification_value', credit_card.verification_value
- end
-
- add_order_details(xml, money, options)
- add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address]
- add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address]
- end
- end
-
- def add_order_details(xml, money, options)
- xml.tag! 'details' do
- xml.tag!('amount', localized_amount(money, options[:currency]), :type => 'decimal') if money
- xml.tag!('currency', options[:currency]) if options[:currency]
- xml.tag!('email', options[:email]) if options[:email]
- xml.tag!('ip', options[:ip]) if options[:ip]
- xml.tag!('shipping', options[:shipping], :type => 'decimal') if options[:shipping]
- xml.tag!('description', options[:description]) if options[:description]
- end
- end
-
- def add_reference_info(xml, money, identification, options)
- xml.tag! 'order_id', identification, :type => 'integer'
- xml.tag! 'amount', localized_amount(money, options[:currency]), :type => 'decimal'
- end
-
- def add_address(xml, address_type, address)
- xml.tag! address_type do
- xml.tag! 'address', address[:address1]
- xml.tag! 'city', address[:city]
- xml.tag! 'country', address[:country]
- xml.tag! 'first_name', address[:first_name]
- xml.tag! 'last_name', address[:last_name]
- xml.tag! 'state', address[:state]
- xml.tag! 'phone', address[:phone]
- xml.tag! 'zip', address[:zip]
- end
- end
-
- def has_any_order_details_key?(options)
- [ :currency, :amount, :email, :ip, :shipping, :description ].any? do |key|
- options.has_key?(key)
- end
- end
-
- def success?(response)
- %w(completed forwarding).include?(state(response)) and
- status(response) == 'success'
- end
-
- def error?(response)
- response['errors']
- end
-
- def state(response)
- response["transaction"].try(:[], "state")
- end
-
- def status(response)
- response['transaction'].try(:[], 'response').try(:[], 'status')
- end
-
- def authorization(response)
- error?(response) ? nil : response["transaction"]["order"]['id'].to_s
- end
-
- def message(response)
- return response['errors'].join('; ') if error?(response)
-
- if state(response) == 'completed'
- response["transaction"]["response"]["message"]
- else
- response['transaction']['message']
- end
- end
-
- def headers
- { 'authorization' => basic_auth,
- 'Accept' => 'application/xml',
- 'Content-Type' => 'application/xml' }
- end
-
- def basic_auth
- 'Basic ' + ["#{@options[:login]}:#{@options[:password]}"].pack('m').delete("\r\n")
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/gateways/checkout.rb b/lib/active_merchant/billing/gateways/checkout.rb
new file mode 100644
index 00000000000..bd6149b246d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/checkout.rb
@@ -0,0 +1,214 @@
+require 'rubygems'
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CheckoutGateway < Gateway
+ self.default_currency = 'USD'
+ self.money_format = :cents
+
+ self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA']
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
+
+ self.homepage_url = 'https://www.checkout.com/'
+ self.display_name = 'Checkout.com'
+
+ self.live_url = 'https://api.checkout.com/Process/gateway.aspx'
+
+ ACTIONS = {
+ 'purchase' => '1',
+ 'authorize' => '4',
+ 'capture' => '5',
+ 'refund' => '2',
+ 'void_purchase' => '3',
+ 'void_authorize' => '9',
+ 'void_capture' => '7'
+ }
+
+ def initialize(options = {})
+ requires!(options, :merchant_id, :password)
+ super
+ end
+
+ def purchase(amount, payment_method, options)
+ commit('purchase', amount, options) do |xml|
+ add_credentials(xml, options)
+ add_invoice(xml, amount, options)
+ add_track_id(xml, options[:order_id] || generate_unique_id)
+ add_payment_method(xml, payment_method)
+ add_billing_info(xml, options)
+ add_shipping_info(xml, options)
+ add_user_defined_fields(xml, options)
+ add_other_fields(xml, options)
+ end
+ end
+
+ def authorize(amount, payment_method, options)
+ commit('authorize', amount, options) do |xml|
+ add_credentials(xml, options)
+ add_invoice(xml, amount, options)
+ add_track_id(xml, options[:order_id] || generate_unique_id)
+ add_payment_method(xml, payment_method)
+ add_billing_info(xml, options)
+ add_shipping_info(xml, options)
+ add_user_defined_fields(xml, options)
+ add_other_fields(xml, options)
+ end
+ end
+
+ def capture(amount, authorization, options = {})
+ commit('capture', amount, options) do |xml|
+ add_credentials(xml, options)
+ add_reference(xml, authorization)
+ add_invoice(xml, amount, options)
+ add_user_defined_fields(xml, options)
+ add_other_fields(xml, options)
+ end
+ end
+
+ def void(authorization, options = {})
+ _, _, orig_action, amount, currency = split_authorization(authorization)
+ commit("void_#{orig_action}") do |xml|
+ add_credentials(xml, options)
+ add_invoice(xml, amount.to_i, options.merge(currency: currency))
+ add_reference(xml, authorization)
+ end
+ end
+
+ def refund(amount, authorization, options = {})
+ commit('refund') do |xml|
+ add_credentials(xml, options)
+ add_invoice(xml, amount.to_i, options)
+ add_reference(xml, authorization)
+ end
+ 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
+
+ private
+
+ def add_credentials(xml, options)
+ xml.merchantid_ @options[:merchant_id]
+ xml.password_ @options[:password]
+ end
+
+ def add_invoice(xml, amount, options)
+ xml.bill_amount_ amount(amount)
+ xml.bill_currencycode_ options[:currency] || currency(amount)
+ end
+
+ def add_payment_method(xml, payment_method)
+ xml.bill_cardholder_ payment_method.name
+ xml.bill_cc_ payment_method.number
+ xml.bill_expmonth_ format(payment_method.month, :two_digits)
+ xml.bill_expyear_ format(payment_method.year, :four_digits)
+ if payment_method.verification_value?
+ xml.bill_cvv2_ payment_method.verification_value
+ end
+ end
+
+ def add_billing_info(xml, options)
+ if options[:billing_address]
+ xml.bill_address_ options[:billing_address][:address1]
+ xml.bill_city_ options[:billing_address][:city]
+ xml.bill_state_ options[:billing_address][:state]
+ xml.bill_postal_ options[:billing_address][:zip]
+ xml.bill_country_ options[:billing_address][:country]
+ xml.bill_phone_ options[:billing_address][:phone]
+ end
+ end
+
+ def add_shipping_info(xml, options)
+ if options[:shipping_address]
+ xml.ship_address_ options[:shipping_address][:address1]
+ xml.ship_address2_ options[:shipping_address][:address2]
+ xml.ship_city_ options[:shipping_address][:city]
+ xml.ship_state_ options[:shipping_address][:state]
+ xml.ship_postal_ options[:shipping_address][:zip]
+ xml.ship_country_ options[:shipping_address][:country]
+ xml.ship_phone_ options[:shipping_address][:phone]
+ end
+ end
+
+ def add_user_defined_fields(xml, options)
+ xml.udf1_ options[:udf1]
+ xml.udf2_ options[:udf2]
+ xml.udf3_ options[:udf3]
+ xml.udf4_ options[:udf4]
+ xml.udf5_ options[:udf5]
+ end
+
+ def add_other_fields(xml, options)
+ xml.bill_email_ options[:email]
+ xml.bill_customerip_ options[:ip]
+ xml.merchantcustomerid_ options[:customer]
+ xml.descriptor_name options[:descriptor_name]
+ xml.descriptor_city options[:descriptor_city]
+ end
+
+ def add_reference(xml, authorization)
+ transid, trackid, _, _, _ = split_authorization(authorization)
+ xml.transid transid
+ add_track_id(xml, trackid)
+ end
+
+ def add_track_id(xml, trackid)
+ xml.trackid(trackid) if trackid
+ end
+
+ def commit(action, amount=nil, options={}, &builder)
+ response = parse_xml(ssl_post(live_url, build_xml(action, &builder)))
+ Response.new(
+ (response[:responsecode] == '0'),
+ (response[:result] || response[:error_text] || 'Unknown Response'),
+ response,
+ authorization: authorization_from(response, action, amount, options),
+ test: test?
+ )
+ end
+
+ def build_xml(action)
+ Nokogiri::XML::Builder.new do |xml|
+ xml.request do
+ xml.action_ ACTIONS[action]
+ yield xml
+ end
+ end.to_xml
+ end
+
+ def parse_xml(xml)
+ response = {}
+
+ Nokogiri::XML(CGI.unescapeHTML(xml)).xpath('//response').children.each do |node|
+ if node.text?
+ next
+ elsif node.elements.size == 0
+ response[node.name.downcase.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name.downcase}_#{childnode.name.downcase}"
+ response[name.to_sym] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ def authorization_from(response, action, amount, options)
+ currency = options[:currency] || currency(amount)
+ [response[:tranid], response[:trackid], action, amount, currency].join('|')
+ end
+
+ def split_authorization(authorization)
+ transid, trackid, action, amount, currency = authorization.split('|')
+ [transid, trackid, action, amount, currency]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/checkout_v2.rb b/lib/active_merchant/billing/gateways/checkout_v2.rb
new file mode 100644
index 00000000000..0f916158a22
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/checkout_v2.rb
@@ -0,0 +1,270 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CheckoutV2Gateway < Gateway
+ self.display_name = 'Checkout.com Unified Payments'
+ self.homepage_url = 'https://www.checkout.com/'
+ self.live_url = 'https://api.checkout.com'
+ self.test_url = 'https://api.sandbox.checkout.com'
+
+ self.supported_countries = ['AD', 'AE', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA']
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :maestro, :discover]
+
+ def initialize(options = {})
+ requires!(options, :secret_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options = {})
+ multi = MultiResponse.run do |r|
+ r.process { authorize(amount, payment_method, options) }
+ r.process { capture(amount, r.authorization, options) }
+ end
+
+ merged_params = multi.responses.map(&:params).reduce({}, :merge)
+ succeeded = success_from(merged_params)
+
+ response(:purchase, succeeded, merged_params)
+ end
+
+ def authorize(amount, payment_method, options = {})
+ post = {}
+ post[:capture] = false
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ add_transaction_data(post, options)
+ add_3ds(post, options)
+
+ commit(:authorize, post)
+ end
+
+ def capture(amount, authorization, options = {})
+ post = {}
+ add_invoice(post, amount, options)
+ add_customer_data(post, options)
+
+ commit(:capture, post, authorization)
+ end
+
+ def void(authorization, _options = {})
+ post = {}
+ commit(:void, post, authorization)
+ end
+
+ def refund(amount, authorization, options = {})
+ post = {}
+ add_invoice(post, amount, options)
+ add_customer_data(post, options)
+
+ commit(:refund, post, authorization)
+ 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 verify_payment(authorization, option={})
+ commit(:verify_payment, authorization)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(/(Authorization: )[^\\]*/i, '\1[FILTERED]').
+ gsub(/("number\\":\\")\d+/, '\1[FILTERED]').
+ gsub(/("cvv\\":\\")\d+/, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_invoice(post, money, options)
+ post[:amount] = localized_amount(money, options[:currency])
+ post[:reference] = options[:order_id]
+ post[:currency] = options[:currency] || currency(money)
+ if options[:descriptor_name] || options[:descriptor_city]
+ post[:billing_descriptor] = {}
+ post[:billing_descriptor][:name] = options[:descriptor_name] if options[:descriptor_name]
+ post[:billing_descriptor][:city] = options[:descriptor_city] if options[:descriptor_city]
+ end
+ post[:metadata] = {}
+ post[:metadata][:udf5] = application_id || 'ActiveMerchant'
+ end
+
+ def add_payment_method(post, payment_method, options)
+ post[:source] = {}
+ post[:source][:type] = 'card'
+ post[:source][:name] = payment_method.name
+ post[:source][:number] = payment_method.number
+ post[:source][:cvv] = payment_method.verification_value
+ post[:source][:expiry_year] = format(payment_method.year, :four_digits)
+ post[:source][:expiry_month] = format(payment_method.month, :two_digits)
+ post[:source][:stored] = 'true' if options[:card_on_file] == true
+ end
+
+ def add_customer_data(post, options)
+ post[:customer] = {}
+ post[:customer][:email] = options[:email] || nil
+ post[:payment_ip] = options[:ip] if options[:ip]
+ address = options[:billing_address]
+ if address && post[:source]
+ post[:source][:billing_address] = {}
+ post[:source][:billing_address][:address_line1] = address[:address1] unless address[:address1].blank?
+ post[:source][:billing_address][:address_line2] = address[:address2] unless address[:address2].blank?
+ post[:source][:billing_address][:city] = address[:city] unless address[:city].blank?
+ post[:source][:billing_address][:state] = address[:state] unless address[:state].blank?
+ post[:source][:billing_address][:country] = address[:country] unless address[:country].blank?
+ post[:source][:billing_address][:zip] = address[:zip] unless address[:zip].blank?
+ end
+ end
+
+ def add_transaction_data(post, options = {})
+ post[:payment_type] = 'Regular' if options[:transaction_indicator] == 1
+ post[:payment_type] = 'Recurring' if options[:transaction_indicator] == 2
+ post[:payment_type] = 'MOTO' if options[:transaction_indicator] == 3
+ post[:previous_payment_id] = options[:previous_charge_id] if options[:previous_charge_id]
+ end
+
+ def add_3ds(post, options)
+ if options[:three_d_secure] || options[:execute_threed]
+ post[:'3ds'] = {}
+ post[:'3ds'][:enabled] = true
+ post[:success_url] = options[:callback_url] if options[:callback_url]
+ post[:failure_url] = options[:callback_url] if options[:callback_url]
+ end
+
+ if options[:three_d_secure]
+ post[:'3ds'][:eci] = options[:three_d_secure][:eci] if options[:three_d_secure][:eci]
+ post[:'3ds'][:cryptogram] = options[:three_d_secure][:cavv] if options[:three_d_secure][:cavv]
+ post[:'3ds'][:version] = options[:three_d_secure][:version] if options[:three_d_secure][:version]
+ post[:'3ds'][:xid] = options[:three_d_secure][:ds_transaction_id] || options[:three_d_secure][:xid]
+ end
+ end
+
+ def commit(action, post, authorization = nil)
+ begin
+ raw_response = (action == :verify_payment ? ssl_get("#{base_url}/payments/#{post}", headers) : ssl_post(url(post, action, authorization), post.to_json, headers))
+ response = parse(raw_response)
+ if action == :capture && response.key?('_links')
+ response['id'] = response['_links']['payment']['href'].split('/')[-1]
+ end
+ rescue ResponseError => e
+ raise unless e.response.code.to_s =~ /4\d\d/
+ response = parse(e.response.body)
+ end
+
+ succeeded = success_from(response)
+
+ response(action, succeeded, response)
+ end
+
+ def response(action, succeeded, response)
+ successful_response = succeeded && action == :purchase || action == :authorize
+ avs_result = successful_response ? avs_result(response) : nil
+ cvv_result = successful_response ? cvv_result(response) : nil
+
+ Response.new(
+ succeeded,
+ message_from(succeeded, response),
+ response,
+ authorization: authorization_from(response),
+ error_code: error_code_from(succeeded, response),
+ test: test?,
+ avs_result: avs_result,
+ cvv_result: cvv_result
+ )
+ end
+
+ def headers
+ {
+ 'Authorization' => @options[:secret_key],
+ 'Content-Type' => 'application/json;charset=UTF-8',
+ }
+ end
+
+ def url(_post, action, authorization)
+ if action == :authorize
+ "#{base_url}/payments"
+ elsif action == :capture
+ "#{base_url}/payments/#{authorization}/captures"
+ elsif action == :refund
+ "#{base_url}/payments/#{authorization}/refunds"
+ elsif action == :void
+ "#{base_url}/payments/#{authorization}/voids"
+ else
+ "#{base_url}/payments/#{authorization}/#{action}"
+ end
+ end
+
+ def base_url
+ test? ? test_url : live_url
+ end
+
+ def avs_result(response)
+ response['source'] && response['source']['avs_check'] ? AVSResult.new(code: response['source']['avs_check']) : nil
+ end
+
+ def cvv_result(response)
+ response['source'] && response['source']['cvv_check'] ? CVVResult.new(response['source']['cvv_check']) : nil
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ rescue JSON::ParserError
+ {
+ 'message' => 'Invalid JSON response received from Checkout.com Unified Payments Gateway. Please contact Checkout.com if you continue to receive this message.',
+ 'raw_response' => scrub(body)
+ }
+ end
+
+ def success_from(response)
+ response['response_summary'] == 'Approved' || response['approved'] == true || !response.key?('response_summary') && response.key?('action_id')
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ elsif response['error_type']
+ response['error_type'] + ': ' + response['error_codes'].first
+ else
+ response['response_summary'] || response['response_code'] || 'Unable to read error message'
+ end
+ end
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '20014' => STANDARD_ERROR_CODE[:invalid_number],
+ '20100' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '20054' => STANDARD_ERROR_CODE[:expired_card],
+ '40104' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '40108' => STANDARD_ERROR_CODE[:incorrect_zip],
+ '40111' => STANDARD_ERROR_CODE[:incorrect_address],
+ '20005' => STANDARD_ERROR_CODE[:card_declined],
+ '20088' => STANDARD_ERROR_CODE[:processing_error],
+ '20001' => STANDARD_ERROR_CODE[:call_issuer],
+ '30004' => STANDARD_ERROR_CODE[:pickup_card]
+ }
+
+ def authorization_from(raw)
+ raw['id']
+ end
+
+ def error_code_from(succeeded, response)
+ return if succeeded
+ if response['error_type'] && response['error_codes']
+ "#{response['error_type']}: #{response['error_codes'].join(', ')}"
+ elsif response['error_type']
+ response['error_type']
+ else
+ STANDARD_ERROR_CODE_MAPPING[response['response_code']]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/citrus_pay.rb b/lib/active_merchant/billing/gateways/citrus_pay.rb
new file mode 100644
index 00000000000..00ab762d196
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/citrus_pay.rb
@@ -0,0 +1,22 @@
+module ActiveMerchant
+ module Billing
+ class CitrusPayGateway < Gateway
+ include MastercardGateway
+
+ class_attribute :live_na_url, :live_ap_url, :test_na_url, :test_ap_url
+
+ self.test_na_url = 'https://test-gateway.mastercard.com/api/rest/version/39/'
+ self.test_ap_url = 'https://test-gateway.mastercard.com/api/rest/version/39/'
+
+ self.live_na_url = 'https://na-gateway.mastercard.com/api/rest/version/39/'
+ self.live_ap_url = 'https://ap-gateway.mastercard.com/api/rest/version/39/'
+
+ self.display_name = 'Citrus Pay'
+ self.homepage_url = 'http://www.citruspay.com/'
+ self.supported_countries = %w(AR AU BR FR DE HK MX NZ SG GB US)
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro]
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/clearhaus.rb b/lib/active_merchant/billing/gateways/clearhaus.rb
new file mode 100644
index 00000000000..b53d792a4f4
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/clearhaus.rb
@@ -0,0 +1,220 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class ClearhausGateway < Gateway
+ self.test_url = 'https://gateway.test.clearhaus.com'
+ self.live_url = 'https://gateway.clearhaus.com'
+
+ self.supported_countries = ['DK', 'NO', 'SE', 'FI', 'DE', 'CH', 'NL', 'AD', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'FO', 'GL', 'EE', 'FR', 'GR',
+ 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'GB']
+
+ self.default_currency = 'EUR'
+ self.currencies_without_fractions = %w(BIF BYR DJF GNF JPY KMF KRW PYG RWF VND VUV XAF XOF XPF)
+ self.supported_cardtypes = [:visa, :master]
+
+ self.homepage_url = 'https://www.clearhaus.com'
+ self.display_name = 'Clearhaus'
+ self.money_format = :cents
+
+ ACTION_CODE_MESSAGES = {
+ 20000 => 'Approved',
+ 40000 => 'General input error',
+ 40110 => 'Invalid card number',
+ 40120 => 'Invalid CSC',
+ 40130 => 'Invalid expire date',
+ 40135 => 'Card expired',
+ 40140 => 'Invalid currency',
+ 40200 => 'Clearhaus rule violation',
+ 40300 => '3-D Secure problem',
+ 40310 => '3-D Secure authentication failure',
+ 40400 => 'Backend problem',
+ 40410 => 'Declined by issuer or card scheme',
+ 40411 => 'Card restricted',
+ 40412 => 'Card lost or stolen',
+ 40413 => 'Insufficient funds',
+ 40414 => 'Suspected fraud',
+ 40415 => 'Amount limit exceeded',
+ 50000 => 'Clearhaus error'
+ }
+
+ def initialize(options={})
+ requires!(options, :api_key)
+ options[:private_key] = options[:private_key].strip if options[:private_key]
+ super
+ end
+
+ def purchase(amount, payment, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(amount, payment, options) }
+ r.process { capture(amount, r.authorization, options) }
+ end
+ end
+
+ def authorize(amount, payment, options={})
+ post = {}
+ add_invoice(post, amount, options)
+
+ action = if payment.respond_to?(:number)
+ add_payment(post, payment)
+ '/authorizations'
+ elsif payment.kind_of?(String)
+ "/cards/#{payment}/authorizations"
+ else
+ raise ArgumentError.new("Unknown payment type #{payment.inspect}")
+ end
+
+ post[:recurring] = options[:recurring] if options[:recurring]
+ post[:card][:pares] = options[:pares] if options[:pares]
+
+ commit(action, post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+
+ commit("/authorizations/#{authorization}/captures", post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_amount(post, amount, options)
+
+ commit("/authorizations/#{authorization}/refunds", post)
+ end
+
+ def void(authorization, options = {})
+ commit("/authorizations/#{authorization}/voids", options)
+ end
+
+ def verify(credit_card, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(0, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def store(credit_card, options={})
+ post = {}
+ add_payment(post, credit_card)
+
+ commit('/cards', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )[\w=]+), '\1[FILTERED]').
+ gsub(%r((&?card(?:\[|%5B)csc(?:\]|%5D)=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?card(?:\[|%5B)pan(?:\]|%5D)=)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_invoice(post, money, options)
+ add_amount(post, money, options)
+ post[:reference] = options[:order_id] if options[:order_id]
+ post[:text_on_statement] = options[:text_on_statement] if options[:text_on_statement]
+ end
+
+ def add_amount(post, amount, options)
+ post[:amount] = localized_amount(amount, options[:currency] || default_currency)
+ post[:currency] = (options[:currency] || default_currency)
+ end
+
+ def add_payment(post, payment)
+ card = {}
+ card[:pan] = payment.number
+ card[:expire_month] = '%02d'% payment.month
+ card[:expire_year] = payment.year
+
+ if payment.verification_value?
+ card[:csc] = payment.verification_value
+ end
+
+ post[:card] = card if card.any?
+ end
+
+ def headers(api_key)
+ {
+ 'Authorization' => 'Basic ' + Base64.strict_encode64("#{api_key}:"),
+ 'User-Agent' => "Clearhaus ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
+ }
+ end
+
+ def parse(body)
+ JSON.parse(body) rescue body
+ end
+
+ def commit(action, parameters)
+ url = (test? ? test_url : live_url) + action
+ headers = headers(@options[:api_key])
+ body = parameters.to_query
+
+ if @options[:signing_key] && @options[:private_key]
+ begin
+ headers['Signature'] = generate_signature(body)
+ rescue OpenSSL::PKey::RSAError => e
+ return Response.new(false, e.message)
+ end
+ end
+
+ response = begin
+ parse(ssl_post(url, body, headers))
+ rescue ResponseError => e
+ raise unless(e.response.code.to_s =~ /400/)
+ parse(e.response.body)
+ end
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(action, response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def success_from(response)
+ (response && (response['status']['code'] == 20000))
+ end
+
+ def message_from(response)
+ default_message = ACTION_CODE_MESSAGES[response['status']['code']]
+
+ if success_from(response)
+ default_message
+ else
+ (response['status']['message'] || default_message)
+ end
+ end
+
+ def authorization_from(action, response)
+ id_of_auth_for_capture(action) || response['id']
+ end
+
+ def id_of_auth_for_capture(action)
+ match = action.match(/authorizations\/(.+)\/captures/)
+ return nil unless match
+
+ match.captures.first
+ end
+
+ def generate_signature(body)
+ key = OpenSSL::PKey::RSA.new(@options[:private_key])
+ hex = key.sign(OpenSSL::Digest.new('sha256'), body).unpack('H*').first
+
+ "#{@options[:signing_key]} RS256-hex #{hex}"
+ end
+
+ def error_code_from(response)
+ unless success_from(response)
+ response['status']['code']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/commercegate.rb b/lib/active_merchant/billing/gateways/commercegate.rb
new file mode 100644
index 00000000000..c1d6d5bc1f4
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/commercegate.rb
@@ -0,0 +1,142 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CommercegateGateway < Gateway
+ self.test_url = self.live_url = 'https://secure.commercegate.com/gateway/nvp'
+
+ self.supported_countries = %w(
+ AD AT AX BE BG CH CY CZ DE DK ES FI FR GB GG
+ GI GR HR HU IE IM IS IT JE LI LT LU LV MC MT
+ NL NO PL PT RO SE SI SK VA
+ )
+
+ self.money_format = :dollars
+ self.default_currency = 'EUR'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+ self.homepage_url = 'http://www.commercegate.com/'
+ self.display_name = 'CommerceGate'
+
+ def initialize(options = {})
+ requires!(options, :login, :password, :site_id, :offer_id)
+ super
+ end
+
+ def authorize(money, creditcard, options = {})
+ post = {}
+ add_creditcard(post, creditcard)
+ add_auth_purchase_options(post, money, options)
+ commit('AUTH', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ post[:currencyCode] = (options[:currency] || currency(money))
+ post[:amount] = amount(money)
+ post[:transID] = authorization
+ commit('CAPTURE', post)
+ end
+
+ def purchase(money, creditcard, options = {})
+ post = {}
+ add_creditcard(post, creditcard)
+ add_auth_purchase_options(post, money, options)
+ commit('SALE', post)
+ end
+
+ def refund(money, identification, options = {})
+ post = {}
+ post[:currencyCode] = options[:currency] || currency(money)
+ post[:amount] = amount(money)
+ post[:transID] = identification
+ commit('REFUND', post)
+ end
+
+ def void(identification, options = {})
+ post = {}
+ post[:transID] = identification
+ commit('VOID_AUTH', post)
+ end
+
+ private
+
+ def add_address(post, address)
+ if address
+ post[:address] = address[:address1]
+ post[:city] = address[:city]
+ post[:state] = address[:state]
+ post[:postalCode] = address[:zip]
+ end
+ post[:countryCode] = ((address && address[:country]) || 'US')
+ end
+
+ def add_auth_purchase_options(post, money, options)
+ add_address(post, options[:address])
+
+ post[:customerIP] = options[:ip] || '127.0.0.1'
+ post[:amount] = amount(money)
+ post[:email] = options[:email] || 'unknown@example.com'
+ post[:currencyCode]= options[:currency] || currency(money)
+ post[:merchAcct] = options[:merchant]
+ end
+
+ def add_creditcard(params, creditcard)
+ params[:firstName] = creditcard.first_name
+ params[:lastName] = creditcard.last_name
+ params[:cardNumber] = creditcard.number
+ params[:expiryMonth] = creditcard.month
+ params[:expiryYear] = creditcard.year
+ params[:cvv] = creditcard.verification_value if creditcard.verification_value?
+ end
+
+ def commit(action, parameters)
+ parameters[:apiUsername] = @options[:login]
+ parameters[:apiPassword] = @options[:password]
+ parameters[:siteID] = @options[:site_id]
+ parameters[:offerID] = @options[:offer_id]
+ parameters[:action] = action
+
+ response = parse(ssl_post(self.live_url, post_data(parameters)))
+
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
+ authorization: response['transID'],
+ test: test?,
+ avs_result: {code: response['avsCode']},
+ cvv_result: response['cvvCode']
+ )
+ end
+
+ def parse(body)
+ results = {}
+
+ body.split(/\&/).each do |pair|
+ key, val = pair.split(%r{=})
+ results[key] = CGI.unescape(val)
+ end
+
+ results
+ end
+
+ def successful?(response)
+ response['returnCode'] == '0'
+ end
+
+ def message_from(response)
+ if response['returnText'].present?
+ response['returnText']
+ else
+ 'Invalid response received from the CommerceGate API. ' \
+ 'Please contact CommerceGate support if you continue to receive this message. ' \
+ "(The raw response returned by the API was #{response.inspect})"
+ end
+ end
+
+ def post_data(parameters)
+ parameters.collect do |key, value|
+ "#{key}=#{CGI.escape(value.to_s)}"
+ end.join('&')
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/conekta.rb b/lib/active_merchant/billing/gateways/conekta.rb
new file mode 100644
index 00000000000..06aad777b09
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/conekta.rb
@@ -0,0 +1,228 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class ConektaGateway < Gateway
+ self.live_url = 'https://api.conekta.io/'
+
+ self.supported_countries = ['MX']
+ self.supported_cardtypes = [:visa, :master, :american_express, :carnet]
+ self.homepage_url = 'https://conekta.io/'
+ self.display_name = 'Conekta Gateway'
+ self.money_format = :cents
+ self.default_currency = 'MXN'
+
+ def initialize(options = {})
+ requires!(options, :key)
+ options[:version] ||= '1.0.0'
+ super
+ end
+
+ def purchase(money, payment_source, options = {})
+ post = {}
+
+ add_order(post, money, options)
+ add_payment_source(post, payment_source, options)
+ add_details_data(post, options)
+
+ commit(:post, 'charges', post, options)
+ end
+
+ def authorize(money, payment_source, options = {})
+ post = {}
+
+ add_order(post, money, options)
+ add_payment_source(post, payment_source, options)
+ add_details_data(post, options)
+
+ post[:capture] = false
+ commit(:post, 'charges', post, options)
+ end
+
+ def capture(money, identifier, options = {})
+ post = {}
+
+ post[:order_id] = identifier
+ add_order(post, money, options)
+
+ commit(:post, "charges/#{identifier}/capture", post, options)
+ end
+
+ def refund(money, identifier, options)
+ post = {}
+
+ post[:order_id] = identifier
+ add_order(post, money, options)
+
+ commit(:post, "charges/#{identifier}/refund", post, options)
+ end
+
+ def void(identifier, options = {})
+ post = {}
+ commit(:post, "charges/#{identifier}/void", post, options)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?card%5Bnumber%5D=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?card%5Bcvc%5D=)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_order(post, money, options)
+ post[:description] = options[:description] || 'Active Merchant Purchase'
+ post[:reference_id] = options[:order_id] if options[:order_id]
+ post[:currency] = (options[:currency] || currency(money)).downcase
+ post[:monthly_installments] = options[:monthly_installments] if options[:monthly_installments]
+ post[:amount] = amount(money)
+ end
+
+ def add_details_data(post, options)
+ details = {}
+ details[:name] = options[:customer] || (options[:billing_address][:name] if options[:billing_address])
+ details[:phone] = options[:phone] || (options[:billing_address][:phone] if options[:billing_address])
+ details[:email] = options[:email] if options[:email]
+ details[:ip] = options[:ip] if options[:ip]
+ add_billing_address(details, options)
+ add_line_items(details, options)
+ add_shipment(details, options)
+ post[:details] = details
+ post[:device_fingerprint] = options[:device_fingerprint] if options[:device_fingerprint]
+ end
+
+ def add_shipment(post, options)
+ shipment = {}
+ shipment[:carrier] = options[:carrier] if options[:carrier]
+ shipment[:service] = options[:service] if options[:service]
+ shipment[:tracking_number] = options[:tracking_number] if options[:tracking_number]
+ shipment[:price] = options[:price] if options[:price]
+ add_shipment_address(shipment, options)
+ post[:shipment] = shipment
+ end
+
+ def add_shipment_address(post, options)
+ if(address = options[:shipping_address])
+ post[:address] = {}
+ post[:address][:street1] = address[:address1] if address[:address1]
+ post[:address][:street2] = address[:address2] if address[:address2]
+ post[:address][:street3] = address[:address3] if address[:address3]
+ post[:address][:city] = address[:city] if address[:city]
+ post[:address][:state] = address[:state] if address[:state]
+ post[:address][:country] = address[:country] if address[:country]
+ post[:address][:zip] = address[:zip] if address[:zip]
+ end
+ end
+
+ def add_line_items(post, options)
+ post[:line_items] = (options[:line_items] || []).collect do |line_item|
+ line_item
+ end
+ end
+
+ def add_billing_address(post, options)
+ if(address = (options[:billing_address] || options[:address]))
+ post[:billing_address] = {}
+ post[:billing_address][:street1] = address[:address1] if address[:address1]
+ post[:billing_address][:street2] = address[:address2] if address[:address2]
+ post[:billing_address][:street3] = address[:address3] if address[:address3]
+ post[:billing_address][:city] = address[:city] if address[:city]
+ post[:billing_address][:state] = address[:state] if address[:state]
+ post[:billing_address][:country] = address[:country] if address[:country]
+ post[:billing_address][:zip] = address[:zip] if address[:zip]
+ post[:billing_address][:company_name] = address[:company_name] if address[:company_name]
+ post[:billing_address][:tax_id] = address[:tax_id] if address[:tax_id]
+ post[:billing_address][:name] = address[:name] if address[:name]
+ post[:billing_address][:phone] = address[:phone] if address[:phone]
+ post[:billing_address][:email] = address[:email] if address[:email]
+ end
+ end
+
+ def add_address(post, options)
+ if(address = (options[:billing_address] || options[:address]))
+ post[:address] = {}
+ post[:address][:street1] = address[:address1] if address[:address1]
+ post[:address][:street2] = address[:address2] if address[:address2]
+ post[:address][:street3] = address[:address3] if address[:address3]
+ post[:address][:city] = address[:city] if address[:city]
+ post[:address][:state] = address[:state] if address[:state]
+ post[:address][:country] = address[:country] if address[:country]
+ post[:address][:zip] = address[:zip] if address[:zip]
+ end
+ end
+
+ def add_payment_source(post, payment_source, options)
+ if payment_source.kind_of?(String)
+ post[:card] = payment_source
+ elsif payment_source.respond_to?(:number)
+ post[:card] = {}
+ post[:card][:name] = payment_source.name
+ post[:card][:cvc] = payment_source.verification_value
+ post[:card][:number] = payment_source.number
+ post[:card][:exp_month] = sprintf('%02d', payment_source.month)
+ post[:card][:exp_year] = payment_source.year.to_s[-2, 2]
+ add_address(post[:card], options)
+ end
+ end
+
+ def parse(body)
+ return {} unless body
+ JSON.parse(body)
+ end
+
+ def headers(options)
+ {
+ 'Accept' => "application/vnd.conekta-v#{@options[:version]}+json",
+ 'Accept-Language' => 'es',
+ 'Authorization' => 'Basic ' + Base64.encode64("#{@options[:key]}:"),
+ 'RaiseHtmlError' => 'false',
+ 'Conekta-Client-User-Agent' => {'agent'=>"Conekta ActiveMerchantBindings/#{ActiveMerchant::VERSION}"}.to_json,
+ 'X-Conekta-Client-User-Agent' => conekta_client_user_agent(options),
+ 'X-Conekta-Client-User-Metadata' => options[:meta].to_json
+ }
+ end
+
+ def conekta_client_user_agent(options)
+ return user_agent unless options[:application]
+ JSON.dump(JSON.parse(user_agent).merge!({application: options[:application]}))
+ end
+
+ def commit(method, url, parameters, options = {})
+ success = false
+ begin
+ raw_response = parse(ssl_request(method, live_url + url, (parameters ? parameters.to_query : nil), headers(options)))
+ success = (raw_response.key?('object') && (raw_response['object'] != 'error'))
+ rescue ResponseError => e
+ raw_response = response_error(e.response.body)
+ rescue JSON::ParserError
+ raw_response = json_error(raw_response)
+ end
+
+ Response.new(
+ success,
+ raw_response['message_to_purchaser'],
+ raw_response,
+ test: test?,
+ authorization: raw_response['id']
+ )
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def json_error(raw_response)
+ msg = 'Invalid response received from the Conekta API.'
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
+ {
+ 'message' => msg
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/creditcall.rb b/lib/active_merchant/billing/gateways/creditcall.rb
new file mode 100644
index 00000000000..45f84569f5d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/creditcall.rb
@@ -0,0 +1,271 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CreditcallGateway < Gateway
+ include Empty
+
+ self.test_url = 'https://test.cardeasexml.com/generic.cex'
+ self.live_url = 'https://live.cardeasexml.com/generic.cex'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'https://www.creditcall.com'
+ self.display_name = 'Creditcall'
+
+ CVV_CODE = {
+ 'matched' => 'M',
+ 'notmatched' => 'N',
+ 'notchecked' => 'P',
+ 'partialmatch' => 'N'
+ }
+
+ AVS_CODE = {
+ 'matched;matched' => 'D',
+ 'matched;notchecked' =>'B',
+ 'matched;notmatched' => 'A',
+ 'matched;partialmatch' => 'A',
+ 'notchecked;matched' => 'P',
+ 'notchecked;notchecked' =>'I',
+ 'notchecked;notmatched' => 'I',
+ 'notchecked;partialmatch' => 'I',
+ 'notmatched;matched' => 'W',
+ 'notmatched;notchecked' =>'C',
+ 'notmatched;notmatched' => 'C',
+ 'notmatched;partialmatch' => 'C',
+ 'partialmatched;matched' => 'W',
+ 'partialmatched;notchecked' =>'C',
+ 'partialmatched;notmatched' => 'C',
+ 'partialmatched;partialmatch' => 'C'
+ }
+
+ def initialize(options={})
+ requires!(options, :terminal_id, :transaction_key)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ multi_response = MultiResponse.run do |r|
+ r.process { authorize(money, payment_method, options) }
+ r.process { capture(money, r.authorization, options) }
+ end
+
+ merged_params = multi_response.responses.map(&:params).reduce({}, :merge)
+
+ Response.new(
+ multi_response.primary_response.success?,
+ multi_response.primary_response.message,
+ merged_params,
+ authorization: multi_response.responses.first.authorization,
+ avs_result: AVSResult.new(code: avs_result_code_from(merged_params)),
+ cvv_result: CVVResult.new(cvv_result_code_from(merged_params)),
+ error_code: error_result_code_from(merged_params),
+ test: test?
+ )
+ end
+
+ def authorize(money, payment_method, options={})
+ request = build_xml_request do |xml|
+ add_transaction_details(xml, money, nil, 'Auth', options)
+ add_terminal_details(xml, options)
+ add_card_details(xml, payment_method, options)
+ end
+
+ commit(request)
+ end
+
+ def capture(money, authorization, options={})
+ request = build_xml_request do |xml|
+ add_transaction_details(xml, money, authorization, 'Conf', options)
+ add_terminal_details(xml, options)
+ end
+
+ commit(request)
+ end
+
+ def refund(money, authorization, options={})
+ request = build_xml_request do |xml|
+ add_transaction_details(xml, money, authorization, 'Refund', options)
+ add_terminal_details(xml, options)
+ end
+
+ commit(request)
+ end
+
+ def void(authorization, options={})
+ request = build_xml_request do |xml|
+ add_transaction_details(xml, nil, authorization, 'Void', options)
+ add_terminal_details(xml, options)
+ 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(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def avs_result_code_from(params)
+ AVS_CODE["#{params['Address']};#{params['Zip']}"]
+ end
+
+ def cvv_result_code_from(params)
+ CVV_CODE[params['CSC']]
+ end
+
+ def error_result_code_from(params)
+ params['ErrorCode']
+ end
+
+ def build_xml_request
+ builder = Nokogiri::XML::Builder.new do |xml|
+ xml.Request(type: 'CardEaseXML', version: '1.0.0') do
+ yield(xml)
+ end
+ end
+ builder.to_xml
+ end
+
+ def add_transaction_details(xml, amount, authorization, type, options={})
+ xml.TransactionDetails do
+ xml.MessageType type
+ xml.Amount(unit: 'Minor') { xml.text(amount) } if amount
+ xml.CardEaseReference authorization if authorization
+ xml.VoidReason '01' if type == 'Void'
+ end
+ end
+
+ def add_terminal_details(xml, options={})
+ xml.TerminalDetails do
+ xml.TerminalID @options[:terminal_id]
+ xml.TransactionKey @options[:transaction_key]
+ xml.Software(version: 'SoftwareVersion') { xml.text('SoftwareName') }
+ end
+ end
+
+ def add_card_details(xml, payment_method, options={})
+ xml.CardDetails do
+ xml.Manual(type: manual_type(options)) do
+ xml.PAN payment_method.number
+ xml.ExpiryDate exp_date(payment_method)
+ xml.CSC payment_method.verification_value unless empty?(payment_method.verification_value)
+ end
+
+ add_additional_verification(xml, options)
+ end
+ end
+
+ def add_additional_verification(xml, options)
+ return unless (options[:verify_zip].to_s == 'true') || (options[:verify_address].to_s == 'true')
+ if address = options[:billing_address]
+ xml.AdditionalVerification do
+ xml.Zip address[:zip] if options[:verify_zip].to_s == 'true'
+ xml.Address address[:address1] if options[:verify_address].to_s == 'true'
+ end
+ end
+ end
+
+ def exp_date(payment_method)
+ "#{format(payment_method.year, :two_digits)}#{format(payment_method.month, :two_digits)}"
+ end
+
+ def parse(body)
+ response = {}
+ xml = Nokogiri::XML(body)
+
+ node = xml.xpath('//Response/TransactionDetails')
+ node.children.each do |childnode|
+ response[childnode.name] = childnode.text
+ end
+
+ node = xml.xpath('//Response/Result')
+ node.children.each do |childnode|
+ if childnode.elements.empty?
+ response[childnode.name] = childnode.text
+ else
+ childnode_to_response(response, childnode)
+ end
+ end
+
+ node = xml.xpath('//Response/CardDetails')
+ node.children.each do |childnode|
+ if childnode.elements.empty?
+ response[childnode.name] = childnode.text
+ else
+ childnode_to_response(response, childnode)
+ end
+ end
+
+ response
+ end
+
+ def childnode_to_response(response, childnode)
+ childnode.elements.each do |element|
+ if element.name == 'Error'
+ response['ErrorCode'] = element.attr('code')
+ response['ErrorMessage'] = element.text
+ else
+ response[element.name] = element.text
+ end
+ end
+ end
+
+ def commit(parameters)
+ response = parse(ssl_post(url, parameters))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: avs_result_code_from(response)),
+ cvv_result: CVVResult.new(cvv_result_code_from(response)),
+ error_code: error_result_code_from(response),
+ test: test?
+ )
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def success_from(response)
+ response['LocalResult'] == '0' || response['LocalResult'] == '00'
+ end
+
+ def message_from(response)
+ if success_from(response)
+ 'Succeeded'
+ else
+ response['ErrorMessage']
+ end
+ end
+
+ def authorization_from(response)
+ response['CardEaseReference']
+ end
+
+ def manual_type(options)
+ options[:manual_type] || 'ecommerce'
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/credorax.rb b/lib/active_merchant/billing/gateways/credorax.rb
new file mode 100644
index 00000000000..6f28d82f259
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/credorax.rb
@@ -0,0 +1,382 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CredoraxGateway < Gateway
+ class_attribute :test_url, :live_na_url, :live_eu_url
+
+ self.display_name = 'Credorax Gateway'
+ self.homepage_url = 'https://www.credorax.com/'
+
+ # NOTE: the IP address you run the remote tests from will need to be
+ # whitelisted by Credorax; contact support@credorax.com as necessary to
+ # request your IP address be added to the whitelist for your test
+ # account.
+ self.test_url = 'https://intconsole.credorax.com/intenv/service/gateway'
+
+ # The live URL is assigned on a per merchant basis once certification has passed
+ # See the Credorax remote tests for the full certification test suite
+ #
+ # Once you have your assigned subdomain, you can override the live URL in your application via:
+ # ActiveMerchant::Billing::CredoraxGateway.live_url = "https://assigned-subdomain.credorax.net/crax_gate/service/gateway"
+ self.live_url = 'https://assigned-subdomain.credorax.net/crax_gate/service/gateway'
+
+ self.supported_countries = %w(AD AT BE BG HR CY CZ DK EE FR DE GI GR GG HU IS IE IM IT JE LV LI LT LU MT MC NO PL PT RO SM SK ES SE CH GB)
+ self.default_currency = 'EUR'
+ self.currencies_without_fractions = %w(CLP JPY KRW PYG VND)
+ self.currencies_with_three_decimal_places = %w(BHD JOD KWD OMR RSD TND)
+
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :maestro]
+
+ RESPONSE_MESSAGES = {
+ '00' => 'Approved or completed successfully',
+ '01' => 'Refer to card issuer',
+ '02' => 'Refer to card issuer special condition',
+ '03' => 'Invalid merchant',
+ '04' => 'Pick up card',
+ '05' => 'Do not Honour',
+ '06' => 'Error',
+ '07' => 'Pick up card special condition',
+ '08' => 'Honour with identification',
+ '09' => 'Request in progress',
+ '10' => 'Approved for partial amount',
+ '11' => 'Approved (VIP)',
+ '12' => 'Invalid transaction',
+ '13' => 'Invalid amount',
+ '14' => 'Invalid card number',
+ '15' => 'No such issuer',
+ '16' => 'Approved, update track 3',
+ '17' => 'Customer cancellation',
+ '18' => 'Customer dispute',
+ '19' => 'Re-enter transaction',
+ '20' => 'Invalid response',
+ '21' => 'No action taken',
+ '22' => 'Suspected malfunction',
+ '23' => 'Unacceptable transaction fee',
+ '24' => 'File update not supported by receiver',
+ '25' => 'No such record',
+ '26' => 'Duplicate record update, old record replaced',
+ '27' => 'File update field edit error',
+ '28' => 'File locked out while update',
+ '29' => 'File update error, contact acquirer',
+ '30' => 'Format error',
+ '31' => 'Issuer signed-off',
+ '32' => 'Completed partially',
+ '33' => 'Pick-up, expired card',
+ '34' => 'Suspect Fraud',
+ '35' => 'Pick-up, card acceptor contact acquirer',
+ '36' => 'Pick up, card restricted',
+ '37' => 'Pick up, call acquirer security',
+ '38' => 'Pick up, Allowable PIN tries exceeded',
+ '39' => 'Transaction Not Allowed',
+ '40' => 'Requested function not supported',
+ '41' => 'Lost Card, Pickup',
+ '42' => 'No universal account',
+ '43' => 'Pick up, stolen card',
+ '44' => 'No investment account',
+ '50' => 'Do not renew',
+ '51' => 'Not sufficient funds',
+ '52' => 'No checking Account',
+ '53' => 'No savings account',
+ '54' => 'Expired card',
+ '55' => 'Pin incorrect',
+ '56' => 'No card record',
+ '57' => 'Transaction not allowed for cardholder',
+ '58' => 'Transaction not allowed for merchant',
+ '59' => 'Suspected Fraud',
+ '60' => 'Card acceptor contact acquirer',
+ '61' => 'Exceeds withdrawal amount limit',
+ '62' => 'Restricted card',
+ '63' => 'Security violation',
+ '64' => 'Wrong original amount',
+ '65' => 'Activity count limit exceeded',
+ '66' => 'Call acquirers security department',
+ '67' => 'Card to be picked up at ATM',
+ '68' => 'Response received too late.',
+ '70' => 'Invalid transaction; contact card issuer',
+ '71' => 'Decline PIN not changed',
+ '75' => 'Pin tries exceeded',
+ '76' => 'Wrong PIN, number of PIN tries exceeded',
+ '77' => 'Wrong Reference No.',
+ '78' => 'Record Not Found',
+ '79' => 'Already reversed',
+ '80' => 'Network error',
+ '81' => 'Foreign network error / PIN cryptographic error',
+ '82' => 'Time out at issuer system',
+ '83' => 'Transaction failed',
+ '84' => 'Pre-authorization timed out',
+ '85' => 'No reason to decline',
+ '86' => 'Cannot verify pin',
+ '87' => 'Purchase amount only, no cashback allowed',
+ '88' => 'MAC sync Error',
+ '89' => 'Authentication failure',
+ '91' => 'Issuer not available',
+ '92' => 'Unable to route at acquirer Module',
+ '93' => 'Cannot be completed, violation of law',
+ '94' => 'Duplicate Transmission',
+ '95' => 'Reconcile error / Auth Not found',
+ '96' => 'System malfunction',
+ 'R0' => 'Stop Payment Order',
+ 'R1' => 'Revocation of Authorisation Order',
+ 'R3' => 'Revocation of all Authorisations Order'
+ }
+
+ def initialize(options={})
+ requires!(options, :merchant_id, :cipher_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+ add_email(post, options)
+ add_3d_secure(post, options)
+ add_echo(post, options)
+ add_submerchant_id(post, options)
+ add_transaction_type(post, options)
+
+ commit(:purchase, post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+ add_email(post, options)
+ add_3d_secure(post, options)
+ add_echo(post, options)
+ add_submerchant_id(post, options)
+ add_transaction_type(post, options)
+
+ commit(:authorize, post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+ add_echo(post, options)
+ add_submerchant_id(post, options)
+
+ commit(:capture, post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_customer_data(post, options)
+ reference_action = add_reference(post, authorization)
+ add_echo(post, options)
+ add_submerchant_id(post, options)
+ post[:a1] = generate_unique_id
+
+ commit(:void, post, reference_action)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+ add_echo(post, options)
+ add_submerchant_id(post, options)
+
+ commit(:refund, post)
+ end
+
+ def credit(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+ add_email(post, options)
+ add_echo(post, options)
+ add_submerchant_id(post, options)
+ add_transaction_type(post, options)
+
+ commit(:credit, 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((b1=)\d+), '\1[FILTERED]').
+ gsub(%r((b5=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_invoice(post, money, options)
+ currency = options[:currency] || currency(money)
+
+ post[:a4] = localized_amount(money, currency)
+ post[:a1] = generate_unique_id
+ post[:a5] = currency
+ post[:h9] = options[:order_id]
+ post[:i2] = options[:billing_descriptor] if options[:billing_descriptor]
+ end
+
+ CARD_TYPES = {
+ 'visa' => '1',
+ 'mastercard' => '2',
+ 'maestro' => '9'
+ }
+
+ def add_payment_method(post, payment_method)
+ post[:c1] = payment_method.name
+ post[:b2] = CARD_TYPES[payment_method.brand] || ''
+ post[:b1] = payment_method.number
+ post[:b5] = payment_method.verification_value
+ post[:b4] = format(payment_method.year, :two_digits)
+ post[:b3] = format(payment_method.month, :two_digits)
+ end
+
+ def add_customer_data(post, options)
+ post[:d1] = options[:ip] || '127.0.0.1'
+ if (billing_address = options[:billing_address])
+ post[:c5] = billing_address[:address1]
+ post[:c7] = billing_address[:city]
+ post[:c10] = billing_address[:zip]
+ post[:c8] = billing_address[:state]
+ post[:c9] = billing_address[:country]
+ post[:c2] = billing_address[:phone]
+ end
+ end
+
+ def add_reference(post, authorization)
+ response_id, authorization_code, request_id, action = authorization.split(';')
+ post[:g2] = response_id
+ post[:g3] = authorization_code
+ post[:g4] = request_id
+ action || :authorize
+ end
+
+ def add_email(post, options)
+ post[:c3] = options[:email] || 'unspecified@example.com'
+ end
+
+ def add_3d_secure(post, options)
+ if options[:eci] && options[:xid]
+ add_3d_secure_1_data(post, options)
+ elsif options[:three_d_secure]
+ add_normalized_3d_secure_2_data(post, options)
+ end
+ end
+
+ def add_3d_secure_1_data(post, options)
+ post[:i8] = build_i8(options[:eci], options[:cavv], options[:xid])
+ end
+
+ def add_normalized_3d_secure_2_data(post, options)
+ three_d_secure_options = options[:three_d_secure]
+
+ post[:i8] = build_i8(
+ three_d_secure_options[:eci],
+ three_d_secure_options[:cavv]
+ )
+ post[:'3ds_version'] = three_d_secure_options[:version]
+ post[:'3ds_dstrxid'] = three_d_secure_options[:ds_transaction_id]
+ end
+
+ def build_i8(eci, cavv=nil, xid=nil)
+ "#{eci}:#{cavv || 'none'}:#{xid || 'none'}"
+ end
+
+ def add_echo(post, options)
+ # The d2 parameter is used during the certification process
+ # See remote tests for full certification test suite
+ post[:d2] = options[:echo] unless options[:echo].blank?
+ end
+
+ def add_submerchant_id(post, options)
+ post[:h3] = options[:submerchant_id] if options[:submerchant_id]
+ end
+
+ def add_transaction_type(post, options)
+ post[:a9] = options[:transaction_type] if options[:transaction_type]
+ end
+
+ ACTIONS = {
+ purchase: '1',
+ authorize: '2',
+ capture: '3',
+ authorize_void: '4',
+ refund: '5',
+ credit: '6',
+ purchase_void: '7',
+ refund_void: '8',
+ capture_void: '9'
+ }
+
+ def commit(action, params, reference_action = nil)
+ raw_response = ssl_post(url, post_data(action, params, reference_action))
+ response = parse(raw_response)
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: "#{response["Z1"]};#{response["Z4"]};#{response["A1"]};#{action}",
+ avs_result: AVSResult.new(code: response['Z9']),
+ cvv_result: CVVResult.new(response['Z14']),
+ test: test?
+ )
+ end
+
+ def sign_request(params)
+ params = params.sort
+ params.each { |param| param[1].gsub!(/[<>()\\]/, ' ') }
+ values = params.map { |param| param[1].strip }
+ Digest::MD5.hexdigest(values.join + @options[:cipher_key])
+ end
+
+ def post_data(action, params, reference_action)
+ params.keys.each { |key| params[key] = params[key].to_s }
+ params[:M] = @options[:merchant_id]
+ params[:O] = request_action(action, reference_action)
+ params[:K] = sign_request(params)
+ params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def request_action(action, reference_action)
+ if reference_action
+ ACTIONS["#{reference_action}_#{action}".to_sym]
+ else
+ ACTIONS[action]
+ end
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def parse(body)
+ Hash[CGI::parse(body).map { |k, v| [k.upcase, v.first] }]
+ end
+
+ def success_from(response)
+ response['Z2'] == '0'
+ end
+
+ def message_from(response)
+ if success_from(response)
+ 'Succeeded'
+ else
+ RESPONSE_MESSAGES[response['Z6']] || response['Z3'] || 'Unable to read error message'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/ct_payment.rb b/lib/active_merchant/billing/gateways/ct_payment.rb
new file mode 100644
index 00000000000..315f16375d8
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/ct_payment.rb
@@ -0,0 +1,268 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class CtPaymentGateway < Gateway
+ self.test_url = 'https://test.ctpaiement.ca/v1/'
+ self.live_url = 'https://www.ctpaiement.com/v1/'
+
+ self.supported_countries = ['US', 'CA']
+ self.default_currency = 'CAD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
+
+ self.homepage_url = 'http://www.ct-payment.com/'
+ self.display_name = 'CT Payment'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '14' => STANDARD_ERROR_CODE[:invalid_number],
+ '05' => STANDARD_ERROR_CODE[:card_declined],
+ 'M6' => STANDARD_ERROR_CODE[:card_declined],
+ '9068' => STANDARD_ERROR_CODE[:incorrect_number],
+ '9067' => STANDARD_ERROR_CODE[:incorrect_number]
+ }
+ CARD_BRAND = {
+ 'american_express' => 'A',
+ 'master' => 'M',
+ 'diners_club' => 'I',
+ 'visa' => 'V',
+ 'discover' => 'O'
+ }
+
+ def initialize(options={})
+ requires!(options, :api_key, :company_number, :merchant_number)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ requires!(options, :order_id)
+ post = {}
+ add_terminal_number(post, options)
+ add_money(post, money)
+ add_operator_id(post, options)
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ payment.is_a?(String) ? commit('purchaseWithToken', post) : commit('purchase', post)
+ end
+
+ def authorize(money, payment, options={})
+ requires!(options, :order_id)
+ post = {}
+ add_money(post, money)
+ add_terminal_number(post, options)
+ add_operator_id(post, options)
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ payment.is_a?(String) ? commit('preAuthorizationWithToken', post) : commit('preAuthorization', post)
+ end
+
+ def capture(money, authorization, options={})
+ requires!(options, :order_id)
+ post = {}
+ add_invoice(post, money, options)
+ add_money(post, money)
+ add_customer_data(post, options)
+ transaction_number, authorization_number, invoice_number = split_authorization(authorization)
+ post[:OriginalTransactionNumber] = transaction_number
+ post[:OriginalAuthorizationNumber] = authorization_number
+ post[:OriginalInvoiceNumber] = invoice_number
+
+ commit('completion', post)
+ end
+
+ def refund(money, authorization, options={})
+ requires!(options, :order_id)
+ post = {}
+ add_invoice(post, money, options)
+ add_money(post, money)
+ add_customer_data(post, options)
+ transaction_number, _, invoice_number = split_authorization(authorization)
+ post[:OriginalTransactionNumber] = transaction_number
+ post[:OriginalInvoiceNumber] = invoice_number
+
+ commit('refundWithoutCard', post)
+ end
+
+ def credit(money, payment, options={})
+ requires!(options, :order_id)
+ post = {}
+ add_terminal_number(post, options)
+ add_money(post, money)
+ add_operator_id(post, options)
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+
+ payment.is_a?(String) ? commit('refundWithToken', post) : commit('refund', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ post[:InputType] = 'I'
+ post[:LanguageCode] = 'E'
+ transaction_number, _, invoice_number = split_authorization(authorization)
+ post[:OriginalTransactionNumber] = transaction_number
+ post[:OriginalInvoiceNumber] = invoice_number
+ add_operator_id(post, options)
+ add_customer_data(post, options)
+
+ commit('void', post)
+ end
+
+ def verify(credit_card, options={})
+ requires!(options, :order_id)
+ post = {}
+ add_terminal_number(post, options)
+ add_operator_id(post, options)
+ add_invoice(post, 0, options)
+ add_payment(post, credit_card)
+ add_address(post, credit_card, options)
+ add_customer_data(post, options)
+
+ commit('verifyAccount', post)
+ end
+
+ def store(credit_card, options={})
+ requires!(options, :email)
+ post = {
+ LanguageCode: 'E',
+ Name: credit_card.name.rjust(50, ' '),
+ Email: options[:email].rjust(240, ' ')
+ }
+ add_operator_id(post, options)
+ add_payment(post, credit_card)
+ add_customer_data(post, options)
+
+ commit('recur/AddUser', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?auth-api-key=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?payload=)[a-zA-Z%0-9=]+)i, '\1[FILTERED]').
+ gsub(%r((&?token:)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?cardNumber:)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_terminal_number(post, options)
+ post[:MerchantTerminalNumber] = options[:merchant_terminal_number] || ' ' * 5
+ end
+
+ def add_money(post, money)
+ post[:Amount] = money.to_s.rjust(11, '0')
+ end
+
+ def add_operator_id(post, options)
+ post[:OperatorID] = options[:operator_id] || '0' * 8
+ end
+
+ def add_customer_data(post, options)
+ post[:CustomerNumber] = options[:customer_number] || '0' * 8
+ end
+
+ def add_address(post, creditcard, options)
+ if address = options[:billing_address] || options[:address]
+ post[:CardHolderAddress] = "#{address[:address1]} #{address[:address2]} #{address[:city]} #{address[:state]}".rjust(20, ' ')
+ post[:CardHolderPostalCode] = address[:zip].gsub(/\s+/, '').rjust(9, ' ')
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:CurrencyCode] = options[:currency] || (currency(money) if money)
+ post[:InvoiceNumber] = options[:order_id].rjust(12, '0')
+ post[:InputType] = 'I'
+ post[:LanguageCode] = 'E'
+ end
+
+ def add_payment(post, payment)
+ if payment.is_a?(String)
+ post[:Token] = split_authorization(payment)[3].strip
+ else
+ post[:CardType] = CARD_BRAND[payment.brand] || ' '
+ post[:CardNumber] = payment.number.rjust(40, ' ')
+ post[:ExpirationDate] = expdate(payment)
+ post[:Cvv2Cvc2Number] = payment.verification_value
+ end
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def split_authorization(authorization)
+ authorization.split(';')
+ end
+
+ def commit_raw(action, parameters)
+ url = (test? ? test_url : live_url) + action
+ response = parse(ssl_post(url, post_data(action, parameters)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['avsStatus']),
+ cvv_result: CVVResult.new(response['cvv2Cvc2Status']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def commit(action, parameters)
+ if action == 'void'
+ commit_raw(action, parameters)
+ else
+ MultiResponse.run(true) do |r|
+ r.process { commit_raw(action, parameters) }
+ r.process {
+ split_auth = split_authorization(r.authorization)
+ auth = (action.include?('recur')? split_auth[4] : split_auth[0])
+ action.include?('recur') ? commit_raw('recur/ack', {ID: auth}) : commit_raw('ack', {TransactionNumber: auth})
+ }
+ end
+ end
+ end
+
+ def success_from(response)
+ return true if response['returnCode'] == ' 00'
+ return true if response['returnCode'] == 'true'
+ return true if response['recurReturnCode'] == ' 00'
+ return false
+ end
+
+ def message_from(response)
+ response['errorDescription'] || response['terminalDisp']&.strip
+ end
+
+ def authorization_from(response)
+ "#{response['transactionNumber']};#{response['authorizationNumber']};"\
+ "#{response['invoiceNumber']};#{response['token']};#{response['id']}"
+ end
+
+ def post_data(action, parameters = {})
+ parameters[:CompanyNumber] = @options[:company_number]
+ parameters[:MerchantNumber] = @options[:merchant_number]
+ parameters = parameters.collect do |key, value|
+ "#{key}=#{value}" unless value.nil? || value.empty?
+ end.join('&')
+ payload = Base64.strict_encode64(parameters)
+ "auth-api-key=#{@options[:api_key]}&payload=#{payload}".strip
+ end
+
+ def error_code_from(response)
+ STANDARD_ERROR_CODE_MAPPING[response['returnCode'].strip || response['recurReturnCode'.strip]] unless success_from(response)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/culqi.rb b/lib/active_merchant/billing/gateways/culqi.rb
new file mode 100644
index 00000000000..80b4d030198
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/culqi.rb
@@ -0,0 +1,277 @@
+require 'digest/md5'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ # Important note:
+ # ===
+ # Culqi merchant accounts are configured for either purchase or auth/capture
+ # modes. This is configured by Culqi when setting up a merchant account and
+ # largely depends on the transaction acquiring bank. Be sure to understand how
+ # your account was configured prior to using this gateway.
+ class CulqiGateway < Gateway
+ self.display_name = 'Culqi'
+ self.homepage_url = 'https://www.culqi.com'
+
+ self.test_url = 'https://staging.paymentz.com/transaction/'
+ self.live_url = 'https://secure.culqi.com/transaction/'
+
+ self.supported_countries = ['PE']
+ self.default_currency = 'PEN'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :diners_club, :american_express]
+
+ def initialize(options={})
+ requires!(options, :merchant_id, :terminal_id, :secret_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ authorize(amount, payment_method, options)
+ end
+
+ def authorize(amount, payment_method, options={})
+ if payment_method.is_a?(String)
+ action = :tokenpay
+ else
+ action = :authorize
+ end
+ post = {}
+ add_credentials(post)
+ add_invoice(action, post, amount, options)
+ add_payment_method(post, payment_method, action, options)
+ add_customer_data(post, options)
+ add_checksum(action, post)
+
+ commit(action, post)
+ end
+
+ def capture(amount, authorization, options={})
+ action = :capture
+ post = {}
+ add_credentials(post)
+ add_invoice(action, post, amount, options)
+ add_reference(post, authorization)
+ add_checksum(action, post)
+
+ commit(action, post)
+ end
+
+ def void(authorization, options={})
+ action = :void
+ post = {}
+ add_credentials(post)
+ add_invoice(action, post, nil, options)
+ add_reference(post, authorization)
+ add_checksum(action, post)
+
+ commit(action, post)
+ end
+
+ def refund(amount, authorization, options={})
+ action = :refund
+ post = {}
+ add_credentials(post)
+ add_invoice(action, post, amount, options)
+ add_reference(post, authorization)
+ add_checksum(action, post)
+
+ commit(action, post)
+ end
+
+ def verify(credit_card, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(1000, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def verify_credentials
+ response = void('0', order_id: '0')
+ response.message.include? 'Transaction not found'
+ end
+
+ def store(credit_card, options={})
+ action = :tokenize
+ post = {}
+ post[:partnerid] = options[:partner_id] if options[:partner_id]
+ post[:cardholderid] = options[:cardholder_id] if options[:cardholder_id]
+ add_credentials(post)
+ add_payment_method(post, credit_card, action, options)
+ add_customer_data(post, options)
+ add_checksum(action, post)
+
+ commit(action, post)
+ end
+
+ def invalidate(authorization, options={})
+ action = :invalidate
+ post = {}
+ post[:partnerid] = options[:partner_id] if options[:partner_id]
+ add_credentials(post)
+ post[:token] = authorization
+ add_checksum(action, post)
+
+ commit(action, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((cardnumber=)\d+), '\1[FILTERED]').
+ gsub(%r((cvv=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_credentials(post)
+ post[:toid] = @options[:merchant_id]
+ post[:totype] = 'Culqi'
+ post[:terminalid] = @options[:terminal_id]
+ post[:language] = 'ENG'
+ end
+
+ def add_invoice(action, post, money, options)
+ case action
+ when :capture
+ post[:captureamount] = amount(money)
+ when :refund
+ post[:refundamount] = amount(money)
+ post[:reason] = 'none'
+ else
+ post[:amount] = amount(money)
+ end
+
+ post[:description] = options[:order_id]
+ post[:redirecturl] = options[:redirect_url] || 'http://www.example.com'
+ end
+
+ def add_payment_method(post, payment_method, action, options)
+ if payment_method.is_a?(String)
+ post[:token] = payment_method
+ post[:cvv] = options[:cvv] if options[:cvv]
+ else
+ post[:cardnumber] = payment_method.number
+ post[:cvv] = payment_method.verification_value
+ post[:firstname], post[:lastname] = payment_method.name.split(' ')
+ if action == :tokenize
+ post[:expirymonth] = format(payment_method.month, :two_digits)
+ post[:expiryyear] = format(payment_method.year, :four_digits)
+ else
+ post[:expiry_month] = format(payment_method.month, :two_digits)
+ post[:expiry_year] = format(payment_method.year, :four_digits)
+ end
+ end
+ end
+
+ def add_customer_data(post, options)
+ post[:emailaddr] = options[:email] || 'unspecified@example.com'
+ if (billing_address = options[:billing_address] || options[:address])
+ post[:street] = [billing_address[:address1], billing_address[:address2]].join(' ')
+ post[:city] = billing_address[:city]
+ post[:state] = billing_address[:state]
+ post[:countrycode] = billing_address[:country]
+ post[:zip] = billing_address[:zip]
+ post[:telno] = billing_address[:phone]
+ post[:telnocc] = options[:telephone_country_code] || '051'
+ end
+ end
+
+ def add_checksum(action, post)
+ checksum_elements = case action
+ when :capture then [post[:toid], post[:trackingid], post[:captureamount], @options[:secret_key]]
+ when :void then [post[:toid], post[:description], post[:trackingid], @options[:secret_key]]
+ when :refund then [post[:toid], post[:trackingid], post[:refundamount], @options[:secret_key]]
+ when :tokenize then [post[:partnerid], post[:cardnumber], post[:cvv], @options[:secret_key]]
+ when :invalidate then [post[:partnerid], post[:token], @options[:secret_key]]
+ else [post[:toid], post[:totype], post[:amount], post[:description], post[:redirecturl],
+ post[:cardnumber] || post[:token], @options[:secret_key]]
+ end
+
+ post[:checksum] = Digest::MD5.hexdigest(checksum_elements.compact.join('|'))
+ end
+
+ def add_reference(post, authorization)
+ post[:trackingid] = authorization
+ end
+
+ ACTIONS = {
+ authorize: 'SingleCallGenericServlet',
+ capture: 'SingleCallGenericCaptureServlet',
+ void: 'SingleCallGenericVoid',
+ refund: 'SingleCallGenericReverse',
+ tokenize: 'SingleCallTokenServlet',
+ invalidate: 'SingleCallInvalidateToken',
+ tokenpay: 'SingleCallTokenTransaction',
+ }
+
+ def commit(action, params)
+ response = begin
+ parse(ssl_post(url + ACTIONS[action], post_data(action, params), headers))
+ rescue ResponseError => e
+ parse(e.response.body)
+ end
+
+ success = success_from(response)
+
+ Response.new(
+ success,
+ message_from(response),
+ response,
+ authorization: success ? authorization_from(response) : nil,
+ cvv_result: success ? cvvresult_from(response) : nil,
+ error_code: success ? nil : error_from(response),
+ test: test?
+ )
+ end
+
+ def headers
+ {
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
+ }
+ end
+
+ def post_data(action, params)
+ params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ rescue JSON::ParserError
+ message = 'Invalid JSON response received from CulqiGateway. Please contact CulqiGateway if you continue to receive this message.'
+ message += "(The raw response returned by the API was #{body.inspect})"
+ {
+ 'status' => 'N',
+ 'statusdescription' => message
+ }
+ end
+
+ def success_from(response)
+ response['status'] == 'Y'
+ end
+
+ def message_from(response)
+ response['statusdescription'] || response['statusDescription']
+ end
+
+ def authorization_from(response)
+ response['trackingid'] || response['token']
+ end
+
+ def cvvresult_from(response)
+ CVVResult.new(response['cvvresult'])
+ end
+
+ def error_from(response)
+ response['resultcode']
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/cyber_source.rb b/lib/active_merchant/billing/gateways/cyber_source.rb
index 3213dd0f4b4..ba4cb8438f9 100644
--- a/lib/active_merchant/billing/gateways/cyber_source.rb
+++ b/lib/active_merchant/billing/gateways/cyber_source.rb
@@ -1,16 +1,7 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- # See the remote and mocked unit test files for example usage. Pay special
- # attention to the contents of the options hash.
- #
# Initial setup instructions can be found in
- # http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf
- #
- # Debugging
- # If you experience an issue with this gateway be sure to examine the
- # transaction information from a general transaction search inside the
- # CyberSource Business Center for the full error messages including field
- # names.
+ # http://apps.cybersource.com/library/documentation/dev_guides/SOAP_Toolkits/SOAP_toolkits.pdf
#
# Important Notes
# * For checks you can purchase and store.
@@ -24,70 +15,79 @@ module Billing #:nodoc:
# CyberSource what kind of item you are selling. It is used when
# calculating tax/VAT.
# * All transactions use dollar values.
- # * To process pinless debit cards throught the pinless debit card
+ # * To process pinless debit cards through the pinless debit card
# network, your Cybersource merchant account must accept pinless
# debit card payments.
+ # * The order of the XML elements does matter, make sure to follow the order in
+ # the documentation exactly.
class CyberSourceGateway < Gateway
- self.test_url = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor'
- self.live_url = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor'
+ self.test_url = 'https://ics2wstesta.ic3.com/commerce/1.x/transactionProcessor'
+ self.live_url = 'https://ics2wsa.ic3.com/commerce/1.x/transactionProcessor'
- XSD_VERSION = "1.69"
+ # Schema files can be found here: https://ics2ws.ic3.com/commerce/1.x/transactionProcessor/
+ TEST_XSD_VERSION = '1.156'
+ PRODUCTION_XSD_VERSION = '1.155'
+
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :dankort, :maestro, :elo]
+ self.supported_countries = %w(US BR CA CN DK FI FR DE IN JP MX NO SE GB SG LB PK)
- # visa, master, american_express, discover
- self.supported_cardtypes = [:visa, :master, :american_express, :discover]
- self.supported_countries = %w(US BR CA CN DK FI FR DE JP MX NO SE GB SG)
self.default_currency = 'USD'
+ self.currencies_without_fractions = %w(JPY)
+
self.homepage_url = 'http://www.cybersource.com'
self.display_name = 'CyberSource'
- # map credit card to the CyberSource expected representation
@@credit_card_codes = {
:visa => '001',
:master => '002',
:american_express => '003',
- :discover => '004'
+ :discover => '004',
+ :diners_club => '005',
+ :jcb => '007',
+ :dankort => '034',
+ :maestro => '042',
+ :elo => '054'
}
- # map response codes to something humans can read
@@response_codes = {
- :r100 => "Successful transaction",
- :r101 => "Request is missing one or more required fields" ,
- :r102 => "One or more fields contains invalid data",
- :r150 => "General failure",
- :r151 => "The request was received but a server time-out occurred",
- :r152 => "The request was received, but a service timed out",
- :r200 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check",
- :r201 => "The issuing bank has questions about the request",
- :r202 => "Expired card",
- :r203 => "General decline of the card",
- :r204 => "Insufficient funds in the account",
- :r205 => "Stolen or lost card",
- :r207 => "Issuing bank unavailable",
- :r208 => "Inactive card or card not authorized for card-not-present transactions",
- :r209 => "American Express Card Identifiction Digits (CID) did not match",
- :r210 => "The card has reached the credit limit",
- :r211 => "Invalid card verification number",
+ :r100 => 'Successful transaction',
+ :r101 => 'Request is missing one or more required fields',
+ :r102 => 'One or more fields contains invalid data',
+ :r150 => 'General failure',
+ :r151 => 'The request was received but a server time-out occurred',
+ :r152 => 'The request was received, but a service timed out',
+ :r200 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check',
+ :r201 => 'The issuing bank has questions about the request',
+ :r202 => 'Expired card',
+ :r203 => 'General decline of the card',
+ :r204 => 'Insufficient funds in the account',
+ :r205 => 'Stolen or lost card',
+ :r207 => 'Issuing bank unavailable',
+ :r208 => 'Inactive card or card not authorized for card-not-present transactions',
+ :r209 => 'American Express Card Identifiction Digits (CID) did not match',
+ :r210 => 'The card has reached the credit limit',
+ :r211 => 'Invalid card verification number',
:r221 => "The customer matched an entry on the processor's negative file",
- :r230 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check",
- :r231 => "Invalid account number",
- :r232 => "The card type is not accepted by the payment processor",
- :r233 => "General decline by the processor",
- :r234 => "A problem exists with your CyberSource merchant configuration",
- :r235 => "The requested amount exceeds the originally authorized amount",
- :r236 => "Processor failure",
- :r237 => "The authorization has already been reversed",
- :r238 => "The authorization has already been captured",
- :r239 => "The requested transaction amount must match the previous transaction amount",
- :r240 => "The card type sent is invalid or does not correlate with the credit card number",
- :r241 => "The request ID is invalid",
- :r242 => "You requested a capture, but there is no corresponding, unused authorization record.",
- :r243 => "The transaction has already been settled or reversed",
- :r244 => "The bank account number failed the validation check",
- :r246 => "The capture or credit is not voidable because the capture or credit information has already been submitted to your processor",
- :r247 => "You requested a credit for a capture that was previously voided",
- :r250 => "The request was received, but a time-out occurred with the payment processor",
- :r254 => "Your CyberSource account is prohibited from processing stand-alone refunds",
- :r255 => "Your CyberSource account is not configured to process the service in the country you specified"
+ :r230 => 'The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check',
+ :r231 => 'Invalid account number',
+ :r232 => 'The card type is not accepted by the payment processor',
+ :r233 => 'General decline by the processor',
+ :r234 => 'A problem exists with your CyberSource merchant configuration',
+ :r235 => 'The requested amount exceeds the originally authorized amount',
+ :r236 => 'Processor failure',
+ :r237 => 'The authorization has already been reversed',
+ :r238 => 'The authorization has already been captured',
+ :r239 => 'The requested transaction amount must match the previous transaction amount',
+ :r240 => 'The card type sent is invalid or does not correlate with the credit card number',
+ :r241 => 'The request ID is invalid',
+ :r242 => 'You requested a capture, but there is no corresponding, unused authorization record.',
+ :r243 => 'The transaction has already been settled or reversed',
+ :r244 => 'The bank account number failed the validation check',
+ :r246 => 'The capture or credit is not voidable because the capture or credit information has already been submitted to your processor',
+ :r247 => 'You requested a credit for a capture that was previously voided',
+ :r250 => 'The request was received, but a time-out occurred with the payment processor',
+ :r254 => 'Your CyberSource account is prohibited from processing stand-alone refunds',
+ :r255 => 'Your CyberSource account is not configured to process the service in the country you specified'
}
# These are the options that can be used when creating a new CyberSource
@@ -102,7 +102,7 @@ class CyberSourceGateway < Gateway
# :vat_reg_number => your VAT registration number
#
# :nexus => "WI CA QC" sets the states/provinces where you have a physical
- # presense for tax purposes
+ # presence for tax purposes
#
# :ignore_avs => true don't want to use AVS so continue processing even
# if AVS would have failed
@@ -114,74 +114,68 @@ def initialize(options = {})
super
end
- # Request an authorization for an amount from CyberSource
- #
- # You must supply an :order_id in the options hash
def authorize(money, creditcard_or_reference, options = {})
- requires!(options, :order_id)
setup_address_hash(options)
- commit(build_auth_request(money, creditcard_or_reference, options), options )
- end
-
- def auth_reversal(money, identification, options = {})
- commit(build_auth_reversal_request(money, identification, options), options)
+ commit(build_auth_request(money, creditcard_or_reference, options), :authorize, money, options)
end
- # Capture an authorization that has previously been requested
def capture(money, authorization, options = {})
setup_address_hash(options)
- commit(build_capture_request(money, authorization, options), options)
+ commit(build_capture_request(money, authorization, options), :capture, money, options)
end
- # Purchase is an auth followed by a capture
- # You must supply an order_id in the options hash
# options[:pinless_debit_card] => true # attempts to process as pinless debit card
def purchase(money, payment_method_or_reference, options = {})
- requires!(options, :order_id)
setup_address_hash(options)
- commit(build_purchase_request(money, payment_method_or_reference, options), options)
+ commit(build_purchase_request(money, payment_method_or_reference, options), :purchase, money, options)
end
def void(identification, options = {})
- commit(build_void_request(identification, options), options)
+ commit(build_void_request(identification, options), :void, nil, options)
end
def refund(money, identification, options = {})
- commit(build_refund_request(money, identification, options), options)
+ commit(build_refund_request(money, identification, options), :refund, money, options)
end
- # Adds credit to a subscription (stand alone credit).
- def credit(money, reference, options = {})
- requires!(options, :order_id)
- commit(build_credit_request(money, reference, options), options)
+ def verify(payment, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, payment, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ # Adds credit to a card or subscription (stand alone credit).
+ def credit(money, creditcard_or_reference, options = {})
+ setup_address_hash(options)
+ commit(build_credit_request(money, creditcard_or_reference, options), :credit, money, options)
end
# Stores a customer subscription/profile with type "on-demand".
# To charge the card while creating a profile, pass
# options[:setup_fee] => money
def store(payment_method, options = {})
- requires!(options, :order_id)
setup_address_hash(options)
- commit(build_create_subscription_request(payment_method, options), options)
+ commit(build_create_subscription_request(payment_method, options), :store, nil, options)
end
# Updates a customer subscription/profile
def update(reference, creditcard, options = {})
requires!(options, :order_id)
setup_address_hash(options)
- commit(build_update_subscription_request(reference, creditcard, options), options)
+ commit(build_update_subscription_request(reference, creditcard, options), :update, nil, options)
end
# Removes a customer subscription/profile
def unstore(reference, options = {})
requires!(options, :order_id)
- commit(build_delete_subscription_request(reference, options), options)
+ commit(build_delete_subscription_request(reference, options), :unstore, nil, options)
end
# Retrieves a customer subscription/profile
def retrieve(reference, options = {})
requires!(options, :order_id)
- commit(build_retrieve_subscription_request(reference, options), options)
+ commit(build_retrieve_subscription_request(reference, options), :retrieve, nil, options)
end
# CyberSource requires that you provide line item information for tax
@@ -213,29 +207,66 @@ def retrieve(reference, options = {})
def calculate_tax(creditcard, options)
requires!(options, :line_items)
setup_address_hash(options)
- commit(build_tax_calculation_request(creditcard, options), options)
+ commit(build_tax_calculation_request(creditcard, options), :calculate_tax, nil, options)
end
# Determines if a card can be used for Pinless Debit Card transactions
def validate_pinless_debit_card(creditcard, options = {})
requires!(options, :order_id)
- commit(build_validate_pinless_debit_request(creditcard,options), options)
+ commit(build_validate_pinless_debit_request(creditcard, options), :validate_pinless_debit_card, nil, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((]*>)[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2')
+ end
+
+ def supports_network_tokenization?
+ true
+ end
+
+ def verify_credentials
+ response = void('0')
+ response.params['reasonCode'] == '102'
end
private
# Create all address hash key value pairs so that we still function if we
- # were only provided with one or two of them
+ # were only provided with one or two of them or even none
def setup_address_hash(options)
- options[:billing_address] = options[:billing_address] || options[:address] || {}
+ default_address = {
+ :address1 => 'Unspecified',
+ :city => 'Unspecified',
+ :state => 'NC',
+ :zip => '00000',
+ :country => 'US'
+ }
+ options[:billing_address] = options[:billing_address] || options[:address] || default_address
options[:shipping_address] = options[:shipping_address] || {}
end
def build_auth_request(money, creditcard_or_reference, options)
xml = Builder::XmlMarkup.new :indent => 2
add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
- add_auth_service(xml)
- add_business_rules_data(xml)
+ add_decision_manager_fields(xml, options)
+ add_mdd_fields(xml, options)
+ add_auth_service(xml, creditcard_or_reference, options)
+ add_threeds_services(xml, options)
+ add_payment_network_token(xml) if network_tokenization?(creditcard_or_reference)
+ add_business_rules_data(xml, creditcard_or_reference, options)
+ add_stored_credential_options(xml, options)
+ add_issuer_additional_data(xml, options)
+
xml.target!
end
@@ -246,53 +277,55 @@ def build_tax_calculation_request(creditcard, options)
add_line_item_data(xml, options)
add_purchase_data(xml, 0, false, options)
add_tax_service(xml)
- add_business_rules_data(xml)
+ add_business_rules_data(xml, creditcard, options)
xml.target!
end
def build_capture_request(money, authorization, options)
- order_id, request_id, request_token = authorization.split(";")
+ order_id, request_id, request_token = authorization.split(';')
options[:order_id] = order_id
xml = Builder::XmlMarkup.new :indent => 2
add_purchase_data(xml, money, true, options)
add_capture_service(xml, request_id, request_token)
- add_business_rules_data(xml)
+ add_business_rules_data(xml, authorization, options)
xml.target!
end
def build_purchase_request(money, payment_method_or_reference, options)
xml = Builder::XmlMarkup.new :indent => 2
add_payment_method_or_subscription(xml, money, payment_method_or_reference, options)
+ add_decision_manager_fields(xml, options)
+ add_mdd_fields(xml, options)
if !payment_method_or_reference.is_a?(String) && card_brand(payment_method_or_reference) == 'check'
add_check_service(xml)
else
- add_purchase_service(xml, options)
- add_business_rules_data(xml) unless options[:pinless_debit_card]
+ add_purchase_service(xml, payment_method_or_reference, options)
+ add_threeds_services(xml, options)
+ add_payment_network_token(xml) if network_tokenization?(payment_method_or_reference)
+ add_business_rules_data(xml, payment_method_or_reference, options) unless options[:pinless_debit_card]
end
+ add_issuer_additional_data(xml, options)
+
xml.target!
end
def build_void_request(identification, options)
- order_id, request_id, request_token = identification.split(";")
+ order_id, request_id, request_token, action, money, currency = identification.split(';')
options[:order_id] = order_id
xml = Builder::XmlMarkup.new :indent => 2
- add_void_service(xml, request_id, request_token)
- xml.target!
- end
-
- def build_auth_reversal_request(money, identification, options)
- order_id, request_id, request_token = identification.split(";")
- options[:order_id] = order_id
- xml = Builder::XmlMarkup.new :indent => 2
- add_purchase_data(xml, money, true, options)
- add_auth_reversal_service(xml, request_id, request_token)
+ if action == 'capture'
+ add_void_service(xml, request_id, request_token)
+ else
+ add_purchase_data(xml, money, true, options.merge(:currency => currency || default_currency))
+ add_auth_reversal_service(xml, request_id, request_token)
+ end
xml.target!
end
def build_refund_request(money, identification, options)
- order_id, request_id, request_token = identification.split(";")
+ order_id, request_id, request_token = identification.split(';')
options[:order_id] = order_id
xml = Builder::XmlMarkup.new :indent => 2
@@ -302,18 +335,20 @@ def build_refund_request(money, identification, options)
xml.target!
end
- def build_credit_request(money, reference, options)
+ def build_credit_request(money, creditcard_or_reference, options)
xml = Builder::XmlMarkup.new :indent => 2
- add_purchase_data(xml, money, true, options)
- add_subscription(xml, options, reference)
+ add_payment_method_or_subscription(xml, money, creditcard_or_reference, options)
add_credit_service(xml)
xml.target!
end
def build_create_subscription_request(payment_method, options)
- options[:subscription] = (options[:subscription] || {}).merge(:frequency => "on-demand", :amount => 0, :automatic_renew => false)
+ default_subscription_params = {:frequency => 'on-demand', :amount => 0, :automatic_renew => false}
+ options[:subscription] = default_subscription_params.update(
+ options[:subscription] || {}
+ )
xml = Builder::XmlMarkup.new :indent => 2
add_address(xml, payment_method, options[:billing_address], options)
@@ -321,15 +356,21 @@ def build_create_subscription_request(payment_method, options)
if card_brand(payment_method) == 'check'
add_check(xml, payment_method)
add_check_payment_method(xml)
- add_check_service(xml, options) if options[:setup_fee]
else
add_creditcard(xml, payment_method)
add_creditcard_payment_method(xml)
- add_purchase_service(xml, options) if options[:setup_fee]
end
add_subscription(xml, options)
+ if options[:setup_fee]
+ if card_brand(payment_method) == 'check'
+ add_check_service(xml)
+ else
+ add_purchase_service(xml, payment_method, options)
+ add_payment_network_token(xml) if network_tokenization?(payment_method)
+ end
+ end
add_subscription_create_service(xml, options)
- add_business_rules_data(xml)
+ add_business_rules_data(xml, payment_method, options)
xml.target!
end
@@ -341,7 +382,7 @@ def build_update_subscription_request(reference, creditcard, options)
add_creditcard_payment_method(xml) if creditcard
add_subscription(xml, options, reference)
add_subscription_update_service(xml, options)
- add_business_rules_data(xml)
+ add_business_rules_data(xml, creditcard, options)
xml.target!
end
@@ -359,24 +400,35 @@ def build_retrieve_subscription_request(reference, options)
xml.target!
end
- def build_validate_pinless_debit_request(creditcard,options)
+ def build_validate_pinless_debit_request(creditcard, options)
xml = Builder::XmlMarkup.new :indent => 2
add_creditcard(xml, creditcard)
add_validate_pinless_debit_service(xml)
xml.target!
end
- def add_business_rules_data(xml)
- xml.tag! 'businessRules' do
- xml.tag!('ignoreAVSResult', 'true') if @options[:ignore_avs]
- xml.tag!('ignoreCVResult', 'true') if @options[:ignore_cvv]
+ def add_business_rules_data(xml, payment_method, options)
+ prioritized_options = [options, @options]
+
+ unless network_tokenization?(payment_method)
+ xml.tag! 'businessRules' do
+ xml.tag!('ignoreAVSResult', 'true') if extract_option(prioritized_options, :ignore_avs)
+ xml.tag!('ignoreCVResult', 'true') if extract_option(prioritized_options, :ignore_cvv)
+ end
+ end
+ end
+
+ def extract_option(prioritized_options, option_name)
+ options_matching_key = prioritized_options.detect do |options|
+ options.has_key? option_name
end
+ options_matching_key[option_name] if options_matching_key
end
def add_line_item_data(xml, options)
options[:line_items].each_with_index do |value, index|
xml.tag! 'item', {'id' => index} do
- xml.tag! 'unitPrice', amount(value[:declared_value])
+ xml.tag! 'unitPrice', localized_amount(value[:declared_value].to_i, options[:currency] || default_currency)
xml.tag! 'quantity', value[:quantity]
xml.tag! 'productCode', value[:code] || 'shipping_only'
xml.tag! 'productName', value[:description]
@@ -387,16 +439,16 @@ def add_line_item_data(xml, options)
def add_merchant_data(xml, options)
xml.tag! 'merchantID', @options[:login]
- xml.tag! 'merchantReferenceCode', options[:order_id]
- xml.tag! 'clientLibrary' ,'Ruby Active Merchant'
+ xml.tag! 'merchantReferenceCode', options[:order_id] || generate_unique_id
+ xml.tag! 'clientLibrary', 'Ruby Active Merchant'
xml.tag! 'clientLibraryVersion', VERSION
- xml.tag! 'clientEnvironment' , RUBY_PLATFORM
+ xml.tag! 'clientEnvironment', RUBY_PLATFORM
end
def add_purchase_data(xml, money = 0, include_grand_total = false, options={})
xml.tag! 'purchaseTotals' do
xml.tag! 'currency', options[:currency] || currency(money)
- xml.tag!('grandTotalAmount', amount(money)) if include_grand_total
+ xml.tag!('grandTotalAmount', localized_amount(money.to_i, options[:currency] || default_currency)) if include_grand_total
end
end
@@ -409,11 +461,12 @@ def add_address(xml, payment_method, address, options, shipTo = false)
xml.tag! 'city', address[:city]
xml.tag! 'state', address[:state]
xml.tag! 'postalCode', address[:zip]
- xml.tag! 'country', address[:country]
+ xml.tag! 'country', lookup_country_code(address[:country]) unless address[:country].blank?
xml.tag! 'company', address[:company] unless address[:company].blank?
xml.tag! 'companyTaxID', address[:companyTaxID] unless address[:company_tax_id].blank?
- xml.tag! 'phoneNumber', address[:phone_number] unless address[:phone_number].blank?
- xml.tag! 'email', options[:email]
+ xml.tag! 'phoneNumber', address[:phone] unless address[:phone].blank?
+ xml.tag! 'email', options[:email] || 'null@cybersource.com'
+ xml.tag! 'ipAddress', options[:ip] unless options[:ip].blank? || shipTo
xml.tag! 'driversLicenseNumber', options[:drivers_license_number] unless options[:drivers_license_number].blank?
xml.tag! 'driversLicenseState', options[:drivers_license_state] unless options[:drivers_license_state].blank?
end
@@ -424,11 +477,39 @@ def add_creditcard(xml, creditcard)
xml.tag! 'accountNumber', creditcard.number
xml.tag! 'expirationMonth', format(creditcard.month, :two_digits)
xml.tag! 'expirationYear', format(creditcard.year, :four_digits)
- xml.tag!('cvNumber', creditcard.verification_value) unless (@options[:ignore_cvv] || creditcard.verification_value.blank? )
+ xml.tag!('cvNumber', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank?
xml.tag! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym]
end
end
+ def add_decision_manager_fields(xml, options)
+ return unless options[:decision_manager_enabled]
+
+ xml.tag! 'decisionManager' do
+ xml.tag! 'enabled', options[:decision_manager_enabled] if options[:decision_manager_enabled]
+ xml.tag! 'profile', options[:decision_manager_profile] if options[:decision_manager_profile]
+ end
+ end
+
+ def add_issuer_additional_data(xml, options)
+ return unless options[:issuer_additional_data]
+
+ xml.tag! 'issuer' do
+ xml.tag! 'additionalData', options[:issuer_additional_data]
+ end
+ end
+
+ def add_mdd_fields(xml, options)
+ return unless options.keys.any? { |key| key.to_s.start_with?('mdd_field') }
+
+ xml.tag! 'merchantDefinedData' do
+ (1..100).each do |each|
+ key = "mdd_field_#{each}".to_sym
+ xml.tag!("field#{each}", options[key]) if options[key]
+ end
+ end
+ end
+
def add_check(xml, check)
xml.tag! 'check' do
xml.tag! 'accountNumber', check.account_number
@@ -444,8 +525,67 @@ def add_tax_service(xml)
end
end
- def add_auth_service(xml)
- xml.tag! 'ccAuthService', {'run' => 'true'}
+ def add_auth_service(xml, payment_method, options)
+ if network_tokenization?(payment_method)
+ add_auth_network_tokenization(xml, payment_method, options)
+ else
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
+ check_for_stored_cred_commerce_indicator(xml, options)
+ end
+ end
+ end
+
+ def check_for_stored_cred_commerce_indicator(xml, options)
+ return unless options[:stored_credential]
+ if commerce_indicator(options)
+ xml.tag!('commerceIndicator', commerce_indicator(options))
+ end
+ end
+
+ def commerce_indicator(options)
+ return if options[:stored_credential][:initial_transaction]
+ case options[:stored_credential][:reason_type]
+ when 'installment' then 'install'
+ when 'recurring' then 'recurring'
+ end
+ end
+
+ def network_tokenization?(payment_method)
+ payment_method.is_a?(NetworkTokenizationCreditCard)
+ end
+
+ def add_auth_network_tokenization(xml, payment_method, options)
+ return unless network_tokenization?(payment_method)
+
+ case card_brand(payment_method).to_sym
+ when :visa
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
+ xml.tag!('cavv', payment_method.payment_cryptogram)
+ xml.tag!('commerceIndicator', 'vbv')
+ xml.tag!('xid', payment_method.payment_cryptogram)
+ end
+ when :master
+ xml.tag! 'ucaf' do
+ xml.tag!('authenticationData', payment_method.payment_cryptogram)
+ xml.tag!('collectionIndicator', '2')
+ end
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
+ xml.tag!('commerceIndicator', 'spa')
+ end
+ when :american_express
+ cryptogram = Base64.decode64(payment_method.payment_cryptogram)
+ xml.tag! 'ccAuthService', {'run' => 'true'} do
+ xml.tag!('cavv', Base64.encode64(cryptogram[0...20]))
+ xml.tag!('commerceIndicator', 'aesk')
+ xml.tag!('xid', Base64.encode64(cryptogram[20...40]))
+ end
+ end
+ end
+
+ def add_payment_network_token(xml)
+ xml.tag! 'paymentNetworkToken' do
+ xml.tag!('transactionType', '1')
+ end
end
def add_capture_service(xml, request_id, request_token)
@@ -455,11 +595,11 @@ def add_capture_service(xml, request_id, request_token)
end
end
- def add_purchase_service(xml, options)
+ def add_purchase_service(xml, payment_method, options)
if options[:pinless_debit_card]
xml.tag! 'pinlessDebitService', {'run' => 'true'}
else
- xml.tag! 'ccAuthService', {'run' => 'true'}
+ add_auth_service(xml, payment_method, options)
xml.tag! 'ccCaptureService', {'run' => 'true'}
end
end
@@ -510,17 +650,17 @@ def add_subscription(xml, options, reference = nil)
xml.tag! 'recurringSubscriptionInfo' do
if reference
- _, subscription_id, _ = reference.split(";")
+ subscription_id = reference.split(';')[6]
xml.tag! 'subscriptionID', subscription_id
end
xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status]
- xml.tag! 'amount', options[:subscription][:amount] if options[:subscription][:amount]
+ xml.tag! 'amount', localized_amount(options[:subscription][:amount].to_i, options[:currency] || default_currency) if options[:subscription][:amount]
xml.tag! 'numberOfPayments', options[:subscription][:occurrences] if options[:subscription][:occurrences]
xml.tag! 'automaticRenew', options[:subscription][:automatic_renew] if options[:subscription][:automatic_renew]
xml.tag! 'frequency', options[:subscription][:frequency] if options[:subscription][:frequency]
- xml.tag! 'startDate', options[:subscription][:start_date].strftime("%Y%m%d") if options[:subscription][:start_date]
- xml.tag! 'endDate', options[:subscription][:end_date].strftime("%Y%m%d") if options[:subscription][:end_date]
+ xml.tag! 'startDate', options[:subscription][:start_date].strftime('%Y%m%d') if options[:subscription][:start_date]
+ xml.tag! 'endDate', options[:subscription][:end_date].strftime('%Y%m%d') if options[:subscription][:end_date]
xml.tag! 'approvalRequired', options[:subscription][:approval_required] || false
xml.tag! 'event', options[:subscription][:event] if options[:subscription][:event]
xml.tag! 'billPayment', options[:subscription][:bill_payment] if options[:subscription][:bill_payment]
@@ -529,13 +669,13 @@ def add_subscription(xml, options, reference = nil)
def add_creditcard_payment_method(xml)
xml.tag! 'subscription' do
- xml.tag! 'paymentMethod', "credit card"
+ xml.tag! 'paymentMethod', 'credit card'
end
end
def add_check_payment_method(xml)
xml.tag! 'subscription' do
- xml.tag! 'paymentMethod', "check"
+ xml.tag! 'paymentMethod', 'check'
end
end
@@ -549,46 +689,86 @@ def add_payment_method_or_subscription(xml, money, payment_method_or_reference,
add_check(xml, payment_method_or_reference)
else
add_address(xml, payment_method_or_reference, options[:billing_address], options)
+ add_address(xml, payment_method_or_reference, options[:shipping_address], options, true)
add_purchase_data(xml, money, true, options)
add_creditcard(xml, payment_method_or_reference)
end
end
def add_validate_pinless_debit_service(xml)
- xml.tag!'pinlessDebitValidateService', {'run' => 'true'}
+ xml.tag! 'pinlessDebitValidateService', {'run' => 'true'}
+ end
+
+ def add_threeds_services(xml, options)
+ xml.tag! 'payerAuthEnrollService', {'run' => 'true'} if options[:payer_auth_enroll_service]
+ if options[:payer_auth_validate_service]
+ xml.tag! 'payerAuthValidateService', {'run' => 'true'} do
+ xml.tag! 'signedPARes', options[:pares]
+ end
+ end
+ end
+
+ def lookup_country_code(country_field)
+ country_code = Country.find(country_field) rescue nil
+ country_code&.code(:alpha2)
+ end
+
+ def add_stored_credential_options(xml, options={})
+ return unless options[:stored_credential]
+ if options[:stored_credential][:initial_transaction]
+ xml.tag! 'subsequentAuthFirst', 'true'
+ elsif options[:stored_credential][:reason_type] == 'unscheduled'
+ xml.tag! 'subsequentAuth', 'true'
+ xml.tag! 'subsequentAuthTransactionID', options[:stored_credential][:network_transaction_id]
+ else
+ xml.tag! 'subsequentAuthTransactionID', options[:stored_credential][:network_transaction_id]
+ end
end
# Where we actually build the full SOAP request using builder
def build_request(body, options)
+ xsd_version = test? ? TEST_XSD_VERSION : PRODUCTION_XSD_VERSION
+
xml = Builder::XmlMarkup.new :indent => 2
- xml.instruct!
- xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do
- xml.tag! 's:Header' do
- xml.tag! 'wsse:Security', {'s:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'} do
- xml.tag! 'wsse:UsernameToken' do
- xml.tag! 'wsse:Username', @options[:login]
- xml.tag! 'wsse:Password', @options[:password], 'Type' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'
- end
+ xml.instruct!
+ xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do
+ xml.tag! 's:Header' do
+ xml.tag! 'wsse:Security', {'s:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'} do
+ xml.tag! 'wsse:UsernameToken' do
+ xml.tag! 'wsse:Username', @options[:login]
+ xml.tag! 'wsse:Password', @options[:password], 'Type' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'
end
end
- xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do
- xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{XSD_VERSION}"} do
- add_merchant_data(xml, options)
- xml << body
- end
+ end
+ xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do
+ xml.tag! 'requestMessage', {'xmlns' => "urn:schemas-cybersource-com:transaction-data-#{xsd_version}"} do
+ add_merchant_data(xml, options)
+ xml << body
end
end
+ end
xml.target!
end
# Contact CyberSource, make the SOAP request, and parse the reply into a
# Response object
- def commit(request, options)
- response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(request, options)))
+ def commit(request, action, amount, options)
+ begin
+ raw_response = ssl_post(test? ? self.test_url : self.live_url, build_request(request, options))
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
+
+ begin
+ response = parse(raw_response)
+ rescue REXML::ParseException => e
+ response = { message: e.to_s }
+ end
- success = response[:decision] == "ACCEPT"
- message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
- authorization = success ? [ options[:order_id], response[:requestID], response[:requestToken] ].compact.join(";") : nil
+ success = response[:decision] == 'ACCEPT'
+ message = response[:message]
+
+ authorization = success ? authorization_from(response, action, amount, options) : nil
Response.new(success, message, response,
:test => test?,
@@ -603,16 +783,17 @@ def commit(request, options)
def parse(xml)
reply = {}
xml = REXML::Document.new(xml)
- if root = REXML::XPath.first(xml, "//c:replyMessage")
+ if root = REXML::XPath.first(xml, '//c:replyMessage')
root.elements.to_a.each do |node|
- case node.name
+ case node.expanded_name
when 'c:reasonCode'
- reply[:message] = reply(node.text)
+ reply[:reasonCode] = node.text
+ reply[:message] = reason_message(node.text)
else
parse_element(reply, node)
end
end
- elsif root = REXML::XPath.first(xml, "//soap:Fault")
+ elsif root = REXML::XPath.first(xml, '//soap:Fault')
parse_element(reply, root)
reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}"
end
@@ -621,17 +802,27 @@ def parse(xml)
def parse_element(reply, node)
if node.has_elements?
- node.elements.each{|e| parse_element(reply, e) }
+ node.elements.each { |e| parse_element(reply, e) }
else
if node.parent.name =~ /item/
- parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '')
- reply[(parent + '_' + node.name).to_sym] = node.text
- else
- reply[node.name.to_sym] = node.text
+ parent = node.parent.name
+ parent += '_' + node.parent.attributes['id'] if node.parent.attributes['id']
+ parent += '_'
end
+ reply["#{parent}#{node.name}".to_sym] ||= node.text
end
return reply
end
+
+ def reason_message(reason_code)
+ return if reason_code.blank?
+ @@response_codes[:"r#{reason_code}"]
+ end
+
+ def authorization_from(response, action, amount, options)
+ [options[:order_id], response[:requestID], response[:requestToken], action, amount,
+ options[:currency], response[:subscriptionID]].join(';')
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/d_local.rb b/lib/active_merchant/billing/gateways/d_local.rb
new file mode 100644
index 00000000000..19739582f00
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/d_local.rb
@@ -0,0 +1,226 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class DLocalGateway < Gateway
+ self.test_url = 'https://sandbox.dlocal.com'
+ self.live_url = 'https://api.dlocal.com'
+
+ self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY', 'TR']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro, :naranja, :cabal]
+
+ self.homepage_url = 'https://dlocal.com/'
+ self.display_name = 'dLocal'
+
+ def initialize(options={})
+ requires!(options, :login, :trans_key, :secret_key)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+ add_auth_purchase_params(post, money, payment, 'purchase', options)
+
+ commit('purchase', post, options)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ add_auth_purchase_params(post, money, payment, 'authorize', options)
+
+ commit('authorize', post, options)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ post[:payment_id] = authorization
+ add_invoice(post, money, options) if money
+ commit('capture', post, options)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ post[:payment_id] = authorization
+ post[:notification_url] = options[:notification_url]
+ add_invoice(post, money, options) if money
+ commit('refund', post, options)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ post[:payment_id] = authorization
+ commit('void', post, options)
+ 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((X-Trans-Key: )\w+), '\1[FILTERED]').
+ gsub(%r((\"number\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"cvv\\\":\\\")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_auth_purchase_params(post, money, card, action, options)
+ add_invoice(post, money, options)
+ post[:payment_method_id] = 'CARD'
+ post[:payment_method_flow] = 'DIRECT'
+ add_country(post, card, options)
+ add_payer(post, card, options)
+ add_card(post, card, action, options)
+ post[:order_id] = options[:order_id] || generate_unique_id
+ post[:description] = options[:description] if options[:description]
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:currency] = (options[:currency] || currency(money))
+ end
+
+ def add_country(post, card, options)
+ return unless address = options[:billing_address] || options[:address]
+ post[:country] = lookup_country_code(address[:country])
+ end
+
+ def lookup_country_code(country)
+ Country.find(country).code(:alpha2).value
+ end
+
+ def add_payer(post, card, options)
+ address = options[:billing_address] || options[:address]
+ post[:payer] = {}
+ post[:payer][:name] = card.name
+ post[:payer][:email] = options[:email] if options[:email]
+ post[:payer][:birth_date] = options[:birth_date] if options[:birth_date]
+ post[:payer][:phone] = address[:phone] if address && address[:phone]
+ post[:payer][:document] = options[:document] if options[:document]
+ post[:payer][:document2] = options[:document2] if options[:document2]
+ post[:payer][:user_reference] = options[:user_reference] if options[:user_reference]
+ post[:payer][:address] = add_address(post, card, options)
+ end
+
+ def add_address(post, card, options)
+ return unless address = options[:billing_address] || options[:address]
+ address_object = {}
+ address_object[:state] = address[:state] if address[:state]
+ address_object[:city] = address[:city] if address[:city]
+ address_object[:zip_code] = address[:zip_code] if address[:zip_code]
+ address_object[:street] = address[:street] if address[:street]
+ address_object[:number] = address[:number] if address[:number]
+ address_object
+ end
+
+ def add_card(post, card, action, options={})
+ post[:card] = {}
+ post[:card][:holder_name] = card.name
+ post[:card][:expiration_month] = card.month
+ post[:card][:expiration_year] = card.year
+ post[:card][:number] = card.number
+ post[:card][:cvv] = card.verification_value
+ post[:card][:descriptor] = options[:dynamic_descriptor] if options[:dynamic_descriptor]
+ post[:card][:capture] = (action == 'purchase')
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, parameters, options={})
+ url = url(action, parameters, options)
+ post = post_data(action, parameters)
+ begin
+ raw = ssl_post(url, post, headers(post, options))
+ response = parse(raw)
+ rescue ResponseError => e
+ raw = e.response.body
+ response = parse(raw)
+ end
+
+ Response.new(
+ success_from(action, response),
+ message_from(action, response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['some_avs_response_key']),
+ cvv_result: CVVResult.new(response['some_cvv_response_key']),
+ test: test?,
+ error_code: error_code_from(action, response)
+ )
+ end
+
+ # A refund may not be immediate, and return a status_code of 100, "Pending".
+ # Since we aren't handling async notifications of eventual success,
+ # we count 100 as a success.
+ def success_from(action, response)
+ return false unless response['status_code']
+ ['100', '200', '400', '600'].include? response['status_code'].to_s
+ end
+
+ def message_from(action, response)
+ response['status_detail'] || response['message']
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def error_code_from(action, response)
+ return if success_from(action, response)
+ code = response['status_code'] || response['code']
+ code&.to_s
+ end
+
+ def url(action, parameters, options={})
+ "#{(test? ? test_url : live_url)}/#{endpoint(action, parameters, options)}/"
+ end
+
+ def endpoint(action, parameters, options)
+ case action
+ when 'purchase'
+ 'secure_payments'
+ when 'authorize'
+ 'secure_payments'
+ when 'refund'
+ 'refunds'
+ when 'capture'
+ "payments/#{parameters[:payment_id]}/capture"
+ when 'void'
+ "payments/#{parameters[:payment_id]}/cancel"
+ end
+ end
+
+ def headers(post, options={})
+ timestamp = Time.now.utc.iso8601
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'X-Date' => timestamp,
+ 'X-Login' => @options[:login],
+ 'X-Trans-Key' => @options[:trans_key],
+ 'Authorization' => signature(post, timestamp)
+ }
+ headers.merge('X-Idempotency-Key' => options[:idempotency_key]) if options[:idempotency_key]
+ headers
+ end
+
+ def signature(post, timestamp)
+ content = "#{@options[:login]}#{timestamp}#{post}"
+ digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @options[:secret_key], content)
+ "V2-HMAC-SHA256, Signature: #{digest}"
+ end
+
+ def post_data(action, parameters = {})
+ parameters.to_json
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/data_cash.rb b/lib/active_merchant/billing/gateways/data_cash.rb
index 2c62302dd86..def3288babf 100644
--- a/lib/active_merchant/billing/gateways/data_cash.rb
+++ b/lib/active_merchant/billing/gateways/data_cash.rb
@@ -1,26 +1,19 @@
+require 'active_support/core_ext/string/access'
+
module ActiveMerchant
module Billing
class DataCashGateway < Gateway
self.default_currency = 'GBP'
self.supported_countries = ['GB']
- # From the DataCash docs; Page 13, the following cards are
- # usable:
- # American Express, ATM, Carte Blanche, Diners Club, Discover,
- # EnRoute, GE Capital, JCB, Laser, Maestro, Mastercard, Solo,
- # Switch, Visa, Visa Delta, VISA Electron, Visa Purchasing
- #
- # Note continuous authority is only supported for :visa, :master and :american_express card types
- self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro, :switch, :solo, :laser ]
+ self.supported_cardtypes = [ :visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro ]
self.homepage_url = 'http://www.datacash.com/'
self.display_name = 'DataCash'
- # Datacash server URLs
self.test_url = 'https://testserver.datacash.com/Transaction'
self.live_url = 'https://mars.transaction.datacash.com/Transaction'
- # Different Card Transaction Types
AUTH_TYPE = 'auth'
CANCEL_TYPE = 'cancel'
FULFILL_TYPE = 'fulfill'
@@ -28,44 +21,14 @@ class DataCashGateway < Gateway
REFUND_TYPE = 'refund'
TRANSACTION_REFUND_TYPE = 'txn_refund'
- # Constant strings for use in the ExtendedPolicy complex element for
- # CV2 checks
POLICY_ACCEPT = 'accept'
POLICY_REJECT = 'reject'
- # Datacash success code
- DATACASH_SUCCESS = '1'
-
- # Creates a new DataCashGateway
- #
- # The gateway requires that a valid login and password be passed
- # in the +options+ hash.
- #
- # ==== Options
- #
- # * :login -- The Datacash account login.
- # * :password -- The Datacash account password.
- # * :test => +true+ or +false+ -- Use the test or live Datacash url.
- #
def initialize(options = {})
requires!(options, :login, :password)
super
end
- # Perform a purchase, which is essentially an authorization and capture in a single operation.
- #
- # ==== Parameters
- # * money The amount to be authorized as an Integer value in cents.
- # * authorization_or_credit_card:: The continuous authority reference or CreditCard details for the transaction.
- # * options A hash of optional parameters.
- # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation)
- # * :set_up_continuous_authority
- # Set to true to set up a recurring historic transaction account be set up.
- # Only supported for :visa, :master and :american_express card types
- # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions.
- # * :address:: billing address for card
- #
- # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one
def purchase(money, authorization_or_credit_card, options = {})
requires!(options, :order_id)
@@ -78,22 +41,6 @@ def purchase(money, authorization_or_credit_card, options = {})
commit(request)
end
- # Performs an authorization, which reserves the funds on the customer's credit card, but does not
- # charge the card.
- #
- # ==== Parameters
- #
- # * money The amount to be authorized as an Integer value in cents.
- # * authorization_or_credit_card:: The continuous authority reference or CreditCard details for the transaction.
- # * options A hash of optional parameters.
- # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation)
- # * :set_up_continuous_authority::
- # Set to true to set up a recurring historic transaction account be set up.
- # Only supported for :visa, :master and :american_express card types
- # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions.
- # * :address:: billing address for card
- #
- # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one
def authorize(money, authorization_or_credit_card, options = {})
requires!(options, :order_id)
@@ -106,42 +53,22 @@ def authorize(money, authorization_or_credit_card, options = {})
commit(request)
end
- # Captures the funds from an authorized transaction.
- #
- # ==== Parameters
- #
- # * money -- The amount to be captured as anInteger value in cents.
- # * authorization -- The authorization returned from the previous authorize request.
def capture(money, authorization, options = {})
commit(build_void_or_capture_request(FULFILL_TYPE, money, authorization, options))
end
- # Void a previous transaction
- #
- # ==== Parameters
- #
- # * authorization - The authorization returned from the previous authorize request.
def void(authorization, options = {})
request = build_void_or_capture_request(CANCEL_TYPE, nil, authorization, options)
commit(request)
end
- # Refund to a card
- #
- # ==== Parameters
- #
- # * money The amount to be refunded as an Integer value in cents. Set to nil for a full refund on existing transaction.
- # * reference_or_credit_card The credit card you want to refund OR the datacash_reference for the existing transaction you are refunding
- # * options Are ignored when refunding via reference to an existing transaction, otherwise
- # * :order_id A unique reference for this order (corresponds to merchantreference in datacash documentation)
- # * :address:: billing address for card
def credit(money, reference_or_credit_card, options = {})
if reference_or_credit_card.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, reference_or_credit_card)
else
- request = build_refund_request(money, reference_or_credit_card, options)
+ request = build_credit_request(money, reference_or_credit_card, options)
commit(request)
end
end
@@ -150,49 +77,30 @@ def refund(money, reference, options = {})
commit(build_transaction_refund_request(money, reference))
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(/()\d+(<\/pan>)/i, '\1[FILTERED]\2').
+ gsub(/()\d+(<\/cv2>)/i, '\1[FILTERED]\2').
+ gsub(/().+(<\/password>)/i, '\1[FILTERED]\2')
+ end
+
private
- # Create the xml document for a 'cancel' or 'fulfill' transaction.
- #
- # Final XML should look like:
- #
- #
- # 99000001
- # ******
- #
- #
- #
- # 25.00
- #
- #
- # 4900200000000001
- # A6
- # fulfill
- #
- #
- #
- #
- # Parameters:
- # * type must be FULFILL_TYPE or CANCEL_TYPE
- # * money - optional - Integer value in cents
- # * authorization - the Datacash authorization from a previous succesful authorize transaction
- # * options
- # * order_id - A unique reference for the transaction
- #
- # Returns:
- # -Builder xml document
- #
- def build_void_or_capture_request(type, money, authorization, options)
- reference, auth_code, ca_reference = authorization.to_s.split(';')
+ def build_void_or_capture_request(type, money, authorization, options)
+ parsed_authorization = parse_authorization_string(authorization)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! :Request do
+ xml.tag! :Request, :version => '2' do
add_authentication(xml)
xml.tag! :Transaction do
xml.tag! :HistoricTxn do
- xml.tag! :reference, reference
- xml.tag! :authcode, auth_code
+ xml.tag! :reference, parsed_authorization[:reference]
+ xml.tag! :authcode, parsed_authorization[:auth_code]
xml.tag! :method, type
end
@@ -200,6 +108,7 @@ def build_void_or_capture_request(type, money, authorization, options)
xml.tag! :TxnDetails do
xml.tag! :merchantreference, format_reference_number(options[:order_id])
xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money)
+ xml.tag! :capturemethod, 'ecomm'
end
end
end
@@ -207,71 +116,10 @@ def build_void_or_capture_request(type, money, authorization, options)
xml.target!
end
- # Create the xml document for an 'auth' or 'pre' transaction with a credit card
- #
- # Final XML should look like:
- #
- #
- #
- # 99000000
- # *******
- #
- #
- #
- # 123456
- # 10.00
- #
- #
- #
- # 4444********1111
- # 03/04
- #
- # Flat 7
- # 89 Jumble
- # Street
- # Mytown
- # AV12FR
- # 123
- #
- #
- #
- #
- #
- #
- #
- # auth
- #
- #
- #
- #
- # Parameters:
- # -type must be 'auth' or 'pre'
- # -money - A money object with the price and currency
- # -credit_card - The credit_card details to use
- # -options:
- # :order_id is the merchant reference number
- # :billing_address is the billing address for the cc
- # :address is the delivery address
- #
- # Returns:
- # -xml: Builder document containing the markup
- #
def build_purchase_or_authorization_request_with_credit_card_request(type, money, credit_card, options)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! :Request do
+ xml.tag! :Request, :version => '2' do
add_authentication(xml)
xml.tag! :Transaction do
@@ -285,58 +133,25 @@ def build_purchase_or_authorization_request_with_credit_card_request(type, money
xml.tag! :TxnDetails do
xml.tag! :merchantreference, format_reference_number(options[:order_id])
xml.tag! :amount, amount(money), :currency => options[:currency] || currency(money)
+ xml.tag! :capturemethod, 'ecomm'
end
end
end
xml.target!
end
- # Create the xml document for an 'auth' or 'pre' transaction with
- # continuous authorization
- #
- # Final XML should look like:
- #
- #
- #
- #
- #
- # 3851231
- # cont_auth
- # 18.50
- #
- #
- # 4500200040925092
- # auth
- #
- #
- #
- # 99000001
- # mypasswd
- #
- #
- #
- # Parameters:
- # -type must be 'auth' or 'pre'
- # -money - A money object with the price and currency
- # -authorization - The authorization containing a continuous authority reference previously set up on a credit card
- # -options:
- # :order_id is the merchant reference number
- #
- # Returns:
- # -xml: Builder document containing the markup
- #
def build_purchase_or_authorization_request_with_continuous_authority_reference_request(type, money, authorization, options)
- reference, auth_code, ca_reference = authorization.to_s.split(';')
- raise ArgumentError, "The continuous authority reference is required for continuous authority transactions" if ca_reference.blank?
+ parsed_authorization = parse_authorization_string(authorization)
+ raise ArgumentError, 'The continuous authority reference is required for continuous authority transactions' if parsed_authorization[:ca_reference].blank?
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! :Request do
+ xml.tag! :Request, :version => '2' do
add_authentication(xml)
xml.tag! :Transaction do
xml.tag! :ContAuthTxn, :type => 'historic'
xml.tag! :HistoricTxn do
- xml.tag! :reference, ca_reference
+ xml.tag! :reference, parsed_authorization[:ca_reference]
xml.tag! :method, type
end
xml.tag! :TxnDetails do
@@ -349,39 +164,21 @@ def build_purchase_or_authorization_request_with_continuous_authority_reference_
xml.target!
end
- # Create the xml document for a full or partial refund transaction with
- #
- # Final XML should look like:
- #
- #
- #
- # 99000001
- # *******
- #
- #
- #
- # txn_refund
- # 12345678
- #
- #
- # 10.00
- #
- #
- #
- #
- def build_transaction_refund_request(money, reference)
+ def build_transaction_refund_request(money, authorization)
+ parsed_authorization = parse_authorization_string(authorization)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! :Request do
+ xml.tag! :Request, :version => '2' do
add_authentication(xml)
xml.tag! :Transaction do
xml.tag! :HistoricTxn do
- xml.tag! :reference, reference
+ xml.tag! :reference, parsed_authorization[:reference]
xml.tag! :method, TRANSACTION_REFUND_TYPE
end
unless money.nil?
xml.tag! :TxnDetails do
xml.tag! :amount, amount(money)
+ xml.tag! :capturemethod, 'ecomm'
end
end
end
@@ -389,34 +186,10 @@ def build_transaction_refund_request(money, reference)
xml.target!
end
- # Create the xml document for a full or partial refund with
- #
- # Final XML should look like:
- #
- #
- #
- # 99000001
- # *****
- #
- #
- #
- #
- # 633300*********1
- # 04/06
- # 01/04
- #
- # refund
- #
- #
- # 1000001
- # 95.99
- #
- #
- #
- def build_refund_request(money, credit_card, options)
+ def build_credit_request(money, credit_card, options)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! :Request do
+ xml.tag! :Request, :version => '2' do
add_authentication(xml)
xml.tag! :Transaction do
xml.tag! :CardTxn do
@@ -426,21 +199,13 @@ def build_refund_request(money, credit_card, options)
xml.tag! :TxnDetails do
xml.tag! :merchantreference, format_reference_number(options[:order_id])
xml.tag! :amount, amount(money)
+ xml.tag! :capturemethod, 'ecomm'
end
end
end
xml.target!
end
-
- # Adds the authentication element to the passed builder xml doc
- #
- # Parameters:
- # -xml: Builder document that is being built up
- #
- # Returns:
- # -none: The results is stored in the passed xml document
- #
def add_authentication(xml)
xml.tag! :Authentication do
xml.tag! :client, @options[:login]
@@ -448,34 +213,12 @@ def add_authentication(xml)
end
end
- # Add credit_card details to the passed XML Builder doc
- #
- # Parameters:
- # -xml: Builder document that is being built up
- # -credit_card: ActiveMerchant::Billing::CreditCard object
- # -billing_address: Hash containing all of the billing address details
- #
- # Returns:
- # -none: The results is stored in the passed xml document
- #
def add_credit_card(xml, credit_card, address)
-
xml.tag! :Card do
-
# DataCash calls the CC number 'pan'
xml.tag! :pan, credit_card.number
xml.tag! :expirydate, format_date(credit_card.month, credit_card.year)
- # optional values - for Solo etc
- if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s)
-
- xml.tag! :issuenumber, credit_card.issue_number unless credit_card.issue_number.blank?
-
- if !credit_card.start_month.blank? && !credit_card.start_year.blank?
- xml.tag! :startdate, format_date(credit_card.start_month, credit_card.start_year)
- end
- end
-
xml.tag! :Cv2Avs do
xml.tag! :cv2, credit_card.verification_value if credit_card.verification_value?
if address
@@ -494,71 +237,45 @@ def add_credit_card(xml, credit_card, address)
# a predefined one
xml.tag! :ExtendedPolicy do
xml.tag! :cv2_policy,
- :notprovided => POLICY_REJECT,
- :notchecked => POLICY_REJECT,
- :matched => POLICY_ACCEPT,
- :notmatched => POLICY_REJECT,
- :partialmatch => POLICY_REJECT
+ :notprovided => POLICY_REJECT,
+ :notchecked => POLICY_REJECT,
+ :matched => POLICY_ACCEPT,
+ :notmatched => POLICY_REJECT,
+ :partialmatch => POLICY_REJECT
xml.tag! :postcode_policy,
- :notprovided => POLICY_ACCEPT,
- :notchecked => POLICY_ACCEPT,
- :matched => POLICY_ACCEPT,
- :notmatched => POLICY_REJECT,
- :partialmatch => POLICY_ACCEPT
+ :notprovided => POLICY_ACCEPT,
+ :notchecked => POLICY_ACCEPT,
+ :matched => POLICY_ACCEPT,
+ :notmatched => POLICY_REJECT,
+ :partialmatch => POLICY_ACCEPT
xml.tag! :address_policy,
- :notprovided => POLICY_ACCEPT,
- :notchecked => POLICY_ACCEPT,
- :matched => POLICY_ACCEPT,
- :notmatched => POLICY_REJECT,
- :partialmatch => POLICY_ACCEPT
+ :notprovided => POLICY_ACCEPT,
+ :notchecked => POLICY_ACCEPT,
+ :matched => POLICY_ACCEPT,
+ :notmatched => POLICY_REJECT,
+ :partialmatch => POLICY_ACCEPT
end
end
end
end
- # Send the passed data to DataCash for processing
- #
- # Parameters:
- # -request: The XML data that is to be sent to Datacash
- #
- # Returns:
- # - ActiveMerchant::Billing::Response object
- #
def commit(request)
response = parse(ssl_post(test? ? self.test_url : self.live_url, request))
- Response.new(response[:status] == DATACASH_SUCCESS, response[:reason], response,
+ Response.new(response[:status] == '1', response[:reason], response,
:test => test?,
:authorization => "#{response[:datacash_reference]};#{response[:authcode]};#{response[:ca_reference]}"
)
end
- # Returns a date string in the format Datacash expects
- #
- # Parameters:
- # -month: integer, the month
- # -year: integer, the year
- #
- # Returns:
- # -String: date in MM/YY format
- #
def format_date(month, year)
- "#{format(month,:two_digits)}/#{format(year, :two_digits)}"
+ "#{format(month, :two_digits)}/#{format(year, :two_digits)}"
end
- # Parse the datacash response and create a Response object
- #
- # Parameters:
- # -body: The XML returned from Datacash
- #
- # Returns:
- # -a hash with all of the values returned in the Datacash XML response
- #
def parse(body)
-
response = {}
xml = REXML::Document.new(body)
- root = REXML::XPath.first(xml, "//Response")
+ root = REXML::XPath.first(xml, '//Response')
root.elements.to_a.each do |node|
parse_element(response, node)
@@ -567,24 +284,21 @@ def parse(body)
response
end
- # Parse an xml element
- #
- # Parameters:
- # -response: The hash that the values are being returned in
- # -node: The node that is currently being read
- #
- # Returns:
- # - none (results are stored in the passed hash)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|e| parse_element(response, e) }
+ node.elements.each { |e| parse_element(response, e) }
else
response[node.name.underscore.to_sym] = node.text
end
end
def format_reference_number(number)
- number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, "0").first(30)
+ number.to_s.gsub(/[^A-Za-z0-9]/, '').rjust(6, '0').first(30)
+ end
+
+ def parse_authorization_string(authorization)
+ reference, auth_code, ca_reference = authorization.to_s.split(';')
+ {:reference => reference, :auth_code => auth_code, :ca_reference => ca_reference}
end
end
end
diff --git a/lib/active_merchant/billing/gateways/decidir.rb b/lib/active_merchant/billing/gateways/decidir.rb
new file mode 100644
index 00000000000..550d6bdb49e
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/decidir.rb
@@ -0,0 +1,233 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class DecidirGateway < Gateway
+ self.test_url = 'https://developers.decidir.com/api/v2'
+ self.live_url = 'https://live.decidir.com/api/v2'
+
+ self.supported_countries = ['AR']
+ self.money_format = :cents
+ self.default_currency = 'ARS'
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :naranja, :cabal]
+
+ self.homepage_url = 'http://www.decidir.com'
+ self.display_name = 'Decidir'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ 1 => STANDARD_ERROR_CODE[:call_issuer],
+ 2 => STANDARD_ERROR_CODE[:call_issuer],
+ 3 => STANDARD_ERROR_CODE[:config_error],
+ 4 => STANDARD_ERROR_CODE[:pickup_card],
+ 5 => STANDARD_ERROR_CODE[:card_declined],
+ 7 => STANDARD_ERROR_CODE[:pickup_card],
+ 12 => STANDARD_ERROR_CODE[:processing_error],
+ 14 => STANDARD_ERROR_CODE[:invalid_number],
+ 28 => STANDARD_ERROR_CODE[:processing_error],
+ 38 => STANDARD_ERROR_CODE[:incorrect_pin],
+ 39 => STANDARD_ERROR_CODE[:invalid_number],
+ 43 => STANDARD_ERROR_CODE[:pickup_card],
+ 45 => STANDARD_ERROR_CODE[:card_declined],
+ 46 => STANDARD_ERROR_CODE[:invalid_number],
+ 47 => STANDARD_ERROR_CODE[:card_declined],
+ 48 => STANDARD_ERROR_CODE[:card_declined],
+ 49 => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ 51 => STANDARD_ERROR_CODE[:card_declined],
+ 53 => STANDARD_ERROR_CODE[:card_declined],
+ 54 => STANDARD_ERROR_CODE[:expired_card],
+ 55 => STANDARD_ERROR_CODE[:incorrect_pin],
+ 56 => STANDARD_ERROR_CODE[:card_declined],
+ 57 => STANDARD_ERROR_CODE[:card_declined],
+ 76 => STANDARD_ERROR_CODE[:call_issuer],
+ 96 => STANDARD_ERROR_CODE[:processing_error],
+ 97 => STANDARD_ERROR_CODE[:processing_error],
+ }
+
+ def initialize(options={})
+ requires!(options, :api_key)
+ super
+ @options[:preauth_mode] ||= false
+ end
+
+ def purchase(money, payment, options={})
+ raise ArgumentError, 'Purchase is not supported on Decidir gateways configured with the preauth_mode option' if @options[:preauth_mode]
+
+ post = {}
+ add_auth_purchase_params(post, money, payment, options)
+ commit(:post, 'payments', post)
+ end
+
+ def authorize(money, payment, options={})
+ raise ArgumentError, 'Authorize is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
+
+ post = {}
+ add_auth_purchase_params(post, money, payment, options)
+ commit(:post, 'payments', post)
+ end
+
+ def capture(money, authorization, options={})
+ raise ArgumentError, 'Capture is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
+
+ post = {}
+ add_amount(post, money, options)
+ commit(:put, "payments/#{authorization}", post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_amount(post, money, options)
+ commit(:post, "payments/#{authorization}/refunds", post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ commit(:post, "payments/#{authorization}/refunds", post)
+ end
+
+ def verify(credit_card, options={})
+ raise ArgumentError, 'Verify is not supported on Decidir gateways unless the preauth_mode option is enabled' unless @options[:preauth_mode]
+
+ 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((apikey: )\w+)i, '\1[FILTERED]').
+ gsub(%r((\"card_number\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"security_code\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"emv_issuer_data\\\":\\\")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_auth_purchase_params(post, money, credit_card, options)
+ post[:payment_method_id] = options[:payment_method_id] ? options[:payment_method_id].to_i : 1
+ post[:site_transaction_id] = options[:order_id]
+ post[:bin] = credit_card.number[0..5]
+ post[:payment_type] = options[:payment_type] || 'single'
+ post[:installments] = options[:installments] ? options[:installments].to_i : 1
+ post[:description] = options[:description] if options[:description]
+ post[:email] = options[:email] if options[:email]
+ post[:sub_payments] = []
+
+ add_invoice(post, money, options)
+ add_payment(post, credit_card, options)
+ end
+
+ def add_invoice(post, money, options)
+ add_amount(post, money, options)
+ post[:currency] = (options[:currency] || currency(money))
+ end
+
+ def add_amount(post, money, options)
+ currency = (options[:currency] || currency(money))
+ post[:amount] = localized_amount(money, currency).to_i
+ end
+
+ def add_payment(post, credit_card, options)
+ card_data = {}
+ card_data[:card_number] = credit_card.number
+ card_data[:card_expiration_month] = format(credit_card.month, :two_digits)
+ card_data[:card_expiration_year] = format(credit_card.year, :two_digits)
+ card_data[:security_code] = credit_card.verification_value if credit_card.verification_value?
+ card_data[:card_holder_name] = credit_card.name if credit_card.name
+
+ # additional data used for Visa transactions
+ card_data[:card_holder_door_number] = options[:card_holder_door_number].to_i if options[:card_holder_door_number]
+ card_data[:card_holder_birthday] = options[:card_holder_birthday] if options[:card_holder_birthday]
+
+ card_data[:card_holder_identification] = {}
+ card_data[:card_holder_identification][:type] = options[:card_holder_identification_type] if options[:card_holder_identification_type]
+ card_data[:card_holder_identification][:number] = options[:card_holder_identification_number] if options[:card_holder_identification_number]
+
+ post[:card_data] = card_data
+ end
+
+ def headers(options = {})
+ {
+ 'apikey' => @options[:api_key],
+ 'Content-type' => 'application/json',
+ 'Cache-Control' => 'no-cache'
+ }
+ end
+
+ def commit(method, endpoint, parameters, options={})
+ url = "#{(test? ? test_url : live_url)}/#{endpoint}"
+
+ begin
+ raw_response = ssl_request(method, url, post_data(parameters), headers(options))
+ response = parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response = parse(raw_response)
+ end
+
+ success = success_from(response)
+ Response.new(
+ success,
+ message_from(success, response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: success ? nil : error_code_from(response)
+ )
+ end
+
+ def post_data(parameters = {})
+ parameters.to_json
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ rescue JSON::ParserError
+ {
+ 'message' => "A non-JSON response was received from Decidir where one was expected. The raw response was:\n\n#{body}"
+ }
+ end
+
+ def message_from(success, response)
+ return response['status'] if success
+ return response['message'] if response['message']
+
+ message = nil
+
+ if error = response.dig('status_details', 'error')
+ message = error.dig('reason', 'description')
+ elsif response['error_type']
+ if response['validation_errors']
+ message = response['validation_errors'].map { |errors| "#{errors['code']}: #{errors['param']}" }.join(', ')
+ end
+ message ||= response['error_type']
+ end
+
+ message
+ end
+
+ def success_from(response)
+ response['status'] == 'approved' || response['status'] == 'pre_approved'
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def error_code_from(response)
+ error_code = nil
+ if error = response.dig('status_details', 'error')
+ code = error.dig('reason', 'id')
+ error_code = STANDARD_ERROR_CODE_MAPPING[code]
+ error_code ||= error['type']
+ elsif response['error_type']
+ error_code = response['error_type'] if response['validation_errors']
+ end
+
+ error_code || STANDARD_ERROR_CODE[:processing_error]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/dibs.rb b/lib/active_merchant/billing/gateways/dibs.rb
new file mode 100644
index 00000000000..e3936bc383f
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/dibs.rb
@@ -0,0 +1,199 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class DibsGateway < Gateway
+ self.display_name = 'DIBS'
+ self.homepage_url = 'http://www.dibspayment.com/'
+
+ self.live_url = 'https://api.dibspayment.com/merchant/v1/JSON/Transaction/'
+
+ self.supported_countries = ['US', 'FI', 'NO', 'SE', 'GB']
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ def initialize(options={})
+ requires!(options, :merchant_id, :secret_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ MultiResponse.run(false) do |r|
+ r.process { authorize(amount, payment_method, options) }
+ r.process { capture(amount, r.authorization, options) }
+ end
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_amount(post, amount)
+ add_invoice(post, amount, options)
+ if payment_method.respond_to?(:number)
+ add_payment_method(post, payment_method, options)
+ commit(:authorize, post)
+ else
+ add_ticket_id(post, payment_method)
+ commit(:authorize_ticket, post)
+ end
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_amount(post, amount)
+ add_reference(post, authorization)
+
+ commit(:capture, post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+
+ commit(:void, post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_amount(post, amount)
+ add_reference(post, authorization)
+
+ commit(:refund, 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_method, options = {})
+ post = {}
+
+ add_invoice(post, 0, options)
+ add_payment_method(post, payment_method, options)
+
+ commit(:store, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(("cardNumber\\?":\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("cvc\\?":\\?")[^"]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") }
+ CURRENCY_CODES['USD'] = '840'
+ CURRENCY_CODES['DKK'] = '208'
+ CURRENCY_CODES['NOK'] = '578'
+ CURRENCY_CODES['SEK'] = '752'
+ CURRENCY_CODES['GBP'] = '826'
+ CURRENCY_CODES['EUR'] = '978'
+
+ def add_invoice(post, money, options)
+ post[:orderId] = options[:order_id] || generate_unique_id
+ post[:currency] = CURRENCY_CODES[options[:currency] || currency(money)]
+ end
+
+ def add_ticket_id(post, payment_method)
+ post[:ticketId] = payment_method
+ end
+
+ def add_payment_method(post, payment_method, options)
+ post[:cardNumber] = payment_method.number
+ post[:cvc] = payment_method.verification_value if payment_method.verification_value
+ post[:expYear] = format(payment_method.year, :two_digits)
+ post[:expMonth] = payment_method.month
+ post[:clientIp] = options[:ip] || '127.0.0.1'
+ post[:test] = true if test?
+ end
+
+ def add_reference(post, authorization)
+ post[:transactionId] = authorization
+ end
+
+ def add_amount(post, amount)
+ post[:amount] = amount
+ end
+
+ ACTIONS = {
+ authorize: 'AuthorizeCard',
+ authorize_ticket: 'AuthorizeTicket',
+ capture: 'CaptureTransaction',
+ void: 'CancelTransaction',
+ refund: 'RefundTransaction',
+ store: 'CreateTicket'
+ }
+
+ def commit(action, post)
+ post[:merchantId] = @options[:merchant_id]
+
+ data = build_request(post)
+ raw = parse(ssl_post(url(action), "request=#{data}", headers))
+ succeeded = success_from(raw)
+ Response.new(
+ succeeded,
+ message_from(succeeded, raw),
+ raw,
+ authorization: authorization_from(post, raw),
+ test: test?
+ )
+ rescue JSON::ParserError
+ unparsable_response(raw)
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/x-www-form-urlencoded'
+ }
+ end
+
+ def build_request(post)
+ add_hmac(post)
+ post.to_json
+ end
+
+ def add_hmac(post)
+ data = post.sort.collect { |key, value| "#{key}=#{value}" }.join('&')
+ digest = OpenSSL::Digest.new('sha256')
+ key = [@options[:secret_key]].pack('H*')
+ post[:MAC] = OpenSSL::HMAC.hexdigest(digest, key, data)
+ end
+
+ def url(action)
+ live_url + ACTIONS[action]
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(raw_response)
+ raw_response['status'] == 'ACCEPT'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response['status'] + ': ' + response['declineReason'] || 'Unable to read error message'
+ end
+ end
+
+ def authorization_from(request, response)
+ response['transactionId'] || response['ticketId'] || request[:transactionId]
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from Dibs. Please contact Dibs if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/digitzs.rb b/lib/active_merchant/billing/gateways/digitzs.rb
new file mode 100644
index 00000000000..bbc82d4a2b8
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/digitzs.rb
@@ -0,0 +1,292 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class DigitzsGateway < Gateway
+ include Empty
+
+ self.test_url = 'https://beta.digitzsapi.com/sandbox'
+ self.live_url = 'https://beta.digitzsapi.com/v3'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+ self.money_format = :cents
+
+ self.homepage_url = 'https://digitzs.com'
+ self.display_name = 'Digitzs'
+
+ def initialize(options={})
+ requires!(options, :app_key, :api_key)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ MultiResponse.run do |r|
+ r.process { commit('auth/token', app_token_request(options)) }
+ r.process { commit('payments', purchase_request(money, payment, options), options.merge({ app_token: app_token_from(r) })) }
+ end
+ end
+
+ def refund(money, authorization, options={})
+ MultiResponse.run do |r|
+ r.process { commit('auth/token', app_token_request(options)) }
+ r.process { commit('payments', refund_request(money, authorization, options), options.merge({ app_token: app_token_from(r) })) }
+ end
+ end
+
+ def store(payment, options = {})
+ MultiResponse.run do |r|
+ r.process { commit('auth/token', app_token_request(options)) }
+ options[:app_token] = app_token_from(r)
+
+ if options[:customer_id].present?
+ customer_id = check_customer_exists(options)
+
+ if customer_id
+ r.process { add_credit_card_to_customer(payment, options) }
+ else
+ r.process { add_customer_with_credit_card(payment, options) }
+ end
+ else
+ r.process { add_customer_with_credit_card(payment, options) }
+ end
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Bearer ).+), '\1[FILTERED]').
+ gsub(%r((X-Api-Key: )\w+), '\1[FILTERED]').
+ gsub(%r((\"id\\\":\\\").+), '\1[FILTERED]').
+ gsub(%r((\"appKey\\\":\\\").+), '\1[FILTERED]').
+ gsub(%r((\"appToken\\\":\\\").+), '\1[FILTERED]').
+ gsub(%r((\"code\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"number\\\":\\\")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def new_post
+ {
+ data: {
+ attributes: {}
+ }
+ }
+ end
+
+ def add_split(post, options)
+ return unless options[:payment_type] == 'card_split' || options[:payment_type] == 'token_split'
+
+ post[:data][:attributes][:split] = {
+ merchantId: options[:split_merchant_id],
+ amount: amount(options[:split_amount])
+ }
+ end
+
+ def add_payment(post, payment, options)
+ if payment.is_a? String
+ customer_id, token = split_authorization(payment)
+ post[:data][:attributes][:token] = {
+ customerId: customer_id,
+ tokenId: token
+ }
+ else
+ post[:data][:attributes][:card] = {
+ type: payment.brand,
+ holder: payment.name,
+ number: payment.number,
+ expiry: expdate(payment),
+ code: payment.verification_value
+ }
+ end
+ end
+
+ def add_transaction(post, money, options)
+ post[:data][:attributes][:transaction] = {
+ amount: amount(money),
+ currency: (options[:currency] || currency(money)),
+ invoice: options[:order_id] || generate_unique_id
+ }
+ end
+
+ def add_address(post, options)
+ if address = options[:billing_address] || options[:address]
+ post[:data][:attributes][:billingAddress] = {
+ line1: address[:address1] || '',
+ line2: address[:address2] || '',
+ city: address[:city] || '',
+ state: address[:state] || '',
+ zip: address[:zip] || '',
+ country: address['country'] || 'USA'
+ }
+ end
+ end
+
+ def app_token_request(options)
+ post = new_post
+ post[:data][:type] = 'auth'
+ post[:data][:attributes] = { appKey: @options[:app_key] }
+
+ post
+ end
+
+ def purchase_request(money, payment, options)
+ post = new_post
+ post[:data][:type] = 'payments'
+ post[:data][:attributes][:merchantId] = options[:merchant_id]
+ post[:data][:attributes][:paymentType] = determine_payment_type(payment, options)
+ add_split(post, options)
+ add_payment(post, payment, options)
+ add_transaction(post, money, options)
+ add_address(post, options)
+
+ post
+ end
+
+ def refund_request(money, authorization, options)
+ post = new_post
+ post[:data][:type] = 'payments'
+ post[:data][:attributes][:merchantId] = options[:merchant_id]
+ post[:data][:attributes][:paymentType] = 'cardRefund'
+ post[:data][:attributes][:originalTransaction] = {id: authorization}
+ add_transaction(post, money, options)
+
+ post
+ end
+
+ def create_customer_request(payment, options)
+ post = new_post
+ post[:data][:type] = 'customers'
+ post[:data][:attributes] = {
+ merchantId: options[:merchant_id],
+ name: payment.name,
+ externalId: SecureRandom.hex(16)
+ }
+
+ post
+ end
+
+ def create_token_request(payment, options)
+ post = new_post
+ post[:data][:type] = 'tokens'
+ post[:data][:attributes] = {
+ tokenType: 'card',
+ customerId: options[:customer_id],
+ label: 'Credit Card',
+ }
+ add_payment(post, payment, options)
+ add_address(post, options)
+
+ post
+ end
+
+ def check_customer_exists(options = {})
+ url = (test? ? test_url : live_url)
+ response = parse(ssl_get(url + "/customers/#{options[:customer_id]}", headers(options)))
+
+ return response.try(:[], 'data').try(:[], 'customerId') if success_from(response)
+ return nil
+ end
+
+ def add_credit_card_to_customer(payment, options = {})
+ commit('tokens', create_token_request(payment, options), options)
+ end
+
+ def add_customer_with_credit_card(payment, options = {})
+ customer_response = commit('customers', create_customer_request(payment, options), options)
+ options[:customer_id] = customer_response.authorization
+ commit('tokens', create_token_request(payment, options), options)
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, parameters, options={})
+ url = (test? ? test_url : live_url)
+ response = parse(ssl_post(url + "/#{action}", parameters.to_json, headers(options)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: avs_result_from(response)),
+ cvv_result: CVVResult.new(cvv_result_from(response)),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def success_from(response)
+ response['errors'].nil? && response['message'].nil?
+ end
+
+ def message_from(response)
+ return response['message'] if response['message']
+ return 'Success' if success_from(response)
+ response['errors'].map { |error_hash| error_hash['detail'] }.join(', ')
+ end
+
+ def authorization_from(response)
+ if customer_id = response.try(:[], 'data').try(:[], 'attributes').try(:[], 'customerId')
+ "#{customer_id}|#{response.try(:[], "data").try(:[], "id")}"
+ else
+ response.try(:[], 'data').try(:[], 'id')
+ end
+ end
+
+ def avs_result_from(response)
+ response.try(:[], 'data').try(:[], 'attributes').try(:[], 'transaction').try(:[], 'avsResult')
+ end
+
+ def cvv_result_from(response)
+ response.try(:[], 'data').try(:[], 'attributes').try(:[], 'transaction').try(:[], 'codeResult')
+ end
+
+ def app_token_from(response)
+ response.params.try(:[], 'data').try(:[], 'attributes').try(:[], 'appToken')
+ end
+
+ def headers(options)
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'x-api-key' => @options[:api_key]
+ }
+
+ headers['Authorization'] = "Bearer #{options[:app_token]}" if options[:app_token]
+ headers
+ end
+
+ def error_code_from(response)
+ unless success_from(response)
+ response['errors'].nil? ? response['message'] : response['errors'].map { |error_hash| error_hash['code'] }.join(', ')
+ end
+ end
+
+ def split_authorization(authorization)
+ customer_id, token = authorization.split('|')
+ [customer_id, token]
+ end
+
+ def determine_payment_type(payment, options)
+ return 'cardSplit' if options[:payment_type] == 'card_split'
+ return 'tokenSplit' if options[:payment_type] == 'token_split'
+ return 'token' if payment.is_a? String
+ 'card'
+ end
+
+ def handle_response(response)
+ case response.code.to_i
+ when 200..499
+ response.body
+ else
+ raise ResponseError.new(response)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/ebanx.rb b/lib/active_merchant/billing/gateways/ebanx.rb
new file mode 100644
index 00000000000..b7d1dc15171
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/ebanx.rb
@@ -0,0 +1,296 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class EbanxGateway < Gateway
+ self.test_url = 'https://sandbox.ebanxpay.com/ws/'
+ self.live_url = 'https://api.ebanxpay.com/ws/'
+
+ self.supported_countries = ['BR', 'MX', 'CO', 'CL', 'AR']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
+
+ self.homepage_url = 'http://www.ebanx.com/'
+ self.display_name = 'EBANX'
+
+ CARD_BRAND = {
+ visa: 'visa',
+ master: 'master_card',
+ american_express: 'amex',
+ discover: 'discover',
+ diners_club: 'diners'
+ }
+
+ URL_MAP = {
+ purchase: 'direct',
+ authorize: 'direct',
+ capture: 'capture',
+ refund: 'refund',
+ void: 'cancel',
+ store: 'token'
+ }
+
+ HTTP_METHOD = {
+ purchase: :post,
+ authorize: :post,
+ capture: :get,
+ refund: :post,
+ void: :get,
+ store: :post
+ }
+
+ def initialize(options={})
+ requires!(options, :integration_key)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = { payment: {} }
+ add_integration_key(post)
+ add_operation(post)
+ add_invoice(post, money, options)
+ add_customer_data(post, payment, options)
+ add_card_or_token(post, payment)
+ add_address(post, options)
+ add_customer_responsible_person(post, payment, options)
+
+ commit(:purchase, post)
+ end
+
+ def authorize(money, payment, options={})
+ post = { payment: {} }
+ add_integration_key(post)
+ add_operation(post)
+ add_invoice(post, money, options)
+ add_customer_data(post, payment, options)
+ add_card_or_token(post, payment)
+ add_address(post, options)
+ add_customer_responsible_person(post, payment, options)
+ post[:payment][:creditcard][:auto_capture] = false
+
+ commit(:authorize, post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ add_integration_key(post)
+ post[:hash] = authorization
+ post[:amount] = amount(money)
+
+ commit(:capture, post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_integration_key(post)
+ add_operation(post)
+ add_authorization(post, authorization)
+ post[:amount] = amount(money)
+ post[:description] = options[:description]
+
+ commit(:refund, post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_integration_key(post)
+ add_authorization(post, authorization)
+
+ commit(:void, post)
+ end
+
+ def store(credit_card, options={})
+ post = {}
+ add_integration_key(post)
+ add_payment_details(post, credit_card)
+ post[:country] = customer_country(options)
+
+ commit(:store, 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(/(integration_key\\?":\\?")(\d*)/, '\1[FILTERED]').
+ gsub(/(card_number\\?":\\?")(\d*)/, '\1[FILTERED]').
+ gsub(/(card_cvv\\?":\\?")(\d*)/, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_integration_key(post)
+ post[:integration_key] = @options[:integration_key].to_s
+ end
+
+ def add_operation(post)
+ post[:operation] = 'request'
+ end
+
+ def add_authorization(post, authorization)
+ post[:hash] = authorization
+ end
+
+ def add_customer_data(post, payment, options)
+ post[:payment][:name] = customer_name(payment, options)
+ post[:payment][:email] = options[:email] || 'unspecified@example.com'
+ post[:payment][:document] = options[:document]
+ post[:payment][:birth_date] = options[:birth_date] if options[:birth_date]
+ end
+
+ def add_customer_responsible_person(post, payment, options)
+ post[:payment][:person_type] = options[:person_type] if options[:person_type]
+ if options[:person_type]&.casecmp('business')&.zero?
+ post[:payment][:responsible] = {}
+ post[:payment][:responsible][:name] = options[:responsible_name] if options[:responsible_name]
+ post[:payment][:responsible][:document] = options[:responsible_document] if options[:responsible_document]
+ post[:payment][:responsible][:birth_date] = options[:responsible_birth_date] if options[:responsible_birth_date]
+ end
+ end
+
+ def add_address(post, options)
+ if address = options[:billing_address] || options[:address]
+ post[:payment][:address] = address[:address1].split[1..-1].join(' ') if address[:address1]
+ post[:payment][:street_number] = address[:address1].split.first if address[:address1]
+ post[:payment][:city] = address[:city]
+ post[:payment][:state] = address[:state]
+ post[:payment][:zipcode] = address[:zip]
+ post[:payment][:country] = address[:country].downcase
+ post[:payment][:phone_number] = address[:phone]
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:payment][:amount_total] = amount(money)
+ post[:payment][:currency_code] = (options[:currency] || currency(money))
+ post[:payment][:merchant_payment_code] = options[:order_id]
+ post[:payment][:instalments] = options[:instalments] || 1
+ end
+
+ def add_card_or_token(post, payment)
+ if payment.is_a?(String)
+ payment, brand = payment.split('|')
+ end
+ post[:payment][:payment_type_code] = payment.is_a?(String) ? brand : CARD_BRAND[payment.brand.to_sym]
+ post[:payment][:creditcard] = payment_details(payment)
+ end
+
+ def add_payment_details(post, payment)
+ post[:payment_type_code] = CARD_BRAND[payment.brand.to_sym]
+ post[:creditcard] = payment_details(payment)
+ end
+
+ def payment_details(payment)
+ if payment.is_a?(String)
+ { token: payment }
+ else
+ {
+ card_number: payment.number,
+ card_name: payment.name,
+ card_due_date: "#{payment.month}/#{payment.year}",
+ card_cvv: payment.verification_value
+ }
+ end
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, parameters)
+ url = url_for((test? ? test_url : live_url), action, parameters)
+ response = parse(ssl_request(HTTP_METHOD[action], url, post_data(action, parameters), {}))
+
+ success = success_from(action, response)
+
+ Response.new(
+ success,
+ message_from(response),
+ response,
+ authorization: authorization_from(action, parameters, response),
+ test: test?,
+ error_code: error_code_from(response, success)
+ )
+ end
+
+ def success_from(action, response)
+ if [:purchase, :capture, :refund].include?(action)
+ response.try(:[], 'payment').try(:[], 'status') == 'CO'
+ elsif action == :authorize
+ response.try(:[], 'payment').try(:[], 'status') == 'PE'
+ elsif action == :void
+ response.try(:[], 'payment').try(:[], 'status') == 'CA'
+ elsif action == :store
+ response.try(:[], 'status') == 'SUCCESS'
+ else
+ false
+ end
+ end
+
+ def message_from(response)
+ return response['status_message'] if response['status'] == 'ERROR'
+ response.try(:[], 'payment').try(:[], 'transaction_status').try(:[], 'description')
+ end
+
+ def authorization_from(action, parameters, response)
+ if action == :store
+ "#{response.try(:[], "token")}|#{CARD_BRAND[parameters[:payment_type_code].to_sym]}"
+ else
+ response.try(:[], 'payment').try(:[], 'hash')
+ end
+ end
+
+ def post_data(action, parameters = {})
+ return nil if requires_http_get(action)
+ return convert_to_url_form_encoded(parameters) if action == :refund
+ "request_body=#{parameters.to_json}"
+ end
+
+ def url_for(hostname, action, parameters)
+ return "#{hostname}#{URL_MAP[action]}?#{convert_to_url_form_encoded(parameters)}" if requires_http_get(action)
+ "#{hostname}#{URL_MAP[action]}"
+ end
+
+ def requires_http_get(action)
+ return true if [:capture, :void].include?(action)
+ false
+ end
+
+ def convert_to_url_form_encoded(parameters)
+ parameters.map do |key, value|
+ next if value != false && value.blank?
+ "#{key}=#{value}"
+ end.compact.join('&')
+ end
+
+ def error_code_from(response, success)
+ unless success
+ return response['status_code'] if response['status'] == 'ERROR'
+ response.try(:[], 'payment').try(:[], 'transaction_status').try(:[], 'code')
+ end
+ end
+
+ def customer_country(options)
+ if country = options[:country] || (options[:billing_address][:country] if options[:billing_address])
+ country.downcase
+ end
+ end
+
+ def customer_name(payment, options)
+ address_name = options[:billing_address][:name] if options[:billing_address] && options[:billing_address][:name]
+ if payment.is_a?(String)
+ address_name || 'Not Provided'
+ else
+ payment.name
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/efsnet.rb b/lib/active_merchant/billing/gateways/efsnet.rb
index fcf28d8e076..ad16cfbf349 100644
--- a/lib/active_merchant/billing/gateways/efsnet.rb
+++ b/lib/active_merchant/billing/gateways/efsnet.rb
@@ -2,7 +2,6 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
-
class EfsnetGateway < Gateway
self.supported_countries = ['US']
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
@@ -36,7 +35,7 @@ def capture(money, identification, options = {})
def credit(money, identification_or_credit_card, options = {})
if identification_or_credit_card.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
# Perform authorization reversal
refund(money, identification_or_credit_card, options)
else
@@ -54,7 +53,7 @@ def refund(money, reference, options = {})
def void(identification, options = {})
requires!(options, :order_id)
- original_transaction_id, original_transaction_amount = identification.split(";")
+ original_transaction_id, _ = identification.split(';')
commit(:void_transaction, {:reference_number => format_reference_number(options[:order_id]), :transaction_id => original_transaction_id})
end
@@ -77,11 +76,11 @@ def system_check
private
def build_refund_or_settle_request(money, identification, options = {})
- original_transaction_id, original_transaction_amount = identification.split(";")
+ original_transaction_id, original_transaction_amount = identification.split(';')
requires!(options, :order_id)
- post = {
+ {
:reference_number => format_reference_number(options[:order_id]),
:transaction_amount => amount(money),
:original_transaction_amount => original_transaction_amount,
@@ -100,16 +99,16 @@ def build_credit_card_request(money, creditcard, options = {})
:client_ip_address => options[:ip]
}
- add_creditcard(post,creditcard)
- add_address(post,options)
+ add_creditcard(post, creditcard)
+ add_address(post, options)
post
end
def format_reference_number(number)
- number.to_s.slice(0,12)
+ number.to_s.slice(0, 12)
end
- def add_address(post,options)
+ def add_address(post, options)
if address = options[:billing_address] || options[:address]
if address[:address2]
post[:billing_address] = address[:address1].to_s << ' ' << address[:address2].to_s
@@ -139,11 +138,10 @@ def add_creditcard(post, creditcard)
post[:billing_name] = creditcard.name if creditcard.name
post[:account_number] = creditcard.number
post[:card_verification_value] = creditcard.verification_value if creditcard.verification_value?
- post[:expiration_month] = sprintf("%.2i", creditcard.month)
- post[:expiration_year] = sprintf("%.4i", creditcard.year)[-2..-1]
+ post[:expiration_month] = sprintf('%.2i', creditcard.month)
+ post[:expiration_year] = sprintf('%.4i', creditcard.year)[-2..-1]
end
-
def commit(action, parameters)
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters), 'Content-Type' => 'text/xml'))
@@ -169,9 +167,7 @@ def parse(xml)
xml = REXML::Document.new(xml)
xml.elements.each('//Reply//TransactionReply/*') do |node|
-
response[node.name.underscore.to_sym] = normalize(node.text)
-
end unless xml.root.nil?
response
@@ -179,10 +175,10 @@ def parse(xml)
def post_data(action, parameters = {})
xml = REXML::Document.new("")
- root = xml.add_element("Request")
- root.attributes["StoreID"] = options[:login]
- root.attributes["StoreKey"] = options[:password]
- root.attributes["ApplicationID"] = 'ot 1.0'
+ root = xml.add_element('Request')
+ root.attributes['StoreID'] = options[:login]
+ root.attributes['StoreKey'] = options[:password]
+ root.attributes['ApplicationID'] = 'ot 1.0'
transaction = root.add_element(action.to_s.camelize)
actions[action].each do |key|
@@ -194,18 +190,7 @@ def post_data(action, parameters = {})
def message_from(message)
return 'Unspecified error' if message.blank?
- message.gsub(/[^\w]/, ' ').split.join(" ").capitalize
- end
-
- # Make a ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
- end
+ message.gsub(/[^\w]/, ' ').split.join(' ').capitalize
end
def actions
@@ -215,15 +200,15 @@ def actions
CREDIT_CARD_FIELDS = %w(AuthorizationNumber ClientIpAddress BillingAddress BillingCity BillingState BillingPostalCode BillingCountry BillingName CardVerificationValue ExpirationMonth ExpirationYear ReferenceNumber TransactionAmount AccountNumber )
ACTIONS = {
- :credit_card_authorize => CREDIT_CARD_FIELDS,
- :credit_card_charge => CREDIT_CARD_FIELDS,
- :credit_card_voice_authorize => CREDIT_CARD_FIELDS,
- :credit_card_capture => CREDIT_CARD_FIELDS,
- :credit_card_credit => CREDIT_CARD_FIELDS + ["OriginalTransactionAmount"],
- :credit_card_refund => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress),
- :void_transaction => %w(ReferenceNumber TransactionID),
- :credit_card_settle => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress),
- :system_check => %w(SystemCheck),
+ :credit_card_authorize => CREDIT_CARD_FIELDS,
+ :credit_card_charge => CREDIT_CARD_FIELDS,
+ :credit_card_voice_authorize => CREDIT_CARD_FIELDS,
+ :credit_card_capture => CREDIT_CARD_FIELDS,
+ :credit_card_credit => CREDIT_CARD_FIELDS + ['OriginalTransactionAmount'],
+ :credit_card_refund => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress),
+ :void_transaction => %w(ReferenceNumber TransactionID),
+ :credit_card_settle => %w(ReferenceNumber TransactionAmount OriginalTransactionAmount OriginalTransactionID ClientIpAddress),
+ :system_check => %w(SystemCheck),
}
end
end
diff --git a/lib/active_merchant/billing/gateways/elavon.rb b/lib/active_merchant/billing/gateways/elavon.rb
index ddc4559b937..7abaa0abfd7 100644
--- a/lib/active_merchant/billing/gateways/elavon.rb
+++ b/lib/active_merchant/billing/gateways/elavon.rb
@@ -1,42 +1,17 @@
-require File.dirname(__FILE__) + '/viaklix'
+require 'active_merchant/billing/gateways/viaklix'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- # = Elavon Virtual Merchant Gateway
- #
- # == Example use:
- #
- # gateway = ActiveMerchant::Billing::ElavonGateway.new(
- # :login => "my_virtual_merchant_id",
- # :password => "my_virtual_merchant_pin",
- # :user => "my_virtual_merchant_user_id" # optional
- # )
- #
- # # set up credit card obj as in main ActiveMerchant example
- # creditcard = ActiveMerchant::Billing::CreditCard.new(
- # :type => 'visa',
- # :number => '41111111111111111',
- # :month => 10,
- # :year => 2011,
- # :first_name => 'Bob',
- # :last_name => 'Bobsen'
- # )
- #
- # # run request
- # response = gateway.purchase(1000, creditcard) # authorize and capture 10 USD
- #
- # puts response.success? # Check whether the transaction was successful
- # puts response.message # Retrieve the message returned by Elavon
- # puts response.authorization # Retrieve the unique transaction ID returned by Elavon
- #
class ElavonGateway < Gateway
+ include Empty
+
class_attribute :test_url, :live_url, :delimiter, :actions
- self.test_url = 'https://demo.myvirtualmerchant.com/VirtualMerchantDemo/process.do'
- self.live_url = 'https://www.myvirtualmerchant.com/VirtualMerchant/process.do'
+ self.test_url = 'https://api.demo.convergepay.com/VirtualMerchantDemo/process.do'
+ self.live_url = 'https://api.convergepay.com/VirtualMerchant/process.do'
self.display_name = 'Elavon MyVirtualMerchant'
- self.supported_countries = ['US', 'CA']
+ self.supported_countries = %w(US CA PR DE IE NO PL LU BE NL MX)
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.homepage_url = 'http://www.elavon.com/'
@@ -47,130 +22,140 @@ class ElavonGateway < Gateway
:refund => 'CCRETURN',
:authorize => 'CCAUTHONLY',
:capture => 'CCFORCE',
- :void => 'CCVOID'
+ :capture_complete => 'CCCOMPLETE',
+ :void => 'CCDELETE',
+ :store => 'CCGETTOKEN',
+ :update => 'CCUPDATETOKEN',
}
- # Initialize the Gateway
- #
- # The gateway requires that a valid login and password be passed
- # in the +options+ hash.
- #
- # ==== Options
- #
- # * :login -- Merchant ID
- # * :password -- PIN
- # * :user -- Specify a subuser of the account (optional)
- # * :test => +true+ or +false+ -- Force test transactions
def initialize(options = {})
requires!(options, :login, :password)
super
end
- # Make a purchase
- def purchase(money, creditcard, options = {})
+ def purchase(money, payment_method, options = {})
form = {}
add_salestax(form, options)
add_invoice(form, options)
- add_creditcard(form, creditcard)
+ if payment_method.is_a?(String)
+ add_token(form, payment_method)
+ else
+ add_creditcard(form, payment_method)
+ end
+ add_currency(form, money, options)
add_address(form, options)
add_customer_data(form, options)
add_test_mode(form, options)
- commit(:purchase, money, form)
+ add_ip(form, options)
+ commit(:purchase, money, form, options)
end
- # Authorize a credit card for a given amount.
- #
- # ==== Parameters
- # * money - The amount to be authorized as an Integer value in cents.
- # * credit_card - The CreditCard details for the transaction.
- # * options
- # * :billing_address - The billing address for the cardholder.
def authorize(money, creditcard, options = {})
form = {}
add_salestax(form, options)
add_invoice(form, options)
add_creditcard(form, creditcard)
+ add_currency(form, money, options)
add_address(form, options)
add_customer_data(form, options)
add_test_mode(form, options)
- commit(:authorize, money, form)
+ add_ip(form, options)
+ commit(:authorize, money, form, options)
end
- # Capture authorized funds from a credit card.
- #
- # ==== Parameters
- # * money - The amount to be captured as an Integer value in cents.
- # * authorization - The approval code returned from the initial authorization.
- # * options
- # * :credit_card - The CreditCard details from the initial transaction (required).
def capture(money, authorization, options = {})
- requires!(options, :credit_card)
-
form = {}
- add_salestax(form, options)
- add_approval_code(form, authorization)
- add_invoice(form, options)
- add_creditcard(form, options[:credit_card])
- add_customer_data(form, options)
- add_test_mode(form, options)
- commit(:capture, money, form)
- end
-
- # Refund a transaction.
- #
- # This transaction indicates to the gateway that
- # money should flow from the merchant to the customer.
- #
- # ==== Parameters
- #
- # * money -- The amount to be credited to the customer as an Integer value in cents.
- # * identification -- The ID of the original transaction against which the refund is being issued.
- # * options -- A hash of parameters.
+ if options[:credit_card]
+ action = :capture
+ add_salestax(form, options)
+ add_approval_code(form, authorization)
+ add_invoice(form, options)
+ add_creditcard(form, options[:credit_card])
+ add_currency(form, money, options)
+ add_customer_data(form, options)
+ add_test_mode(form, options)
+ else
+ action = :capture_complete
+ add_txn_id(form, authorization)
+ add_partial_shipment_flag(form, options)
+ add_test_mode(form, options)
+ end
+ commit(action, money, form, options)
+ end
+
def refund(money, identification, options = {})
form = {}
add_txn_id(form, identification)
add_test_mode(form, options)
- commit(:refund, money, form)
+ commit(:refund, money, form, options)
end
- # Void a previous transaction
- #
- # ==== Parameters
- #
- # * authorization - The authorization returned from the previous request.
def void(identification, options = {})
form = {}
add_txn_id(form, identification)
add_test_mode(form, options)
- commit(:void, nil, form)
+ commit(:void, nil, form, options)
end
- # Make a credit to a card. Use the refund method if you'd like to credit using
- # previous transaction
- #
- # ==== Parameters
- # * money - The amount to be credited as an Integer value in cents.
- # * creditcard - The credit card to be credited.
- # * options
def credit(money, creditcard, options = {})
if creditcard.is_a?(String)
- raise ArgumentError, "Reference credits are not supported. Please supply the original credit card or use the #refund method."
+ raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.'
end
form = {}
add_invoice(form, options)
add_creditcard(form, creditcard)
+ add_currency(form, money, options)
add_address(form, options)
add_customer_data(form, options)
add_test_mode(form, options)
- commit(:credit, money, form)
+ commit(:credit, money, form, options)
+ 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(creditcard, options = {})
+ form = {}
+ add_creditcard(form, creditcard)
+ add_address(form, options)
+ add_customer_data(form, options)
+ add_test_mode(form, options)
+ add_verification(form, options)
+ form[:add_token] = 'Y'
+ commit(:store, nil, form, options)
+ end
+
+ def update(token, creditcard, options = {})
+ form = {}
+ add_token(form, token)
+ add_creditcard(form, creditcard)
+ add_address(form, options)
+ add_customer_data(form, options)
+ add_test_mode(form, options)
+ commit(:update, nil, form, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?ssl_pin=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?ssl_card_number=)[^&\\n\r\n]*)i, '\1[FILTERED]').
+ gsub(%r((&?ssl_cvv2cvc2=)[^&]*)i, '\1[FILTERED]')
+ end
private
- def add_invoice(form,options)
- form[:invoice_number] = (options[:order_id] || options[:invoice]).to_s.slice(0, 10)
- form[:description] = options[:description].to_s.slice(0, 255)
+
+ def add_invoice(form, options)
+ form[:invoice_number] = truncate((options[:order_id] || options[:invoice]), 10)
+ form[:description] = truncate(options[:description], 255)
end
def add_approval_code(form, authorization)
@@ -193,8 +178,17 @@ def add_creditcard(form, creditcard)
add_verification_value(form, creditcard)
end
- form[:first_name] = creditcard.first_name.to_s.slice(0, 20)
- form[:last_name] = creditcard.last_name.to_s.slice(0, 30)
+ form[:first_name] = truncate(creditcard.first_name, 20)
+ form[:last_name] = truncate(creditcard.last_name, 30)
+ end
+
+ def add_currency(form, money, options)
+ currency = options[:currency] || currency(money)
+ form[:transaction_currency] = currency if currency && (@options[:multi_currency] || options[:multi_currency])
+ end
+
+ def add_token(form, token)
+ form[:token] = token
end
def add_verification_value(form, creditcard)
@@ -203,60 +197,62 @@ def add_verification_value(form, creditcard)
end
def add_customer_data(form, options)
- form[:email] = options[:email].to_s.slice(0, 100) unless options[:email].blank?
- form[:customer_code] = options[:customer].to_s.slice(0, 10) unless options[:customer].blank?
+ form[:email] = truncate(options[:email], 100) unless empty?(options[:email])
+ form[:customer_code] = truncate(options[:customer], 10) unless empty?(options[:customer])
+ form[:customer_number] = options[:customer_number] unless empty?(options[:customer_number])
+ options[:custom_fields]&.each do |key, value|
+ form[key.to_s] = value
+ end
end
def add_salestax(form, options)
form[:salestax] = options[:tax] if options[:tax].present?
end
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
- "#{month}#{year[2..3]}"
- end
-
- def add_address(form,options)
+ def add_address(form, options)
billing_address = options[:billing_address] || options[:address]
if billing_address
- form[:avs_address] = billing_address[:address1].to_s.slice(0, 30)
- form[:address2] = billing_address[:address2].to_s.slice(0, 30)
- form[:avs_zip] = billing_address[:zip].to_s.slice(0, 10)
- form[:city] = billing_address[:city].to_s.slice(0, 30)
- form[:state] = billing_address[:state].to_s.slice(0, 10)
- form[:company] = billing_address[:company].to_s.slice(0, 50)
- form[:phone] = billing_address[:phone].to_s.slice(0, 20)
- form[:country] = billing_address[:country].to_s.slice(0, 50)
+ form[:avs_address] = truncate(billing_address[:address1], 30)
+ form[:address2] = truncate(billing_address[:address2], 30)
+ form[:avs_zip] = truncate(billing_address[:zip].to_s.gsub(/[^a-zA-Z0-9]/, ''), 9)
+ form[:city] = truncate(billing_address[:city], 30)
+ form[:state] = truncate(billing_address[:state], 10)
+ form[:company] = truncate(billing_address[:company], 50)
+ form[:phone] = truncate(billing_address[:phone], 20)
+ form[:country] = truncate(billing_address[:country], 50)
end
if shipping_address = options[:shipping_address]
- first_name, last_name = parse_first_and_last_name(shipping_address[:name])
- form[:ship_to_first_name] = first_name.to_s.slice(0, 20)
- form[:ship_to_last_name] = last_name.to_s.slice(0, 30)
- form[:ship_to_address1] = shipping_address[:address1].to_s.slice(0, 30)
- form[:ship_to_address2] = shipping_address[:address2].to_s.slice(0, 30)
- form[:ship_to_city] = shipping_address[:city].to_s.slice(0, 30)
- form[:ship_to_state] = shipping_address[:state].to_s.slice(0, 10)
- form[:ship_to_company] = shipping_address[:company].to_s.slice(0, 50)
- form[:ship_to_country] = shipping_address[:country].to_s.slice(0, 50)
- form[:ship_to_zip] = shipping_address[:zip].to_s.slice(0, 10)
+ first_name, last_name = split_names(shipping_address[:name])
+ form[:ship_to_first_name] = truncate(first_name, 20)
+ form[:ship_to_last_name] = truncate(last_name, 30)
+ form[:ship_to_address1] = truncate(shipping_address[:address1], 30)
+ form[:ship_to_address2] = truncate(shipping_address[:address2], 30)
+ form[:ship_to_city] = truncate(shipping_address[:city], 30)
+ form[:ship_to_state] = truncate(shipping_address[:state], 10)
+ form[:ship_to_company] = truncate(shipping_address[:company], 50)
+ form[:ship_to_country] = truncate(shipping_address[:country], 50)
+ form[:ship_to_zip] = truncate(shipping_address[:zip], 10)
end
end
- def parse_first_and_last_name(value)
- name = value.to_s.split(' ')
-
- last_name = name.pop || ''
- first_name = name.join(' ')
- [ first_name, last_name ]
+ def add_verification(form, options)
+ form[:verify] = 'Y' if options[:verify]
end
def add_test_mode(form, options)
form[:test_mode] = 'TRUE' if options[:test_mode]
end
+ def add_partial_shipment_flag(form, options)
+ form[:partial_shipment_flag] = 'Y' if options[:partial_shipment_flag]
+ end
+
+ def add_ip(form, options)
+ form[:cardholder_ip] = options[:ip] if options.has_key?(:ip)
+ end
+
def message_from(response)
success?(response) ? response['result_message'] : response['errorMessage']
end
@@ -265,11 +261,11 @@ def success?(response)
!response.has_key?('errorMessage')
end
- def commit(action, money, parameters)
+ def commit(action, money, parameters, options)
parameters[:amount] = amount(money)
parameters[:transaction_type] = self.actions[action]
- response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)) )
+ response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(parameters, options)))
Response.new(response['result'] == '0', message_from(response), response,
:test => @options[:test] || test?,
@@ -279,10 +275,23 @@ def commit(action, money, parameters)
)
end
- def post_data(parameters)
+ def post_data(parameters, options)
result = preamble
result.merge!(parameters)
- result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ result.collect { |key, value| post_data_string(key, value, options) }.join('&')
+ end
+
+ def post_data_string(key, value, options)
+ if custom_field?(key, options)
+ "#{key}=#{CGI.escape(value.to_s)}"
+ else
+ "ssl_#{key}=#{CGI.escape(value.to_s)}"
+ end
+ end
+
+ def custom_field?(field_name, options)
+ return true if options[:custom_fields]&.include?(field_name.to_sym)
+ field_name == :customer_number
end
def preamble
@@ -293,20 +302,19 @@ def preamble
'result_format' => 'ASCII'
}
- result['user_id'] = @options[:user] unless @options[:user].blank?
+ result['user_id'] = @options[:user] unless empty?(@options[:user])
result
end
def parse(msg)
resp = {}
- msg.split(self.delimiter).collect{|li|
- key, value = li.split("=")
- resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip
- }
+ msg.split(self.delimiter).collect { |li|
+ key, value = li.split('=')
+ resp[key.to_s.strip.gsub(/^ssl_/, '')] = value.to_s.strip
+ }
resp
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/element.rb b/lib/active_merchant/billing/gateways/element.rb
new file mode 100644
index 00000000000..a82803884ba
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/element.rb
@@ -0,0 +1,356 @@
+require 'nokogiri'
+require 'securerandom'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class ElementGateway < Gateway
+ self.test_url = 'https://certtransaction.elementexpress.com/express.asmx'
+ self.live_url = 'https://transaction.elementexpress.com/express.asmx'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+
+ self.homepage_url = 'http://www.elementps.com'
+ self.display_name = 'Element'
+
+ SERVICE_TEST_URL = 'https://certservices.elementexpress.com/express.asmx'
+ SERVICE_LIVE_URL = 'https://services.elementexpress.com/express.asmx'
+
+ def initialize(options={})
+ requires!(options, :account_id, :account_token, :application_id, :acceptor_id, :application_name, :application_version)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ action = payment.is_a?(Check) ? 'CheckSale' : 'CreditCardSale'
+
+ request = build_soap_request do |xml|
+ xml.send(action, xmlns: 'https://transaction.elementexpress.com') do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options)
+ add_terminal(xml, options)
+ add_address(xml, options)
+ end
+ end
+
+ commit(action, request, money)
+ end
+
+ def authorize(money, payment, options={})
+ request = build_soap_request do |xml|
+ xml.CreditCardAuthorization(xmlns: 'https://transaction.elementexpress.com') do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_transaction(xml, money, options)
+ add_terminal(xml, options)
+ add_address(xml, options)
+ end
+ end
+
+ commit('CreditCardAuthorization', request, money)
+ end
+
+ def capture(money, authorization, options={})
+ trans_id, _ = split_authorization(authorization)
+ options[:trans_id] = trans_id
+
+ request = build_soap_request do |xml|
+ xml.CreditCardAuthorizationCompletion(xmlns: 'https://transaction.elementexpress.com') do
+ add_credentials(xml)
+ add_transaction(xml, money, options)
+ add_terminal(xml, options)
+ end
+ end
+
+ commit('CreditCardAuthorizationCompletion', request, money)
+ end
+
+ def refund(money, authorization, options={})
+ trans_id, _ = split_authorization(authorization)
+ options[:trans_id] = trans_id
+
+ request = build_soap_request do |xml|
+ xml.CreditCardReturn(xmlns: 'https://transaction.elementexpress.com') do
+ add_credentials(xml)
+ add_transaction(xml, money, options)
+ add_terminal(xml, options)
+ end
+ end
+
+ commit('CreditCardReturn', request, money)
+ end
+
+ def void(authorization, options={})
+ trans_id, trans_amount = split_authorization(authorization)
+ options.merge!({trans_id: trans_id, trans_amount: trans_amount, reversal_type: 'Full'})
+
+ request = build_soap_request do |xml|
+ xml.CreditCardReversal(xmlns: 'https://transaction.elementexpress.com') do
+ add_credentials(xml)
+ add_transaction(xml, trans_amount, options)
+ add_terminal(xml, options)
+ end
+ end
+
+ commit('CreditCardReversal', request, trans_amount)
+ end
+
+ def store(payment, options = {})
+ request = build_soap_request do |xml|
+ xml.PaymentAccountCreate(xmlns: 'https://services.elementexpress.com') do
+ add_credentials(xml)
+ add_payment_method(xml, payment)
+ add_payment_account(xml, payment, options[:payment_account_reference_number] || SecureRandom.hex(20))
+ add_address(xml, options)
+ end
+ end
+
+ commit('PaymentAccountCreate', request, nil)
+ 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(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_credentials(xml)
+ xml.credentials do
+ xml.AccountID @options[:account_id]
+ xml.AccountToken @options[:account_token]
+ xml.AcceptorID @options[:acceptor_id]
+ end
+ xml.application do
+ xml.ApplicationID @options[:application_id]
+ xml.ApplicationName @options[:application_name]
+ xml.ApplicationVersion @options[:application_version]
+ end
+ end
+
+ def add_payment_method(xml, payment)
+ if payment.is_a?(String)
+ add_payment_account_id(xml, payment)
+ elsif payment.is_a?(Check)
+ add_echeck(xml, payment)
+ else
+ add_credit_card(xml, payment)
+ end
+ end
+
+ def add_payment_account(xml, payment, payment_account_reference_number)
+ xml.paymentAccount do
+ xml.PaymentAccountType payment_account_type(payment)
+ xml.PaymentAccountReferenceNumber payment_account_reference_number
+ end
+ end
+
+ def add_payment_account_id(xml, payment)
+ xml.extendedParameters do
+ xml.ExtendedParameters do
+ xml.Key 'PaymentAccount'
+ xml.Value('xsi:type' => 'PaymentAccount') do
+ xml.PaymentAccountID payment
+ end
+ end
+ end
+ end
+
+ def add_transaction(xml, money, options = {})
+ xml.transaction do
+ xml.ReversalType options[:reversal_type] if options[:reversal_type]
+ xml.TransactionID options[:trans_id] if options[:trans_id]
+ xml.TransactionAmount amount(money.to_i) if money
+ xml.MarketCode 'Default' if money
+ xml.ReferenceNumber options[:order_id] || SecureRandom.hex(20)
+ end
+ end
+
+ def add_terminal(xml, options)
+ xml.terminal do
+ xml.TerminalID '01'
+ xml.CardPresentCode 'UseDefault'
+ xml.CardholderPresentCode 'UseDefault'
+ xml.CardInputCode 'UseDefault'
+ xml.CVVPresenceCode 'UseDefault'
+ xml.TerminalCapabilityCode 'UseDefault'
+ xml.TerminalEnvironmentCode 'UseDefault'
+ xml.MotoECICode 'NonAuthenticatedSecureECommerceTransaction'
+ end
+ end
+
+ def add_credit_card(xml, payment)
+ xml.card do
+ xml.CardNumber payment.number
+ xml.ExpirationMonth format(payment.month, :two_digits)
+ xml.ExpirationYear format(payment.year, :two_digits)
+ xml.CardholderName payment.first_name + ' ' + payment.last_name
+ xml.CVV payment.verification_value
+ end
+ end
+
+ def add_echeck(xml, payment)
+ xml.demandDepositAccount do
+ xml.AccountNumber payment.account_number
+ xml.RoutingNumber payment.routing_number
+ xml.DDAAccountType payment.account_type.capitalize
+ end
+ end
+
+ def add_address(xml, options)
+ if address = options[:billing_address] || options[:address]
+ xml.address do
+ xml.BillingAddress1 address[:address1] if address[:address1]
+ xml.BillingAddress2 address[:address2] if address[:address2]
+ xml.BillingCity address[:city] if address[:city]
+ xml.BillingState address[:state] if address[:state]
+ xml.BillingZipcode address[:zip] if address[:zip]
+ xml.BillingEmail address[:email] if address[:email]
+ xml.BillingPhone address[:phone_number] if address[:phone_number]
+ end
+ end
+ if shipping_address = options[:shipping_address]
+ xml.address do
+ xml.ShippingAddress1 shipping_address[:address1] if shipping_address[:address1]
+ xml.ShippingAddress2 shipping_address[:address2] if shipping_address[:address2]
+ xml.ShippingCity shipping_address[:city] if shipping_address[:city]
+ xml.ShippingState shipping_address[:state] if shipping_address[:state]
+ xml.ShippingZipcode shipping_address[:zip] if shipping_address[:zip]
+ xml.ShippingEmail shipping_address[:email] if shipping_address[:email]
+ xml.ShippingPhone shipping_address[:phone_number] if shipping_address[:phone_number]
+ end
+ end
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.remove_namespaces!
+ root = doc.root.xpath('//response/*')
+
+ if root.empty?
+ root = doc.root.xpath('//Response/*')
+ end
+
+ root.each do |node|
+ if node.elements.empty?
+ response[node.name.downcase] = node.text
+ else
+ node_name = node.name.downcase
+ response[node_name] = Hash.new
+
+ node.elements.each do |childnode|
+ response[node_name][childnode.name.downcase] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ def commit(action, xml, amount)
+ response = parse(ssl_post(url(action), xml, headers(action)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(action, response, amount),
+ avs_result: success_from(response) ? avs_from(response) : nil,
+ cvv_result: success_from(response) ? cvv_from(response) : nil,
+ test: test?
+ )
+ end
+
+ def authorization_from(action, response, amount)
+ if action == 'PaymentAccountCreate'
+ response['paymentaccount']['paymentaccountid']
+ else
+ "#{response['transaction']['transactionid']}|#{amount}" if response['transaction']
+ end
+ end
+
+ def success_from(response)
+ response['expressresponsecode'] == '0'
+ end
+
+ def message_from(response)
+ response['expressresponsemessage']
+ end
+
+ def avs_from(response)
+ AVSResult.new(code: response['card']['avsresponsecode']) if response['card']
+ end
+
+ def cvv_from(response)
+ CVVResult.new(response['card']['cvvresponsecode']) if response['card']
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def build_soap_request
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
+ xml['soap'].Envelope('xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/') do
+
+ xml['soap'].Body do
+ yield(xml)
+ end
+ end
+ end
+
+ builder.to_xml
+ end
+
+ def payment_account_type(payment)
+ if payment.is_a?(Check)
+ payment_account_type = payment.account_type
+ else
+ payment_account_type = 'CreditCard'
+ end
+ payment_account_type
+ end
+
+ def url(action)
+ if action == 'PaymentAccountCreate'
+ test? ? SERVICE_TEST_URL : SERVICE_LIVE_URL
+ else
+ test? ? test_url : live_url
+ end
+ end
+
+ def interface(action)
+ return 'transaction' if action != 'PaymentAccountCreate'
+ return 'services' if action == 'PaymentAccountCreate'
+ end
+
+ def headers(action)
+ {
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'SOAPAction' => "https://#{interface(action)}.elementexpress.com/#{action}"
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/epay.rb b/lib/active_merchant/billing/gateways/epay.rb
index ff242f9d93b..4f33ae1afe8 100644
--- a/lib/active_merchant/billing/gateways/epay.rb
+++ b/lib/active_merchant/billing/gateways/epay.rb
@@ -1,8 +1,7 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class EpayGateway < Gateway
- API_HOST = 'ssl.ditonlinebetalingssystem.dk'
- self.live_url = 'https://' + API_HOST + '/remote/payment'
+ self.live_url = 'https://ssl.ditonlinebetalingssystem.dk/'
self.default_currency = 'DKK'
self.money_format = :cents
@@ -106,10 +105,21 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(((?:\?|&)cardno=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?cvc=)\d*(&?)), '\1[FILTERED]\2')
+ end
+
private
def add_amount(post, money, options)
@@ -153,16 +163,16 @@ def commit(action, params)
if action == :authorize
Response.new response['accept'].to_i == 1,
- response['errortext'],
- response,
- :test => test?,
- :authorization => response['tid']
+ response['errortext'],
+ response,
+ :test => test?,
+ :authorization => response['tid']
else
Response.new response['result'] == 'true',
- messages(response['epay'], response['pbs']),
- response,
- :test => test?,
- :authorization => params[:transaction]
+ messages(response['epay'], response['pbs']),
+ response,
+ :test => test?,
+ :authorization => params[:transaction]
end
end
@@ -175,19 +185,19 @@ def messages(epay, pbs = nil)
def soap_post(method, params)
data = xml_builder(params, method)
headers = make_headers(data, method)
- REXML::Document.new(ssl_post('https://' + API_HOST + '/remote/payment.asmx', data, headers))
+ REXML::Document.new(ssl_post(live_url + 'remote/payment.asmx', data, headers))
end
def do_authorize(params)
headers = {}
- headers['Referer'] = (options[:password] || "activemerchant.org")
+ headers['Referer'] = (options[:password] || 'activemerchant.org')
- response = raw_ssl_request(:post, 'https://' + API_HOST + '/auth/default.aspx', authorize_post_data(params), headers)
+ response = raw_ssl_request(:post, live_url + 'auth/default.aspx', authorize_post_data(params), headers)
# Authorize gives the response back by redirecting with the values in
# the URL query
if location = response['Location']
- query = CGI::parse(URI.parse(location.gsub(' ', '%20')).query)
+ query = CGI::parse(URI.parse(location.gsub(' ', '%20').gsub('<', '%3C').gsub('>', '%3E')).query)
else
return {
'accept' => '0',
@@ -198,7 +208,7 @@ def do_authorize(params)
end
result = {}
- query.each_pair do |k,v|
+ query.each_pair do |k, v|
result[k] = v.is_a?(Array) && v.size == 1 ? v[0] : v # make values like ['v'] into 'v'
end
result
@@ -233,42 +243,42 @@ def do_void(params)
def make_headers(data, soap_call)
{
'Content-Type' => 'text/xml; charset=utf-8',
- 'Host' => API_HOST,
+ 'Host' => 'ssl.ditonlinebetalingssystem.dk',
'Content-Length' => data.size.to_s,
- 'SOAPAction' => self.live_url + '/' + soap_call
+ 'SOAPAction' => self.live_url + 'remote/payment/' + soap_call
}
end
def xml_builder(params, soap_call)
xml = Builder::XmlMarkup.new(:indent => 2)
xml.instruct!
- xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/',
- 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
- 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } do
- xml.tag! 'soap:Body' do
- xml.tag! soap_call, { 'xmlns' => self.live_url } do
- xml.tag! 'merchantnumber', @options[:login]
- xml.tag! 'transactionid', params[:transaction]
- xml.tag! 'amount', params[:amount].to_s if soap_call != 'delete'
- end
+ xml.tag! 'soap:Envelope', { 'xmlns:xsi' => 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' } do
+ xml.tag! 'soap:Body' do
+ xml.tag! soap_call, { 'xmlns' => "#{self.live_url}remote/payment" } do
+ xml.tag! 'merchantnumber', @options[:login]
+ xml.tag! 'transactionid', params[:transaction]
+ xml.tag! 'amount', params[:amount].to_s if soap_call != 'delete'
end
end
+ end
xml.target!
end
def authorize_post_data(params = {})
params[:language] = '2'
params[:cms] = 'activemerchant'
- params[:accepturl] = 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?accept=1'
- params[:declineurl] = 'https://ssl.ditonlinebetalingssystem.dk/auth/default.aspx?decline=1'
+ params[:accepturl] = live_url + 'auth/default.aspx?accept=1'
+ params[:declineurl] = live_url + 'auth/default.aspx?decline=1'
params[:merchantnumber] = @options[:login]
- params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
# Limited to 20 digits max
def format_order_number(number)
- number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20]
+ number.to_s.gsub(/[^\w]/, '').rjust(4, '0')[0...20]
end
end
end
diff --git a/lib/active_merchant/billing/gateways/evo_ca.rb b/lib/active_merchant/billing/gateways/evo_ca.rb
index c2f76883ebf..b5f976b7cee 100644
--- a/lib/active_merchant/billing/gateways/evo_ca.rb
+++ b/lib/active_merchant/billing/gateways/evo_ca.rb
@@ -301,7 +301,7 @@ def post_data(action, parameters = {})
post[:username] = options[:username]
post[:password] = options[:password]
end
- post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" unless value.nil? }.compact.join("&")
+ post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" unless value.nil? }.compact.join('&')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/eway.rb b/lib/active_merchant/billing/gateways/eway.rb
index 6c60255fdf9..04874aac7ba 100644
--- a/lib/active_merchant/billing/gateways/eway.rb
+++ b/lib/active_merchant/billing/gateways/eway.rb
@@ -8,7 +8,7 @@ class EwayGateway < Gateway
self.live_url = 'https://www.eway.com.au'
self.money_format = :cents
- self.supported_countries = ['AU', 'NZ', 'GB']
+ self.supported_countries = ['AU']
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
self.homepage_url = 'http://www.eway.com.au/'
self.display_name = 'eWAY'
@@ -52,15 +52,27 @@ def refund(money, authorization, options={})
commit(refund_url, money, post)
end
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2')
+ end
+
private
+
def requires_address!(options)
- raise ArgumentError.new("Missing eWay required parameters: address or billing_address") unless (options.has_key?(:address) or options.has_key?(:billing_address))
+ raise ArgumentError.new('Missing eWay required parameters: address or billing_address') unless options.has_key?(:address) or options.has_key?(:billing_address)
end
def add_creditcard(post, creditcard)
post[:CardNumber] = creditcard.number
- post[:CardExpiryMonth] = sprintf("%.2i", creditcard.month)
- post[:CardExpiryYear] = sprintf("%.4i", creditcard.year)[-2..-1]
+ post[:CardExpiryMonth] = sprintf('%.2i', creditcard.month)
+ post[:CardExpiryYear] = sprintf('%.4i', creditcard.year)[-2..-1]
post[:CustomerFirstName] = creditcard.first_name
post[:CustomerLastName] = creditcard.last_name
post[:CardHoldersName] = creditcard.name
@@ -108,7 +120,7 @@ def commit(url, money, parameters)
end
def success?(response)
- response[:ewaytrxnstatus] == "True"
+ response[:ewaytrxnstatus] == 'True'
end
def parse(xml)
@@ -123,7 +135,7 @@ def parse(xml)
def post_data(parameters = {})
xml = REXML::Document.new
- root = xml.add_element("ewaygateway")
+ root = xml.add_element('ewaygateway')
parameters.each do |key, value|
root.add_element("eway#{key}").text = value
@@ -133,18 +145,7 @@ def post_data(parameters = {})
def message_from(message)
return '' if message.blank?
- MESSAGES[message[0,2]] || message
- end
-
- # Make a ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
- end
+ MESSAGES[message[0, 2]] || message
end
def purchase_url(cvn)
@@ -159,66 +160,66 @@ def refund_url
end
MESSAGES = {
- "00" => "Transaction Approved",
- "01" => "Refer to Issuer",
- "02" => "Refer to Issuer, special",
- "03" => "No Merchant",
- "04" => "Pick Up Card",
- "05" => "Do Not Honour",
- "06" => "Error",
- "07" => "Pick Up Card, Special",
- "08" => "Honour With Identification",
- "09" => "Request In Progress",
- "10" => "Approved For Partial Amount",
- "11" => "Approved, VIP",
- "12" => "Invalid Transaction",
- "13" => "Invalid Amount",
- "14" => "Invalid Card Number",
- "15" => "No Issuer",
- "16" => "Approved, Update Track 3",
- "19" => "Re-enter Last Transaction",
- "21" => "No Action Taken",
- "22" => "Suspected Malfunction",
- "23" => "Unacceptable Transaction Fee",
- "25" => "Unable to Locate Record On File",
- "30" => "Format Error",
- "31" => "Bank Not Supported By Switch",
- "33" => "Expired Card, Capture",
- "34" => "Suspected Fraud, Retain Card",
- "35" => "Card Acceptor, Contact Acquirer, Retain Card",
- "36" => "Restricted Card, Retain Card",
- "37" => "Contact Acquirer Security Department, Retain Card",
- "38" => "PIN Tries Exceeded, Capture",
- "39" => "No Credit Account",
- "40" => "Function Not Supported",
- "41" => "Lost Card",
- "42" => "No Universal Account",
- "43" => "Stolen Card",
- "44" => "No Investment Account",
- "51" => "Insufficient Funds",
- "52" => "No Cheque Account",
- "53" => "No Savings Account",
- "54" => "Expired Card",
- "55" => "Incorrect PIN",
- "56" => "No Card Record",
- "57" => "Function Not Permitted to Cardholder",
- "58" => "Function Not Permitted to Terminal",
- "59" => "Suspected Fraud",
- "60" => "Acceptor Contact Acquirer",
- "61" => "Exceeds Withdrawal Limit",
- "62" => "Restricted Card",
- "63" => "Security Violation",
- "64" => "Original Amount Incorrect",
- "66" => "Acceptor Contact Acquirer, Security",
- "67" => "Capture Card",
- "75" => "PIN Tries Exceeded",
- "82" => "CVV Validation Error",
- "90" => "Cutoff In Progress",
- "91" => "Card Issuer Unavailable",
- "92" => "Unable To Route Transaction",
- "93" => "Cannot Complete, Violation Of The Law",
- "94" => "Duplicate Transaction",
- "96" => "System Error"
+ '00' => 'Transaction Approved',
+ '01' => 'Refer to Issuer',
+ '02' => 'Refer to Issuer, special',
+ '03' => 'No Merchant',
+ '04' => 'Pick Up Card',
+ '05' => 'Do Not Honour',
+ '06' => 'Error',
+ '07' => 'Pick Up Card, Special',
+ '08' => 'Honour With Identification',
+ '09' => 'Request In Progress',
+ '10' => 'Approved For Partial Amount',
+ '11' => 'Approved, VIP',
+ '12' => 'Invalid Transaction',
+ '13' => 'Invalid Amount',
+ '14' => 'Invalid Card Number',
+ '15' => 'No Issuer',
+ '16' => 'Approved, Update Track 3',
+ '19' => 'Re-enter Last Transaction',
+ '21' => 'No Action Taken',
+ '22' => 'Suspected Malfunction',
+ '23' => 'Unacceptable Transaction Fee',
+ '25' => 'Unable to Locate Record On File',
+ '30' => 'Format Error',
+ '31' => 'Bank Not Supported By Switch',
+ '33' => 'Expired Card, Capture',
+ '34' => 'Suspected Fraud, Retain Card',
+ '35' => 'Card Acceptor, Contact Acquirer, Retain Card',
+ '36' => 'Restricted Card, Retain Card',
+ '37' => 'Contact Acquirer Security Department, Retain Card',
+ '38' => 'PIN Tries Exceeded, Capture',
+ '39' => 'No Credit Account',
+ '40' => 'Function Not Supported',
+ '41' => 'Lost Card',
+ '42' => 'No Universal Account',
+ '43' => 'Stolen Card',
+ '44' => 'No Investment Account',
+ '51' => 'Insufficient Funds',
+ '52' => 'No Cheque Account',
+ '53' => 'No Savings Account',
+ '54' => 'Expired Card',
+ '55' => 'Incorrect PIN',
+ '56' => 'No Card Record',
+ '57' => 'Function Not Permitted to Cardholder',
+ '58' => 'Function Not Permitted to Terminal',
+ '59' => 'Suspected Fraud',
+ '60' => 'Acceptor Contact Acquirer',
+ '61' => 'Exceeds Withdrawal Limit',
+ '62' => 'Restricted Card',
+ '63' => 'Security Violation',
+ '64' => 'Original Amount Incorrect',
+ '66' => 'Acceptor Contact Acquirer, Security',
+ '67' => 'Capture Card',
+ '75' => 'PIN Tries Exceeded',
+ '82' => 'CVV Validation Error',
+ '90' => 'Cutoff In Progress',
+ '91' => 'Card Issuer Unavailable',
+ '92' => 'Unable To Route Transaction',
+ '93' => 'Cannot Complete, Violation Of The Law',
+ '94' => 'Duplicate Transaction',
+ '96' => 'System Error'
}
end
end
diff --git a/lib/active_merchant/billing/gateways/eway_managed.rb b/lib/active_merchant/billing/gateways/eway_managed.rb
index 2dcae353a1e..fcdd34afbd4 100644
--- a/lib/active_merchant/billing/gateways/eway_managed.rb
+++ b/lib/active_merchant/billing/gateways/eway_managed.rb
@@ -12,7 +12,7 @@ class EwayManagedGateway < Gateway
self.default_currency = 'AUD'
- #accepted money format
+ # accepted money format
self.money_format = :cents
# The homepage URL of the gateway
@@ -46,7 +46,7 @@ def store(creditcard, options = {})
add_address(post, billing_address)
add_misc_fields(post, options)
- commit("CreateCustomer", post)
+ commit('CreateCustomer', post)
end
def update(billing_id, creditcard, options={})
@@ -64,7 +64,7 @@ def update(billing_id, creditcard, options={})
add_address(post, billing_address)
add_misc_fields(post, options)
- commit("UpdateCustomer", post)
+ commit('UpdateCustomer', post)
end
# Process a payment in the given amount against the stored credit card given by billing_id
@@ -86,7 +86,7 @@ def purchase(money, billing_id, options={})
post[:amount]=money
add_invoice(post, options)
- commit("ProcessPayment", post)
+ commit('ProcessPayment', post)
end
# Get customer's stored credit card details given by billing_id
@@ -98,7 +98,7 @@ def retrieve(billing_id)
post = {}
post[:managedCustomerID] = billing_id.to_s
- commit("QueryCustomer", post)
+ commit('QueryCustomer', post)
end
# TODO: eWay API also provides QueryPayment
@@ -106,8 +106,8 @@ def retrieve(billing_id)
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)
+ 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)
@@ -136,12 +136,11 @@ def add_invoice(post, options)
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[: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
@@ -150,8 +149,8 @@ def add_creditcard(post, creditcard)
def parse(body)
reply = {}
xml = REXML::Document.new(body)
- if root = REXML::XPath.first(xml, "//soap:Fault") then
- reply=parse_fault(root)
+ 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
@@ -166,19 +165,19 @@ def parse(body)
reply[:success]=true
else
if root = REXML::XPath.first(xml, '//UpdateCustomerResult') then
- if root.text.downcase == 'true' 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"
+ 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"
+ raise StandardError, 'Unexpected response'
end
end
end
@@ -232,34 +231,34 @@ def commit(action, post)
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)
+ 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
+ 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
- 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
+ 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
diff --git a/lib/active_merchant/billing/gateways/eway_rapid.rb b/lib/active_merchant/billing/gateways/eway_rapid.rb
index ae2caf64d89..34470c07183 100644
--- a/lib/active_merchant/billing/gateways/eway_rapid.rb
+++ b/lib/active_merchant/billing/gateways/eway_rapid.rb
@@ -1,221 +1,305 @@
-require "nokogiri"
-require "cgi"
+require 'json'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class EwayRapidGateway < Gateway
- self.test_url = "https://api.sandbox.ewaypayments.com/"
- self.live_url = "https://api.ewaypayments.com/"
+ self.test_url = 'https://api.sandbox.ewaypayments.com/'
+ self.live_url = 'https://api.ewaypayments.com/'
self.money_format = :cents
- self.supported_countries = ["AU"]
- self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
- self.homepage_url = "http://www.eway.com.au/"
- self.display_name = "eWAY Rapid 3.0"
- self.default_currency = "AUD"
+ self.supported_countries = ['AU', 'NZ', 'GB', 'SG', 'MY', 'HK']
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
+ self.homepage_url = 'http://www.eway.com.au/'
+ self.display_name = 'eWAY Rapid 3.1'
+ self.default_currency = 'AUD'
+
+ class_attribute :partner_id
def initialize(options = {})
requires!(options, :login, :password)
super
end
- # Public: Run a purchase transaction. Treats the Rapid 3.0 transparent
- # redirect as an API endpoint in order to conform to the standard
- # ActiveMerchant #purchase API.
+ # Public: Run a purchase transaction.
#
- # amount - The monetary amount of the transaction in cents.
- # options - A standard ActiveMerchant options hash:
- # :order_id - A merchant-supplied identifier for the
- # transaction (optional).
- # :description - A merchant-supplied description of the
- # transaction (optional).
- # :currency - Three letter currency code for the
- # transaction (default: "AUD")
- # :billing_address - Standard ActiveMerchant address hash
- # (optional).
- # :shipping_address - Standard ActiveMerchant address hash
- # (optional).
- # :ip - The ip of the consumer initiating the
- # transaction (optional).
- # :application_id - A string identifying the application
- # submitting the transaction
- # (default: "https://github.com/Shopify/active_merchant")
+ # amount - The monetary amount of the transaction in cents.
+ # payment_method - The payment method or authorization token returned from store.
+ # options - A standard ActiveMerchant options hash:
+ # :transaction_type - One of: Purchase (default), MOTO
+ # or Recurring. For stored card payments (aka - TokenPayments),
+ # this must be either MOTO or Recurring.
+ # :invoice - The merchant’s invoice number for this
+ # transaction (optional).
+ # :order_id - A merchant-supplied identifier for the
+ # transaction (optional).
+ # :description - A merchant-supplied description of the
+ # transaction (optional).
+ # :currency - Three letter currency code for the
+ # transaction (default: "AUD")
+ # :billing_address - Standard ActiveMerchant address hash
+ # (optional).
+ # :shipping_address - Standard ActiveMerchant address hash
+ # (optional).
+ # :ip - The ip of the consumer initiating the
+ # transaction (optional).
+ # :application_id - A string identifying the application
+ # submitting the transaction
+ # (default: "https://github.com/activemerchant/active_merchant")
#
- # Returns an ActiveMerchant::Billing::Response object
+ # Returns an ActiveMerchant::Billing::Response object where authorization is the Transaction ID on success
def purchase(amount, payment_method, options={})
- MultiResponse.new.tap do |r|
- # Rather than follow the redirect, we detect the 302 and capture the
- # token out of the Location header in the run_purchase step. But we
- # still need a placeholder url to pass to eWay, and that is what
- # example.com is used for here.
- r.process{setup_purchase(amount, options.merge(:redirect_url => "http://example.com/"))}
- r.process{run_purchase(r.authorization, payment_method, r.params["formactionurl"])}
- r.process{status(r.authorization)}
- end
+ params = {}
+ add_metadata(params, options)
+ add_invoice(params, amount, options)
+ add_customer_data(params, options, payment_method)
+ add_credit_card(params, payment_method, options)
+ params['Method'] = payment_method.respond_to?(:number) ? 'ProcessPayment' : 'TokenPayment'
+ commit(url_for('Transaction'), params)
end
- # Public: Acquire the token necessary to run a transparent redirect.
- #
- # amount - The monetary amount of the transaction in cents.
- # options - A supplemented ActiveMerchant options hash:
- # :redirect_url - The url to return the customer to after
- # the transparent redirect is completed
- # (required).
- # :order_id - A merchant-supplied identifier for the
- # transaction (optional).
- # :description - A merchant-supplied description of the
- # transaction (optional).
- # :currency - Three letter currency code for the
- # transaction (default: "AUD")
- # :billing_address - Standard ActiveMerchant address hash
- # (optional).
- # :shipping_address - Standard ActiveMerchant address hash
- # (optional).
- # :ip - The ip of the consumer initiating the
- # transaction (optional).
- # :application_id - A string identifying the application
- # submitting the transaction
- # (default: "https://github.com/Shopify/active_merchant")
- #
- # Returns an EwayRapidResponse object, which conforms to the
- # ActiveMerchant::Billing::Response API, but also exposes #form_url.
- def setup_purchase(amount, options={})
- requires!(options, :redirect_url)
- request = build_xml_request("CreateAccessCodeRequest") do |doc|
- add_metadata(doc, options)
- add_invoice(doc, amount, options)
- add_customer_data(doc, options)
- end
+ def authorize(amount, payment_method, options={})
+ params = {}
+ add_metadata(params, options)
+ add_invoice(params, amount, options)
+ add_customer_data(params, options, payment_method)
+ add_credit_card(params, payment_method, options)
+ params['Method'] = 'Authorise'
+ commit(url_for('Authorisation'), params)
+ end
- commit(url_for("CreateAccessCode"), request)
+ def capture(amount, identification, options = {})
+ params = {}
+ add_metadata(params, options)
+ add_invoice(params, amount, options)
+ add_reference(params, identification)
+ commit(url_for('CapturePayment'), params)
end
- # Public: Retrieve the status of a transaction.
+ def void(identification, options = {})
+ params = {}
+ add_reference(params, identification)
+ commit(url_for('CancelAuthorisation'), params)
+ end
+
+ # Public: Refund a transaction.
#
- # identification - The Eway Rapid 3.0 access code for the transaction
- # (returned as the response.authorization by
- # #setup_purchase).
+ # amount - The monetary amount of the transaction in cents
+ # identification - The transaction id which is returned in the
+ # authorization of the successful purchase transaction
+ # options - A standard ActiveMerchant options hash:
+ # :invoice - The merchant’s invoice number for this
+ # transaction (optional).
+ # :order_id - A merchant-supplied identifier for the
+ # transaction (optional).
+ # :description - A merchant-supplied description of the
+ # transaction (optional).
+ # :currency - Three letter currency code for the
+ # transaction (default: "AUD")
+ # :billing_address - Standard ActiveMerchant address hash
+ # (optional).
+ # :shipping_address - Standard ActiveMerchant address hash
+ # (optional).
+ # :ip - The ip of the consumer initiating the
+ # transaction (optional).
+ # :application_id - A string identifying the application
+ # submitting the transaction
+ # (default: "https://github.com/activemerchant/active_merchant")
#
- # Returns an EwayRapidResponse object.
- def status(identification)
- request = build_xml_request("GetAccessCodeResultRequest") do |doc|
- doc.AccessCode identification
- end
- commit(url_for("GetAccessCodeResult"), request)
+ # Returns an ActiveMerchant::Billing::Response object
+ def refund(amount, identification, options = {})
+ params = {}
+ add_metadata(params, options)
+ add_invoice(params, amount, options, 'Refund')
+ add_reference(params['Refund'], identification)
+ add_customer_data(params, options)
+ commit(url_for("Transaction/#{identification}/Refund"), params)
end
# Public: Store card details and return a valid token
#
- # options - A supplemented ActiveMerchant options hash:
- # :order_id - A merchant-supplied identifier for the
- # transaction (optional).
- # :billing_address - Standard ActiveMerchant address hash
- # (required).
- # :ip - The ip of the consumer initiating the
- # transaction (optional).
- # :application_id - A string identifying the application
- # submitting the transaction
- # (default: "https://github.com/Shopify/active_merchant")
+ # payment_method - The payment method or nil if :customer_token is provided
+ # options - A supplemented ActiveMerchant options hash:
+ # :order_id - A merchant-supplied identifier for the
+ # transaction (optional).
+ # :description - A merchant-supplied description of the
+ # transaction (optional).
+ # :billing_address - Standard ActiveMerchant address hash
+ # (required).
+ # :ip - The ip of the consumer initiating the
+ # transaction (optional).
+ # :application_id - A string identifying the application
+ # submitting the transaction
+ # (default: "https://github.com/activemerchant/active_merchant")
+ #
+ # Returns an ActiveMerchant::Billing::Response object where the authorization is the customer_token on success
def store(payment_method, options = {})
requires!(options, :billing_address)
- purchase(0, payment_method, options.merge(:request_method => "CreateTokenCustomer"))
+ params = {}
+ add_metadata(params, options)
+ add_invoice(params, 0, options)
+ add_customer_data(params, options, payment_method)
+ add_credit_card(params, payment_method, options)
+ params['Method'] = 'CreateTokenCustomer'
+ commit(url_for('Transaction'), params)
+ end
+
+ # Public: Update a customer's data
+ #
+ # customer_token - The customer token returned in the authorization of
+ # a successful store transaction.
+ # payment_method - The payment method or nil if :customer_token is provided
+ # options - A supplemented ActiveMerchant options hash:
+ # :order_id - A merchant-supplied identifier for the
+ # transaction (optional).
+ # :description - A merchant-supplied description of the
+ # transaction (optional).
+ # :billing_address - Standard ActiveMerchant address hash
+ # (optional).
+ # :ip - The ip of the consumer initiating the
+ # transaction (optional).
+ # :application_id - A string identifying the application
+ # submitting the transaction
+ # (default: "https://github.com/activemerchant/active_merchant")
+ #
+ # Returns an ActiveMerchant::Billing::Response object where the authorization is the customer_token on success
+ def update(customer_token, payment_method, options = {})
+ params = {}
+ add_metadata(params, options)
+ add_invoice(params, 0, options)
+ add_customer_data(params, options, payment_method)
+ add_credit_card(params, payment_method, options)
+ add_customer_token(params, customer_token)
+ params['Method'] = 'UpdateTokenCustomer'
+ commit(url_for('Transaction'), params)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(("Number\\?":\\?")[^"]*)i, '\1[FILTERED]').
+ gsub(%r(("CVN\\?":\\?"?)[^",]*)i, '\1[FILTERED]')
end
private
- def run_purchase(identification, payment_method, endpoint)
- post = {
- "accesscode" => identification
+ def add_metadata(params, options)
+ params['RedirectUrl'] = options[:redirect_url] || 'http://example.com'
+ params['CustomerIP'] = options[:ip] if options[:ip]
+ params['TransactionType'] = options[:transaction_type] || 'Purchase'
+ params['DeviceID'] = options[:application_id] || application_id
+ if partner = options[:partner_id] || partner_id
+ params['PartnerID'] = truncate(partner, 50)
+ end
+ params
+ end
+
+ def add_invoice(params, money, options, key = 'Payment')
+ currency_code = options[:currency] || currency(money)
+ params[key] = {
+ 'TotalAmount' => localized_amount(money, currency_code),
+ 'InvoiceReference' => truncate(options[:order_id], 50),
+ 'InvoiceNumber' => truncate(options[:invoice] || options[:order_id], 12),
+ 'InvoiceDescription' => truncate(options[:description], 64),
+ 'CurrencyCode' => currency_code,
}
- add_credit_card(post, payment_method)
+ end
- commit_form(endpoint, build_form_request(post))
+ def add_reference(params, reference)
+ params['TransactionID'] = reference
end
- def add_metadata(doc, options)
- doc.RedirectUrl(options[:redirect_url])
- doc.CustomerIP options[:ip] if options[:ip]
- doc.Method options[:request_method] || "ProcessPayment"
- doc.DeviceID(options[:application_id] || application_id)
+ def add_customer_data(params, options, payment_method = nil)
+ add_customer_fields(params, options, payment_method)
+ add_shipping_fields(params, options)
end
- def add_invoice(doc, money, options)
- doc.Payment do
- doc.TotalAmount amount(money)
- doc.InvoiceReference options[:order_id]
- doc.InvoiceDescription options[:description]
- currency_code = (options[:currency] || currency(money) || default_currency)
- doc.CurrencyCode currency_code
- end
+ def add_customer_fields(params, options, payment_method)
+ key = 'Customer'
+ params[key] ||= {}
+
+ customer_address = options[:billing_address] || options[:address]
+
+ add_name_and_email(params[key], customer_address, options[:email], payment_method)
+ add_address(params[key], customer_address)
end
- def add_customer_data(doc, options)
- doc.Customer do
- add_address(doc, (options[:billing_address] || options[:address]), {:email => options[:email]})
- end
- doc.ShippingAddress do
- add_address(doc, options[:shipping_address], {:skip_company => true})
- end
+ def add_shipping_fields(params, options)
+ key = 'ShippingAddress'
+ params[key] = {}
+
+ add_name_and_email(params[key], options[:shipping_address], options[:email])
+ add_address(params[key], options[:shipping_address], {:skip_company => true})
end
- def add_address(doc, address, options={})
- return unless address
- if name = address[:name]
- parts = name.split(/\s+/)
- doc.FirstName parts.shift if parts.size > 1
- doc.LastName parts.join(" ")
+ def add_name_and_email(params, address, email, payment_method = nil)
+ if address.present?
+ params['FirstName'], params['LastName'] = split_names(address[:name])
+ elsif payment_method_name_available?(payment_method)
+ params['FirstName'] = payment_method.first_name
+ params['LastName'] = payment_method.last_name
end
- doc.Title address[:title]
- doc.CompanyName address[:company] unless options[:skip_company]
- doc.Street1 address[:address1]
- doc.Street2 address[:address2]
- doc.City address[:city]
- doc.State address[:state]
- doc.PostalCode address[:zip]
- doc.Country address[:country].to_s.downcase
- doc.Phone address[:phone]
- doc.Fax address[:fax]
- doc.Email options[:email]
+
+ params['Email'] = email
end
- def add_credit_card(post, credit_card)
- post["cardname"] = credit_card.name
- post["cardnumber"] = credit_card.number
- post["cardexpirymonth"] = credit_card.month
- post["cardexpiryyear"] = credit_card.year
- post["cardcvn"] = credit_card.verification_value
+ def payment_method_name_available?(payment_method)
+ payment_method.respond_to?(:first_name) && payment_method.respond_to?(:last_name) &&
+ payment_method.first_name.present? && payment_method.last_name.present?
end
- def build_xml_request(root)
- builder = Nokogiri::XML::Builder.new
- builder.__send__(root) do |doc|
- yield(doc)
- end
- builder.to_xml
+ def add_address(params, address, options={})
+ return unless address
+
+ params['Title'] = address[:title]
+ params['CompanyName'] = address[:company] unless options[:skip_company]
+ params['Street1'] = truncate(address[:address1], 50)
+ params['Street2'] = truncate(address[:address2], 50)
+ params['City'] = truncate(address[:city], 50)
+ params['State'] = address[:state]
+ params['PostalCode'] = address[:zip]
+ params['Country'] = address[:country].to_s.downcase
+ params['Phone'] = address[:phone] || address[:phone_number]
+ params['Fax'] = address[:fax]
end
- def build_form_request(post)
- request = []
- post.each do |key, value|
- request << "EWAY_#{key.upcase}=#{CGI.escape(value.to_s)}"
+ def add_credit_card(params, credit_card, options)
+ return unless credit_card
+ params['Customer'] ||= {}
+ if credit_card.respond_to? :number
+ card_details = params['Customer']['CardDetails'] = {}
+ card_details['Name'] = truncate(credit_card.name, 50)
+ card_details['Number'] = credit_card.number
+ card_details['ExpiryMonth'] = '%02d' % (credit_card.month || 0)
+ card_details['ExpiryYear'] = '%02d' % (credit_card.year || 0)
+ card_details['CVN'] = credit_card.verification_value
+ else
+ add_customer_token(params, credit_card)
end
- request.join("&")
+ end
+
+ def add_customer_token(params, token)
+ params['Customer'] ||= {}
+ params['Customer']['TokenCustomerID'] = token
end
def url_for(action)
- (test? ? test_url : live_url) + action + ".xml"
+ (test? ? test_url : live_url) + action
end
- def commit(url, request, form_post=false)
+ def commit(url, params)
headers = {
- "Authorization" => ("Basic " + Base64.strict_encode64(@options[:login].to_s + ":" + @options[:password].to_s).chomp),
- "Content-Type" => "text/xml"
+ 'Authorization' => ('Basic ' + Base64.strict_encode64(@options[:login].to_s + ':' + @options[:password].to_s).chomp),
+ 'Content-Type' => 'application/json'
}
-
+ request = params.to_json
raw = parse(ssl_post(url, request, headers))
succeeded = success?(raw)
- EwayRapidResponse.new(
+ ActiveMerchant::Billing::Response.new(
succeeded,
message_from(succeeded, raw),
raw,
@@ -225,96 +309,255 @@ def commit(url, request, form_post=false)
:cvv_result => cvv_result_from(raw)
)
rescue ActiveMerchant::ResponseError => e
- return EwayRapidResponse.new(false, e.response.message, {:status_code => e.response.code}, :test => test?)
- end
-
- def commit_form(url, request)
- http_response = raw_ssl_request(:post, url, request)
-
- success = (http_response.code.to_s == "302")
- message = (success ? "Succeeded" : http_response.body)
- if success
- authorization = CGI.unescape(http_response["Location"].split("=").last)
- end
- Response.new(success, message, {:location => http_response["Location"]}, :authorization => authorization, :test => test?)
+ return ActiveMerchant::Billing::Response.new(false, e.response.message, {:status_code => e.response.code}, :test => test?)
end
- def parse(xml)
- response = {}
-
- doc = Nokogiri::XML(xml)
- doc.root.xpath("*").each do |node|
- if (node.elements.size == 0)
- response[node.name.downcase.to_sym] = node.text
- else
- node.elements.each do |childnode|
- name = "#{node.name.downcase}_#{childnode.name.downcase}"
- response[name.to_sym] = childnode.text
- end
- end
- end unless doc.root.nil?
-
- response
+ def parse(data)
+ JSON.parse(data)
end
def success?(response)
- if response[:errors]
- false
- elsif response[:responsecode] == "00"
+ if response['ResponseCode'] == '00'
true
- elsif response[:transactionstatus]
- (response[:transactionstatus] == "true")
+ elsif response['TransactionStatus']
+ (response['TransactionStatus'] == true)
+ elsif response['Succeeded']
+ (response['Succeeded'] == true)
else
- true
+ false
end
end
+ def parse_errors(message)
+ errors = message.split(',').collect { |code| MESSAGES[code.strip] }.flatten.join(',')
+ errors.presence || message
+ end
+
def message_from(succeeded, response)
- if response[:errors]
- response[:errors]
- elsif response[:responsecode]
- ActiveMerchant::Billing::EwayGateway::MESSAGES[response[:responsecode]]
- elsif response[:responsemessage]
- response[:responsemessage]
+ if response['Errors']
+ parse_errors(response['Errors'])
+ elsif response['ResponseMessage']
+ parse_errors(response['ResponseMessage'])
+ elsif response['ResponseCode']
+ ActiveMerchant::Billing::EwayGateway::MESSAGES[response['ResponseCode']]
elsif succeeded
- "Succeeded"
+ 'Succeeded'
else
- "Failed"
+ 'Failed'
end
end
def authorization_from(response)
- response[:accesscode]
+ # Note: TransactionID is always null for store requests, but TokenCustomerID is also sent back for purchase from
+ # stored card transactions so we give precedence to TransactionID
+ response['TransactionID'] || response['Customer']['TokenCustomerID']
end
def avs_result_from(response)
- code = case response[:verification_address]
- when "Valid"
- "M"
- when "Invalid"
- "N"
+ verification = response['Verification'] || {}
+ code = case verification['Address']
+ when 'Valid'
+ 'M'
+ when 'Invalid'
+ 'N'
else
- "I"
+ 'I'
end
{:code => code}
end
def cvv_result_from(response)
- case response[:verification_cvn]
- when "Valid"
- "M"
- when "Invalid"
- "N"
+ verification = response['Verification'] || {}
+ case verification['CVN']
+ when 'Valid'
+ 'M'
+ when 'Invalid'
+ 'N'
else
- "P"
+ 'P'
end
end
- class EwayRapidResponse < ActiveMerchant::Billing::Response
- def form_url
- params["formactionurl"]
- end
- end
+ MESSAGES = {
+ 'A2000' => 'Transaction Approved Successful',
+ 'A2008' => 'Honour With Identification Successful',
+ 'A2010' => 'Approved For Partial Amount Successful',
+ 'A2011' => 'Approved, VIP Successful',
+ 'A2016' => 'Approved, Update Track 3 Successful',
+ 'D4401' => 'Refer to Issuer Failed',
+ 'D4402' => 'Refer to Issuer, special Failed',
+ 'D4403' => 'No Merchant Failed',
+ 'D4404' => 'Pick Up Card Failed',
+ 'D4405' => 'Do Not Honour Failed',
+ 'D4406' => 'Error Failed',
+ 'D4407' => 'Pick Up Card, Special Failed',
+ 'D4409' => 'Request In Progress Failed',
+ 'D4412' => 'Invalid Transaction Failed',
+ 'D4413' => 'Invalid Amount Failed',
+ 'D4414' => 'Invalid Card Number Failed',
+ 'D4415' => 'No Issuer Failed',
+ 'D4419' => 'Re-enter Last Transaction Failed',
+ 'D4421' => 'No Action Taken Failed',
+ 'D4422' => 'Suspected Malfunction Failed',
+ 'D4423' => 'Unacceptable Transaction Fee Failed',
+ 'D4425' => 'Unable to Locate Record On File Failed',
+ 'D4430' => 'Format Error Failed ',
+ 'D4431' => 'Bank Not Supported By Switch Failed',
+ 'D4433' => 'Expired Card, Capture Failed ',
+ 'D4434' => 'Suspected Fraud, Retain Card Failed',
+ 'D4435' => 'Card Acceptor, Contact Acquirer, Retain Card Failed',
+ 'D4436' => 'Restricted Card, Retain Card Failed',
+ 'D4437' => 'Contact Acquirer Security Department, Retain Card Failed',
+ 'D4438' => 'PIN Tries Exceeded, Capture Failed',
+ 'D4439' => 'No Credit Account Failed',
+ 'D4440' => 'Function Not Supported Failed',
+ 'D4441' => 'Lost Card Failed',
+ 'D4442' => 'No Universal Account Failed',
+ 'D4443' => 'Stolen Card Failed',
+ 'D4444' => 'No Investment Account Failed',
+ 'D4451' => 'Insufficient Funds Failed',
+ 'D4452' => 'No Cheque Account Failed',
+ 'D4453' => 'No Savings Account Failed',
+ 'D4454' => 'Expired Card Failed',
+ 'D4455' => 'Incorrect PIN Failed',
+ 'D4456' => 'No Card Record Failed',
+ 'D4457' => 'Function Not Permitted to Cardholder Failed',
+ 'D4458' => 'Function Not Permitted to Terminal Failed',
+ 'D4459' => 'Suspected Fraud Failed',
+ 'D4460' => 'Acceptor Contact Acquirer Failed',
+ 'D4461' => 'Exceeds Withdrawal Limit Failed',
+ 'D4462' => 'Restricted Card Failed',
+ 'D4463' => 'Security Violation Failed',
+ 'D4464' => 'Original Amount Incorrect Failed',
+ 'D4466' => 'Acceptor Contact Acquirer, Security Failed',
+ 'D4467' => 'Capture Card Failed',
+ 'D4475' => 'PIN Tries Exceeded Failed',
+ 'D4482' => 'CVV Validation Error Failed',
+ 'D4490' => 'Cut off In Progress Failed',
+ 'D4491' => 'Card Issuer Unavailable Failed',
+ 'D4492' => 'Unable To Route Transaction Failed',
+ 'D4493' => 'Cannot Complete, Violation Of The Law Failed',
+ 'D4494' => 'Duplicate Transaction Failed',
+ 'D4496' => 'System Error Failed',
+ 'D4497' => 'MasterPass Error Failed',
+ 'D4498' => 'PayPal Create Transaction Error Failed',
+ 'D4499' => 'Invalid Transaction for Auth/Void Failed',
+ 'S5000' => 'System Error',
+ 'S5011' => 'PayPal Connection Error',
+ 'S5012' => 'PayPal Settings Error',
+ 'S5085' => 'Started 3dSecure',
+ 'S5086' => 'Routed 3dSecure',
+ 'S5087' => 'Completed 3dSecure',
+ 'S5088' => 'PayPal Transaction Created',
+ 'S5099' => 'Incomplete (Access Code in progress/incomplete)',
+ 'S5010' => 'Unknown error returned by gateway',
+ 'V6000' => 'Validation error',
+ 'V6001' => 'Invalid CustomerIP',
+ 'V6002' => 'Invalid DeviceID',
+ 'V6003' => 'Invalid Request PartnerID',
+ 'V6004' => 'Invalid Request Method',
+ 'V6010' => 'Invalid TransactionType, account not certified for eCome only MOTO or Recurring available',
+ 'V6011' => 'Invalid Payment TotalAmount',
+ 'V6012' => 'Invalid Payment InvoiceDescription',
+ 'V6013' => 'Invalid Payment InvoiceNumber',
+ 'V6014' => 'Invalid Payment InvoiceReference',
+ 'V6015' => 'Invalid Payment CurrencyCode',
+ 'V6016' => 'Payment Required',
+ 'V6017' => 'Payment CurrencyCode Required',
+ 'V6018' => 'Unknown Payment CurrencyCode',
+ 'V6021' => 'EWAY_CARDHOLDERNAME Required',
+ 'V6022' => 'EWAY_CARDNUMBER Required',
+ 'V6023' => 'EWAY_CARDCVN Required',
+ 'V6033' => 'Invalid Expiry Date',
+ 'V6034' => 'Invalid Issue Number',
+ 'V6035' => 'Invalid Valid From Date',
+ 'V6040' => 'Invalid TokenCustomerID',
+ 'V6041' => 'Customer Required',
+ 'V6042' => 'Customer FirstName Required',
+ 'V6043' => 'Customer LastName Required',
+ 'V6044' => 'Customer CountryCode Required',
+ 'V6045' => 'Customer Title Required ',
+ 'V6046' => 'TokenCustomerID Required',
+ 'V6047' => 'RedirectURL Required',
+ 'V6048' => 'Invalid Checkout URL',
+ 'V6051' => 'Invalid Customer FirstName',
+ 'V6052' => 'Invalid Customer LastName',
+ 'V6053' => 'Invalid Customer CountryCode',
+ 'V6058' => 'Invalid Customer Title ',
+ 'V6059' => 'Invalid RedirectURL',
+ 'V6060' => 'Invalid TokenCustomerID',
+ 'V6061' => 'Invalid Customer Reference',
+ 'V6062' => 'Invalid Customer CompanyName',
+ 'V6063' => 'Invalid Customer JobDescription',
+ 'V6064' => 'Invalid Customer Street1',
+ 'V6065' => 'Invalid Customer Street2',
+ 'V6066' => 'Invalid Customer City',
+ 'V6067' => 'Invalid Customer State',
+ 'V6068' => 'Invalid Customer PostalCode',
+ 'V6069' => 'Invalid Customer Email ',
+ 'V6070' => 'Invalid Customer Phone',
+ 'V6071' => 'Invalid Customer Mobile',
+ 'V6072' => 'Invalid Customer Comments',
+ 'V6073' => 'Invalid Customer Fax',
+ 'V6074' => 'Invalid Customer URL',
+ 'V6075' => 'Invalid ShippingAddress FirstName',
+ 'V6076' => 'Invalid ShippingAddress LastName',
+ 'V6077' => 'Invalid ShippingAddress Street1',
+ 'V6078' => 'Invalid ShippingAddress Street2',
+ 'V6079' => 'Invalid ShippingAddress City',
+ 'V6080' => 'Invalid ShippingAddress State',
+ 'V6081' => 'Invalid ShippingAddress PostalCode',
+ 'V6082' => 'Invalid ShippingAddress Email',
+ 'V6083' => 'Invalid ShippingAddress Phone',
+ 'V6084' => 'Invalid ShippingAddress Country',
+ 'V6085' => 'Invalid ShippingAddress ShippingMethod',
+ 'V6086' => 'Invalid ShippingAddress Fax',
+ 'V6091' => 'Unknown Customer CountryCode',
+ 'V6092' => 'Unknown ShippingAddress CountryCode',
+ 'V6100' => 'Invalid EWAY_CARDNAME',
+ 'V6101' => 'Invalid EWAY_CARDEXPIRYMONTH',
+ 'V6102' => 'Invalid EWAY_CARDEXPIRYYEAR ',
+ 'V6103' => 'Invalid EWAY_CARDSTARTMONTH',
+ 'V6104' => 'Invalid EWAY_CARDSTARTYEAR',
+ 'V6105' => 'Invalid EWAY_CARDISSUENUMBER ',
+ 'V6106' => 'Invalid EWAY_CARDCVN',
+ 'V6107' => 'Invalid EWAY_ACCESSCODE',
+ 'V6108' => 'Invalid CustomerHostAddress',
+ 'V6109' => 'Invalid UserAgent',
+ 'V6110' => 'Invalid EWAY_CARDNUMBER',
+ 'V6111' => 'Unauthorised API Access, Account Not PCI Certified',
+ 'V6112' => 'Redundant card details other than expiry year and month',
+ 'V6113' => 'Invalid transaction for refund',
+ 'V6114' => 'Gateway validation error',
+ 'V6115' => 'Invalid DirectRefundRequest, Transaction ID',
+ 'V6116' => 'Invalid card data on original TransactionID',
+ 'V6117' => 'Invalid CreateAccessCodeSharedRequest, FooterText',
+ 'V6118' => 'Invalid CreateAccessCodeSharedRequest, HeaderText',
+ 'V6119' => 'Invalid CreateAccessCodeSharedRequest, Language',
+ 'V6120' => 'Invalid CreateAccessCodeSharedRequest, LogoUrl ',
+ 'V6121' => 'Invalid TransactionSearch, Filter Match Type',
+ 'V6122' => 'Invalid TransactionSearch, Non numeric Transaction ID',
+ 'V6123' => 'Invalid TransactionSearch,no TransactionID or AccessCode specified',
+ 'V6124' => 'Invalid Line Items. The line items have been provided however the totals do not match the TotalAmount field',
+ 'V6125' => 'Selected Payment Type not enabled',
+ 'V6126' => 'Invalid encrypted card number, decryption failed',
+ 'V6127' => 'Invalid encrypted cvn, decryption failed',
+ 'V6128' => 'Invalid Method for Payment Type',
+ 'V6129' => 'Transaction has not been authorised for Capture/Cancellation',
+ 'V6130' => 'Generic customer information error ',
+ 'V6131' => 'Generic shipping information error',
+ 'V6132' => 'Transaction has already been completed or voided, operation not permitted',
+ 'V6133' => 'Checkout not available for Payment Type',
+ 'V6134' => 'Invalid Auth Transaction ID for Capture/Void',
+ 'V6135' => 'PayPal Error Processing Refund',
+ 'V6140' => 'Merchant account is suspended',
+ 'V6141' => 'Invalid PayPal account details or API signature',
+ 'V6142' => 'Authorise not available for Bank/Branch',
+ 'V6150' => 'Invalid Refund Amount',
+ 'V6151' => 'Refund amount greater than original transaction',
+ 'V6152' => 'Original transaction already refunded for total amount',
+ 'V6153' => 'Card type not support by merchant',
+ }
end
end
end
diff --git a/lib/active_merchant/billing/gateways/exact.rb b/lib/active_merchant/billing/gateways/exact.rb
index 8b6f04ec282..d9649b84e21 100644
--- a/lib/active_merchant/billing/gateways/exact.rb
+++ b/lib/active_merchant/billing/gateways/exact.rb
@@ -3,23 +3,22 @@ module Billing #:nodoc:
class ExactGateway < Gateway
self.live_url = self.test_url = 'https://secure2.e-xact.com/vplug-in/transaction/rpc-enc/service.asmx'
- API_VERSION = "8.5"
+ API_VERSION = '8.5'
- TEST_LOGINS = [ {:login => "A00049-01", :password => "test1"},
- {:login => "A00427-01", :password => "testus"} ]
-
- TRANSACTIONS = { :sale => "00",
- :authorization => "01",
- :capture => "32",
- :credit => "34" }
+ TEST_LOGINS = [ {:login => 'A00049-01', :password => 'test1'},
+ {:login => 'A00427-01', :password => 'testus'} ]
+ TRANSACTIONS = { :sale => '00',
+ :authorization => '01',
+ :capture => '32',
+ :credit => '34' }
ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
}
- SEND_AND_COMMIT_ATTRIBUTES = { 'xmlns:n1' => "http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Request",
+ SEND_AND_COMMIT_ATTRIBUTES = { 'xmlns:n1' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/Request',
'env:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/'
}
@@ -27,11 +26,11 @@ class ExactGateway < Gateway
'xsi:type' => 'n2:Transaction'
}
- POST_HEADERS = { 'soapAction' => "http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/SendAndCommit",
+ POST_HEADERS = { 'soapAction' => 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/SendAndCommit',
'Content-Type' => 'text/xml'
}
- SUCCESS = "true"
+ SUCCESS = 'true'
SENSITIVE_FIELDS = [ :verification_str2, :expiry_date, :card_number ]
@@ -59,7 +58,7 @@ def capture(money, authorization, options = {})
end
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
@@ -68,6 +67,7 @@ def refund(money, authorization, options = {})
end
private
+
def build_request(action, body)
xml = Builder::XmlMarkup.new
@@ -160,14 +160,21 @@ def expdate(credit_card)
end
def commit(action, request)
- response = parse(ssl_post(self.live_url, build_request(action, request), POST_HEADERS))
-
- Response.new(successful?(response), message_from(response), response,
- :test => test?,
- :authorization => authorization_from(response),
- :avs_result => { :code => response[:avs] },
- :cvv_result => response[:cvv2]
- )
+ response = parse(ssl_post(self.live_url, build_request(action, request), POST_HEADERS))
+
+ Response.new(successful?(response), message_from(response), response,
+ :test => test?,
+ :authorization => authorization_from(response),
+ :avs_result => { :code => response[:avs] },
+ :cvv_result => response[:cvv2]
+ )
+ rescue ResponseError => e
+ case e.response.code
+ when '401'
+ return Response.new(false, "Invalid Login: #{e.response.body}", {}, :test => test?)
+ else
+ raise
+ end
end
def successful?(response)
@@ -176,9 +183,9 @@ def successful?(response)
def authorization_from(response)
if response[:authorization_num] && response[:transaction_tag]
- "#{response[:authorization_num]};#{response[:transaction_tag]}"
+ "#{response[:authorization_num]};#{response[:transaction_tag]}"
else
- ''
+ ''
end
end
@@ -198,13 +205,13 @@ def parse(xml)
response = {}
xml = REXML::Document.new(xml)
- if root = REXML::XPath.first(xml, "//types:TransactionResult")
+ if root = REXML::XPath.first(xml, '//types:TransactionResult')
parse_elements(response, root)
- elsif root = REXML::XPath.first(xml, "//soap:Fault")
+ elsif root = REXML::XPath.first(xml, '//soap:Fault')
parse_elements(response, root)
end
- response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) }
+ response.delete_if { |k, v| SENSITIVE_FIELDS.include?(k) }
end
def parse_elements(response, root)
@@ -215,4 +222,3 @@ def parse_elements(response, root)
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/ezic.rb b/lib/active_merchant/billing/gateways/ezic.rb
new file mode 100644
index 00000000000..3bfe469c857
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/ezic.rb
@@ -0,0 +1,195 @@
+module ActiveMerchant
+ module Billing
+ class EzicGateway < Gateway
+ self.live_url = 'https://secure-dm3.ezic.com/gw/sas/direct3.2'
+
+ self.supported_countries = %w(AU CA CN FR DE GI IL MT MU MX NL NZ PA PH RU SG KR ES KN GB US)
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
+
+ self.homepage_url = 'http://www.ezic.com/'
+ self.display_name = 'Ezic'
+
+ def initialize(options={})
+ requires!(options, :account_id)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+
+ add_account_id(post)
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_customer_data(post, options)
+
+ commit('S', post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+
+ add_account_id(post)
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_customer_data(post, options)
+
+ commit('A', post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+
+ add_account_id(post)
+ add_invoice(post, money, options)
+ add_authorization(post, authorization)
+ add_pay_type(post)
+
+ commit('D', post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+
+ add_account_id(post)
+ add_invoice(post, money, options)
+ add_authorization(post, authorization)
+ add_pay_type(post)
+
+ commit('R', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+
+ add_account_id(post)
+ add_authorization(post, authorization)
+ add_pay_type(post)
+
+ commit('U', 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((card_number=)\w+), '\1[FILTERED]').
+ gsub(%r((card_cvv2=)\w+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_account_id(post)
+ post[:account_id] = @options[:account_id]
+ end
+
+ def add_addresses(post, options)
+ add_billing_address(post, options)
+ add_shipping_address(post, options)
+ end
+
+ def add_billing_address(post, options)
+ address = options[:billing_address] || {}
+
+ post[:bill_name1], post[:bill_name2] = split_names(address[:name])
+ post[:bill_street] = address[:address1] if address[:address1]
+ post[:bill_city] = address[:city] if address[:city]
+ post[:bill_state] = address[:state] if address[:state]
+ post[:bill_zip] = address[:zip] if address[:zip]
+ post[:bill_country] = address[:country] if address[:country]
+ post[:cust_phone] = address[:phone] if address[:phone]
+ end
+
+ def add_shipping_address(post, options)
+ address = options[:shipping_address] || {}
+
+ post[:ship_name1], post[:ship_name2] = split_names(address[:name])
+ post[:ship_street] = address[:address1] if address[:address1]
+ post[:ship_city] = address[:city] if address[:city]
+ post[:ship_state] = address[:state] if address[:state]
+ post[:ship_zip] = address[:zip] if address[:zip]
+ post[:ship_country] = address[:country] if address[:country]
+ end
+
+ def add_customer_data(post, options)
+ post[:cust_ip] = options[:ip] if options[:ip]
+ post[:cust_email] = options[:email] if options[:email]
+ add_addresses(post, options)
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:description] = options[:description] if options[:description]
+ end
+
+ def add_payment(post, payment)
+ add_pay_type(post)
+ post[:card_number] = payment.number
+ post[:card_cvv2] = payment.verification_value
+ post[:card_expire] = expdate(payment)
+ end
+
+ def add_authorization(post, authorization)
+ post[:orig_id] = authorization
+ end
+
+ def add_pay_type(post)
+ post[:pay_type] = 'C'
+ end
+
+ def parse(body)
+ CGI::parse(body).inject({}) { |hash, (key, value)| hash[key] = value.first; hash }
+ end
+
+ def commit(transaction_type, parameters)
+ parameters[:tran_type] = transaction_type
+
+ begin
+ response = parse(ssl_post(live_url, post_data(parameters), headers))
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ avs_result: AVSResult.new(code: response['avs_code']),
+ cvv_result: CVVResult.new(response['cvv2_code']),
+ test: test?
+ )
+ rescue ResponseError => e
+ Response.new(false, e.response.message)
+ end
+ end
+
+ def success_from(response)
+ response['status_code'] == '1' || response['status_code'] == 'T'
+ end
+
+ def message_from(response)
+ response['auth_msg']
+ end
+
+ def authorization_from(response)
+ response['trans_id']
+ end
+
+ def post_data(parameters = {})
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ def headers
+ {
+ 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/fat_zebra.rb b/lib/active_merchant/billing/gateways/fat_zebra.rb
index e9d05365ebf..9903e010a99 100644
--- a/lib/active_merchant/billing/gateways/fat_zebra.rb
+++ b/lib/active_merchant/billing/gateways/fat_zebra.rb
@@ -3,8 +3,8 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class FatZebraGateway < Gateway
- self.live_url = "https://gateway.fatzebra.com.au/v1.0"
- self.test_url = "https://gateway.sandbox.fatzebra.com.au/v1.0"
+ self.live_url = 'https://gateway.fatzebra.com.au/v1.0'
+ self.test_url = 'https://gateway.sandbox.fatzebra.com.au/v1.0'
self.supported_countries = ['AU']
self.default_currency = 'AUD'
@@ -14,69 +14,94 @@ class FatZebraGateway < Gateway
self.homepage_url = 'https://www.fatzebra.com.au/'
self.display_name = 'Fat Zebra'
- # Setup a new instance of the gateway.
- #
- # The options hash should include :username and :token
- # You can find your username and token at https://dashboard.fatzebra.com.au
- # Under the Your Account section
def initialize(options = {})
requires!(options, :username, :token)
- @username = options[:username]
- @token = options[:token]
super
end
- # To create a purchase on a credit card use:
- #
- # purchase(money, creditcard)
- #
- # To charge a tokenized card
- #
- # purchase(money, "abzy87u", :cvv => "123")
def purchase(money, creditcard, options = {})
post = {}
add_amount(post, money, options)
add_creditcard(post, creditcard, options)
- post[:reference] = options[:order_id]
- post[:customer_ip] = options[:ip]
+ add_extra_options(post, options)
+ add_order_id(post, options)
+ add_ip(post, options)
+ add_metadata(post, options)
commit(:post, 'purchases', post)
end
- # Refund a transaction
- #
- # amount - Integer - the amount to refund
- # txn_id - String - the original transaction to be refunded
- # reference - String - your transaction reference
- def refund(money, txn_id, reference)
+ def authorize(money, creditcard, options = {})
post = {}
- post[:amount] = money
+ add_amount(post, money, options)
+ add_creditcard(post, creditcard, options)
+ add_extra_options(post, options)
+ add_order_id(post, options)
+ add_ip(post, options)
+ add_metadata(post, options)
+
+ post[:capture] = false
+
+ commit(:post, 'purchases', post)
+ end
+
+ def capture(money, authorization, options = {})
+ txn_id, _ = authorization.to_s.split('|')
+ post = {}
+
+ add_amount(post, money, options)
+ add_extra_options(post, options)
+
+ commit(:post, "purchases/#{CGI.escape(txn_id)}/capture", post)
+ end
+
+ def refund(money, authorization, options={})
+ txn_id, _ = authorization.to_s.split('|')
+ post = {}
+
+ add_extra_options(post, options)
+ add_amount(post, money, options)
post[:transaction_id] = txn_id
- post[:reference] = reference
+ add_order_id(post, options)
+
+ commit(:post, 'refunds', post)
+ end
+
+ def void(authorization, options={})
+ txn_id, endpoint = authorization.to_s.split('|')
- commit(:post, "refunds", post)
+ commit(:post, "#{endpoint}/void?id=#{txn_id}", {})
end
- # Tokenize a credit card
- #
- # The token is returned in the Response#authorization
- def store(creditcard)
+ def store(creditcard, options={})
post = {}
+
add_creditcard(post, creditcard)
- commit(:post, "credit_cards", post)
+ commit(:post, 'credit_cards', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(("card_number\\?":\\?")[^"\\]*)i, '\1[FILTERED]').
+ gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]')
end
private
- # Add the money details to the request
def add_amount(post, money, options)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:currency] = post[:currency].upcase if post[:currency]
post[:amount] = money
end
- # Add the credit card details to the request
def add_creditcard(post, creditcard, options = {})
if creditcard.respond_to?(:number)
post[:card_number] = creditcard.number
@@ -84,10 +109,11 @@ def add_creditcard(post, creditcard, options = {})
post[:cvv] = creditcard.verification_value if creditcard.verification_value?
post[:card_holder] = creditcard.name if creditcard.name
elsif creditcard.is_a?(String)
- post[:card_token] = creditcard
+ id, _ = creditcard.to_s.split('|')
+ post[:card_token] = id
post[:cvv] = options[:cvv]
elsif creditcard.is_a?(Hash)
- deprecated "Passing the credit card as a Hash is deprecated. Use a String and put the (optional) CVV in the options hash instead."
+ ActiveMerchant.deprecated 'Passing the credit card as a Hash is deprecated. Use a String and put the (optional) CVV in the options hash instead.'
post[:card_token] = creditcard[:token]
post[:cvv] = creditcard[:cvv]
else
@@ -95,12 +121,34 @@ def add_creditcard(post, creditcard, options = {})
end
end
- # Post the data to the gateway
+ def add_extra_options(post, options)
+ extra = {}
+ extra[:ecm] = '32' if options[:recurring]
+ extra[:cavv] = options[:cavv] if options[:cavv]
+ extra[:xid] = options[:xid] if options[:xid]
+ extra[:sli] = options[:sli] if options[:sli]
+ extra[:name] = options[:merchant] if options[:merchant]
+ extra[:location] = options[:merchant_location] if options[:merchant_location]
+ post[:extra] = extra if extra.any?
+ end
+
+ def add_order_id(post, options)
+ post[:reference] = options[:order_id] || SecureRandom.hex(15)
+ end
+
+ def add_ip(post, options)
+ post[:customer_ip] = options[:ip] || '127.0.0.1'
+ end
+
+ def add_metadata(post, options)
+ post[:metadata] = options.fetch(:metadata, {})
+ end
+
def commit(method, uri, parameters=nil)
response = begin
parse(ssl_request(method, get_url(uri), parameters.to_json, headers))
rescue ResponseError => e
- return Response.new(false, "Invalid Login") if(e.response.code == "401")
+ return Response.new(false, 'Invalid Login') if(e.response.code == '401')
parse(e.response.body)
end
@@ -109,63 +157,60 @@ def commit(method, uri, parameters=nil)
success,
message_from(response),
response,
- :test => response["test"],
- :authorization => authorization_from(response, success)
+ :test => response['test'],
+ :authorization => authorization_from(response, success, uri)
)
end
def success_from(response)
(
- response["successful"] &&
- response["response"] &&
- (response["response"]["successful"] || response["response"]["token"])
+ response['successful'] &&
+ response['response'] &&
+ (response['response']['successful'] || response['response']['token'] || response['response']['response_code'] == '00')
)
end
- def authorization_from(response, success)
+ def authorization_from(response, success, uri)
+ endpoint = uri.split('/')[0]
if success
- (response["response"]["id"] || response["response"]["token"])
+ id = response['response']['id'] || response['response']['token']
+ "#{id}|#{endpoint}"
else
nil
end
end
def message_from(response)
- if !response["errors"].empty?
- response["errors"].join(", ")
- elsif response["response"]["message"]
- response["response"]["message"]
+ if !response['errors'].empty?
+ response['errors'].join(', ')
+ elsif response['response']['message']
+ response['response']['message']
else
- "Unknown Error"
+ 'Unknown Error'
end
end
- # Parse the returned JSON, if parse errors are raised then return a detailed error.
def parse(response)
- begin
- JSON.parse(response)
- rescue JSON::ParserError
- msg = 'Invalid JSON response received from Fat Zebra. Please contact support@fatzebra.com.au if you continue to receive this message.'
- msg += " (The raw response returned by the API was #{response.inspect})"
- {
- "successful" => false,
- "response" => {},
- "errors" => [msg]
- }
- end
+ JSON.parse(response)
+ rescue JSON::ParserError
+ msg = 'Invalid JSON response received from Fat Zebra. Please contact support@fatzebra.com.au if you continue to receive this message.'
+ msg += " (The raw response returned by the API was #{response.inspect})"
+ {
+ 'successful' => false,
+ 'response' => {},
+ 'errors' => [msg]
+ }
end
- # Build the URL based on the AM mode and the URI
def get_url(uri)
base = test? ? self.test_url : self.live_url
- base + "/" + uri
+ base + '/' + uri
end
- # Builds the auth and U-A headers for the request
def headers
{
- "Authorization" => "Basic " + Base64.strict_encode64(@username.to_s + ":" + @token.to_s).strip,
- "User-Agent" => "Fat Zebra v1.0/ActiveMerchant #{ActiveMerchant::VERSION}"
+ 'Authorization' => 'Basic ' + Base64.strict_encode64(@options[:username].to_s + ':' + @options[:token].to_s).strip,
+ 'User-Agent' => "Fat Zebra v1.0/ActiveMerchant #{ActiveMerchant::VERSION}"
}
end
end
diff --git a/lib/active_merchant/billing/gateways/federated_canada.rb b/lib/active_merchant/billing/gateways/federated_canada.rb
index d08e87a44e4..b6666a9fa44 100644
--- a/lib/active_merchant/billing/gateways/federated_canada.rb
+++ b/lib/active_merchant/billing/gateways/federated_canada.rb
@@ -58,7 +58,7 @@ def refund(money, authorization, options = {})
end
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
@@ -107,12 +107,6 @@ def add_creditcard(post, creditcard)
post[:cvv] = creditcard.verification_value
end
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
- "#{month}#{year[-2..-1]}"
- end
-
def parse(body)
body.split('&').inject({}) do |memo, x|
k, v = x.split('=')
@@ -126,7 +120,6 @@ def commit(action, money, parameters)
data = ssl_post(self.live_url, post_data(action, parameters))
response = parse(data)
message = message_from(response)
- test_mode = test?
Response.new(success?(response), message, response,
:test => test?,
@@ -141,17 +134,17 @@ def success?(response)
end
def test?
- (@options[:login].eql?('demo')) && (@options[:password].eql?('password'))
+ @options[:login].eql?('demo') && @options[:password].eql?('password')
end
def message_from(response)
case response['response'].to_i
when APPROVED
- "Transaction Approved"
+ 'Transaction Approved'
when DECLINED
- "Transaction Declined"
+ 'Transaction Declined'
else
- "Error in transaction data or system error"
+ 'Error in transaction data or system error'
end
end
@@ -159,9 +152,8 @@ def post_data(action, parameters = {})
parameters[:type] = action
parameters[:username] = @options[:login]
parameters[:password] = @options[:password]
- parameters.map{|k, v| "#{k}=#{CGI.escape(v.to_s)}"}.join('&')
+ parameters.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/finansbank.rb b/lib/active_merchant/billing/gateways/finansbank.rb
index a81cecd5448..5f496570853 100644
--- a/lib/active_merchant/billing/gateways/finansbank.rb
+++ b/lib/active_merchant/billing/gateways/finansbank.rb
@@ -1,9 +1,10 @@
-require File.dirname(__FILE__) + '/cc5'
+require 'active_merchant/billing/gateways/cc5'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class FinansbankGateway < CC5Gateway
- self.live_url = self.test_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer'
+ self.live_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer'
+ self.test_url = 'https://entegrasyon.asseco-see.com.tr/fim/api'
# The countries the gateway supports merchants from as 2 digit ISO country codes
self.supported_countries = ['US', 'TR']
@@ -19,4 +20,3 @@ class FinansbankGateway < CC5Gateway
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/first_giving.rb b/lib/active_merchant/billing/gateways/first_giving.rb
new file mode 100644
index 00000000000..09dea7f8e5a
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/first_giving.rb
@@ -0,0 +1,142 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class FirstGivingGateway < Gateway
+ self.test_url = 'http://usapisandbox.fgdev.net'
+ self.live_url = 'https://api.firstgiving.com'
+
+ self.supported_countries = ['US']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+ self.homepage_url = 'http://www.firstgiving.com/'
+ self.default_currency = 'USD'
+ self.display_name = 'FirstGiving'
+
+ def initialize(options = {})
+ requires!(options, :application_key, :security_token, :charity_id)
+ super
+ end
+
+ def purchase(money, creditcard, options = {})
+ post = {}
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_address(post, options)
+ add_customer_data(post, options)
+ add_donation_data(post, money, options)
+ commit('/donation/creditcard', post)
+ end
+
+ def refund(money, identifier, options = {})
+ get = {}
+ get[:transactionId] = identifier
+ get[:tranType] = 'REFUNDREQUEST'
+ commit('/transaction/refundrequest?' + encode(get))
+ end
+
+ private
+
+ def add_donation_data(post, money, options)
+ post[:amount] = amount(money)
+ post[:charityId] = @options[:charity_id]
+ post[:description] = (options[:description] || 'Purchase')
+ post[:currencyCode] = (options[:currency] || currency(money))
+ end
+
+ def add_customer_data(post, options)
+ post[:billToEmail] = (options[:email] || 'activemerchant@example.com')
+ post[:remoteAddr] = (options[:ip] || '127.0.0.1')
+ end
+
+ def add_address(post, options)
+ if(billing_address = (options[:billing_address] || options[:address]))
+ post[:billToAddressLine1] = billing_address[:address1]
+ post[:billToCity] = billing_address[:city]
+ post[:billToState] = billing_address[:state]
+ post[:billToZip] = billing_address[:zip]
+ post[:billToCountry] = billing_address[:country]
+ end
+ end
+
+ def add_invoice(post, options)
+ post[:orderId] = options[:order_id]
+ end
+
+ def add_creditcard(post, creditcard)
+ post[:billToFirstName] = creditcard.first_name
+ post[:billToLastName] = creditcard.last_name
+ post[:ccNumber] = creditcard.number
+ post[:ccType] = creditcard_brand(creditcard.brand)
+ post[:ccExpDateMonth] = creditcard.month
+ post[:ccExpDateYear] = creditcard.year
+ post[:ccCardValidationNum] = creditcard.verification_value
+ end
+
+ def parse(body)
+ response = {}
+
+ xml = Nokogiri::XML(body)
+ element = xml.xpath('//firstGivingDonationApi/firstGivingResponse').first
+
+ element.attributes.each do |name, attribute|
+ response[name] = attribute.content
+ end
+ element.children.each do |child|
+ next if child.text?
+ response[child.name] = child.text
+ end
+
+ response
+ end
+
+ def commit(action, post=nil)
+ url = (test? ? self.test_url : self.live_url) + action
+
+ begin
+ if post
+ response = parse(ssl_post(url, post_data(post), headers))
+ else
+ response = parse(ssl_get(url, headers))
+ end
+ rescue ResponseError => e
+ response = parse(e.response.body)
+ end
+
+ Response.new(
+ (response['acknowledgement'] == 'Success'),
+ (response['friendlyErrorMessage'] || response['verboseErrorMessage'] || response['acknowledgement']),
+ response,
+ authorization: response['transactionId'],
+ test: test?
+ )
+ end
+
+ def post_data(post)
+ post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ def encode(hash)
+ hash.collect { |(k, v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def creditcard_brand(brand)
+ case brand
+ when 'visa' then 'VI'
+ when 'master' then 'MC'
+ when 'discover' then 'DI'
+ when 'american_express' then 'AX'
+ else
+ raise "Unhandled credit card brand #{brand}"
+ end
+ end
+
+ def headers
+ {
+ 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ 'JG_APPLICATIONKEY' => @options[:application_key].to_s,
+ 'JG_SECURITYTOKEN' => @options[:security_token].to_s
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/first_pay.rb b/lib/active_merchant/billing/gateways/first_pay.rb
index b356dddb7cf..3c197f0d79e 100644
--- a/lib/active_merchant/billing/gateways/first_pay.rb
+++ b/lib/active_merchant/billing/gateways/first_pay.rb
@@ -1,176 +1,182 @@
+require 'nokogiri'
+
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class FirstPayGateway < Gateway
- class FirstPayPostData < PostData
- # Fields that will be sent even if they are blank
- self.required_fields = [ :action, :amount, :trackid ]
- end
+ self.live_url = 'https://secure.goemerchant.com/secure/gateway/xmlgateway.aspx'
- # both URLs are IP restricted
- self.test_url = 'https://apgcert.first-pay.com/AcqENGIN/SecureCapture'
- self.live_url = 'https://acqengin.first-pay.com/AcqENGIN/SecureCapture'
-
- # The countries the gateway supports merchants from as 2 digit ISO country codes
self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
- # The card types supported by the payment gateway
- self.supported_cardtypes = [:visa, :master]
-
- # The homepage URL of the gateway
- self.homepage_url = 'http://www.first-pay.com'
+ self.homepage_url = 'http://1stpaygateway.net/'
+ self.display_name = '1stPayGateway.Net'
- # The name of the gateway
- self.display_name = 'First Pay'
-
- # all transactions are in cents
- self.money_format = :cents
-
- ACTIONS = {
- 'sale' => 1,
- 'credit' => 2,
- 'void' => 3
- }
-
- def initialize(options = {})
- requires!(options, :login, :password)
+ def initialize(options={})
+ requires!(options, :transaction_center_id, :gateway_id)
super
end
- def purchase(money, creditcard, options = {})
- post = FirstPayPostData.new
- add_invoice(post, options)
- add_creditcard(post, creditcard)
- add_address(post, options)
+ def purchase(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment, options)
+ add_address(post, payment, options)
add_customer_data(post, options)
- commit('sale', money, post)
+ commit('sale', post)
end
- def refund(money, reference, options = {})
- requires!(options, :credit_card)
-
- post = FirstPayPostData.new
- add_invoice(post, options)
- add_creditcard(post, options[:credit_card])
- add_address(post, options)
+ def authorize(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment, options)
+ add_address(post, payment, options)
add_customer_data(post, options)
- add_credit_data(post, reference)
- commit('credit', money, post)
+ commit('auth', post)
end
- def credit(money, reference, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, reference, options)
+ def capture(money, authorization, options={})
+ post = {}
+ add_reference(post, 'settle', money, authorization)
+ commit('settle', post)
end
- def void(money, creditcard, options = {})
- post = FirstPayPostData.new
- add_creditcard(post, creditcard)
- add_void_data(post, options)
- add_invoice(post, options)
- add_customer_data(post, options)
+ def refund(money, authorization, options={})
+ post = {}
+ add_reference(post, 'credit', money, authorization)
+ commit('credit', post)
+ end
- commit('void', money, post)
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, 'void', nil, authorization)
+ commit('void', post)
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((gateway_id)[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r((card_number)[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r((cvv2)[^<]*())i, '\1[FILTERED]\2')
+ end
private
- def add_customer_data(post, options)
- post[:cardip] = options[:ip]
- post[:email] = options[:email]
+ def add_authentication(post, options)
+ post[:transaction_center_id] = options[:transaction_center_id]
+ post[:gateway_id] = options[:gateway_id]
end
- def add_address(post, options)
- if billing_address = options[:billing_address] || options[:address]
- post[:addr] = billing_address[:address1].to_s + ' ' + billing_address[:address2].to_s
- post[:city] = billing_address[:city]
- post[:state] = billing_address[:state]
- post[:zip] = billing_address[:zip]
- post[:country] = billing_address[:country]
+ def add_customer_data(post, options)
+ post[:owner_email] = options[:email] if options[:email]
+ post[:remote_ip_address] = options[:ip] if options[:ip]
+ post[:processor_id] = options[:processor_id] if options[:processor_id]
+ end
+
+ def add_address(post, creditcard, options)
+ if address = options[:billing_address] || options[:address]
+ post[:owner_name] = address[:name]
+ post[:owner_street] = address[:address1]
+ post[:owner_street2] = address[:address2] if address[:address2]
+ post[:owner_city] = address[:city]
+ post[:owner_state] = address[:state]
+ post[:owner_zip] = address[:zip]
+ post[:owner_country] = address[:country]
+ post[:owner_phone] = address[:phone] if address[:phone]
end
end
- def add_invoice(post, options)
- post[:trackid] = rand(Time.now.to_i)
+ def add_invoice(post, money, options)
+ post[:order_id] = options[:order_id]
+ post[:total] = amount(money)
end
- def add_creditcard(post, creditcard)
- post[:member] = creditcard.first_name.to_s + " " + creditcard.last_name.to_s
- post[:card] = creditcard.number
- post[:exp] = expdate(creditcard)
+ def add_payment(post, payment, options)
+ post[:card_name] = payment.brand # Unclear if need to map to known names or open text field??
+ post[:card_number] = payment.number
+ post[:card_exp] = expdate(payment)
+ post[:cvv2] = payment.verification_value
+ post[:recurring] = options[:recurring] if options[:recurring]
+ post[:recurring_start_date] = options[:recurring_start_date] if options[:recurring_start_date]
+ post[:recurring_end_date] = options[:recurring_end_date] if options[:recurring_end_date]
+ post[:recurring_type] = options[:recurring_type] if options[:recurring_type]
end
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
+ def add_reference(post, action, money, authorization)
+ post[:"#{action}_amount1"] = amount(money) if money
+ post[:total_number_transactions] = 1
+ post[:reference_number1] = authorization
end
- def add_credit_data(post, transaction_id)
- post[:transid] = transaction_id
- end
+ def parse(xml)
+ response = {}
- def add_void_data(post, options)
- post[:transid] = options[:transactionid]
+ doc = Nokogiri::XML(xml)
+ doc.root&.xpath('//RESPONSE/FIELDS/FIELD')&.each do |field|
+ response[field['KEY']] = field.text
+ end
+
+ response
end
- def commit(action, money, post)
- response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(action, post, money)) )
+ def commit(action, parameters)
+ response = parse(ssl_post(live_url, post_data(action, parameters)))
- Response.new(response[:response] == 'CAPTURED', response[:message], response,
- :test => test?,
- :authorization => response[:authorization],
- :avs_result => { :code => response[:avsresponse] },
- :cvv_result => response[:cvvresponse])
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ error_code: error_code_from(response),
+ test: test?
+ )
end
- def parse(body)
- response = {}
+ def success_from(response)
+ (
+ (response['status'] == '1') ||
+ (response['status1'] == '1')
+ )
+ end
- # check for an error first
- if body.include?('!ERROR!')
- response[:response] = 'ERROR'
- response[:message] = error_message_from(body)
- else
- # a capture / not captured response will be : delimited
- split = body.split(':')
- response[:response] = split[0]
-
- # FirstPay docs are worthless. turns out the transactionid is required for credits
- # so we need to store that in authorization, not the actual auth.
- if response[:response] == 'CAPTURED'
- response[:message] = 'CAPTURED'
- response[:authorization] = split[9] # actually the transactionid
- response[:auth] = split[1]
- response[:avsresponse] = split[3]
- response[:cvvresponse] = split[17]
- else
- # NOT CAPTURED response
- response[:message] = split[1]
- response[:transactionid] = split[9]
- end
- end
+ def message_from(response)
+ # Silly inconsistent gateway. Always make capitalized (but not all caps)
+ msg = (response['auth_response'] || response['response1'])
+ msg&.downcase&.capitalize
+ end
- return response
+ def error_code_from(response)
+ response['error']
end
- def error_message_from(response)
- # error messages use this format - '!ERROR! 704-MISSING BASIC DATA TYPE:card, exp, zip, addr, member, amount\n'
- response.split("! ")[1].chomp
+ def authorization_from(response)
+ response['reference_number'] || response['reference_number1']
end
- def post_data(action, post, money)
- post[:vid] = @options[:login]
- post[:password] = @options[:password]
- post[:action] = ACTIONS[action]
- post[:amount] = amount(money)
+ def post_data(action, parameters = {})
+ parameters[:transaction_center_id] = @options[:transaction_center_id]
+ parameters[:gateway_id] = @options[:gateway_id]
+
+ parameters[:operation_type] = action
- return post.to_post_data
+ xml = Builder::XmlMarkup.new
+ xml.instruct!
+ xml.tag! 'TRANSACTION' do
+ xml.tag! 'FIELDS' do
+ parameters.each do |key, value|
+ xml.tag! 'FIELD', value, { 'KEY' => key }
+ end
+ end
+ end
+ xml.target!
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/firstdata_e4.rb b/lib/active_merchant/billing/gateways/firstdata_e4.rb
old mode 100644
new mode 100755
index 1915ca758cc..12290ad51c6
--- a/lib/active_merchant/billing/gateways/firstdata_e4.rb
+++ b/lib/active_merchant/billing/gateways/firstdata_e4.rb
@@ -2,32 +2,66 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class FirstdataE4Gateway < Gateway
# TransArmor support requires v11 or lower
- self.test_url = "https://api.demo.globalgatewaye4.firstdata.com/transaction/v11"
- self.live_url = "https://api.globalgatewaye4.firstdata.com/transaction/v11"
+ self.test_url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v11'
+ self.live_url = 'https://api.globalgatewaye4.firstdata.com/transaction/v11'
TRANSACTIONS = {
- :sale => "00",
- :authorization => "01",
- :capture => "32",
- :void => "33",
- :credit => "34",
- :store => "05"
+ sale: '00',
+ authorization: '01',
+ verify: '05',
+ capture: '32',
+ void: '33',
+ credit: '34',
+ store: '05'
}
POST_HEADERS = {
- "Accepts" => "application/xml",
- "Content-Type" => "application/xml"
+ 'Accepts' => 'application/xml',
+ 'Content-Type' => 'application/xml'
}
- SUCCESS = "true"
+ SUCCESS = 'true'
SENSITIVE_FIELDS = [:verification_str2, :expiry_date, :card_number]
- self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover]
- self.supported_countries = ["CA", "US"]
- self.default_currency = "USD"
- self.homepage_url = "http://www.firstdata.com"
- self.display_name = "FirstData Global Gateway e4"
+ BRANDS = {
+ :visa => 'Visa',
+ :master => 'Mastercard',
+ :american_express => 'American Express',
+ :jcb => 'JCB',
+ :discover => 'Discover'
+ }
+
+ E4_BRANDS = BRANDS.merge({:mastercard => 'Mastercard'})
+
+ DEFAULT_ECI = '07'
+
+ self.supported_cardtypes = BRANDS.keys
+ self.supported_countries = ['CA', 'US']
+ self.default_currency = 'USD'
+ self.homepage_url = 'http://www.firstdata.com'
+ self.display_name = 'FirstData Global Gateway e4'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ # Bank error codes: https://firstdata.zendesk.com/entries/471297-First-Data-Global-Gateway-e4-Bank-Response-Codes
+ '201' => STANDARD_ERROR_CODE[:incorrect_number],
+ '531' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '503' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '811' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '605' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '522' => STANDARD_ERROR_CODE[:expired_card],
+ '303' => STANDARD_ERROR_CODE[:card_declined],
+ '530' => STANDARD_ERROR_CODE[:card_declined],
+ '401' => STANDARD_ERROR_CODE[:call_issuer],
+ '402' => STANDARD_ERROR_CODE[:call_issuer],
+ '501' => STANDARD_ERROR_CODE[:pickup_card],
+ # Ecommerce error codes -- https://firstdata.zendesk.com/entries/451980-ecommerce-response-codes-etg-codes
+ '22' => STANDARD_ERROR_CODE[:invalid_number],
+ '25' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '31' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '44' => STANDARD_ERROR_CODE[:incorrect_zip],
+ '42' => STANDARD_ERROR_CODE[:processing_error]
+ }
# Create a new FirstdataE4Gateway
#
@@ -66,6 +100,10 @@ def refund(money, authorization, options = {})
commit(:credit, build_capture_or_credit_request(money, authorization, options))
end
+ def verify(credit_card, options = {})
+ commit(:verify, build_sale_or_authorization_request(0, credit_card, options))
+ end
+
# Tokenize a credit card with TransArmor
#
# The TransArmor token and other card data necessary for subsequent
@@ -93,13 +131,35 @@ def store(credit_card, options = {})
commit(:store, build_store_request(credit_card, options), credit_card)
end
+ def verify_credentials
+ response = void('0')
+ response.message != 'Unauthorized Request. Bad or missing credentials.'
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+())i, '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r((Card Number : ).*\d)i, '\1[FILTERED]')
+ end
+
+ def supports_network_tokenization?
+ true
+ end
+
private
def build_request(action, body)
xml = Builder::XmlMarkup.new
xml.instruct!
- xml.tag! "Transaction" do
+ xml.tag! 'Transaction', xmlns: 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes' do
add_credentials(xml)
add_transaction_type(xml, action)
xml << body
@@ -111,16 +171,18 @@ def build_request(action, body)
def build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)
xml = Builder::XmlMarkup.new
- add_amount(xml, money)
+ add_amount(xml, money, options)
if credit_card_or_store_authorization.is_a? String
- add_credit_card_token(xml, credit_card_or_store_authorization)
+ add_credit_card_token(xml, credit_card_or_store_authorization, options)
else
add_credit_card(xml, credit_card_or_store_authorization, options)
end
add_customer_data(xml, options)
add_invoice(xml, options)
+ add_tax_fields(xml, options)
+ add_level_3(xml, options)
xml.target!
end
@@ -129,8 +191,9 @@ def build_capture_or_credit_request(money, identification, options)
xml = Builder::XmlMarkup.new
add_identification(xml, identification)
- add_amount(xml, money)
+ add_amount(xml, money, options)
add_customer_data(xml, options)
+ add_card_authentication_data(xml, options)
xml.target!
end
@@ -145,32 +208,53 @@ def build_store_request(credit_card, options)
end
def add_credentials(xml)
- xml.tag! "ExactID", @options[:login]
- xml.tag! "Password", @options[:password]
+ xml.tag! 'ExactID', @options[:login]
+ xml.tag! 'Password', @options[:password]
end
def add_transaction_type(xml, action)
- xml.tag! "Transaction_Type", TRANSACTIONS[action]
+ xml.tag! 'Transaction_Type', TRANSACTIONS[action]
end
def add_identification(xml, identification)
- authorization_num, transaction_tag, _ = identification.split(";")
+ authorization_num, transaction_tag, _ = identification.split(';')
- xml.tag! "Authorization_Num", authorization_num
- xml.tag! "Transaction_Tag", transaction_tag
+ xml.tag! 'Authorization_Num', authorization_num
+ xml.tag! 'Transaction_Tag', transaction_tag
end
- def add_amount(xml, money)
- xml.tag! "DollarAmount", amount(money)
+ def add_amount(xml, money, options)
+ currency_code = options[:currency] || default_currency
+ xml.tag! 'DollarAmount', localized_amount(money, currency_code)
+ xml.tag! 'Currency', currency_code
end
def add_credit_card(xml, credit_card, options)
- xml.tag! "Card_Number", credit_card.number
- xml.tag! "Expiry_Date", expdate(credit_card)
- xml.tag! "CardHoldersName", credit_card.name
- xml.tag! "CardType", credit_card.brand
+ if credit_card.respond_to?(:track_data) && credit_card.track_data.present?
+ xml.tag! 'Track1', credit_card.track_data
+ xml.tag! 'Ecommerce_Flag', 'R'
+ else
+ xml.tag! 'Card_Number', credit_card.number
+ xml.tag! 'Expiry_Date', expdate(credit_card)
+ xml.tag! 'CardHoldersName', credit_card.name
+ xml.tag! 'CardType', card_type(credit_card.brand)
+
+ add_credit_card_eci(xml, credit_card, options)
+ add_credit_card_verification_strings(xml, credit_card, options)
+ end
+ end
+
+ def add_credit_card_eci(xml, credit_card, options)
+ eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover'
+ # Discover requires any Apple Pay transaction, regardless of in-app
+ # or web, and regardless of the ECI contained in the PKPaymentToken,
+ # to have an ECI value explicitly of 04.
+ '04'
+ else
+ (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
+ end
- add_credit_card_verification_strings(xml, credit_card, options)
+ xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci
end
def add_credit_card_verification_strings(xml, credit_card, options)
@@ -178,17 +262,40 @@ def add_credit_card_verification_strings(xml, credit_card, options)
if address
address_values = []
[:address1, :zip, :city, :state, :country].each { |part| address_values << address[part].to_s }
- xml.tag! "VerificationStr1", address_values.join("|")
+ xml.tag! 'VerificationStr1', address_values.join('|')
end
- if credit_card.verification_value?
- xml.tag! "CVD_Presence_Ind", "1"
- xml.tag! "VerificationStr2", credit_card.verification_value
+ if credit_card.is_a?(NetworkTokenizationCreditCard)
+ add_network_tokenization_credit_card(xml, credit_card)
+ else
+ if credit_card.verification_value?
+ xml.tag! 'CVD_Presence_Ind', '1'
+ xml.tag! 'VerificationStr2', credit_card.verification_value
+ end
+
+ add_card_authentication_data(xml, options)
end
end
- def add_credit_card_token(xml, store_authorization)
- params = store_authorization.split(";")
+ def add_network_tokenization_credit_card(xml, credit_card)
+ case card_brand(credit_card).to_sym
+ when :american_express
+ cryptogram = Base64.decode64(credit_card.payment_cryptogram)
+ xml.tag!('XID', Base64.encode64(cryptogram[20...40]))
+ xml.tag!('CAVV', Base64.encode64(cryptogram[0...20]))
+ else
+ xml.tag!('XID', credit_card.transaction_id) if credit_card.transaction_id
+ xml.tag!('CAVV', credit_card.payment_cryptogram)
+ end
+ end
+
+ def add_card_authentication_data(xml, options)
+ xml.tag! 'CAVV', options[:cavv]
+ xml.tag! 'XID', options[:xid]
+ end
+
+ def add_credit_card_token(xml, store_authorization, options)
+ params = store_authorization.split(';')
credit_card = CreditCard.new(
:brand => params[1],
:first_name => params[2],
@@ -196,33 +303,47 @@ def add_credit_card_token(xml, store_authorization)
:month => params[4],
:year => params[5])
- xml.tag! "TransarmorToken", params[0]
- xml.tag! "Expiry_Date", expdate(credit_card)
- xml.tag! "CardHoldersName", credit_card.name
- xml.tag! "CardType", credit_card.brand
+ xml.tag! 'TransarmorToken', params[0]
+ xml.tag! 'Expiry_Date', expdate(credit_card)
+ xml.tag! 'CardHoldersName', credit_card.name
+ xml.tag! 'CardType', card_type(credit_card.brand)
+ add_card_authentication_data(xml, options)
end
def add_customer_data(xml, options)
- xml.tag! "Customer_Ref", options[:customer] if options[:customer]
- xml.tag! "Client_IP", options[:ip] if options[:ip]
- xml.tag! "Client_Email", options[:email] if options[:email]
+ xml.tag! 'Customer_Ref', options[:customer] if options[:customer]
+ xml.tag! 'Client_IP', options[:ip] if options[:ip]
+ xml.tag! 'Client_Email', options[:email] if options[:email]
end
def add_address(xml, options)
if address = (options[:billing_address] || options[:address])
- xml.tag! "ZipCode", address[:zip]
+ xml.tag! 'ZipCode', address[:zip]
end
end
def add_invoice(xml, options)
- xml.tag! "Reference_No", options[:order_id]
- xml.tag! "Reference_3", options[:description] if options[:description]
+ xml.tag! 'Reference_No', options[:order_id]
+ xml.tag! 'Reference_3', options[:description] if options[:description]
+ end
+
+ def add_tax_fields(xml, options)
+ xml.tag! 'Tax1Amount', options[:tax1_amount] if options[:tax1_amount]
+ xml.tag! 'Tax1Number', options[:tax1_number] if options[:tax1_number]
+ end
+
+ def add_level_3(xml, options)
+ xml.tag!('Level3') { |x| x << options[:level_3] } if options[:level_3]
end
def expdate(credit_card)
"#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
end
+ def card_type(credit_card_brand)
+ E4_BRANDS[credit_card_brand.to_sym] if credit_card_brand
+ end
+
def commit(action, request, credit_card = nil)
url = (test? ? self.test_url : self.live_url)
begin
@@ -233,9 +354,10 @@ def commit(action, request, credit_card = nil)
Response.new(successful?(response), message_from(response), response,
:test => test?,
- :authorization => response_authorization(action, response, credit_card),
+ :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '',
:avs_result => {:code => response[:avs]},
- :cvv_result => response[:cvv2]
+ :cvv_result => response[:cvv2],
+ :error_code => standard_error_code(response)
)
end
@@ -256,10 +378,10 @@ def authorization_from(response)
[
response[:authorization_num],
response[:transaction_tag],
- (response[:dollar_amount].to_f * 100).to_i
- ].join(";")
+ (response[:dollar_amount].to_f * 100).round
+ ].join(';')
else
- ""
+ ''
end
end
@@ -272,7 +394,7 @@ def store_authorization_from(response, credit_card)
credit_card.last_name,
credit_card.month,
credit_card.year
- ].map { |value| value.to_s.gsub(/;/, "") }.join(";")
+ ].map { |value| value.to_s.gsub(/;/, '') }.join(';')
else
raise StandardError, "TransArmor support is not enabled on your #{display_name} account"
end
@@ -280,16 +402,16 @@ def store_authorization_from(response, credit_card)
def money_from_authorization(auth)
_, _, amount = auth.split(/;/, 3)
- amount.to_i # return the # of cents, no need to divide
+ amount.to_i
end
def message_from(response)
if(response[:faultcode] && response[:faultstring])
response[:faultstring]
- elsif(response[:error_number] && response[:error_number] != "0")
+ elsif(response[:error_number] && response[:error_number] != '0')
response[:error_description]
else
- result = (response[:exact_message] || "")
+ result = (response[:exact_message] || '')
result << " - #{response[:bank_message]}" if response[:bank_message].present?
result
end
@@ -297,29 +419,33 @@ def message_from(response)
def parse_error(error)
{
- :transaction_approved => "false",
+ :transaction_approved => 'false',
:error_number => error.code,
- :error_description => error.body
+ :error_description => error.body,
+ :ecommerce_error_code => error.body.gsub(/[^\d]/, '')
}
end
+ def standard_error_code(response)
+ STANDARD_ERROR_CODE_MAPPING[response[:bank_resp_code] || response[:ecommerce_error_code]]
+ end
+
def parse(xml)
response = {}
xml = REXML::Document.new(xml)
- if root = REXML::XPath.first(xml, "//TransactionResult")
+ if root = REXML::XPath.first(xml, '//TransactionResult')
parse_elements(response, root)
end
- response.delete_if{ |k,v| SENSITIVE_FIELDS.include?(k) }
+ response.delete_if { |k, v| SENSITIVE_FIELDS.include?(k) }
end
def parse_elements(response, root)
root.elements.to_a.each do |node|
- response[node.name.gsub(/EXact/, "Exact").underscore.to_sym] = (node.text || "").strip
+ response[node.name.gsub(/EXact/, 'Exact').underscore.to_sym] = (node.text || '').strip
end
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
new file mode 100644
index 00000000000..4124139a7ca
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/firstdata_e4_v27.rb
@@ -0,0 +1,485 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class FirstdataE4V27Gateway < Gateway
+ self.test_url = 'https://api.demo.globalgatewaye4.firstdata.com/transaction/v28'
+ self.live_url = 'https://api.globalgatewaye4.firstdata.com/transaction/v28'
+
+ TRANSACTIONS = {
+ sale: '00',
+ authorization: '01',
+ verify: '05',
+ capture: '32',
+ void: '33',
+ credit: '34',
+ store: '05'
+ }
+
+ SUCCESS = 'true'
+
+ SENSITIVE_FIELDS = [:cvdcode, :expiry_date, :card_number]
+
+ BRANDS = {
+ :visa => 'Visa',
+ :master => 'Mastercard',
+ :american_express => 'American Express',
+ :jcb => 'JCB',
+ :discover => 'Discover'
+ }
+
+ DEFAULT_ECI = '07'
+
+ self.supported_cardtypes = BRANDS.keys
+ self.supported_countries = ['CA', 'US']
+ self.default_currency = 'USD'
+ self.homepage_url = 'http://www.firstdata.com'
+ self.display_name = 'FirstData Global Gateway e4 v27'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ # Bank error codes: https://support.payeezy.com/hc/en-us/articles/203730509-First-Data-Global-Gateway-e4-Bank-Response-Codes
+ '201' => STANDARD_ERROR_CODE[:incorrect_number],
+ '531' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '503' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '811' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '605' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '522' => STANDARD_ERROR_CODE[:expired_card],
+ '303' => STANDARD_ERROR_CODE[:card_declined],
+ '530' => STANDARD_ERROR_CODE[:card_declined],
+ '401' => STANDARD_ERROR_CODE[:call_issuer],
+ '402' => STANDARD_ERROR_CODE[:call_issuer],
+ '501' => STANDARD_ERROR_CODE[:pickup_card],
+ # Ecommerce error codes: https://support.payeezy.com/hc/en-us/articles/203730499-eCommerce-Response-Codes-ETG-e4-Transaction-Gateway-Codes
+ '22' => STANDARD_ERROR_CODE[:invalid_number],
+ '25' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '31' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '44' => STANDARD_ERROR_CODE[:incorrect_zip],
+ '42' => STANDARD_ERROR_CODE[:processing_error]
+ }
+
+ def initialize(options = {})
+ requires!(options, :login, :password, :key_id, :hmac_key)
+ @options = options
+
+ super
+ end
+
+ def authorize(money, credit_card_or_store_authorization, options = {})
+ commit(:authorization, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options))
+ end
+
+ def purchase(money, credit_card_or_store_authorization, options = {})
+ commit(:sale, build_sale_or_authorization_request(money, credit_card_or_store_authorization, options))
+ end
+
+ def capture(money, authorization, options = {})
+ commit(:capture, build_capture_or_credit_request(money, authorization, options))
+ end
+
+ def void(authorization, options = {})
+ commit(:void, build_capture_or_credit_request(money_from_authorization(authorization), authorization, options))
+ end
+
+ def refund(money, authorization, options = {})
+ commit(:credit, build_capture_or_credit_request(money, authorization, options))
+ end
+
+ def verify(credit_card, options = {})
+ commit(:verify, build_sale_or_authorization_request(0, credit_card, options))
+ end
+
+ # Tokenize a credit card with TransArmor
+ #
+ # The TransArmor token and other card data necessary for subsequent
+ # transactions is stored in the response's +authorization+ attribute.
+ # The authorization string may be passed to +authorize+ and +purchase+
+ # instead of a +ActiveMerchant::Billing::CreditCard+ instance.
+ #
+ # TransArmor support must be explicitly activated on your gateway
+ # account by FirstData. If your authorization string is empty, contact
+ # FirstData support for account setup assistance.
+ #
+ # https://support.payeezy.com/hc/en-us/articles/203731189-TransArmor-Tokenization
+ def store(credit_card, options = {})
+ commit(:store, build_store_request(credit_card, options), credit_card)
+ end
+
+ def verify_credentials
+ response = void('0')
+ response.message != 'Unauthorized Request. Bad or missing credentials.'
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+())i, '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r((CARD NUMBER\s+: )#+\d+), '\1[FILTERED]')
+ end
+
+ def supports_network_tokenization?
+ true
+ end
+
+ private
+
+ def build_request(action, body)
+ xml = Builder::XmlMarkup.new
+
+ xml.instruct!
+ xml.tag! 'Transaction', xmlns: 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/encodedTypes' do
+ add_credentials(xml)
+ add_transaction_type(xml, action)
+ xml << body
+ end
+
+ xml.target!
+ end
+
+ def build_sale_or_authorization_request(money, credit_card_or_store_authorization, options)
+ xml = Builder::XmlMarkup.new
+
+ add_amount(xml, money, options)
+
+ if credit_card_or_store_authorization.is_a? String
+ add_credit_card_token(xml, credit_card_or_store_authorization, options)
+ else
+ add_credit_card(xml, credit_card_or_store_authorization, options)
+ add_stored_credentials(xml, credit_card_or_store_authorization, options)
+ end
+
+ add_address(xml, options)
+ add_customer_data(xml, options)
+ add_invoice(xml, options)
+ add_tax_fields(xml, options)
+ add_level_3(xml, options)
+
+ xml.target!
+ end
+
+ def build_capture_or_credit_request(money, identification, options)
+ xml = Builder::XmlMarkup.new
+
+ add_identification(xml, identification)
+ add_amount(xml, money, options)
+ add_customer_data(xml, options)
+ add_card_authentication_data(xml, options)
+
+ xml.target!
+ end
+
+ def build_store_request(credit_card, options)
+ xml = Builder::XmlMarkup.new
+
+ add_credit_card(xml, credit_card, options)
+ add_address(xml, options)
+ add_customer_data(xml, options)
+
+ xml.target!
+ end
+
+ def add_credentials(xml)
+ xml.tag! 'ExactID', @options[:login]
+ xml.tag! 'Password', @options[:password]
+ end
+
+ def add_transaction_type(xml, action)
+ xml.tag! 'Transaction_Type', TRANSACTIONS[action]
+ end
+
+ def add_identification(xml, identification)
+ authorization_num, transaction_tag, _ = identification.split(';')
+
+ xml.tag! 'Authorization_Num', authorization_num
+ xml.tag! 'Transaction_Tag', transaction_tag
+ end
+
+ def add_amount(xml, money, options)
+ currency_code = options[:currency] || default_currency
+ xml.tag! 'DollarAmount', localized_amount(money, currency_code)
+ xml.tag! 'Currency', currency_code
+ end
+
+ def add_credit_card(xml, credit_card, options)
+ if credit_card.respond_to?(:track_data) && credit_card.track_data.present?
+ xml.tag! 'Track1', credit_card.track_data
+ xml.tag! 'Ecommerce_Flag', 'R'
+ else
+ xml.tag! 'Card_Number', credit_card.number
+ xml.tag! 'Expiry_Date', expdate(credit_card)
+ xml.tag! 'CardHoldersName', credit_card.name
+ xml.tag! 'CardType', card_type(credit_card.brand)
+ xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id]
+
+ add_credit_card_eci(xml, credit_card, options)
+ add_credit_card_verification_strings(xml, credit_card, options)
+ end
+ end
+
+ def add_credit_card_eci(xml, credit_card, options)
+ eci = if credit_card.is_a?(NetworkTokenizationCreditCard) && credit_card.source == :apple_pay && card_brand(credit_card) == 'discover'
+ # Discover requires any Apple Pay transaction, regardless of in-app
+ # or web, and regardless of the ECI contained in the PKPaymentToken,
+ # to have an ECI value explicitly of 04.
+ '04'
+ else
+ (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
+ end
+
+ xml.tag! 'Ecommerce_Flag', eci.to_s =~ /^[0-9]+$/ ? eci.to_s.rjust(2, '0') : eci
+ end
+
+ def add_credit_card_verification_strings(xml, credit_card, options)
+ if credit_card.is_a?(NetworkTokenizationCreditCard)
+ add_network_tokenization_credit_card(xml, credit_card)
+ else
+ if credit_card.verification_value?
+ xml.tag! 'CVD_Presence_Ind', '1'
+ xml.tag! 'CVDCode', credit_card.verification_value
+ end
+
+ add_card_authentication_data(xml, options)
+ end
+ end
+
+ def add_network_tokenization_credit_card(xml, credit_card)
+ case card_brand(credit_card).to_sym
+ when :american_express
+ cryptogram = Base64.decode64(credit_card.payment_cryptogram)
+ xml.tag!('XID', Base64.encode64(cryptogram[20...40]))
+ xml.tag!('CAVV', Base64.encode64(cryptogram[0...20]))
+ else
+ xml.tag!('XID', credit_card.transaction_id) if credit_card.transaction_id
+ xml.tag!('CAVV', credit_card.payment_cryptogram)
+ end
+ end
+
+ def add_card_authentication_data(xml, options)
+ xml.tag! 'CAVV', options[:cavv]
+ xml.tag! 'XID', options[:xid]
+ end
+
+ def add_credit_card_token(xml, store_authorization, options)
+ params = store_authorization.split(';')
+ credit_card = CreditCard.new(
+ :brand => params[1],
+ :first_name => params[2],
+ :last_name => params[3],
+ :month => params[4],
+ :year => params[5])
+
+ xml.tag! 'TransarmorToken', params[0]
+ xml.tag! 'Expiry_Date', expdate(credit_card)
+ xml.tag! 'CardHoldersName', credit_card.name
+ xml.tag! 'CardType', card_type(credit_card.brand)
+ xml.tag! 'WalletProviderID', options[:wallet_provider_id] if options[:wallet_provider_id]
+ add_card_authentication_data(xml, options)
+ end
+
+ def add_customer_data(xml, options)
+ xml.tag! 'Customer_Ref', options[:customer] if options[:customer]
+ xml.tag! 'Client_IP', options[:ip] if options[:ip]
+ xml.tag! 'Client_Email', options[:email] if options[:email]
+ end
+
+ def add_address(xml, options)
+ if (address = options[:billing_address] || options[:address])
+ xml.tag! 'Address' do
+ xml.tag! 'Address1', address[:address1]
+ xml.tag! 'Address2', address[:address2] if address[:address2]
+ xml.tag! 'City', address[:city]
+ xml.tag! 'State', address[:state]
+ xml.tag! 'Zip', address[:zip]
+ xml.tag! 'CountryCode', address[:country]
+ end
+ xml.tag! 'ZipCode', address[:zip]
+ end
+ end
+
+ def add_invoice(xml, options)
+ xml.tag! 'Reference_No', options[:order_id]
+ xml.tag! 'Reference_3', options[:description] if options[:description]
+ end
+
+ def add_tax_fields(xml, options)
+ xml.tag! 'Tax1Amount', options[:tax1_amount] if options[:tax1_amount]
+ xml.tag! 'Tax1Number', options[:tax1_number] if options[:tax1_number]
+ end
+
+ def add_level_3(xml, options)
+ xml.tag!('Level3') { |x| x << options[:level_3] } if options[:level_3]
+ end
+
+ def add_stored_credentials(xml, card, options)
+ return unless options[:stored_credential]
+ xml.tag! 'StoredCredentials' do
+ xml.tag! 'Indicator', stored_credential_indicator(xml, card, options)
+ if initiator = options.dig(:stored_credential, :initiator)
+ xml.tag! initiator == 'merchant' ? 'M' : 'C'
+ end
+ if reason_type = options.dig(:stored_credential, :reason_type)
+ xml.tag! 'Schedule', reason_type == 'unscheduled' ? 'U' : 'S'
+ end
+ xml.tag! 'AuthorizationTypeOverride', options[:authorization_type_override] if options[:authorization_type_override]
+ if network_transaction_id = options[:stored_credential][:network_transaction_id]
+ xml.tag! 'TransactionId', network_transaction_id
+ else
+ xml.tag! 'TransactionId', 'new'
+ end
+ xml.tag! 'OriginalAmount', options[:original_amount] if options[:original_amount]
+ xml.tag! 'ProtectbuyIndicator', options[:protectbuy_indicator] if options[:protectbuy_indicator]
+ end
+ end
+
+ def stored_credential_indicator(xml, card, options)
+ if card.brand == 'master' || options.dig(:stored_credential, :initial_transaction) == false
+ 'S'
+ else
+ '1'
+ end
+ end
+
+ def expdate(credit_card)
+ "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
+ end
+
+ def card_type(credit_card_brand)
+ BRANDS[credit_card_brand.to_sym] if credit_card_brand
+ end
+
+ def commit(action, data, credit_card = nil)
+ url = (test? ? self.test_url : self.live_url)
+ request = build_request(action, data)
+ begin
+ response = parse(ssl_post(url, request, headers('POST', url, request)))
+ rescue ResponseError => e
+ response = parse_error(e.response)
+ end
+
+ Response.new(successful?(response), message_from(response), response,
+ :test => test?,
+ :authorization => successful?(response) ? response_authorization(action, response, credit_card) : '',
+ :avs_result => {:code => response[:avs]},
+ :cvv_result => response[:cvv2],
+ :error_code => standard_error_code(response)
+ )
+ end
+
+ def headers(method, url, request)
+ content_type = 'application/xml'
+ content_digest = Digest::SHA1.hexdigest(request)
+ sending_time = Time.now.utc.iso8601
+ payload = [method, content_type, content_digest, sending_time, url.split('.com')[1]].join("\n")
+ hmac = OpenSSL::HMAC.digest('sha1', @options[:hmac_key], payload)
+ encoded = Base64.strict_encode64(hmac)
+
+ {
+ 'x-gge4-date' => sending_time,
+ 'x-gge4-content-sha1' => content_digest,
+ 'Authorization' => 'GGE4_API ' + @options[:key_id].to_s + ':' + encoded,
+ 'Accepts' => content_type,
+ 'Content-Type' => content_type
+ }
+ end
+
+ def successful?(response)
+ response[:transaction_approved] == SUCCESS
+ end
+
+ def response_authorization(action, response, credit_card)
+ if action == :store
+ store_authorization_from(response, credit_card)
+ else
+ authorization_from(response)
+ end
+ end
+
+ def authorization_from(response)
+ if response[:authorization_num] && response[:transaction_tag]
+ [
+ response[:authorization_num],
+ response[:transaction_tag],
+ (response[:dollar_amount].to_f * 100).round
+ ].join(';')
+ else
+ ''
+ end
+ end
+
+ def store_authorization_from(response, credit_card)
+ if response[:transarmor_token].present?
+ [
+ response[:transarmor_token],
+ credit_card.brand,
+ credit_card.first_name,
+ credit_card.last_name,
+ credit_card.month,
+ credit_card.year
+ ].map { |value| value.to_s.tr(';', '') }.join(';')
+ else
+ raise StandardError, "TransArmor support is not enabled on your #{display_name} account"
+ end
+ end
+
+ def money_from_authorization(auth)
+ _, _, amount = auth.split(/;/, 3)
+ amount.to_i
+ end
+
+ def message_from(response)
+ if response[:faultcode] && response[:faultstring]
+ response[:faultstring]
+ elsif response[:error_number] && response[:error_number] != '0'
+ response[:error_description]
+ else
+ result = (response[:exact_message] || '')
+ result << " - #{response[:bank_message]}" if response[:bank_message].present?
+ result
+ end
+ end
+
+ def parse_error(error)
+ {
+ :transaction_approved => 'false',
+ :error_number => error.code,
+ :error_description => error.body,
+ :ecommerce_error_code => error.body.gsub(/[^\d]/, '')
+ }
+ end
+
+ def standard_error_code(response)
+ STANDARD_ERROR_CODE_MAPPING[response[:bank_resp_code] || response[:ecommerce_error_code]]
+ end
+
+ def parse(xml)
+ response = {}
+ xml = REXML::Document.new(xml)
+
+ if (root = REXML::XPath.first(xml, '//TransactionResult'))
+ parse_elements(response, root)
+ end
+
+ SENSITIVE_FIELDS.each { |key| response.delete(key) }
+ response
+ end
+
+ def parse_elements(response, root)
+ root.elements.to_a.each do |node|
+ if node.has_elements?
+ parse_elements(response, node)
+ else
+ response[name_node(root, node)] = (node.text || '').strip
+ end
+ end
+ end
+
+ def name_node(root, node)
+ parent = root.name unless root.name == 'TransactionResult'
+ "#{parent}#{node.name}".gsub(/EXact/, 'Exact').underscore.to_sym
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/flo2cash.rb b/lib/active_merchant/billing/gateways/flo2cash.rb
new file mode 100644
index 00000000000..1f5c9d8076b
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/flo2cash.rb
@@ -0,0 +1,215 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class Flo2cashGateway < Gateway
+ self.display_name = 'Flo2Cash'
+ self.homepage_url = 'http://www.flo2cash.co.nz/'
+
+ self.test_url = 'https://demo.flo2cash.co.nz/ws/paymentws.asmx'
+ self.live_url = 'https://secure.flo2cash.co.nz/ws/paymentws.asmx'
+
+ self.supported_countries = ['NZ']
+ self.default_currency = 'NZD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
+
+ BRAND_MAP = {
+ 'visa' => 'VISA',
+ 'master' => 'MC',
+ 'american_express' => 'AMEX',
+ 'diners_club' => 'DINERS'
+ }
+
+ def initialize(options={})
+ requires!(options, :username, :password, :account_id)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ MultiResponse.run do |r|
+ r.process { authorize(amount, payment_method, options) }
+ r.process { capture(amount, r.authorization, options) }
+ end
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit('ProcessAuthorise', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit('ProcessCapture', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit('ProcessRefund', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") }
+ CURRENCY_CODES['NZD'] = '554'
+
+ def add_invoice(post, money, options)
+ post[:Amount] = amount(money)
+ post[:Reference] = options[:order_id]
+ post[:Particular] = options[:description]
+ end
+
+ def add_payment_method(post, payment_method)
+ post[:CardNumber] = payment_method.number
+ post[:CardType] = BRAND_MAP[payment_method.brand.to_s]
+ post[:CardExpiry] = format(payment_method.month, :two_digits) + format(payment_method.year, :two_digits)
+ post[:CardHolderName] = payment_method.name
+ post[:CardCSC] = payment_method.verification_value
+ end
+
+ def add_customer_data(post, options)
+ if(billing_address = (options[:billing_address] || options[:address]))
+ post[:Email] = billing_address[:email]
+ end
+ end
+
+ def add_reference(post, authorization)
+ post[:OriginalTransactionId] = authorization
+ end
+
+ def commit(action, post)
+ post[:Username] = @options[:username]
+ post[:Password] = @options[:password]
+ post[:AccountId] = @options[:account_id]
+
+ data = build_request(action, post)
+ begin
+ raw = parse(ssl_post(url, data, headers(action)), action)
+ rescue ActiveMerchant::ResponseError => e
+ if(e.response.code == '500' && e.response.body.start_with?(' authorization_from(action, raw[:transaction_id], post[:OriginalTransactionId]),
+ :error_code => error_code_from(succeeded, raw),
+ :test => test?
+ )
+ end
+
+ def headers(action)
+ {
+ 'Content-Type' => 'application/soap+xml; charset=utf-8',
+ 'SOAPAction' => %{"http://www.flo2cash.co.nz/webservices/paymentwebservice/#{action}"}
+ }
+ end
+
+ def build_request(action, post)
+ xml = Builder::XmlMarkup.new :indent => 2
+ post.each do |field, value|
+ xml.tag!(field, value)
+ end
+ body = xml.target!
+ envelope_wrap(action, body)
+ end
+
+ def envelope_wrap(action, body)
+ <<-EOS
+
+
+
+ <#{action} xmlns="http://www.flo2cash.co.nz/webservices/paymentwebservice">
+ #{body}
+ #{action}>
+
+
+ EOS
+ end
+
+ def url
+ (test? ? test_url : live_url)
+ end
+
+ def parse(body, action)
+ response = {}
+ xml = REXML::Document.new(body)
+ root = (REXML::XPath.first(xml, "//#{action}Response") || REXML::XPath.first(xml, '//detail'))
+
+ root.elements.to_a.each do |node|
+ parse_element(response, node)
+ end if root
+
+ response
+ end
+
+ def parse_element(response, node)
+ if node.has_elements?
+ node.elements.each { |element| parse_element(response, element) }
+ else
+ response[node.name.underscore.to_sym] = node.text
+ end
+ end
+
+ def success_from(response)
+ response == 'SUCCESSFUL'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response[:message] || response[:errormessage] || 'Unable to read error message'
+ end
+ end
+
+ def authorization_from(action, current, original)
+ # Refunds require the authorization from the authorize() of the MultiResponse.
+ if action == 'ProcessCapture'
+ original
+ else
+ current
+ end
+ end
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ 'Transaction Declined - Expired Card' => STANDARD_ERROR_CODE[:expired_card],
+ 'Bank Declined Transaction' => STANDARD_ERROR_CODE[:card_declined],
+ 'Insufficient Funds' => STANDARD_ERROR_CODE[:card_declined],
+ 'Transaction Declined - Bank Error' => STANDARD_ERROR_CODE[:processing_error],
+ 'No Reply from Bank' => STANDARD_ERROR_CODE[:processing_error],
+ }
+
+ def error_code_from(succeeded, response)
+ succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response[:message]]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/flo2cash_simple.rb b/lib/active_merchant/billing/gateways/flo2cash_simple.rb
new file mode 100644
index 00000000000..f0662ff463c
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/flo2cash_simple.rb
@@ -0,0 +1,20 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class Flo2cashSimpleGateway < Flo2cashGateway
+ self.display_name = 'Flo2Cash Simple'
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit('ProcessPurchase', post)
+ end
+
+ # Flo2Cash's "simple mode" does not support auth/capture
+ undef_method :authorize
+ undef_method :capture
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/forte.rb b/lib/active_merchant/billing/gateways/forte.rb
new file mode 100644
index 00000000000..bdc06c7a3f5
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/forte.rb
@@ -0,0 +1,270 @@
+require 'json'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class ForteGateway < Gateway
+ include Empty
+
+ self.test_url = 'https://sandbox.forte.net/api/v2'
+ self.live_url = 'https://api.forte.net/v2'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'https://www.forte.net'
+ self.display_name = 'Forte'
+
+ def initialize(options={})
+ requires!(options, :api_key, :secret, :location_id, :account_id)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ post = {}
+ add_amount(post, money, options)
+ add_invoice(post, options)
+ add_payment_method(post, payment_method)
+ add_billing_address(post, payment_method, options)
+ add_shipping_address(post, options)
+ post[:action] = 'sale'
+
+ commit(:post, post)
+ end
+
+ def authorize(money, payment_method, options={})
+ post = {}
+ add_amount(post, money, options)
+ add_invoice(post, options)
+ add_payment_method(post, payment_method)
+ add_billing_address(post, payment_method, options)
+ add_shipping_address(post, options)
+ post[:action] = 'authorize'
+
+ commit(:post, post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ post[:transaction_id] = transaction_id_from(authorization)
+ post[:authorization_code] = authorization_code_from(authorization) || ''
+ post[:action] = 'capture'
+
+ commit(:put, post)
+ end
+
+ def credit(money, payment_method, options={})
+ post = {}
+ add_amount(post, money, options)
+ add_invoice(post, options)
+ add_payment_method(post, payment_method)
+ add_billing_address(post, payment_method, options)
+ post[:action] = 'disburse'
+
+ commit(:post, post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_amount(post, money, options)
+ post[:original_transaction_id] = transaction_id_from(authorization)
+ post[:authorization_code] = authorization_code_from(authorization)
+ post[:action] = 'reverse'
+
+ commit(:post, post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ post[:transaction_id] = transaction_id_from(authorization)
+ post[:authorization_code] = authorization_code_from(authorization)
+ post[:action] = 'void'
+
+ commit(:put, 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((account_number)\W+\d+), '\1[FILTERED]').
+ gsub(%r((card_verification_value)\W+\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_auth(post)
+ post[:account_id] = "act_#{@options[:account_id]}"
+ post[:location_id] = "loc_#{@options[:location_id]}"
+ end
+
+ def add_invoice(post, options)
+ post[:order_number] = options[:order_id]
+ end
+
+ def add_amount(post, money, options)
+ post[:authorization_amount] = amount(money)
+ end
+
+ def add_billing_address(post, payment, options)
+ post[:billing_address] = {}
+ if address = options[:billing_address] || options[:address]
+ first_name, last_name = split_names(address[:name])
+ post[:billing_address][:first_name] = first_name if first_name
+ post[:billing_address][:last_name] = last_name if last_name
+ post[:billing_address][:physical_address] = {}
+ post[:billing_address][:physical_address][:street_line1] = address[:address1] if address[:address1]
+ post[:billing_address][:physical_address][:street_line2] = address[:address2] if address[:address2]
+ post[:billing_address][:physical_address][:postal_code] = address[:zip] if address[:zip]
+ post[:billing_address][:physical_address][:region] = address[:state] if address[:state]
+ post[:billing_address][:physical_address][:locality] = address[:city] if address[:city]
+ end
+
+ if empty?(post[:billing_address][:first_name]) && payment.first_name
+ post[:billing_address][:first_name] = payment.first_name
+ end
+
+ if empty?(post[:billing_address][:last_name]) && payment.last_name
+ post[:billing_address][:last_name] = payment.last_name
+ end
+ end
+
+ def add_shipping_address(post, options)
+ return unless options[:shipping_address]
+ address = options[:shipping_address]
+
+ post[:shipping_address] = {}
+ first_name, last_name = split_names(address[:name])
+ post[:shipping_address][:first_name] = first_name if first_name
+ post[:shipping_address][:last_name] = last_name if last_name
+ post[:shipping_address][:physical_address][:street_line1] = address[:address1] if address[:address1]
+ post[:shipping_address][:physical_address][:street_line2] = address[:address2] if address[:address2]
+ post[:shipping_address][:physical_address][:postal_code] = address[:zip] if address[:zip]
+ post[:shipping_address][:physical_address][:region] = address[:state] if address[:state]
+ post[:shipping_address][:physical_address][:locality] = address[:city] if address[:city]
+ end
+
+ def add_payment_method(post, payment_method)
+ if payment_method.respond_to?(:brand)
+ add_credit_card(post, payment_method)
+ else
+ add_echeck(post, payment_method)
+ end
+ end
+
+ def add_echeck(post, payment)
+ post[:echeck] = {}
+ post[:echeck][:account_holder] = payment.name
+ post[:echeck][:account_number] = payment.account_number
+ post[:echeck][:routing_number] = payment.routing_number
+ post[:echeck][:account_type] = payment.account_type
+ post[:echeck][:check_number] = payment.number
+ end
+
+ def add_credit_card(post, payment)
+ post[:card] = {}
+ post[:card][:card_type] = format_card_brand(payment.brand)
+ post[:card][:name_on_card] = payment.name
+ post[:card][:account_number] = payment.number
+ post[:card][:expire_month] = payment.month
+ post[:card][:expire_year] = payment.year
+ post[:card][:card_verification_value] = payment.verification_value
+ end
+
+ def commit(type, parameters)
+ add_auth(parameters)
+
+ url = (test? ? test_url : live_url)
+ response = parse(handle_resp(raw_ssl_request(type, url + endpoint, parameters.to_json, headers)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, parameters),
+ avs_result: AVSResult.new(code: response['response']['avs_result']),
+ cvv_result: CVVResult.new(response['response']['cvv_code']),
+ test: test?
+ )
+ end
+
+ def handle_resp(response)
+ case response.code.to_i
+ when 200..499
+ response.body
+ else
+ raise ResponseError.new(response)
+ end
+ end
+
+ def parse(response_body)
+ JSON.parse(response_body)
+ end
+
+ def success_from(response)
+ response['response']['response_code'] == 'A01'
+ end
+
+ def message_from(response)
+ response['response']['response_desc']
+ end
+
+ def authorization_from(response, parameters)
+ if parameters[:action] == 'capture'
+ [response['transaction_id'], response.dig('response', 'authorization_code'), parameters[:transaction_id], parameters[:authorization_code]].join('#')
+ else
+ [response['transaction_id'], response.dig('response', 'authorization_code')].join('#')
+ end
+ end
+
+ def endpoint
+ "/accounts/act_#{@options[:account_id].strip}/locations/loc_#{@options[:location_id].strip}/transactions/"
+ end
+
+ def headers
+ {
+ 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_key]}:#{@options[:secret]}")),
+ 'X-Forte-Auth-Account-Id' => "act_#{@options[:account_id]}",
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def format_card_brand(card_brand)
+ case card_brand
+ when 'visa'
+ return 'visa'
+ when 'master'
+ return 'mast'
+ when 'american_express'
+ return 'amex'
+ when 'discover'
+ return 'disc'
+ end
+ end
+
+ def split_authorization(authorization)
+ authorization.split('#')
+ end
+
+ def authorization_code_from(authorization)
+ _, authorization_code, _, original_auth_authorization_code = split_authorization(authorization)
+ original_auth_authorization_code.present? ? original_auth_authorization_code : authorization_code
+ end
+
+ def transaction_id_from(authorization)
+ transaction_id, _, original_auth_transaction_id, _= split_authorization(authorization)
+ original_auth_transaction_id.present? ? original_auth_transaction_id : transaction_id
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/garanti.rb b/lib/active_merchant/billing/gateways/garanti.rb
index c6de668a19c..3c7f19efc5d 100644
--- a/lib/active_merchant/billing/gateways/garanti.rb
+++ b/lib/active_merchant/billing/gateways/garanti.rb
@@ -1,10 +1,11 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class GarantiGateway < Gateway
- self.live_url = self.test_url = 'https://sanalposprov.garanti.com.tr/VPServlet'
+ self.live_url = 'https://sanalposprov.garanti.com.tr/VPServlet'
+ self.test_url = 'https://sanalposprovtest.garanti.com.tr/VPServlet'
# The countries the gateway supports merchants from as 2 digit ISO country codes
- self.supported_countries = ['US','TR']
+ self.supported_countries = ['US', 'TR']
# The card types supported by the payment gateway
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
@@ -29,31 +30,30 @@ class GarantiGateway < Gateway
'JPY' => 392
}
-
def initialize(options = {})
requires!(options, :login, :password, :terminal_id, :merchant_id)
super
end
def purchase(money, credit_card, options = {})
- options = options.merge(:gvp_order_type => "sales")
+ options = options.merge(:gvp_order_type => 'sales')
commit(money, build_sale_request(money, credit_card, options))
end
def authorize(money, credit_card, options = {})
- options = options.merge(:gvp_order_type => "preauth")
+ options = options.merge(:gvp_order_type => 'preauth')
commit(money, build_authorize_request(money, credit_card, options))
end
def capture(money, ref_id, options = {})
- options = options.merge(:gvp_order_type => "postauth")
+ options = options.merge(:gvp_order_type => 'postauth')
commit(money, build_capture_request(money, ref_id, options))
end
private
def security_data
- rjusted_terminal_id = @options[:terminal_id].to_s.rjust(9, "0")
+ rjusted_terminal_id = @options[:terminal_id].to_s.rjust(9, '0')
Digest::SHA1.hexdigest(@options[:password].to_s + rjusted_terminal_id).upcase
end
@@ -67,7 +67,7 @@ def build_xml_request(money, credit_card, options, &block)
hash_data = generate_hash_data(format_order_id(options[:order_id]), @options[:terminal_id], card_number, amount(money), security_data)
xml = Builder::XmlMarkup.new(:indent => 2)
- xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
+ xml.instruct! :xml, :version => '1.0', :encoding => 'UTF-8'
xml.tag! 'GVPSRequest' do
xml.tag! 'Mode', test? ? 'TEST' : 'PROD'
@@ -91,7 +91,7 @@ def build_xml_request(money, credit_card, options, &block)
def build_sale_request(money, credit_card, options)
build_xml_request(money, credit_card, options) do |xml|
add_customer_data(xml, options)
- add_order_data(xml, options) do |xml|
+ add_order_data(xml, options) do
add_addresses(xml, options)
end
add_credit_card(xml, credit_card)
@@ -102,9 +102,9 @@ def build_sale_request(money, credit_card, options)
end
def build_authorize_request(money, credit_card, options)
- build_xml_request(money, credit_card, options) do |xml|
+ build_xml_request(money, credit_card, options) do |xml|
add_customer_data(xml, options)
- add_order_data(xml, options) do |xml|
+ add_order_data(xml, options) do
add_addresses(xml, options)
end
add_credit_card(xml, credit_card)
@@ -116,7 +116,7 @@ def build_authorize_request(money, credit_card, options)
def build_capture_request(money, ref_id, options)
options = options.merge(:order_id => ref_id)
- build_xml_request(money, ref_id, options) do |xml|
+ build_xml_request(money, ref_id, options) do |xml|
add_customer_data(xml, options)
add_order_data(xml, options)
add_transaction_data(xml, money, options)
@@ -181,7 +181,7 @@ def add_addresses(xml, options)
def add_address(xml, address)
xml.tag! 'Name', normalize(address[:name])
address_text = address[:address1]
- address_text << " #{ address[:address2]}" if address[:address2]
+ address_text << " #{address[:address2]}" if address[:address2]
xml.tag! 'Text', normalize(address_text)
xml.tag! 'City', normalize(address[:city])
xml.tag! 'District', normalize(address[:state])
@@ -195,11 +195,9 @@ def normalize(text)
return unless text
if ActiveSupport::Inflector.method(:transliterate).arity == -2
- ActiveSupport::Inflector.transliterate(text,'')
- elsif RUBY_VERSION >= '1.9'
- text.gsub(/[^\x00-\x7F]+/, '')
+ ActiveSupport::Inflector.transliterate(text, '')
else
- ActiveSupport::Inflector.transliterate(text).to_s
+ text.gsub(/[^\x00-\x7F]+/, '')
end
end
@@ -216,21 +214,22 @@ def currency_code(currency)
CURRENCY_CODES[currency] || CURRENCY_CODES[default_currency]
end
- def commit(money,request)
- raw_response = ssl_post(self.live_url, "data=" + request)
+ def commit(money, request)
+ url = test? ? self.test_url : self.live_url
+ raw_response = ssl_post(url, 'data=' + request)
response = parse(raw_response)
success = success?(response)
Response.new(success,
- success ? 'Approved' : "Declined (Reason: #{response[:reason_code]} - #{response[:error_msg]} - #{response[:sys_err_msg]})",
- response,
- :test => test?,
- :authorization => response[:order_id])
+ success ? 'Approved' : "Declined (Reason: #{response[:reason_code]} - #{response[:error_msg]} - #{response[:sys_err_msg]})",
+ response,
+ :test => test?,
+ :authorization => response[:order_id])
end
def parse(body)
- xml = REXML::Document.new(body)
+ xml = REXML::Document.new(strip_invalid_xml_chars(body))
response = {}
xml.root.elements.to_a.each do |node|
@@ -241,17 +240,20 @@ def parse(body)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|element| parse_element(response, element) }
+ node.elements.each { |element| parse_element(response, element) }
else
response[node.name.underscore.to_sym] = node.text
end
end
def success?(response)
- response[:message] == "Approved"
+ response[:message] == 'Approved'
+ end
+
+ def strip_invalid_xml_chars(xml)
+ xml.gsub(/&(?!(?:[a-z]+|#[0-9]+|x[a-zA-Z0-9]+);)/, '&')
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/global_collect.rb b/lib/active_merchant/billing/gateways/global_collect.rb
new file mode 100644
index 00000000000..879ff3acc6f
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/global_collect.rb
@@ -0,0 +1,336 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class GlobalCollectGateway < Gateway
+ self.display_name = 'GlobalCollect'
+ self.homepage_url = 'http://www.globalcollect.com/'
+
+ self.test_url = 'https://eu.sandbox.api-ingenico.com'
+ self.live_url = 'https://api.globalcollect.com'
+
+ self.supported_countries = ['AD', 'AE', 'AG', 'AI', 'AL', 'AM', 'AO', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IS', 'IT', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PL', 'PN', 'PS', 'PT', 'PW', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SR', 'ST', 'SV', 'SZ', 'TC', 'TD', 'TG', 'TH', 'TJ', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'US', 'UY', 'UZ', 'VC', 'VE', 'VG', 'VI', 'VN', 'WF', 'WS', 'ZA', 'ZM', 'ZW']
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :naranja, :cabal]
+
+ def initialize(options={})
+ requires!(options, :merchant_id, :api_key_id, :secret_api_key)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ MultiResponse.run do |r|
+ r.process { authorize(money, payment, options) }
+ r.process { capture(money, r.authorization, options) } unless capture_requested?(r)
+ end
+ end
+
+ def authorize(money, payment, options={})
+ post = nestable_hash
+ add_order(post, money, options)
+ add_payment(post, payment, options)
+ add_customer_data(post, options, payment)
+ add_address(post, payment, options)
+ add_creator_info(post, options)
+ add_fraud_fields(post, options)
+
+ commit(:authorize, post)
+ end
+
+ def capture(money, authorization, options={})
+ post = nestable_hash
+ add_order(post, money, options, capture: true)
+ add_customer_data(post, options)
+ add_creator_info(post, options)
+ commit(:capture, post, authorization)
+ end
+
+ def refund(money, authorization, options={})
+ post = nestable_hash
+ add_amount(post, money, options)
+ add_refund_customer_data(post, options)
+ add_creator_info(post, options)
+ commit(:refund, post, authorization)
+ end
+
+ def void(authorization, options={})
+ post = nestable_hash
+ add_creator_info(post, options)
+ commit(:void, post, authorization)
+ end
+
+ def verify(payment, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, payment, options) }
+ r.process { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: )[^\\]*)i, '\1[FILTERED]').
+ gsub(%r(("cardNumber\\+":\\+")\d+), '\1[FILTERED]').
+ gsub(%r(("cvv\\+":\\+")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ BRAND_MAP = {
+ 'visa' => '1',
+ 'american_express' => '2',
+ 'master' => '3',
+ 'discover' => '128',
+ 'jcb' => '125',
+ 'diners_club' => '132'
+ }
+
+ def add_order(post, money, options, capture: false)
+ if capture
+ post['amount'] = amount(money)
+ else
+ add_amount(post['order'], money, options)
+ end
+ post['order']['references'] = {
+ 'merchantReference' => options[:order_id],
+ 'descriptor' => options[:description] # Max 256 chars
+ }
+ post['order']['references']['invoiceData'] = {
+ 'invoiceNumber' => options[:invoice]
+ }
+ end
+
+ def add_creator_info(post, options)
+ post['sdkIdentifier'] = options[:sdk_identifier] if options[:sdk_identifier]
+ post['sdkCreator'] = options[:sdk_creator] if options[:sdk_creator]
+ post['integrator'] = options[:integrator] if options[:integrator]
+ post['shoppingCartExtension'] = {}
+ post['shoppingCartExtension']['creator'] = options[:creator] if options[:creator]
+ post['shoppingCartExtension']['name'] = options[:name] if options[:name]
+ post['shoppingCartExtension']['version'] = options[:version] if options[:version]
+ post['shoppingCartExtension']['extensionID'] = options[:extension_ID] if options[:extension_ID]
+ end
+
+ def add_amount(post, money, options={})
+ post['amountOfMoney'] = {
+ 'amount' => amount(money),
+ 'currencyCode' => options[:currency] || currency(money)
+ }
+ end
+
+ def add_payment(post, payment, options)
+ year = format(payment.year, :two_digits)
+ month = format(payment.month, :two_digits)
+ expirydate = "#{month}#{year}"
+ pre_authorization = options[:pre_authorization] ? 'PRE_AUTHORIZATION' : 'FINAL_AUTHORIZATION'
+
+ post['cardPaymentMethodSpecificInput'] = {
+ 'paymentProductId' => BRAND_MAP[payment.brand],
+ 'skipAuthentication' => 'true', # refers to 3DSecure
+ 'skipFraudService' => 'true',
+ 'authorizationMode' => pre_authorization
+ }
+ post['cardPaymentMethodSpecificInput']['card'] = {
+ 'cvv' => payment.verification_value,
+ 'cardNumber' => payment.number,
+ 'expiryDate' => expirydate,
+ 'cardholderName' => payment.name
+ }
+ end
+
+ def add_customer_data(post, options, payment = nil)
+ if payment
+ post['order']['customer']['personalInformation']['name']['firstName'] = payment.first_name[0..14] if payment.first_name
+ post['order']['customer']['personalInformation']['name']['surname'] = payment.last_name[0..69] if payment.last_name
+ end
+ post['order']['customer']['merchantCustomerId'] = options[:customer] if options[:customer]
+ post['order']['customer']['companyInformation']['name'] = options[:company] if options[:company]
+ post['order']['customer']['contactDetails']['emailAddress'] = options[:email] if options[:email]
+ if address = options[:billing_address] || options[:address]
+ post['order']['customer']['contactDetails']['phoneNumber'] = address[:phone] if address[:phone]
+ end
+ end
+
+ def add_refund_customer_data(post, options)
+ if address = options[:billing_address] || options[:address]
+ post['customer']['address'] = {
+ 'countryCode' => address[:country]
+ }
+ post['customer']['contactDetails']['emailAddress'] = options[:email] if options[:email]
+ if address = options[:billing_address] || options[:address]
+ post['customer']['contactDetails']['phoneNumber'] = address[:phone] if address[:phone]
+ end
+ end
+ end
+
+ def add_address(post, creditcard, options)
+ shipping_address = options[:shipping_address]
+ if billing_address = options[:billing_address] || options[:address]
+ post['order']['customer']['billingAddress'] = {
+ 'street' => billing_address[:address1],
+ 'additionalInfo' => billing_address[:address2],
+ 'zip' => billing_address[:zip],
+ 'city' => billing_address[:city],
+ 'state' => billing_address[:state],
+ 'countryCode' => billing_address[:country]
+ }
+ end
+ if shipping_address
+ post['order']['customer']['shippingAddress'] = {
+ 'street' => shipping_address[:address1],
+ 'additionalInfo' => shipping_address[:address2],
+ 'zip' => shipping_address[:zip],
+ 'city' => shipping_address[:city],
+ 'state' => shipping_address[:state],
+ 'countryCode' => shipping_address[:country]
+ }
+ post['order']['customer']['shippingAddress']['name'] = {
+ 'firstName' => shipping_address[:firstname],
+ 'surname' => shipping_address[:lastname]
+ }
+ end
+ end
+
+ def add_fraud_fields(post, options)
+ fraud_fields = {}
+ fraud_fields.merge!(options[:fraud_fields]) if options[:fraud_fields]
+ fraud_fields[:customerIpAddress] = options[:ip] if options[:ip]
+
+ post['fraudFields'] = fraud_fields unless fraud_fields.empty?
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def url(action, authorization)
+ (test? ? test_url : live_url) + uri(action, authorization)
+ end
+
+ def uri(action, authorization)
+ uri = "/v1/#{@options[:merchant_id]}/"
+ case action
+ when :authorize
+ uri + 'payments'
+ when :capture
+ uri + "payments/#{authorization}/approve"
+ when :refund
+ uri + "payments/#{authorization}/refund"
+ when :void
+ uri + "payments/#{authorization}/cancel"
+ end
+ end
+
+ def commit(action, post, authorization = nil)
+ begin
+ raw_response = ssl_post(url(action, authorization), post.to_json, headers(action, post, authorization))
+ response = parse(raw_response)
+ rescue ResponseError => e
+ if e.response.code.to_i >= 400
+ response = parse(e.response.body)
+ end
+ rescue JSON::ParserError
+ response = json_error(raw_response)
+ end
+
+ succeeded = success_from(response)
+ Response.new(
+ succeeded,
+ message_from(succeeded, response),
+ response,
+ authorization: authorization_from(succeeded, response),
+ error_code: error_code_from(succeeded, response),
+ test: test?
+ )
+ end
+
+ def json_error(raw_response)
+ {
+ 'error_message' => 'Invalid response received from the Ingenico ePayments (formerly GlobalCollect) API. Please contact Ingenico ePayments if you continue to receive this message.' \
+ " (The raw response returned by the API was #{raw_response.inspect})",
+ 'status' => 'REJECTED'
+ }
+ end
+
+ def headers(action, post, authorization = nil)
+ {
+ 'Content-Type' => content_type,
+ 'Authorization' => auth_digest(action, post, authorization),
+ 'Date' => date
+ }
+ end
+
+ def auth_digest(action, post, authorization = nil)
+ data = <<-EOS
+POST
+#{content_type}
+#{date}
+#{uri(action, authorization)}
+EOS
+ digest = OpenSSL::Digest.new('sha256')
+ key = @options[:secret_api_key]
+ "GCS v1HMAC:#{@options[:api_key_id]}:#{Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))}"
+ end
+
+ def date
+ @date ||= Time.now.strftime('%a, %d %b %Y %H:%M:%S %Z') # Must be same in digest and HTTP header
+ end
+
+ def content_type
+ 'application/json'
+ end
+
+ def success_from(response)
+ !response['errorId'] && response['status'] != 'REJECTED'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ if errors = response['errors']
+ errors.first.try(:[], 'message')
+ elsif response['error_message']
+ response['error_message']
+ elsif response['status']
+ 'Status: ' + response['status']
+ else
+ 'No message available'
+ end
+ end
+ end
+
+ def authorization_from(succeeded, response)
+ if succeeded
+ response['id'] || response['payment']['id'] || response['paymentResult']['payment']['id']
+ elsif response['errorId']
+ response['errorId']
+ else
+ 'GATEWAY ERROR'
+ end
+ end
+
+ def error_code_from(succeeded, response)
+ unless succeeded
+ if errors = response['errors']
+ errors.first.try(:[], 'code')
+ elsif status = response.try(:[], 'statusOutput').try(:[], 'statusCode')
+ status.to_s
+ else
+ 'No error code available'
+ end
+ end
+ end
+
+ def nestable_hash
+ Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
+ end
+
+ def capture_requested?(response)
+ response.params.try(:[], 'payment').try(:[], 'status') == 'CAPTURE_REQUESTED'
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/global_transport.rb b/lib/active_merchant/billing/gateways/global_transport.rb
new file mode 100644
index 00000000000..94087b23e17
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/global_transport.rb
@@ -0,0 +1,194 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class GlobalTransportGateway < Gateway
+ self.test_url = 'https://certapia.globalpay.com/GlobalPay/transact.asmx/ProcessCreditCard'
+ self.live_url = 'https://api.globalpay.com/GlobalPay/transact.asmx/ProcessCreditCard'
+
+ self.supported_countries = %w(CA PR US)
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+
+ self.homepage_url = 'https://www.globalpaymentsinc.com'
+ self.display_name = 'Global Transport'
+
+ # Public: Create a new Global Transport gateway.
+ #
+ # options - A hash of options:
+ # :global_user_name - Your Global user name
+ # :global_password - Your Global password
+ # :term_type - 3 character field assigned by Global Transport after
+ # - your application is certified.
+ def initialize(options={})
+ requires!(options, :global_user_name, :global_password, :term_type)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment_method(post, payment_method)
+ add_address(post, options)
+
+ commit('Sale', post, options)
+ end
+
+ def authorize(money, payment_method, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment_method(post, payment_method)
+ add_address(post, options)
+
+ commit('Auth', post, options)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_auth(post, authorization)
+
+ commit('Force', post, options)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_auth(post, authorization)
+
+ commit('Return', post, options)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_auth(post, authorization)
+
+ commit('Void', post, options)
+ end
+
+ def verify(payment_method, options={})
+ post = {}
+ add_payment_method(post, payment_method)
+ add_address(post, options)
+
+ commit('CardVerify', post, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?CardNum=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?CVNum=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?GlobalPassword=)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_address(post, options)
+ if address = (options[:billing_address] || options[:address])
+ post[:Street] = address[:address1]
+ post[:Zip] = address[:zip]
+ end
+ end
+
+ def add_auth(post, authorization)
+ post[:PNRef] = authorization
+ end
+
+ def add_invoice(post, money, options)
+ currency = (options[:currency] || currency(money))
+
+ post[:Amount] = localized_amount(money, currency)
+ post[:InvNum] = truncate(options[:order_id], 16)
+ end
+
+ def add_payment_method(post, payment_method)
+ post[:CardNum] = payment_method.number
+ post[:ExpDate] = expdate(payment_method)
+ post[:NameOnCard] = payment_method.name
+ post[:CVNum] = payment_method.verification_value
+ end
+
+ def parse(body)
+ response = {}
+
+ Nokogiri::XML(body).root.xpath('*').each do |node|
+ response[node.name.downcase.to_sym] = node.text
+ end
+
+ ext_data = Nokogiri::HTML.parse(response[:extdata])
+ response[:approved_amount] = ext_data.xpath('//approvedamount').text
+ response[:balance_due] = ext_data.xpath('//balancedue').text
+
+ response
+ end
+
+ def commit(action, parameters, options)
+ raw = parse(ssl_post(url, post_data(action, parameters, options)))
+ Response.new(
+ success_from(raw),
+ message_from(raw),
+ raw,
+ authorization: authorization_from(raw),
+ test: test?,
+ avs_result: avs_from(raw),
+ cvv_result: cvv_from(raw)
+ )
+ end
+
+ def post_data(action, params, options)
+ post = default_params
+ post[:GlobalUserName] = @options[:global_user_name]
+ post[:GlobalPassword] = @options[:global_password]
+ post[:TransType] = action
+ post[:ExtData] = "#{@options[:term_type]}"
+
+ post.merge(params).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ def url
+ (test? ? test_url : live_url)
+ end
+
+ def success_from(response)
+ response[:result] == '0' || response[:result] == '200'
+ end
+
+ def message_from(response)
+ response[:respmsg]
+ end
+
+ def authorization_from(response)
+ response[:pnref]
+ end
+
+ def avs_from(response)
+ { code: response[:getavsresult] }
+ end
+
+ def cvv_from(response)
+ response[:getcvresult]
+ end
+
+ def default_params
+ {
+ CardNum: '',
+ ExpDate: '',
+ NameOnCard: '',
+ Amount: '',
+ PNRef: '',
+ Zip: '',
+ Street: '',
+ CVNum: '',
+ MagData: '',
+ InvNum: '',
+ ExtData: ''
+ }
+ end
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/hdfc.rb b/lib/active_merchant/billing/gateways/hdfc.rb
index be973857764..142d7c83b82 100644
--- a/lib/active_merchant/billing/gateways/hdfc.rb
+++ b/lib/active_merchant/billing/gateways/hdfc.rb
@@ -1,16 +1,16 @@
-require "nokogiri"
+require 'nokogiri'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class HdfcGateway < Gateway
- self.display_name = "HDFC"
- self.homepage_url = "http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i"
+ self.display_name = 'HDFC'
+ self.homepage_url = 'http://www.hdfcbank.com/sme/sme-details/merchant-services/guzh6m0i'
- self.test_url = "https://securepgtest.fssnet.co.in/pgway/servlet/"
- self.live_url = "https://securepg.fssnet.co.in/pgway/servlet/"
+ self.test_url = 'https://securepgtest.fssnet.co.in/pgway/servlet/'
+ self.live_url = 'https://securepg.fssnet.co.in/pgway/servlet/'
- self.supported_countries = ["IN"]
- self.default_currency = "INR"
+ self.supported_countries = ['IN']
+ self.default_currency = 'INR'
self.money_format = :dollars
self.supported_cardtypes = [:visa, :master, :discover, :diners_club]
@@ -25,7 +25,7 @@ def purchase(amount, payment_method, options={})
add_payment_method(post, payment_method)
add_customer_data(post, options)
- commit("purchase", post)
+ commit('purchase', post)
end
def authorize(amount, payment_method, options={})
@@ -34,7 +34,7 @@ def authorize(amount, payment_method, options={})
add_payment_method(post, payment_method)
add_customer_data(post, options)
- commit("authorize", post)
+ commit('authorize', post)
end
def capture(amount, authorization, options={})
@@ -43,7 +43,7 @@ def capture(amount, authorization, options={})
add_reference(post, authorization)
add_customer_data(post, options)
- commit("capture", post)
+ commit('capture', post)
end
def refund(amount, authorization, options={})
@@ -52,22 +52,22 @@ def refund(amount, authorization, options={})
add_reference(post, authorization)
add_customer_data(post, options)
- commit("refund", post)
+ commit('refund', post)
end
private
- CURRENCY_CODES = Hash.new{|h,k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}")}
- CURRENCY_CODES["AED"] = "784"
- CURRENCY_CODES["AUD"] = "036"
- CURRENCY_CODES["CAD"] = "124"
- CURRENCY_CODES["EUR"] = "978"
- CURRENCY_CODES["GBP"] = "826"
- CURRENCY_CODES["INR"] = "356"
- CURRENCY_CODES["OMR"] = "512"
- CURRENCY_CODES["QAR"] = "634"
- CURRENCY_CODES["SGD"] = "702"
- CURRENCY_CODES["USD"] = "840"
+ CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency for HDFC: #{k}") }
+ CURRENCY_CODES['AED'] = '784'
+ CURRENCY_CODES['AUD'] = '036'
+ CURRENCY_CODES['CAD'] = '124'
+ CURRENCY_CODES['EUR'] = '978'
+ CURRENCY_CODES['GBP'] = '826'
+ CURRENCY_CODES['INR'] = '356'
+ CURRENCY_CODES['OMR'] = '512'
+ CURRENCY_CODES['QAR'] = '634'
+ CURRENCY_CODES['SGD'] = '702'
+ CURRENCY_CODES['USD'] = '840'
def add_invoice(post, amount, options)
post[:amt] = amount(amount)
@@ -113,7 +113,7 @@ def parse(xml)
doc.children.each do |node|
if node.text?
next
- elsif (node.elements.size == 0)
+ elsif node.elements.size == 0
response[node.name.downcase.to_sym] = node.text
else
node.elements.each do |childnode|
@@ -127,14 +127,14 @@ def parse(xml)
end
def fix_xml(xml)
- xml.gsub(/&(?!(?:amp|quot|apos|lt|gt);)/, "&")
+ xml.gsub(/&(?!(?:amp|quot|apos|lt|gt);)/, '&')
end
ACTIONS = {
- "purchase" => "1",
- "refund" => "2",
- "authorize" => "4",
- "capture" => "5",
+ 'purchase' => '1',
+ 'refund' => '2',
+ 'authorize' => '4',
+ 'capture' => '5',
}
def commit(action, post)
@@ -164,13 +164,13 @@ def build_request(post)
end
def url(action)
- endpoint = "TranPortalXMLServlet"
+ endpoint = 'TranPortalXMLServlet'
(test? ? test_url : live_url) + endpoint
end
def success_from(result)
case result
- when "CAPTURED", "APPROVED", "NOT ENROLLED", "ENROLLED"
+ when 'CAPTURED', 'APPROVED', 'NOT ENROLLED', 'ENROLLED'
true
else
false
@@ -179,23 +179,23 @@ def success_from(result)
def message_from(succeeded, response)
if succeeded
- "Succeeded"
+ 'Succeeded'
else
- (response[:error_text] || response[:result] || "Unable to read error message").split("-").last
+ (response[:error_text] || response[:result] || 'Unable to read error message').split('-').last
end
end
def authorization_from(request, response)
- [response[:tranid], request[:member]].join("|")
+ [response[:tranid], request[:member]].join('|')
end
def split_authorization(authorization)
- tranid, member = authorization.split("|")
+ tranid, member = authorization.split('|')
[tranid, member]
end
def escape(string, max_length=250)
- return "" unless string
+ return '' unless string
if max_length
string = string[0...max_length]
end
@@ -204,4 +204,3 @@ def escape(string, max_length=250)
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/hps.rb b/lib/active_merchant/billing/gateways/hps.rb
new file mode 100644
index 00000000000..6069040003c
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/hps.rb
@@ -0,0 +1,350 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class HpsGateway < Gateway
+ self.live_url = 'https://posgateway.secureexchange.net/Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl'
+ self.test_url = 'https://posgateway.cert.secureexchange.net/Hps.Exchange.PosGateway/PosGatewayService.asmx?wsdl'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jbc, :diners_club]
+
+ self.homepage_url = 'http://developer.heartlandpaymentsystems.com/SecureSubmit/'
+ self.display_name = 'Heartland Payment Systems'
+
+ self.money_format = :dollars
+
+ PAYMENT_DATA_SOURCE_MAPPING = {
+ apple_pay: 'ApplePay',
+ master: 'MasterCard 3DSecure',
+ visa: 'Visa 3DSecure',
+ american_express: 'AMEX 3DSecure',
+ discover: 'Discover 3DSecure',
+ }
+
+ def initialize(options={})
+ requires!(options, :secret_api_key)
+ super
+ end
+
+ def authorize(money, card_or_token, options={})
+ commit('CreditAuth') do |xml|
+ add_amount(xml, money)
+ add_allow_dup(xml)
+ add_customer_data(xml, card_or_token, options)
+ add_details(xml, options)
+ add_descriptor_name(xml, options)
+ add_payment(xml, card_or_token, options)
+ add_three_d_secure(xml, card_or_token, options)
+ end
+ end
+
+ def capture(money, transaction_id, options={})
+ commit('CreditAddToBatch') do |xml|
+ add_amount(xml, money)
+ add_reference(xml, transaction_id)
+ end
+ end
+
+ def purchase(money, card_or_token, options={})
+ commit('CreditSale') do |xml|
+ add_amount(xml, money)
+ add_allow_dup(xml)
+ add_customer_data(xml, card_or_token, options)
+ add_details(xml, options)
+ add_descriptor_name(xml, options)
+ add_payment(xml, card_or_token, options)
+ add_three_d_secure(xml, card_or_token, options)
+ end
+ end
+
+ def refund(money, transaction_id, options={})
+ commit('CreditReturn') do |xml|
+ add_amount(xml, money)
+ add_allow_dup(xml)
+ add_reference(xml, transaction_id)
+ add_customer_data(xml, transaction_id, options)
+ add_details(xml, options)
+ end
+ end
+
+ def verify(card_or_token, options={})
+ commit('CreditAccountVerify') do |xml|
+ add_customer_data(xml, card_or_token, options)
+ add_descriptor_name(xml, options)
+ add_payment(xml, card_or_token, options)
+ end
+ end
+
+ def void(transaction_id, options={})
+ commit('CreditVoid') do |xml|
+ add_reference(xml, transaction_id)
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]*(<\/hps:CardNbr>))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*(<\/hps:CVV2>))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*(<\/hps:SecretAPIKey>))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*(<\/hps:PaymentData>))i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_reference(xml, transaction_id)
+ xml.hps :GatewayTxnId, transaction_id
+ end
+
+ def add_amount(xml, money)
+ xml.hps :Amt, amount(money) if money
+ end
+
+ def add_customer_data(xml, credit_card, options)
+ xml.hps :CardHolderData do
+ if credit_card.respond_to?(:number)
+ xml.hps :CardHolderFirstName, credit_card.first_name if credit_card.first_name
+ xml.hps :CardHolderLastName, credit_card.last_name if credit_card.last_name
+ end
+
+ xml.hps :CardHolderEmail, options[:email] if options[:email]
+ xml.hps :CardHolderPhone, options[:phone] if options[:phone]
+
+ if(billing_address = (options[:billing_address] || options[:address]))
+ xml.hps :CardHolderAddr, billing_address[:address1] if billing_address[:address1]
+ xml.hps :CardHolderCity, billing_address[:city] if billing_address[:city]
+ xml.hps :CardHolderState, billing_address[:state] if billing_address[:state]
+ xml.hps :CardHolderZip, billing_address[:zip] if billing_address[:zip]
+ end
+ end
+ end
+
+ def add_payment(xml, card_or_token, options)
+ xml.hps :CardData do
+ if card_or_token.respond_to?(:number)
+ if card_or_token.track_data
+ xml.tag!('hps:TrackData', 'method'=>'swipe') do
+ xml.text! card_or_token.track_data
+ end
+ if options[:encryption_type]
+ xml.hps :EncryptionData do
+ xml.hps :Version, options[:encryption_type]
+ if options[:encryption_type] == '02'
+ xml.hps :EncryptedTrackNumber, options[:encrypted_track_number]
+ xml.hps :KTB, options[:ktb]
+ end
+ end
+ end
+ else
+ xml.hps :ManualEntry do
+ xml.hps :CardNbr, card_or_token.number
+ xml.hps :ExpMonth, card_or_token.month
+ xml.hps :ExpYear, card_or_token.year
+ xml.hps :CVV2, card_or_token.verification_value if card_or_token.verification_value
+ xml.hps :CardPresent, 'N'
+ xml.hps :ReaderPresent, 'N'
+ end
+ end
+ else
+ xml.hps :TokenData do
+ xml.hps :TokenValue, card_or_token
+ end
+ end
+ xml.hps :TokenRequest, (options[:store] ? 'Y' : 'N')
+ end
+ end
+
+ def add_details(xml, options)
+ xml.hps :AdditionalTxnFields do
+ xml.hps :Description, options[:description] if options[:description]
+ xml.hps :InvoiceNbr, options[:order_id] if options[:order_id]
+ xml.hps :CustomerID, options[:customer_id] if options[:customer_id]
+ end
+ end
+
+ def add_allow_dup(xml)
+ xml.hps :AllowDup, 'Y'
+ end
+
+ def add_descriptor_name(xml, options)
+ xml.hps :TxnDescriptor, options[:descriptor_name] if options[:descriptor_name]
+ end
+
+ def add_three_d_secure(xml, card_or_token, options)
+ if card_or_token.is_a?(NetworkTokenizationCreditCard)
+ build_three_d_secure(xml, {
+ source: card_or_token.source,
+ cavv: card_or_token.payment_cryptogram,
+ eci: card_or_token.eci,
+ xid: card_or_token.transaction_id,
+ })
+ elsif options[:three_d_secure]
+ options[:three_d_secure][:source] ||= card_brand(card_or_token)
+ build_three_d_secure(xml, options[:three_d_secure])
+ end
+ end
+
+ def build_three_d_secure(xml, three_d_secure)
+ # PaymentDataSource is required when supplying the SecureECommerce data group,
+ # and the gateway currently only allows the values within the mapping
+ return unless PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym]
+
+ xml.hps :SecureECommerce do
+ xml.hps :PaymentDataSource, PAYMENT_DATA_SOURCE_MAPPING[three_d_secure[:source].to_sym]
+ xml.hps :TypeOfPaymentData, '3DSecure' # Only type currently supported
+ xml.hps :PaymentData, three_d_secure[:cavv] if three_d_secure[:cavv]
+ # the gateway only allows a single character for the ECI
+ xml.hps :ECommerceIndicator, strip_leading_zero(three_d_secure[:eci]) if three_d_secure[:eci]
+ xml.hps :XID, three_d_secure[:xid] if three_d_secure[:xid]
+ end
+ end
+
+ def strip_leading_zero(value)
+ return value unless value[0] == '0'
+ value[1, 1]
+ end
+
+ def build_request(action)
+ xml = Builder::XmlMarkup.new(encoding: 'UTF-8')
+ xml.instruct!(:xml, encoding: 'UTF-8')
+ xml.SOAP :Envelope, {
+ 'xmlns:SOAP' => 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'xmlns:hps' => 'http://Hps.Exchange.PosGateway' } do
+ xml.SOAP :Body do
+ xml.hps :PosRequest do
+ xml.hps 'Ver1.0'.to_sym do
+ xml.hps :Header do
+ xml.hps :SecretAPIKey, @options[:secret_api_key]
+ xml.hps :DeveloperID, @options[:developer_id] if @options[:developer_id]
+ xml.hps :VersionNbr, @options[:version_number] if @options[:version_number]
+ xml.hps :SiteTrace, @options[:site_trace] if @options[:site_trace]
+ end
+ xml.hps :Transaction do
+ xml.hps action.to_sym do
+ if %w(CreditVoid CreditAddToBatch).include?(action)
+ yield(xml)
+ else
+ xml.hps :Block1 do
+ yield(xml)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ xml.target!
+ end
+
+ def parse(raw)
+ response = {}
+
+ doc = Nokogiri::XML(raw)
+ doc.remove_namespaces!
+ if(header = doc.xpath('//Header').first)
+ header.elements.each do |node|
+ if node.elements.size == 0
+ response[node.name] = node.text
+ else
+ node.elements.each do |childnode|
+ response[childnode.name] = childnode.text
+ end
+ end
+ end
+ end
+ if(transaction = doc.xpath('//Transaction/*[1]').first)
+ transaction.elements.each do |node|
+ response[node.name] = node.text
+ end
+ end
+ if(fault = doc.xpath('//Fault/Reason/Text').first)
+ response['Fault'] = fault.text
+ end
+
+ response
+ end
+
+ def commit(action, &request)
+ data = build_request(action, &request)
+
+ response = begin
+ parse(ssl_post((test? ? test_url : live_url), data, 'Content-Type' => 'text/xml'))
+ rescue ResponseError => e
+ parse(e.response.body)
+ end
+
+ ActiveMerchant::Billing::Response.new(
+ successful?(response),
+ message_from(response),
+ response,
+ test: test?,
+ authorization: authorization_from(response),
+ avs_result: {
+ code: response['AVSRsltCode'],
+ message: response['AVSRsltText']
+ },
+ cvv_result: response['CVVRsltCode']
+ )
+ end
+
+ def successful?(response)
+ (
+ (response['GatewayRspCode'] == '0') &&
+ ((response['RspCode'] || '00') == '00' || response['RspCode'] == '85')
+ )
+ end
+
+ def message_from(response)
+ if(response['Fault'])
+ response['Fault']
+ elsif(response['GatewayRspCode'] == '0')
+ if(response['RspCode'] != '00' && response['RspCode'] != '85')
+ issuer_message(response['RspCode'])
+ else
+ response['GatewayRspMsg']
+ end
+ else
+ (GATEWAY_MESSAGES[response['GatewayRspCode']] || response['GatewayRspMsg'])
+ end
+ end
+
+ def authorization_from(response)
+ response['GatewayTxnId']
+ end
+
+ def test?
+ @options[:secret_api_key]&.include?('_cert_')
+ end
+
+ ISSUER_MESSAGES = {
+ '13' => 'Must be greater than or equal 0.',
+ '14' => 'The card number is incorrect.',
+ '54' => 'The card has expired.',
+ '55' => 'The 4-digit pin is invalid.',
+ '75' => 'Maximum number of pin retries exceeded.',
+ '80' => 'Card expiration date is invalid.',
+ '86' => "Can't verify card pin number."
+ }
+ def issuer_message(code)
+ return 'The card was declined.' if %w(02 03 04 05 41 43 44 51 56 61 62 63 65 78).include?(code)
+ return 'An error occurred while processing the card.' if %w(06 07 12 15 19 12 52 53 57 58 76 77 91 96 EC).include?(code)
+ return "The card's security code is incorrect." if %w(EB N7).include?(code)
+ ISSUER_MESSAGES[code]
+ end
+
+ GATEWAY_MESSAGES = {
+ '-2' => 'Authentication error. Please double check your service configuration.',
+ '12' => 'Invalid CPC data.',
+ '13' => 'Invalid card data.',
+ '14' => 'The card number is not a valid credit card number.',
+ '30' => 'Gateway timed out.'
+ }
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/iats_payments.rb b/lib/active_merchant/billing/gateways/iats_payments.rb
index 8db8cfb54e9..c2d4505dfb9 100644
--- a/lib/active_merchant/billing/gateways/iats_payments.rb
+++ b/lib/active_merchant/billing/gateways/iats_payments.rb
@@ -1,34 +1,289 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- class IatsPaymentsGateway < AuthorizeNetGateway
- self.live_url = self.test_url = 'https://www.iatspayments.com/netgate/AEGateway.aspx'
+ class IatsPaymentsGateway < Gateway
+ class_attribute :live_na_url, :live_uk_url
- self.homepage_url = 'http://www.iatspayments.com/'
- self.display_name = 'IATSPayments'
+ self.live_na_url = 'https://www.iatspayments.com/NetGate'
+ self.live_uk_url = 'https://www.uk.iatspayments.com/NetGate'
- def authorize(money, paysource, options = {})
- raise NotImplementedError
+ self.supported_countries = %w(AU BR CA CH DE DK ES FI FR GR HK IE IT NL NO PT SE SG TR GB US TH ID PH BE)
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'http://home.iatspayments.com/'
+ self.display_name = 'iATS Payments'
+
+ ACTIONS = {
+ purchase: 'ProcessCreditCardV1',
+ purchase_check: 'ProcessACHEFTV1',
+ refund: 'ProcessCreditCardRefundWithTransactionIdV1',
+ refund_check: 'ProcessACHEFTRefundWithTransactionIdV1',
+ store: 'CreateCreditCardCustomerCodeV1',
+ unstore: 'DeleteCustomerCodeV1'
+ }
+
+ def initialize(options={})
+ if(options[:login])
+ ActiveMerchant.deprecated("The 'login' option is deprecated in favor of 'agent_code' and will be removed in a future version.")
+ options[:agent_code] = options[:login]
+ end
+
+ options[:region] = 'na' unless options[:region]
+
+ requires!(options, :agent_code, :password, :region)
+ super
end
- def capture(money, authorization, options = {})
- raise NotImplementedError
+ def purchase(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, options)
+ add_ip(post, options)
+ add_description(post, options)
+
+ commit((payment.is_a?(Check) ? :purchase_check : :purchase), post)
end
- def void(authorization, options = {})
- raise NotImplementedError
+ def refund(money, authorization, options={})
+ post = {}
+ transaction_id, payment_type = split_authorization(authorization)
+ post[:transaction_id] = transaction_id
+ add_invoice(post, -money, options)
+ add_ip(post, options)
+ add_description(post, options)
+
+ commit((payment_type == 'check' ? :refund_check : :refund), post)
end
- def refund(money, identification, options = {})
- raise NotImplementedError
+ def store(credit_card, options = {})
+ post = {}
+ add_payment(post, credit_card)
+ add_address(post, options)
+ add_ip(post, options)
+ add_description(post, options)
+ add_store_defaults(post)
+
+ commit(:store, post)
end
- def credit(money, identification, options = {})
- raise NotImplementedError
+ def unstore(authorization, options = {})
+ post = {}
+ post[:customer_code] = authorization
+ add_ip(post, options)
+
+ commit(:unstore, post)
+ 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').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2')
end
private
- def split(response)
- response.split(',')
+
+ def add_ip(post, options)
+ post[:customer_ip_address] = options[:ip] if options.has_key?(:ip)
+ end
+
+ def add_address(post, options)
+ billing_address = options[:billing_address] || options[:address]
+ if(billing_address)
+ post[:address] = billing_address[:address1]
+ post[:city] = billing_address[:city]
+ post[:state] = billing_address[:state]
+ post[:zip_code] = billing_address[:zip]
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:invoice_num] = options[:order_id] if options[:order_id]
+ post[:total] = amount(money)
+ end
+
+ def add_description(post, options)
+ post[:comment] = options[:description] if options[:description]
+ end
+
+ def add_payment(post, payment)
+ if payment.is_a?(Check)
+ add_check(post, payment)
+ else
+ add_credit_card(post, payment)
+ end
+ end
+
+ def add_credit_card(post, payment)
+ post[:first_name] = payment.first_name
+ post[:last_name] = payment.last_name
+ post[:credit_card_num] = payment.number
+ post[:credit_card_expiry] = expdate(payment)
+ post[:cvv2] = payment.verification_value if payment.verification_value?
+ post[:mop] = creditcard_brand(payment.brand)
+ end
+
+ def add_check(post, payment)
+ post[:first_name] = payment.first_name
+ post[:last_name] = payment.last_name
+ post[:account_num] = "#{payment.routing_number}#{payment.account_number}"
+ post[:account_type] = payment.account_type.upcase
+ end
+
+ def add_store_defaults(post)
+ post[:recurring] = false
+ post[:begin_date] = Time.now.xmlschema
+ post[:end_date] = Time.now.xmlschema
+ post[:amount] = 0
+ end
+
+ def expdate(creditcard)
+ year = sprintf('%.4i', creditcard.year)
+ month = sprintf('%.2i', creditcard.month)
+
+ "#{month}/#{year[-2..-1]}"
+ end
+
+ def creditcard_brand(brand)
+ case brand
+ when 'visa' then 'VISA'
+ when 'master' then 'MC'
+ when 'discover' then 'DSC'
+ when 'american_express' then 'AMX'
+ when 'maestro' then 'MAESTR'
+ else
+ raise "Unhandled credit card brand #{brand}"
+ end
+ end
+
+ def commit(action, parameters)
+ response = parse(ssl_post(url(action), post_data(action, parameters),
+ { 'Content-Type' => 'application/soap+xml; charset=utf-8'}))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(action, response),
+ test: test?
+ )
+ end
+
+ def endpoints
+ {
+ purchase: 'ProcessLink.asmx',
+ purchase_check: 'ProcessLink.asmx',
+ refund: 'ProcessLink.asmx',
+ refund_check: 'ProcessLink.asmx',
+ store: 'CustomerLink.asmx',
+ unstore: 'CustomerLink.asmx'
+ }
+ end
+
+ def url(action)
+ base_url = @options[:region] == 'uk' ? live_uk_url : live_na_url
+ "#{base_url}/#{endpoints[action]}?op=#{ACTIONS[action]}"
+ end
+
+ def parse(body)
+ response = {}
+ hashify_xml!(body, response)
+ response
+ end
+
+ def dexmlize_param_name(name)
+ names = {
+ 'AUTHORIZATIONRESULT' => :authorization_result,
+ 'SETTLEMENTBATCHDATE' => :settlement_batch_date,
+ 'SETTLEMENTDATE' => :settlement_date,
+ 'TRANSACTIONID' => :transaction_id
+ }
+ names[name] || name.to_s.downcase.intern
+ end
+
+ def hashify_xml!(xml, response)
+ xml = REXML::Document.new(xml)
+
+ xml.elements.each('//IATSRESPONSE/*') do |node|
+ recursively_parse_element(node, response)
+ end
+ end
+
+ def recursively_parse_element(node, response)
+ if(node.has_elements?)
+ node.elements.each { |n| recursively_parse_element(n, response) }
+ else
+ response[dexmlize_param_name(node.name)] = (node.text ? node.text.strip : nil)
+ end
+ end
+
+ def successful_result_message?(response)
+ response[:authorization_result] ? response[:authorization_result].start_with?('OK') : false
+ end
+
+ def success_from(response)
+ response[:status] == 'Success' && successful_result_message?(response)
+ end
+
+ def message_from(response)
+ if !successful_result_message?(response) && response[:authorization_result]
+ return response[:authorization_result].strip
+ elsif(response[:status] == 'Failure')
+ return response[:errors]
+ else
+ response[:status]
+ end
+ end
+
+ def authorization_from(action, response)
+ if [:store, :unstore].include?(action)
+ response[:customercode]
+ elsif [:purchase_check].include?(action)
+ response[:transaction_id] ? "#{response[:transaction_id]}|check" : nil
+ else
+ response[:transaction_id]
+ end
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def envelope_namespaces
+ {
+ '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'
+ }
+ end
+
+ def post_data(action, parameters = {})
+ xml = Builder::XmlMarkup.new
+ xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
+ xml.tag! 'soap12:Envelope', envelope_namespaces do
+ xml.tag! 'soap12:Body' do
+ xml.tag! ACTIONS[action], { 'xmlns' => 'https://www.iatspayments.com/NetGate/' } do
+ xml.tag!('agentCode', @options[:agent_code])
+ xml.tag!('password', @options[:password])
+ parameters.each do |name, value|
+ xml.tag!(xmlize_param_name(name), value)
+ end
+ end
+ end
+ end
+ xml.target!
+ end
+
+ def xmlize_param_name(name)
+ names = { customer_ip_address: 'customerIPAddress' }
+ names[name] || name.to_s.camelcase(:lower)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/ideal/ideal_base.rb b/lib/active_merchant/billing/gateways/ideal/ideal_base.rb
deleted file mode 100644
index 360f0ad5e7f..00000000000
--- a/lib/active_merchant/billing/gateways/ideal/ideal_base.rb
+++ /dev/null
@@ -1,249 +0,0 @@
-require File.dirname(__FILE__) + '/ideal_response'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- # Implementation contains some simplifications
- # - does not support multiple subID per merchant
- # - language is fixed to 'nl'
- class IdealBaseGateway < Gateway
- class_attribute :server_pem, :pem_password, :default_expiration_period
- self.default_expiration_period = 'PT10M'
- self.default_currency = 'EUR'
- self.pem_password = true
-
- self.abstract_class = true
-
- # These constants will never change for most users
- AUTHENTICATION_TYPE = 'SHA1_RSA'
- LANGUAGE = 'nl'
- SUB_ID = '0'
- API_VERSION = '1.1.0'
-
- def initialize(options = {})
- requires!(options, :login, :password, :pem)
-
- options[:pem_password] = options[:password]
- super
- end
-
- # Setup transaction. Get redirect_url from response.service_url
- def setup_purchase(money, options = {})
- requires!(options, :issuer_id, :return_url, :order_id, :currency, :description, :entrance_code)
-
- commit(build_transaction_request(money, options))
- end
-
- # Check status of transaction and confirm payment
- # transaction_id must be a valid transaction_id from a prior setup.
- def capture(transaction, options = {})
- options[:transaction_id] = transaction
- commit(build_status_request(options))
- end
-
- # Get list of issuers from response.issuer_list
- def issuers
- commit(build_directory_request)
- end
-
- private
-
- def url
- (test? ? test_url : live_url)
- end
-
- def token
- if @token.nil?
- @token = create_fingerprint(@options[:pem])
- end
- @token
- end
-
- #
- #
- # 2001-12-17T09:30:47.0Z
- #
- # 1003
- #
- #
- # 000123456
- # 0
- # passkey
- # 1
- # 3823ad872eff23
- # https://www.mijnwinkel.nl/betaalafhandeling
- #
- #
- #
- # iDEAL-aankoop 21
- # 5999
- # EUR
- # PT3M30S
- # nl
- # Documentensuite
- # D67tyx6rw9IhY71
- #
- #
- def build_transaction_request(money, options)
- date_time_stamp = create_time_stamp
- message = date_time_stamp +
- options[:issuer_id] +
- @options[:login] +
- SUB_ID +
- options[:return_url] +
- options[:order_id] +
- money.to_s +
- (options[:currency] || currency(money)) +
- LANGUAGE +
- options[:description] +
- options[:entrance_code]
- token_code = sign_message(@options[:pem], @options[:password], message)
-
- xml = Builder::XmlMarkup.new(:indent => 2)
- xml.instruct!
- xml.tag! 'AcquirerTrxReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do
- xml.tag! 'createDateTimeStamp', date_time_stamp
- xml.tag! 'Issuer' do
- xml.tag! 'issuerID', options[:issuer_id]
- end
- xml.tag! 'Merchant' do
- xml.tag! 'merchantID', @options[:login]
- xml.tag! 'subID', SUB_ID
- xml.tag! 'authentication', AUTHENTICATION_TYPE
- xml.tag! 'token', token
- xml.tag! 'tokenCode', token_code
- xml.tag! 'merchantReturnURL', options[:return_url]
- end
- xml.tag! 'Transaction' do
- xml.tag! 'purchaseID', options[:order_id]
- xml.tag! 'amount', money
- xml.tag! 'currency', options[:currency]
- xml.tag! 'expirationPeriod', options[:expiration_period] || default_expiration_period
- xml.tag! 'language', LANGUAGE
- xml.tag! 'description', options[:description]
- xml.tag! 'entranceCode', options[:entrance_code]
- end
- xml.target!
- end
- end
-
- #
- #
- # 2001-12-17T09:30:47.0Z
- #
- # 000123456
- # 0
- # keyed hash
- # 1
- # 3823ad872eff23
- #
- #
- # 0001023456789112
- #
- #
- def build_status_request(options)
- datetimestamp = create_time_stamp
- message = datetimestamp + @options[:login] + SUB_ID + options[:transaction_id]
- tokenCode = sign_message(@options[:pem], @options[:password], message)
-
- xml = Builder::XmlMarkup.new(:indent => 2)
- xml.instruct!
- xml.tag! 'AcquirerStatusReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do
- xml.tag! 'createDateTimeStamp', datetimestamp
- xml.tag! 'Merchant' do
- xml.tag! 'merchantID', @options[:login]
- xml.tag! 'subID', SUB_ID
- xml.tag! 'authentication' , AUTHENTICATION_TYPE
- xml.tag! 'token', token
- xml.tag! 'tokenCode', tokenCode
- end
- xml.tag! 'Transaction' do
- xml.tag! 'transactionID', options[:transaction_id]
- end
- end
- xml.target!
- end
-
- #
- #
- # 2001-12-17T09:30:47.0Z
- #
- # 000000001
- # 0
- # 1
- # hashkey
- # WajqV1a3nDen0be2r196g9FGFF=
- #
- #
- def build_directory_request
- datetimestamp = create_time_stamp
- message = datetimestamp + @options[:login] + SUB_ID
- tokenCode = sign_message(@options[:pem], @options[:password], message)
-
- xml = Builder::XmlMarkup.new(:indent => 2)
- xml.instruct!
- xml.tag! 'DirectoryReq', 'xmlns' => 'http://www.idealdesk.com/Message', 'version' => API_VERSION do
- xml.tag! 'createDateTimeStamp', datetimestamp
- xml.tag! 'Merchant' do
- xml.tag! 'merchantID', @options[:login]
- xml.tag! 'subID', SUB_ID
- xml.tag! 'authentication', AUTHENTICATION_TYPE
- xml.tag! 'token', token
- xml.tag! 'tokenCode', tokenCode
- end
- end
- xml.target!
- end
-
- def commit(request)
- raw_response = ssl_post(url, request)
- response = Hash.from_xml(raw_response.to_s)
- response_type = response.keys[0]
-
- case response_type
- when 'AcquirerTrxRes', 'DirectoryRes'
- success = true
- when 'ErrorRes'
- success = false
- when 'AcquirerStatusRes'
- raise SecurityError, "Message verification failed.", caller unless status_response_verified?(response)
- success = (response['AcquirerStatusRes']['Transaction']['status'] == 'Success')
- else
- raise ArgumentError, "Unknown response type.", caller
- end
-
- return IdealResponse.new(success, response.keys[0], response, :test => test?)
- end
-
- def create_fingerprint(cert_file)
- cert_data = OpenSSL::X509::Certificate.new(cert_file).to_s
- cert_data = cert_data.sub(/-----BEGIN CERTIFICATE-----/, '')
- cert_data = cert_data.sub(/-----END CERTIFICATE-----/, '')
- fingerprint = Base64.decode64(cert_data)
- fingerprint = Digest::SHA1.hexdigest(fingerprint)
- return fingerprint.upcase
- end
-
- def sign_message(private_key_data, password, data)
- private_key = OpenSSL::PKey::RSA.new(private_key_data, password)
- signature = private_key.sign(OpenSSL::Digest::SHA1.new, data.gsub('\s', ''))
- return Base64.encode64(signature).gsub(/\n/, '')
- end
-
- def verify_message(cert_file, data, signature)
- public_key = OpenSSL::X509::Certificate.new(cert_file).public_key
- return public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), data)
- end
-
- def status_response_verified?(response)
- transaction = response['AcquirerStatusRes']['Transaction']
- message = response['AcquirerStatusRes']['createDateTimeStamp'] + transaction['transactionID' ] + transaction['status']
- message << transaction['consumerAccountNumber'].to_s
- verify_message(server_pem, message, response['AcquirerStatusRes']['Signature']['signatureValue'])
- end
-
- def create_time_stamp
- Time.now.gmtime.strftime('%Y-%m-%dT%H:%M:%S.000Z')
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem b/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem
deleted file mode 100755
index 3259cfa330a..00000000000
--- a/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem
+++ /dev/null
@@ -1,13 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICQDCCAakCBELvbPYwDQYJKoZIhvcNAQEEBQAwZzELMAkGA1UEBhMCREUxDzANBgNVBAgTBkhl
-c3NlbjESMBAGA1UEBxMJRnJhbmtmdXJ0MQ4wDAYDVQQKEwVpREVBTDEOMAwGA1UECxMFaURFQUwx
-EzARBgNVBAMTCmlERUFMIFJhYm8wHhcNMDUwODAyMTI1NDE0WhcNMTUwNzMxMTI1NDE0WjBnMQsw
-CQYDVQQGEwJERTEPMA0GA1UECBMGSGVzc2VuMRIwEAYDVQQHEwlGcmFua2Z1cnQxDjAMBgNVBAoT
-BWlERUFMMQ4wDAYDVQQLEwVpREVBTDETMBEGA1UEAxMKaURFQUwgUmFibzCBnzANBgkqhkiG9w0B
-AQEFAAOBjQAwgYkCgYEA486iIKVhr8RNjxH+PZ3yTWx/8k2fqDFm8XU8I1Z5esRmPFnXmlgA8cG7
-e9AaBPaLoP7Dc8dRQoUO66KMakzGI/WAVdHIJiiKrz8xOcioIgrzPSqec7aqse3J28UraEHkGESJ
-7dAW7Pw/shrmpmkzKsunLt6AkXss4W3JUndZUN0CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCGy/FK
-Lotp2ZOTtuLMgvDy74eicq/Znv4bLfpglzAPHycRHcHsXuer/lNHyvpKf2gdYe+IFalUW3OJZWIM
-jpm4UniJ16RPdgwWVRJEdPr/P7JXMIqD2IEOgujuuTQ7x0VgCf9XrsPsP9ZR5DIPcDDhbrpSE0yF
-Do77nwG61xMaGA==
------END CERTIFICATE-----
diff --git a/lib/active_merchant/billing/gateways/ideal/ideal_response.rb b/lib/active_merchant/billing/gateways/ideal/ideal_response.rb
deleted file mode 100644
index e050964ae40..00000000000
--- a/lib/active_merchant/billing/gateways/ideal/ideal_response.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class IdealResponse < Response
-
- def issuer_list
- list = @params.values[0]['Directory']['Issuer']
- case list
- when Hash
- return [list]
- when Array
- return list
- end
- end
-
- def service_url
- @params.values[0]['Issuer']['issuerAuthenticationURL']
- end
-
- def transaction
- @params.values[0]['Transaction']
- end
-
- def error
- @params.values[0]['Error']
- end
-
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/gateways/ideal_rabobank.rb b/lib/active_merchant/billing/gateways/ideal_rabobank.rb
deleted file mode 100644
index 1edf7bfbcf8..00000000000
--- a/lib/active_merchant/billing/gateways/ideal_rabobank.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require File.dirname(__FILE__) + '/ideal/ideal_base'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- # First, make sure you have everything setup correctly and all of your dependencies in place with:
- #
- # require 'rubygems'
- # require 'active_merchant'
- #
- # ActiveMerchant expects the amounts to be given as an Integer in cents. In this case, 10 EUR becomes 1000.
- #
- # Create certificates for authentication:
- #
- # The PEM file expected should contain both the certificate and the generated PEM file.
- # Some sample shell commands to generate the certificates:
- #
- # openssl genrsa -aes128 -out priv.pem -passout pass:[YOUR PASSWORD] 1024
- # openssl req -x509 -new -key priv.pem -passin pass:[YOUR PASSWORD] -days 3000 -out cert.cer
- # cat cert.cer priv.pem > ideal.pem
- #
- # Following the steps above, upload cert.cer to the ideal web interface and pass the path of ideal.pem to the :pem option.
- #
- # Configure the gateway using your iDEAL bank account info and security settings:
- #
- # Create gateway:
- # gateway = ActiveMerchant::Billing::IdealRabobankGateway.new(
- # :login => '123456789', # 9 digit merchant number
- # :pem => File.read(Rails.root + 'config/ideal.pem'),
- # :password => 'password' # password for the PEM key
- # )
- #
- # Get list of issuers to fill selection list on your payment form:
- # response = gateway.issuers
- # list = response.issuer_list
- #
- # Request transaction:
- #
- # options = {
- # :issuer_id => '0001',
- # :expiration_period => 'PT10M',
- # :return_url => 'http://www.return.url',
- # :order_id => '1234567890123456',
- # :currency => 'EUR',
- # :description => 'Een omschrijving',
- # :entrance_code => '1234'
- # }
- #
- # response = gateway.setup_purchase(amount, options)
- # transaction_id = response.transaction['transactionID']
- # redirect_url = response.service_url
- #
- # Mandatory status request will confirm transaction:
- # response = gateway.capture(transaction_id)
- #
- # Implementation contains some simplifications
- # - does not support multiple subID per merchant
- # - language is fixed to 'nl'
- class IdealRabobankGateway < IdealBaseGateway
- class_attribute :test_url, :live_url
-
- self.test_url = 'https://idealtest.rabobank.nl/ideal/iDeal'
- self.live_url = 'https://ideal.rabobank.nl/ideal/iDeal'
- self.server_pem = File.read(File.dirname(__FILE__) + '/ideal/ideal_rabobank.pem')
- end
- end
-end
diff --git a/lib/active_merchant/billing/gateways/in_context_paypal_express.rb b/lib/active_merchant/billing/gateways/in_context_paypal_express.rb
new file mode 100644
index 00000000000..e746d978ca0
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/in_context_paypal_express.rb
@@ -0,0 +1,15 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class InContextPaypalExpressGateway < PaypalExpressGateway
+ self.test_redirect_url = 'https://www.sandbox.paypal.com/checkoutnow'
+ self.live_redirect_url = 'https://www.paypal.com/checkoutnow'
+
+ def redirect_url_for(token, options = {})
+ options = {review: true}.update(options)
+ url = "#{redirect_url}?token=#{token}"
+ url += '&useraction=commit' unless options[:review]
+ url
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/inspire.rb b/lib/active_merchant/billing/gateways/inspire.rb
index a3646b7ff02..e7771e9ec81 100644
--- a/lib/active_merchant/billing/gateways/inspire.rb
+++ b/lib/active_merchant/billing/gateways/inspire.rb
@@ -17,7 +17,7 @@ class InspireGateway < Gateway
# ==== Options
#
# * :login -- The Inspire Username.
- # * :password -- The Inspire Passowrd.
+ # * :password -- The Inspire Password.
# See the Inspire Integration Guide for details. (default: +false+)
def initialize(options = {})
requires!(options, :login, :password)
@@ -33,7 +33,7 @@ def initialize(options = {})
def authorize(money, creditcard, options = {})
post = {}
add_invoice(post, options)
- add_payment_source(post, creditcard,options)
+ add_payment_source(post, creditcard, options)
add_address(post, creditcard, options)
add_customer_data(post, options)
@@ -62,12 +62,18 @@ def void(authorization, options = {})
commit('void', nil, post)
end
+ def refund(money, authorization, options = {})
+ post = {}
+ post[:transactionid] = authorization
+ commit('refund', money, post)
+ end
+
# Update the values (such as CC expiration) stored at
# InspireGateway. The CC number must be supplied in the
# CreditCard object.
def update(vault_id, creditcard, options = {})
post = {}
- post[:customer_vault] = "update_customer"
+ post[:customer_vault] = 'update_customer'
add_customer_vault_id(post, vault_id)
add_creditcard(post, creditcard, options)
add_address(post, creditcard, options)
@@ -78,7 +84,7 @@ def update(vault_id, creditcard, options = {})
def delete(vault_id)
post = {}
- post[:customer_vault] = "delete_customer"
+ post[:customer_vault] = 'delete_customer'
add_customer_vault_id(post, vault_id)
commit(nil, nil, post)
end
@@ -93,6 +99,7 @@ def store(creditcard, options = {})
alias_method :unstore, :delete
private
+
def add_customer_data(post, options)
if options.has_key? :email
post[:email] = options[:email]
@@ -129,13 +136,13 @@ def add_payment_source(params, source, options={})
end
end
- def add_customer_vault_id(params,vault_id)
+ def add_customer_vault_id(params, vault_id)
params[:customer_vault_id] = vault_id
end
- def add_creditcard(post, creditcard,options)
+ def add_creditcard(post, creditcard, options)
if options[:store]
- post[:customer_vault] = "add_customer"
+ post[:customer_vault] = 'add_customer'
post[:customer_vault_id] = options[:store] unless options[:store] == true
end
post[:ccnumber] = creditcard.number
@@ -157,7 +164,7 @@ def add_check(post, check)
def parse(body)
results = {}
body.split(/&/).each do |pair|
- key,val = pair.split(/=/)
+ key, val = pair.split(%r{=})
results[key] = val
end
@@ -167,33 +174,24 @@ def parse(body)
def commit(action, money, parameters)
parameters[:amount] = amount(money) if money
- response = parse( ssl_post(self.live_url, post_data(action,parameters)) )
+ response = parse(ssl_post(self.live_url, post_data(action, parameters)))
- Response.new(response["response"] == "1", message_from(response), response,
- :authorization => response["transactionid"],
+ Response.new(response['response'] == '1', message_from(response), response,
+ :authorization => response['transactionid'],
:test => test?,
- :cvv_result => response["cvvresponse"],
- :avs_result => { :code => response["avsresponse"] }
+ :cvv_result => response['cvvresponse'],
+ :avs_result => { :code => response['avsresponse'] }
)
-
end
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
-
def message_from(response)
- case response["responsetext"]
- when "SUCCESS","Approved"
- "This transaction has been approved"
- when "DECLINE"
- "This transaction has been declined"
+ case response['responsetext']
+ when 'SUCCESS', 'Approved'
+ 'This transaction has been approved'
+ when 'DECLINE'
+ 'This transaction has been declined'
else
- response["responsetext"]
+ response['responsetext']
end
end
@@ -203,19 +201,18 @@ def post_data(action, parameters = {})
post[:password] = @options[:password]
post[:type] = action if action
- request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&")
+ request = post.merge(parameters).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
request
end
def determine_funding_source(source)
case
when source.is_a?(String) then :vault
- when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card
+ when CreditCard.card_companies.include?(card_brand(source)) then :credit_card
when card_brand(source) == 'check' then :check
- else raise ArgumentError, "Unsupported funding source provided"
+ else raise ArgumentError, 'Unsupported funding source provided'
end
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/instapay.rb b/lib/active_merchant/billing/gateways/instapay.rb
old mode 100755
new mode 100644
index ee4ddc55007..7d18c8da05b
--- a/lib/active_merchant/billing/gateways/instapay.rb
+++ b/lib/active_merchant/billing/gateways/instapay.rb
@@ -16,8 +16,8 @@ class InstapayGateway < Gateway
# The name of the gateway
self.display_name = 'InstaPay'
- SUCCESS = "Accepted"
- SUCCESS_MESSAGE = "The transaction has been approved"
+ SUCCESS = 'Accepted'
+ SUCCESS_MESSAGE = 'The transaction has been approved'
def initialize(options = {})
requires!(options, :login)
@@ -66,7 +66,7 @@ def add_reference(post, reference)
def add_customer_data(post, options)
post[:ci_email] = options[:email]
- post["ci_IP Address"] = options[:ip]
+ post['ci_IP Address'] = options[:ip]
end
def add_address(post, options)
@@ -140,7 +140,7 @@ def commit(action, parameters)
data = ssl_post self.live_url, post_data(action, parameters)
response = parse(data)
- Response.new(response[:success] , response[:message], response,
+ Response.new(response[:success], response[:message], response,
:authorization => response[:transaction_id],
:avs_result => { :code => response[:avs_result] },
:cvv_result => response[:cvv_result]
@@ -154,10 +154,9 @@ def post_data(action, parameters = {})
post[:merchantpin] = @options[:password]
end
post[:action] = action
- request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
request
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/ipp.rb b/lib/active_merchant/billing/gateways/ipp.rb
new file mode 100644
index 00000000000..fa672636787
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/ipp.rb
@@ -0,0 +1,176 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class IppGateway < Gateway
+ self.live_url = 'https://www.ippayments.com.au/interface/api/dts.asmx'
+ self.test_url = 'https://demo.ippayments.com.au/interface/api/dts.asmx'
+
+ self.supported_countries = ['AU']
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
+
+ self.homepage_url = 'http://www.ippayments.com.au/'
+ self.display_name = 'IPP'
+
+ self.money_format = :cents
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '05' => STANDARD_ERROR_CODE[:card_declined],
+ '06' => STANDARD_ERROR_CODE[:processing_error],
+ '14' => STANDARD_ERROR_CODE[:invalid_number],
+ '54' => STANDARD_ERROR_CODE[:expired_card],
+ }
+
+ def initialize(options={})
+ ActiveMerchant.deprecated('IPP gateway is now named Bambora Asia-Pacific')
+ requires!(options, :username, :password)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ commit('SubmitSinglePayment') do |xml|
+ xml.Transaction do
+ xml.CustRef options[:order_id]
+ add_amount(xml, money)
+ xml.TrnType '1'
+ add_credit_card(xml, payment)
+ add_credentials(xml)
+ xml.TrnSource options[:ip]
+ end
+ end
+ end
+
+ def authorize(money, payment, options={})
+ commit('SubmitSinglePayment') do |xml|
+ xml.Transaction do
+ xml.CustRef options[:order_id]
+ add_amount(xml, money)
+ xml.TrnType '2'
+ add_credit_card(xml, payment)
+ add_credentials(xml)
+ xml.TrnSource options[:ip]
+ end
+ end
+ end
+
+ def capture(money, authorization, options={})
+ commit('SubmitSingleCapture') do |xml|
+ xml.Capture do
+ xml.Receipt authorization
+ add_amount(xml, money)
+ add_credentials(xml)
+ end
+ end
+ end
+
+ def refund(money, authorization, options={})
+ commit('SubmitSingleRefund') do |xml|
+ xml.Refund do
+ xml.Receipt authorization
+ add_amount(xml, money)
+ add_credentials(xml)
+ end
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_credentials(xml)
+ xml.Security do
+ xml.UserName @options[:username]
+ xml.Password @options[:password]
+ end
+ end
+
+ def add_amount(xml, money)
+ xml.Amount amount(money)
+ end
+
+ def add_credit_card(xml, payment)
+ xml.CreditCard :Registered => 'False' do
+ xml.CardNumber payment.number
+ xml.ExpM format(payment.month, :two_digits)
+ xml.ExpY format(payment.year, :four_digits)
+ xml.CVN payment.verification_value
+ xml.CardHolderName payment.name
+ end
+ end
+
+ def parse(body)
+ element = Nokogiri::XML(body).root.first_element_child.first_element_child
+
+ response = {}
+ doc = Nokogiri::XML(element)
+ doc.root.elements.each do |e|
+ response[e.name.underscore.to_sym] = e.inner_text
+ end
+ response
+ end
+
+ def commit(action, &block)
+ headers = {
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'SOAPAction' => "http://www.ippayments.com.au/interface/api/dts/#{action}",
+ }
+ response = parse(ssl_post(commit_url, new_submit_xml(action, &block), headers))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ error_code: error_code_from(response),
+ test: test?
+ )
+ end
+
+ def new_submit_xml(action)
+ xml = Builder::XmlMarkup.new(indent: 2)
+ xml.instruct!
+ xml.soap :Envelope, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
+ xml.soap :Body do
+ xml.__send__(action, 'xmlns' => 'http://www.ippayments.com.au/interface/api/dts') do
+ xml.trnXML do
+ inner_xml = Builder::XmlMarkup.new(indent: 2)
+ yield(inner_xml)
+ xml.cdata!(inner_xml.target!)
+ end
+ end
+ end
+ end
+ xml.target!
+ end
+
+ def commit_url
+ (test? ? test_url : live_url)
+ end
+
+ def success_from(response)
+ (response[:response_code] == '0')
+ end
+
+ def error_code_from(response)
+ STANDARD_ERROR_CODE_MAPPING[response[:declined_code]]
+ end
+
+ def message_from(response)
+ response[:declined_message]
+ end
+
+ def authorization_from(response)
+ response[:receipt]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/iridium.rb b/lib/active_merchant/billing/gateways/iridium.rb
index f0156b3d543..6b6eda3805b 100644
--- a/lib/active_merchant/billing/gateways/iridium.rb
+++ b/lib/active_merchant/billing/gateways/iridium.rb
@@ -15,7 +15,7 @@ class IridiumGateway < Gateway
self.money_format = :cents
# The card types supported by the payment gateway
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :solo, :diners_club]
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :diners_club]
# The homepage URL of the gateway
self.homepage_url = 'http://www.iridiumcorp.co.uk/'
@@ -24,13 +24,202 @@ class IridiumGateway < Gateway
self.display_name = 'Iridium'
CURRENCY_CODES = {
- "AUD" => '036',
- "CAD" => '124',
- "EUR" => '978',
- "GBP" => '826',
- "MXN" => '484',
- "NZD" => '554',
- "USD" => '840',
+ 'AED' => '784',
+ 'AFN' => '971',
+ 'ALL' => '008',
+ 'AMD' => '051',
+ 'ANG' => '532',
+ 'AOA' => '973',
+ 'ARS' => '032',
+ 'AUD' => '036',
+ 'AWG' => '533',
+ 'AZN' => '944',
+ 'BAM' => '977',
+ 'BBD' => '052',
+ 'BDT' => '050',
+ 'BGN' => '975',
+ 'BHD' => '048',
+ 'BIF' => '108',
+ 'BMD' => '060',
+ 'BND' => '096',
+ 'BOB' => '068',
+ 'BOV' => '984',
+ 'BRL' => '986',
+ 'BSD' => '044',
+ 'BTN' => '064',
+ 'BWP' => '072',
+ 'BYR' => '974',
+ 'BZD' => '084',
+ 'CAD' => '124',
+ 'CDF' => '976',
+ 'CHE' => '947',
+ 'CHF' => '756',
+ 'CHW' => '948',
+ 'CLF' => '990',
+ 'CLP' => '152',
+ 'CNY' => '156',
+ 'COP' => '170',
+ 'COU' => '970',
+ 'CRC' => '188',
+ 'CUP' => '192',
+ 'CVE' => '132',
+ 'CYP' => '196',
+ 'CZK' => '203',
+ 'DJF' => '262',
+ 'DKK' => '208',
+ 'DOP' => '214',
+ 'DZD' => '012',
+ 'EEK' => '233',
+ 'EGP' => '818',
+ 'ERN' => '232',
+ 'ETB' => '230',
+ 'EUR' => '978',
+ 'FJD' => '242',
+ 'FKP' => '238',
+ 'GBP' => '826',
+ 'GEL' => '981',
+ 'GHS' => '288',
+ 'GIP' => '292',
+ 'GMD' => '270',
+ 'GNF' => '324',
+ 'GTQ' => '320',
+ 'GYD' => '328',
+ 'HKD' => '344',
+ 'HNL' => '340',
+ 'HRK' => '191',
+ 'HTG' => '332',
+ 'HUF' => '348',
+ 'IDR' => '360',
+ 'ILS' => '376',
+ 'INR' => '356',
+ 'IQD' => '368',
+ 'IRR' => '364',
+ 'ISK' => '352',
+ 'JMD' => '388',
+ 'JOD' => '400',
+ 'JPY' => '392',
+ 'KES' => '404',
+ 'KGS' => '417',
+ 'KHR' => '116',
+ 'KMF' => '174',
+ 'KPW' => '408',
+ 'KRW' => '410',
+ 'KWD' => '414',
+ 'KYD' => '136',
+ 'KZT' => '398',
+ 'LAK' => '418',
+ 'LBP' => '422',
+ 'LKR' => '144',
+ 'LRD' => '430',
+ 'LSL' => '426',
+ 'LTL' => '440',
+ 'LVL' => '428',
+ 'LYD' => '434',
+ 'MAD' => '504',
+ 'MDL' => '498',
+ 'MGA' => '969',
+ 'MKD' => '807',
+ 'MMK' => '104',
+ 'MNT' => '496',
+ 'MOP' => '446',
+ 'MRO' => '478',
+ 'MTL' => '470',
+ 'MUR' => '480',
+ 'MVR' => '462',
+ 'MWK' => '454',
+ 'MXN' => '484',
+ 'MXV' => '979',
+ 'MYR' => '458',
+ 'MZN' => '943',
+ 'NAD' => '516',
+ 'NGN' => '566',
+ 'NIO' => '558',
+ 'NOK' => '578',
+ 'NPR' => '524',
+ 'NZD' => '554',
+ 'OMR' => '512',
+ 'PAB' => '590',
+ 'PEN' => '604',
+ 'PGK' => '598',
+ 'PHP' => '608',
+ 'PKR' => '586',
+ 'PLN' => '985',
+ 'PYG' => '600',
+ 'QAR' => '634',
+ 'ROL' => '642',
+ 'RON' => '946',
+ 'RSD' => '941',
+ 'RUB' => '643',
+ 'RWF' => '646',
+ 'SAR' => '682',
+ 'SBD' => '090',
+ 'SCR' => '690',
+ 'SDG' => '938',
+ 'SEK' => '752',
+ 'SGD' => '702',
+ 'SHP' => '654',
+ 'SKK' => '703',
+ 'SLL' => '694',
+ 'SOS' => '706',
+ 'SRD' => '968',
+ 'STD' => '678',
+ 'SYP' => '760',
+ 'SZL' => '748',
+ 'THB' => '764',
+ 'TJS' => '972',
+ 'TMM' => '795',
+ 'TND' => '788',
+ 'TOP' => '776',
+ 'TRY' => '949',
+ 'TTD' => '780',
+ 'TWD' => '901',
+ 'TZS' => '834',
+ 'UAH' => '980',
+ 'UGX' => '800',
+ 'USD' => '840',
+ 'USN' => '997',
+ 'USS' => '998',
+ 'UYU' => '858',
+ 'UZS' => '860',
+ 'VEB' => '862',
+ 'VND' => '704',
+ 'VUV' => '548',
+ 'WST' => '882',
+ 'XAF' => '950',
+ 'XAG' => '961',
+ 'XAU' => '959',
+ 'XBA' => '955',
+ 'XBB' => '956',
+ 'XBC' => '957',
+ 'XBD' => '958',
+ 'XCD' => '951',
+ 'XDR' => '960',
+ 'XOF' => '952',
+ 'XPD' => '964',
+ 'XPF' => '953',
+ 'XPT' => '962',
+ 'XTS' => '963',
+ 'XXX' => '999',
+ 'YER' => '886',
+ 'ZAR' => '710',
+ 'ZMK' => '894',
+ 'ZWD' => '716',
+ }
+
+ AVS_CODE = {
+ 'PASSED' => 'Y',
+ 'FAILED' => 'N',
+ 'PARTIAL' => 'X',
+ 'NOT_CHECKED' => 'X',
+ 'UNKNOWN' => 'X'
+ }
+
+ CVV_CODE = {
+ 'PASSED' => 'M',
+ 'FAILED' => 'N',
+ 'PARTIAL' => 'I',
+ 'NOT_CHECKED' => 'P',
+ 'UNKNOWN' => 'U'
}
def initialize(options = {})
@@ -63,7 +252,7 @@ def capture(money, authorization, options = {})
end
def credit(money, authorization, options={})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
@@ -75,10 +264,21 @@ def void(authorization, options={})
commit(build_reference_request('VOID', nil, authorization, options), options)
end
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(()\d+()), '\1[FILTERED]\2').
+ gsub(%r(()\d+()), '\1[FILTERED]\2')
+ end
+
private
def build_purchase_request(type, money, creditcard, options)
- options.merge!(:action => 'CardDetailsTransaction')
+ options[:action] = 'CardDetailsTransaction'
build_request(options) do |xml|
add_purchase_data(xml, type, money, options)
add_creditcard(xml, creditcard)
@@ -87,8 +287,8 @@ def build_purchase_request(type, money, creditcard, options)
end
def build_reference_request(type, money, authorization, options)
- options.merge!(:action => 'CrossReferenceTransaction')
- order_id, cross_reference, auth_id = authorization.split(";")
+ options[:action] = 'CrossReferenceTransaction'
+ order_id, cross_reference, _ = authorization.split(';')
build_request(options) do |xml|
if money
details = {'CurrencyCode' => currency_code(options[:currency] || default_currency), 'Amount' => amount(money)}
@@ -110,7 +310,7 @@ def build_request(options)
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do
xml.tag! 'soap:Body' do
- xml.tag! options[:action], {'xmlns' => "https://www.thepaymentgateway.net/"} do
+ xml.tag! options[:action], {'xmlns' => 'https://www.thepaymentgateway.net/'} do
xml.tag! 'PaymentMessage' do
add_merchant_data(xml, options)
yield(xml)
@@ -157,7 +357,7 @@ def add_customerdetails(xml, creditcard, address, options, shipTo = false)
end
xml.tag! 'EmailAddress', options[:email]
- xml.tag! 'CustomerIPAddress', options[:ip] || "127.0.0.1"
+ xml.tag! 'CustomerIPAddress', options[:ip] || '127.0.0.1'
end
end
@@ -166,34 +366,40 @@ def add_creditcard(xml, creditcard)
xml.tag! 'CardName', creditcard.name
xml.tag! 'CV2', creditcard.verification_value if creditcard.verification_value
xml.tag! 'CardNumber', creditcard.number
- xml.tag! 'ExpiryDate', { 'Month' => creditcard.month.to_s.rjust(2, "0"), 'Year' => creditcard.year.to_s[/\d\d$/] }
+ xml.tag! 'ExpiryDate', { 'Month' => creditcard.month.to_s.rjust(2, '0'), 'Year' => creditcard.year.to_s[/\d\d$/] }
end
end
def add_merchant_data(xml, options)
- xml.tag! 'MerchantAuthentication', {"MerchantID" => @options[:login], "Password" => @options[:password]}
+ xml.tag! 'MerchantAuthentication', {'MerchantID' => @options[:login], 'Password' => @options[:password]}
end
def commit(request, options)
requires!(options, :action)
response = parse(ssl_post(test? ? self.test_url : self.live_url, request,
- {"SOAPAction" => "https://www.thepaymentgateway.net/#{options[:action]}",
- "Content-Type" => "text/xml; charset=utf-8" }))
+ {'SOAPAction' => 'https://www.thepaymentgateway.net/' + options[:action],
+ 'Content-Type' => 'text/xml; charset=utf-8' }))
- success = response[:transaction_result][:status_code] == "0"
+ success = response[:transaction_result][:status_code] == '0'
message = response[:transaction_result][:message]
- authorization = success ? [ options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code] ].compact.join(";") : nil
+ authorization = success ? [ options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code] ].compact.join(';') : nil
Response.new(success, message, response,
:test => test?,
- :authorization => authorization)
+ :authorization => authorization,
+ :avs_result => {
+ :street_match => AVS_CODE[ response[:transaction_output_data][:address_numeric_check_result] ],
+ :postal_match => AVS_CODE[ response[:transaction_output_data][:post_code_check_result] ],
+ },
+ :cvv_result => CVV_CODE[ response[:transaction_output_data][:cv2_check_result] ]
+ )
end
def parse(xml)
reply = {}
xml = REXML::Document.new(xml)
- if (root = REXML::XPath.first(xml, "//CardDetailsTransactionResponse")) or
- (root = REXML::XPath.first(xml, "//CrossReferenceTransactionResponse"))
+ if (root = REXML::XPath.first(xml, '//CardDetailsTransactionResponse')) or
+ (root = REXML::XPath.first(xml, '//CrossReferenceTransactionResponse'))
root.elements.to_a.each do |node|
case node.name
when 'Message'
@@ -202,7 +408,7 @@ def parse(xml)
parse_element(reply, node)
end
end
- elsif root = REXML::XPath.first(xml, "//soap:Fault")
+ elsif root = REXML::XPath.first(xml, '//soap:Fault')
parse_element(reply, root)
reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}"
end
@@ -211,41 +417,41 @@ def parse(xml)
def parse_element(reply, node)
case node.name
- when "CrossReferenceTransactionResult"
+ when 'CrossReferenceTransactionResult'
reply[:transaction_result] = {}
- node.attributes.each do |a,b|
+ node.attributes.each do |a, b|
reply[:transaction_result][a.underscore.to_sym] = b
end
- node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements?
+ node.elements.each { |e| parse_element(reply[:transaction_result], e) } if node.has_elements?
- when "CardDetailsTransactionResult"
+ when 'CardDetailsTransactionResult'
reply[:transaction_result] = {}
- node.attributes.each do |a,b|
+ node.attributes.each do |a, b|
reply[:transaction_result][a.underscore.to_sym] = b
end
- node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements?
+ node.elements.each { |e| parse_element(reply[:transaction_result], e) } if node.has_elements?
- when "TransactionOutputData"
+ when 'TransactionOutputData'
reply[:transaction_output_data] = {}
- node.attributes.each{|a,b| reply[:transaction_output_data][a.underscore.to_sym] = b }
- node.elements.each{|e| parse_element(reply[:transaction_output_data], e) } if node.has_elements?
- when "CustomVariables"
+ node.attributes.each { |a, b| reply[:transaction_output_data][a.underscore.to_sym] = b }
+ node.elements.each { |e| parse_element(reply[:transaction_output_data], e) } if node.has_elements?
+ when 'CustomVariables'
reply[:custom_variables] = {}
- node.attributes.each{|a,b| reply[:custom_variables][a.underscore.to_sym] = b }
- node.elements.each{|e| parse_element(reply[:custom_variables], e) } if node.has_elements?
- when "GatewayEntryPoints"
+ node.attributes.each { |a, b| reply[:custom_variables][a.underscore.to_sym] = b }
+ node.elements.each { |e| parse_element(reply[:custom_variables], e) } if node.has_elements?
+ when 'GatewayEntryPoints'
reply[:gateway_entry_points] = {}
- node.attributes.each{|a,b| reply[:gateway_entry_points][a.underscore.to_sym] = b }
- node.elements.each{|e| parse_element(reply[:gateway_entry_points], e) } if node.has_elements?
+ node.attributes.each { |a, b| reply[:gateway_entry_points][a.underscore.to_sym] = b }
+ node.elements.each { |e| parse_element(reply[:gateway_entry_points], e) } if node.has_elements?
else
k = node.name.underscore.to_sym
if node.has_elements?
reply[k] = {}
- node.elements.each{|e| parse_element(reply[k], e) }
+ node.elements.each { |e| parse_element(reply[k], e) }
else
if node.has_attributes?
reply[k] = {}
- node.attributes.each{|a,b| reply[k][a.underscore.to_sym] = b }
+ node.attributes.each { |a, b| reply[k][a.underscore.to_sym] = b }
else
reply[k] = node.text
end
diff --git a/lib/active_merchant/billing/gateways/itransact.rb b/lib/active_merchant/billing/gateways/itransact.rb
index 0d96f7556e6..8bac0734e0a 100644
--- a/lib/active_merchant/billing/gateways/itransact.rb
+++ b/lib/active_merchant/billing/gateways/itransact.rb
@@ -302,7 +302,7 @@ def add_invoice(xml, money, options)
xml.AuthCode options[:force] if options[:force]
if options[:order_items].blank?
xml.Total(amount(money)) unless(money.nil? || money < 0.01)
- xml.Description(options[:description]) unless( options[:description].blank?)
+ xml.Description(options[:description]) unless(options[:description].blank?)
else
xml.OrderItems {
options[:order_items].each do |item|
@@ -336,7 +336,7 @@ def add_creditcard(xml, creditcard)
xml.AccountInfo {
xml.CardAccount {
xml.AccountNumber(creditcard.number.to_s)
- xml.ExpirationMonth(creditcard.month.to_s.rjust(2,'0'))
+ xml.ExpirationMonth(creditcard.month.to_s.rjust(2, '0'))
xml.ExpirationYear(creditcard.year.to_s)
xml.CVVNumber(creditcard.verification_value.to_s) unless creditcard.verification_value.blank?
}
@@ -372,7 +372,7 @@ def add_transaction_control(xml, options)
def add_vendor_data(xml, options)
return if options[:vendor_data].blank?
xml.VendorData {
- options[:vendor_data].each do |k,v|
+ options[:vendor_data].each do |k, v|
xml.Element {
xml.Name(k)
xml.Key(v)
@@ -424,7 +424,7 @@ def parse(raw_xml)
def successful?(response)
# Turns out the PaymentClearing gateway is not consistent...
- response[:status].downcase =='ok'
+ response[:status].casecmp('ok').zero?
end
def test_mode?(response)
@@ -445,4 +445,3 @@ def sign_payload(payload)
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/iveri.rb b/lib/active_merchant/billing/gateways/iveri.rb
new file mode 100644
index 00000000000..4a8d96e4752
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/iveri.rb
@@ -0,0 +1,251 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class IveriGateway < Gateway
+ self.live_url = self.test_url = 'https://portal.nedsecure.co.za/iVeriWebService/Service.asmx'
+
+ self.supported_countries = ['US', 'ZA', 'GB']
+ self.default_currency = 'ZAR'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express]
+
+ self.homepage_url = 'http://www.iveri.com'
+ self.display_name = 'iVeri'
+
+ def initialize(options={})
+ requires!(options, :app_id, :cert_id)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ post = build_vxml_request('Debit', options) do |xml|
+ add_auth_purchase_params(xml, money, payment_method, options)
+ end
+
+ commit(post)
+ end
+
+ def authorize(money, payment_method, options={})
+ post = build_vxml_request('Authorisation', options) do |xml|
+ add_auth_purchase_params(xml, money, payment_method, options)
+ end
+
+ commit(post)
+ end
+
+ def capture(money, authorization, options={})
+ post = build_vxml_request('Debit', options) do |xml|
+ add_authorization(xml, authorization, options)
+ end
+
+ commit(post)
+ end
+
+ def refund(money, authorization, options={})
+ post = build_vxml_request('Credit', options) do |xml|
+ add_amount(xml, money, options)
+ add_authorization(xml, authorization, options)
+ end
+
+ commit(post)
+ end
+
+ def void(authorization, options={})
+ post = build_vxml_request('Void', options) do |xml|
+ add_authorization(xml, authorization, options)
+ end
+
+ commit(post)
+ end
+
+ def verify(credit_card, options={})
+ authorize(0, credit_card, options)
+ end
+
+ def verify_credentials
+ void = void('', options)
+ return true if void.message == 'Missing OriginalMerchantTrace'
+ false
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((CertificateID=\\\")[^\\]*), '\1[FILTERED]').
+ gsub(%r((<PAN>)[^&]*), '\1[FILTERED]').
+ gsub(%r((<CardSecurityCode>)[^&]*), '\1[FILTERED]')
+ end
+
+ private
+
+ def build_xml_envelope(vxml)
+ builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
+ xml[:soap].Envelope 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' do
+ xml[:soap].Body do
+ xml.Execute 'xmlns' => 'http://iveri.com/' do
+ xml.validateRequest 'true'
+ xml.protocol 'V_XML'
+ xml.protocolVersion '2.0'
+ xml.request vxml
+ end
+ end
+ end
+ end
+
+ builder.to_xml
+ end
+
+ def build_vxml_request(action, options)
+ builder = Nokogiri::XML::Builder.new do |xml|
+ xml.V_XML('Version' => '2.0', 'CertificateID' => @options[:cert_id], 'Direction' => 'Request') do
+ xml.Transaction('ApplicationID' => @options[:app_id], 'Command' => action, 'Mode' => mode) do
+ yield(xml)
+ end
+ end
+ end
+
+ builder.doc.root.to_xml
+ end
+
+ def add_auth_purchase_params(post, money, payment_method, options)
+ add_card_holder_authentication(post, options)
+ add_amount(post, money, options)
+ add_electronic_commerce_indicator(post, options)
+ add_payment_method(post, payment_method, options)
+ end
+
+ def add_amount(post, money, options)
+ post.Amount(amount(money))
+ post.Currency(options[:currency] || default_currency)
+ end
+
+ def add_electronic_commerce_indicator(post, options)
+ post.ElectronicCommerceIndicator(options[:eci]) if options[:eci]
+ end
+
+ def add_authorization(post, authorization, options)
+ post.MerchantReference(split_auth(authorization)[2])
+ post.TransactionIndex(split_auth(authorization)[1])
+ post.OriginalRequestID(split_auth(authorization)[0])
+ end
+
+ def add_payment_method(post, payment_method, options)
+ post.ExpiryDate("#{format(payment_method.month, :two_digits)}#{payment_method.year}")
+ add_new_reference(post, options)
+ post.CardSecurityCode(payment_method.verification_value)
+ post.PAN(payment_method.number)
+ end
+
+ def add_new_reference(post, options)
+ post.MerchantReference(options[:order_id] || generate_unique_id)
+ end
+
+ def add_card_holder_authentication(post, options)
+ post.CardHolderAuthenticationID(options[:xid]) if options[:xid]
+ post.CardHolderAuthenticationData(options[:cavv]) if options[:cavv]
+ end
+
+ def commit(post)
+ raw_response = begin
+ ssl_post(live_url, build_xml_envelope(post), headers(post))
+ rescue ActiveMerchant::ResponseError => e
+ e.response.body
+ end
+
+ parsed = parse(raw_response)
+ succeeded = success_from(parsed)
+
+ Response.new(
+ succeeded,
+ message_from(parsed, succeeded),
+ parsed,
+ authorization: authorization_from(parsed),
+ error_code: error_code_from(parsed, succeeded),
+ test: test?
+ )
+ end
+
+ def mode
+ test? ? 'Test' : 'Live'
+ end
+
+ def headers(post)
+ {
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'Content-Length' => post.size.to_s,
+ 'SOAPAction' => 'http://iveri.com/Execute'
+ }
+ end
+
+ def parse(body)
+ parsed = {}
+
+ vxml = Nokogiri::XML(body).remove_namespaces!.xpath('//Envelope/Body/ExecuteResponse/ExecuteResult').inner_text
+ doc = Nokogiri::XML(vxml)
+ doc.xpath('*').each do |node|
+ if node.elements.empty?
+ parsed[underscore(node.name)] = node.text
+ else
+ node.elements.each do |childnode|
+ parse_element(parsed, childnode)
+ end
+ end
+ end
+ parsed
+ end
+
+ def parse_element(parsed, node)
+ if !node.attributes.empty?
+ node.attributes.each do |a|
+ parsed[underscore(node.name)+ '_' + underscore(a[1].name)] = a[1].value
+ end
+ end
+
+ if !node.elements.empty?
+ node.elements.each { |e| parse_element(parsed, e) }
+ else
+ parsed[underscore(node.name)] = node.text
+ end
+ end
+
+ def success_from(response)
+ response['result_status'] == '0'
+ end
+
+ def message_from(response, succeeded)
+ if succeeded
+ 'Succeeded'
+ else
+ response['result_description'] || response['result_acquirer_description']
+ end
+ end
+
+ def authorization_from(response)
+ "#{response['transaction_request_id']}|#{response['transaction_index']}|#{response['merchant_reference']}"
+ end
+
+ def split_auth(authorization)
+ request_id, transaction_index, merchant_reference = authorization.to_s.split('|')
+ [request_id, transaction_index, merchant_reference]
+ end
+
+ def error_code_from(response, succeeded)
+ unless succeeded
+ response['result_code']
+ 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
+end
diff --git a/lib/active_merchant/billing/gateways/jetpay.rb b/lib/active_merchant/billing/gateways/jetpay.rb
index 7a7002d0b04..aaa955dd24a 100644
--- a/lib/active_merchant/billing/gateways/jetpay.rb
+++ b/lib/active_merchant/billing/gateways/jetpay.rb
@@ -1,11 +1,14 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class JetpayGateway < Gateway
+ class_attribute :live_us_url, :live_ca_url
+
self.test_url = 'https://test1.jetpay.com/jetpay'
- self.live_url = 'https://gateway17.jetpay.com/jetpay'
+ self.live_us_url = 'https://gateway17.jetpay.com/jetpay'
+ self.live_ca_url = 'https://gateway17.jetpay.com/canada-bb'
# The countries the gateway supports merchants from as 2 digit ISO country codes
- self.supported_countries = ['US']
+ self.supported_countries = ['US', 'CA']
# The card types supported by the payment gateway
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
@@ -20,46 +23,132 @@ class JetpayGateway < Gateway
self.money_format = :cents
ACTION_CODE_MESSAGES = {
- "001" => "Refer to card issuer.",
- "002" => "Refer to card issuer, special condition.",
- "003" => "Pick up card.",
- "200" => "Deny - Pick up card.",
- "005" => "Do not honor.",
- "100" => "Deny.",
- "006" => "Error.",
- "181" => "Format error.",
- "007" => "Pickup card, special condition.",
- "104" => "Deny - New card issued.",
- "110" => "Invalid amount.",
- "014" => "Invalid account number (no such number).",
- "111" => "Invalid account.",
- "015" => "No such issuer.",
- "103" => "Deny - Invalid manual Entry 4DBC.",
- "182" => "Please wait.",
- "109" => "Invalid merchant.",
- "041" => "Pick up card (lost card).",
- "043" => "Pick up card (stolen card).",
- "051" => "Insufficient funds.",
- "052" => "No checking account.",
- "105" => "Deny - Account Cancelled.",
- "054" => "Expired Card.",
- "101" => "Expired Card.",
- "183" => "Invalid currency code.",
- "057" => "Transaction not permitted to cardholder.",
- "115" => "Service not permitted.",
- "062" => "Restricted card.",
- "189" => "Deny - Cancelled or Closed Merchant/SE.",
- "188" => "Deny - Expiration date required.",
- "125" => "Invalid effective date.",
- "122" => "Invalid card (CID) security code.",
- "400" => "Reversal accepted.",
- "992" => "DECLINE/TIMEOUT.",
- "107" => "Please Call Issuer.",
- "025" => "Transaction Not Found.",
- "981" => "AVS Error.",
- "913" => "Invalid Card Type.",
- "996" => "Terminal ID Not Found.",
- nil => "No response returned (missing credentials?)."
+ '000' => 'Approved.',
+ '001' => 'Refer to card issuer.',
+ '002' => 'Refer to card issuer, special condition.',
+ '003' => 'Invalid merchant or service provider.',
+ '004' => 'Pick up card.',
+ '005' => 'Do not honor.',
+ '006' => 'Error.',
+ '007' => 'Pick up card, special condition.',
+ '008' => 'Honor with ID (Show ID).',
+ '010' => 'Partial approval.',
+ '011' => 'VIP approval.',
+ '012' => 'Invalid transaction.',
+ '013' => 'Invalid amount or exceeds maximum for card program.',
+ '014' => 'Invalid account number (no such number).',
+ '015' => 'No such issuer.',
+ '019' => 'Re-enter Transaction.',
+ '021' => 'No action taken (unable to back out prior transaction).',
+ '025' => 'Transaction Not Found.',
+ '027' => 'File update field edit error.',
+ '028' => 'File is temporarily unavailable.',
+ '030' => 'Format error.',
+ '039' => 'No credit account.',
+ '041' => 'Pick up card (lost card).',
+ '043' => 'Pick up card (stolen card).',
+ '051' => 'Insufficient funds.',
+ '052' => 'No checking account.',
+ '053' => 'No savings account.',
+ '054' => 'Expired Card.',
+ '055' => 'Incorrect PIN.',
+ '057' => 'Transaction not permitted to cardholder.',
+ '058' => 'Transaction not allowed at terminal.',
+ '061' => 'Exceeds withdrawal limit.',
+ '062' => 'Restricted card (eg, Country Exclusion).',
+ '063' => 'Security violation.',
+ '065' => 'Activity count limit exceeded.',
+ '068' => 'Response late.',
+ '070' => 'Contact card issuer.',
+ '071' => 'PIN not changed.',
+ '075' => 'Allowable number of PIN-entry tries exceeded.',
+ '076' => 'Unable to locate previous message (no matching retrieval reference number).',
+ '077' => 'Repeat or reversal data are inconsistent with original message.',
+ '078' => 'Blocked (first use), or non-existent account.',
+ '079' => 'Key exchange validation failed.',
+ '080' => 'Credit issuer unavailable or invalid date.',
+ '081' => 'PIN cryptographic error found.',
+ '082' => 'Negative online CVV results.',
+ '084' => 'Invalid auth life cycle.',
+ '085' => 'No reason to decline - CVV or AVS approved.',
+ '086' => 'Cannot verify PIN.',
+ '087' => 'Cashback not allowed.',
+ '089' => 'Issuer Down.',
+ '091' => 'Issuer Down.',
+ '092' => 'Unable to route transaction.',
+ '093' => 'Transaction cannot be completed - violation of law.',
+ '094' => 'Duplicate transmission.',
+ '096' => 'System error.',
+ '100' => 'Deny.',
+ '101' => 'Expired Card.',
+ '103' => 'Deny - Invalid manual Entry 4DBC.',
+ '104' => 'Deny - New card issued.',
+ '105' => 'Deny - Account Cancelled.',
+ '106' => 'Exceeded PIN Attempts.',
+ '107' => 'Please Call Issuer.',
+ '109' => 'Invalid merchant.',
+ '110' => 'Invalid amount.',
+ '111' => 'Invalid account.',
+ '115' => 'Service not permitted.',
+ '122' => 'Invalid card (CID) security code.',
+ '125' => 'Invalid effective date.',
+ '181' => 'Format error.',
+ '182' => 'Please wait.',
+ '183' => 'Invalid currency code.',
+ '187' => 'Deny - new card issued.',
+ '188' => 'Deny - Expiration date required.',
+ '189' => 'Deny - Cancelled or Closed Merchant/SE.',
+ '200' => 'Deny - Pick up card.',
+ '400' => 'Reversal accepted.',
+ '601' => 'Reject - EMV Chip Declined Transaction.',
+ '602' => 'Reject - Suspected Fraud.',
+ '603' => 'Reject - Communications Error.',
+ '604' => 'Reject - Insufficient Approval.',
+ '750' => 'Velocity Check Fail.',
+ '899' => 'Misc Decline.',
+ '900' => 'Invalid Message Type.',
+ '901' => 'Invalid Merchant ID.',
+ '903' => 'Debit not supported.',
+ '904' => 'Private label not supported.',
+ '905' => 'Invalid card type.',
+ '906' => 'Unit not active.',
+ '908' => 'Manual card entry invalid.',
+ '909' => 'Invalid track information.',
+ '911' => 'Master merchant not found.',
+ '912' => 'Invalid card format.',
+ '913' => 'Invalid card type.',
+ '914' => 'Invalid card length.',
+ '917' => 'Expired card.',
+ '919' => 'Invalid entry type.',
+ '920' => 'Invalid amount.',
+ '921' => 'Invalid messge format.',
+ '923' => 'Invalid ABA.',
+ '924' => 'Invalid DDA.',
+ '925' => 'Invalid TID.',
+ '926' => 'Invalid Password.',
+ '930' => 'Invalid zipcode.',
+ '931' => 'Invalid Address.',
+ '932' => 'Invalid ZIP and Address.',
+ '933' => 'Invalid CVV2.',
+ '934' => 'Program Not Allowed.',
+ '940' => 'Record Not Found.',
+ '941' => 'Merchant ID error.',
+ '942' => 'Refund Not Allowed.',
+ '943' => 'Refund denied.',
+ '955' => 'Invalid PIN block.',
+ '956' => 'Invalid KSN.',
+ '958' => 'Bad Status.',
+ '959' => 'Seek Record limit exceeded.',
+ '962' => 'Invalid PIN key (Unknown KSN).',
+ '981' => 'Invalid AVS.',
+ '987' => 'Issuer Unavailable.',
+ '988' => 'System error SD.',
+ '989' => 'Database Error.',
+ '992' => 'Transaction Timeout.',
+ '996' => 'Bad Terminal ID.',
+ '997' => 'Message rejected by association.',
+ '999' => 'Communication failure',
+ nil => 'No response returned (missing credentials?).'
}
def initialize(options = {})
@@ -76,39 +165,57 @@ def authorize(money, credit_card, options = {})
end
def capture(money, reference, options = {})
- commit(money, build_capture_request('CAPT', reference.split(";").first))
+ split_authorization = reference.split(';')
+ transaction_id = split_authorization[0]
+ token = split_authorization[3]
+ commit(money, build_capture_request(transaction_id, money, options), token)
end
def void(reference, options = {})
- transaction_id, approval, amount = reference.split(";")
- commit(amount.to_i, build_void_request(amount.to_i, transaction_id, approval))
+ transaction_id, approval, amount, token = reference.split(';')
+ commit(amount.to_i, build_void_request(amount.to_i, transaction_id, approval, token, options), token)
end
def credit(money, transaction_id_or_card, options = {})
if transaction_id_or_card.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, transaction_id_or_card, options)
else
- commit(money, build_credit_request('CREDIT', money, nil, transaction_id_or_card))
+ commit(money, build_credit_request('CREDIT', money, nil, transaction_id_or_card, nil, options))
end
end
def refund(money, reference, options = {})
- transaction_id = reference.split(";").first
+ split_authorization = reference.split(';')
+ transaction_id = split_authorization[0]
+ token = split_authorization[3]
credit_card = options[:credit_card]
- commit(money, build_credit_request('CREDIT', money, transaction_id, credit_card))
+ commit(money, build_credit_request('CREDIT', money, transaction_id, credit_card, token, options))
+ end
+
+ def supports_scrubbing
+ true
end
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((>)\d+()), '\1[FILTERED]\2').
+ gsub(%r(()\d+()), '\1[FILTERED]\2')
+ end
private
- def build_xml_request(transaction_type, transaction_id = nil, &block)
+ def build_xml_request(transaction_type, options = {}, transaction_id = nil, &block)
xml = Builder::XmlMarkup.new
xml.tag! 'JetPay' do
# The basic values needed for any request
xml.tag! 'TerminalID', @options[:login]
xml.tag! 'TransactionType', transaction_type
xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id
+ if options && options[:origin]
+ xml.tag! 'Origin', options[:origin]
+ end
if block_given?
yield xml
@@ -119,11 +226,12 @@ def build_xml_request(transaction_type, transaction_id = nil, &block)
end
def build_sale_request(money, credit_card, options)
- build_xml_request('SALE') do |xml|
+ build_xml_request('SALE', options) do |xml|
add_credit_card(xml, credit_card)
add_addresses(xml, options)
add_customer_data(xml, options)
add_invoice_data(xml, options)
+ add_user_defined_fields(xml, options)
xml.tag! 'TotalAmount', amount(money)
xml.target!
@@ -136,49 +244,63 @@ def build_authonly_request(money, credit_card, options)
add_addresses(xml, options)
add_customer_data(xml, options)
add_invoice_data(xml, options)
+ add_user_defined_fields(xml, options)
xml.tag! 'TotalAmount', amount(money)
xml.target!
end
end
- def build_capture_request(transaction_type, transaction_id)
- build_xml_request(transaction_type, transaction_id)
+ def build_capture_request(transaction_id, money, options)
+ build_xml_request('CAPT', options, transaction_id) do |xml|
+ xml.tag! 'TotalAmount', amount(money)
+ add_user_defined_fields(xml, options)
+ end
end
- def build_void_request(money, transaction_id, approval)
- build_xml_request('VOID', transaction_id) do |xml|
+ def build_void_request(money, transaction_id, approval, token, options)
+ build_xml_request('VOID', options, transaction_id) do |xml|
xml.tag! 'Approval', approval
xml.tag! 'TotalAmount', amount(money)
-
+ xml.tag! 'Token', token if token
xml.target!
end
end
# `transaction_id` may be nil for unlinked credit transactions.
- def build_credit_request(transaction_type, money, transaction_id, card)
- build_xml_request(transaction_type, transaction_id) do |xml|
+ def build_credit_request(transaction_type, money, transaction_id, card, token, options)
+ build_xml_request(transaction_type, options, transaction_id) do |xml|
add_credit_card(xml, card) if card
+ add_invoice_data(xml, options)
+ add_addresses(xml, options)
+ add_customer_data(xml, options)
+ add_user_defined_fields(xml, options)
xml.tag! 'TotalAmount', amount(money)
+ xml.tag! 'Token', token if token
xml.target!
end
end
- def commit(money, request)
- response = parse(ssl_post(test? ? self.test_url : self.live_url, request))
+ def commit(money, request, token = nil)
+ response = parse(ssl_post(url, request))
success = success?(response)
Response.new(success,
success ? 'APPROVED' : message_from(response),
response,
:test => test?,
- :authorization => authorization_from(response, money),
+ :authorization => authorization_from(response, money, token),
:avs_result => { :code => response[:avs] },
:cvv_result => response[:cvv2]
)
end
+ def url
+ live_url = @options[:region] == 'CA' ? live_ca_url : live_us_url
+ test? ? test_url : live_url
+ end
+
def parse(body)
return {} if body.blank?
@@ -193,7 +315,7 @@ def parse(body)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|element| parse_element(response, element) }
+ node.elements.each { |element| parse_element(response, element) }
else
response[node.name.underscore.to_sym] = node.text
end
@@ -204,25 +326,25 @@ def format_exp(value)
end
def success?(response)
- response[:action_code] == "000"
+ response[:action_code] == '000'
end
def message_from(response)
ACTION_CODE_MESSAGES[response[:action_code]]
end
- def authorization_from(response, money)
+ def authorization_from(response, money, previous_token)
original_amount = amount(money) if money
- [ response[:transaction_id], response[:approval], original_amount ].join(";")
+ [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';')
end
def add_credit_card(xml, credit_card)
- xml.tag! 'CardNum', credit_card.number
+ xml.tag! 'CardNum', credit_card.number, 'Tokenize' => true
xml.tag! 'CardExpMonth', format_exp(credit_card.month)
xml.tag! 'CardExpYear', format_exp(credit_card.year)
if credit_card.first_name || credit_card.last_name
- xml.tag! 'CardName', [credit_card.first_name,credit_card.last_name].compact.join(' ')
+ xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ')
end
unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0)
@@ -232,7 +354,7 @@ def add_credit_card(xml, credit_card)
def add_addresses(xml, options)
if billing_address = options[:billing_address] || options[:address]
- xml.tag! 'BillingAddress', [billing_address[:address1], billing_address[:address2]].compact.join(" ")
+ xml.tag! 'BillingAddress', [billing_address[:address1], billing_address[:address2]].compact.join(' ')
xml.tag! 'BillingCity', billing_address[:city]
xml.tag! 'BillingStateProv', billing_address[:state]
xml.tag! 'BillingPostalCode', billing_address[:zip]
@@ -245,7 +367,7 @@ def add_addresses(xml, options)
xml.tag! 'ShippingName', shipping_address[:name]
xml.tag! 'ShippingAddr' do
- xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(" ")
+ xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(' ')
xml.tag! 'City', shipping_address[:city]
xml.tag! 'StateProv', shipping_address[:state]
xml.tag! 'PostalCode', shipping_address[:zip]
@@ -265,11 +387,16 @@ def add_invoice_data(xml, options)
xml.tag! 'TaxAmount', amount(options[:tax]) if options[:tax]
end
+ def add_user_defined_fields(xml, options)
+ xml.tag! 'UDField1', options[:ud_field_1] if options[:ud_field_1]
+ xml.tag! 'UDField2', options[:ud_field_2] if options[:ud_field_2]
+ xml.tag! 'UDField3', options[:ud_field_3] if options[:ud_field_3]
+ end
+
def lookup_country_code(code)
country = Country.find(code) rescue nil
- country && country.code(:alpha3)
+ country&.code(:alpha3)
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/jetpay_v2.rb b/lib/active_merchant/billing/gateways/jetpay_v2.rb
new file mode 100644
index 00000000000..515bba8fc84
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/jetpay_v2.rb
@@ -0,0 +1,437 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class JetpayV2Gateway < Gateway
+ self.test_url = 'https://test1.jetpay.com/jetpay'
+ self.live_url = 'https://gateway20.jetpay.com/jetpay'
+
+ self.money_format = :cents
+ self.default_currency = 'USD'
+ self.supported_countries = ['US', 'CA']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'http://www.jetpay.com'
+ self.display_name = 'JetPay'
+
+ API_VERSION = '2.2'
+
+ ACTION_CODE_MESSAGES = {
+ '000' => 'Approved.',
+ '001' => 'Refer to card issuer.',
+ '002' => 'Refer to card issuer, special condition.',
+ '003' => 'Invalid merchant or service provider.',
+ '004' => 'Pick up card.',
+ '005' => 'Do not honor.',
+ '006' => 'Error.',
+ '007' => 'Pick up card, special condition.',
+ '008' => 'Honor with ID (Show ID).',
+ '010' => 'Partial approval.',
+ '011' => 'VIP approval.',
+ '012' => 'Invalid transaction.',
+ '013' => 'Invalid amount or exceeds maximum for card program.',
+ '014' => 'Invalid account number (no such number).',
+ '015' => 'No such issuer.',
+ '019' => 'Re-enter Transaction.',
+ '021' => 'No action taken (unable to back out prior transaction).',
+ '025' => 'Transaction Not Found.',
+ '027' => 'File update field edit error.',
+ '028' => 'File is temporarily unavailable.',
+ '030' => 'Format error.',
+ '039' => 'No credit account.',
+ '041' => 'Pick up card (lost card).',
+ '043' => 'Pick up card (stolen card).',
+ '051' => 'Insufficient funds.',
+ '052' => 'No checking account.',
+ '053' => 'No savings account.',
+ '054' => 'Expired Card.',
+ '055' => 'Incorrect PIN.',
+ '057' => 'Transaction not permitted to cardholder.',
+ '058' => 'Transaction not allowed at terminal.',
+ '061' => 'Exceeds withdrawal limit.',
+ '062' => 'Restricted card (eg, Country Exclusion).',
+ '063' => 'Security violation.',
+ '065' => 'Activity count limit exceeded.',
+ '068' => 'Response late.',
+ '070' => 'Contact card issuer.',
+ '071' => 'PIN not changed.',
+ '075' => 'Allowable number of PIN-entry tries exceeded.',
+ '076' => 'Unable to locate previous message (no matching retrieval reference number).',
+ '077' => 'Repeat or reversal data are inconsistent with original message.',
+ '078' => 'Blocked (first use), or non-existent account.',
+ '079' => 'Key exchange validation failed.',
+ '080' => 'Credit issuer unavailable or invalid date.',
+ '081' => 'PIN cryptographic error found.',
+ '082' => 'Negative online CVV results.',
+ '084' => 'Invalid auth life cycle.',
+ '085' => 'No reason to decline - CVV or AVS approved.',
+ '086' => 'Cannot verify PIN.',
+ '087' => 'Cashback not allowed.',
+ '089' => 'Issuer Down.',
+ '091' => 'Issuer Down.',
+ '092' => 'Unable to route transaction.',
+ '093' => 'Transaction cannot be completed - violation of law.',
+ '094' => 'Duplicate transmission.',
+ '096' => 'System error.',
+ '100' => 'Deny.',
+ '101' => 'Expired Card.',
+ '103' => 'Deny - Invalid manual Entry 4DBC.',
+ '104' => 'Deny - New card issued.',
+ '105' => 'Deny - Account Cancelled.',
+ '106' => 'Exceeded PIN Attempts.',
+ '107' => 'Please Call Issuer.',
+ '109' => 'Invalid merchant.',
+ '110' => 'Invalid amount.',
+ '111' => 'Invalid account.',
+ '115' => 'Service not permitted.',
+ '117' => 'Invalid PIN.',
+ '119' => 'Card member not enrolled.',
+ '122' => 'Invalid card (CID) security code.',
+ '125' => 'Invalid effective date.',
+ '181' => 'Format error.',
+ '182' => 'Please wait.',
+ '183' => 'Invalid currency code.',
+ '187' => 'Deny - new card issued.',
+ '188' => 'Deny - Expiration date required.',
+ '189' => 'Deny - Cancelled or Closed Merchant/SE.',
+ '200' => 'Deny - Pick up card.',
+ '400' => 'Reversal accepted.',
+ '601' => 'Reject - EMV Chip Declined Transaction.',
+ '602' => 'Reject - Suspected Fraud.',
+ '603' => 'Reject - Communications Error.',
+ '604' => 'Reject - Insufficient Approval.',
+ '750' => 'Velocity Check Fail.',
+ '899' => 'Misc Decline.',
+ '900' => 'Invalid Message Type.',
+ '901' => 'Invalid Merchant ID.',
+ '903' => 'Debit not supported.',
+ '904' => 'Private label not supported.',
+ '905' => 'Invalid card type.',
+ '906' => 'Unit not active.',
+ '908' => 'Manual card entry invalid.',
+ '909' => 'Invalid track information.',
+ '911' => 'Master merchant not found.',
+ '912' => 'Invalid card format.',
+ '913' => 'Invalid card type.',
+ '914' => 'Invalid card length.',
+ '917' => 'Expired card.',
+ '919' => 'Invalid entry type.',
+ '920' => 'Invalid amount.',
+ '921' => 'Invalid messge format.',
+ '923' => 'Invalid ABA.',
+ '924' => 'Invalid DDA.',
+ '925' => 'Invalid TID.',
+ '926' => 'Invalid Password.',
+ '930' => 'Invalid zipcode.',
+ '931' => 'Invalid Address.',
+ '932' => 'Invalid ZIP and Address.',
+ '933' => 'Invalid CVV2.',
+ '934' => 'Program Not Allowed.',
+ '935' => 'Invalid Device/App.',
+ '940' => 'Record Not Found.',
+ '941' => 'Merchant ID error.',
+ '942' => 'Refund Not Allowed.',
+ '943' => 'Refund denied.',
+ '955' => 'Invalid PIN block.',
+ '956' => 'Invalid KSN.',
+ '958' => 'Bad Status.',
+ '959' => 'Seek Record limit exceeded.',
+ '960' => 'Internal Key Database Error.',
+ '961' => 'TRANS not Supported. Cash Disbursement required a specific MCC.',
+ '962' => 'Invalid PIN key (Unknown KSN).',
+ '981' => 'Invalid AVS.',
+ '987' => 'Issuer Unavailable.',
+ '988' => 'System error SD.',
+ '989' => 'Database Error.',
+ '992' => 'Transaction Timeout.',
+ '996' => 'Bad Terminal ID.',
+ '997' => 'Message rejected by association.',
+ '999' => 'Communication failure',
+ nil => 'No response returned (missing credentials?).'
+ }
+
+ def initialize(options = {})
+ requires!(options, :login)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ commit(money, build_sale_request(money, payment, options))
+ end
+
+ def authorize(money, payment, options = {})
+ commit(money, build_authonly_request(money, payment, options))
+ end
+
+ def capture(money, reference, options = {})
+ transaction_id, _, _, token = reference.split(';')
+ commit(money, build_capture_request(money, transaction_id, options), token)
+ end
+
+ def void(reference, options = {})
+ transaction_id, _, amount, token = reference.split(';')
+ commit(amount.to_i, build_void_request(amount.to_i, transaction_id, options), token)
+ end
+
+ def credit(money, payment, options = {})
+ commit(money, build_credit_request(money, nil, payment, options))
+ end
+
+ def refund(money, reference, options = {})
+ transaction_id, _, _, token = reference.split(';')
+ commit(money, build_credit_request(money, transaction_id, token, options), token)
+ end
+
+ def verify(credit_card, options = {})
+ authorize(0, credit_card, options)
+ end
+
+ def store(credit_card, options = {})
+ commit(nil, build_store_request(credit_card, options))
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((>)\d+()), '\1[FILTERED]\2').
+ gsub(%r(()\d+()), '\1[FILTERED]\2')
+ end
+
+ private
+
+ def build_xml_request(transaction_type, options = {}, transaction_id = nil, &block)
+ xml = Builder::XmlMarkup.new
+ xml.tag! 'JetPay', 'Version' => API_VERSION do
+ # Basic values needed for any request
+ xml.tag! 'TerminalID', @options[:login]
+ xml.tag! 'TransactionType', transaction_type
+ xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id
+ xml.tag! 'Origin', options[:origin] || 'INTERNET'
+ xml.tag! 'IndustryInfo', 'Type' => options[:industry_info] || 'ECOMMERCE'
+ xml.tag! 'Application', (options[:application] || 'n/a'), {'Version' => options[:application_version] || '1.0'}
+ xml.tag! 'Device', (options[:device] || 'n/a'), {'Version' => options[:device_version] || '1.0'}
+ xml.tag! 'Library', 'VirtPOS SDK', 'Version' => '1.5'
+ xml.tag! 'Gateway', 'JetPay'
+ xml.tag! 'DeveloperID', options[:developer_id] || 'n/a'
+
+ if block_given?
+ yield xml
+ else
+ xml.target!
+ end
+ end
+ end
+
+ def build_sale_request(money, payment, options)
+ build_xml_request('SALE', options) do |xml|
+ add_payment(xml, payment)
+ add_addresses(xml, options)
+ add_customer_data(xml, options)
+ add_invoice_data(xml, options)
+ add_user_defined_fields(xml, options)
+ xml.tag! 'TotalAmount', amount(money)
+
+ xml.target!
+ end
+ end
+
+ def build_authonly_request(money, payment, options)
+ build_xml_request('AUTHONLY', options) do |xml|
+ add_payment(xml, payment)
+ add_addresses(xml, options)
+ add_customer_data(xml, options)
+ add_invoice_data(xml, options)
+ add_user_defined_fields(xml, options)
+ xml.tag! 'TotalAmount', amount(money)
+
+ xml.target!
+ end
+ end
+
+ def build_capture_request(money, transaction_id, options)
+ build_xml_request('CAPT', options, transaction_id) do |xml|
+ add_invoice_data(xml, options)
+ add_purchase_order(xml, options)
+ add_user_defined_fields(xml, options)
+ xml.tag! 'TotalAmount', amount(money)
+
+ xml.target!
+ end
+ end
+
+ def build_void_request(money, transaction_id, options)
+ build_xml_request('VOID', options, transaction_id) do |xml|
+ xml.tag! 'TotalAmount', amount(money)
+ xml.target!
+ end
+ end
+
+ def build_credit_request(money, transaction_id, payment, options)
+ build_xml_request('CREDIT', options, transaction_id) do |xml|
+ add_payment(xml, payment)
+ add_invoice_data(xml, options)
+ add_addresses(xml, options)
+ add_customer_data(xml, options)
+ add_user_defined_fields(xml, options)
+ xml.tag! 'TotalAmount', amount(money)
+
+ xml.target!
+ end
+ end
+
+ def build_store_request(credit_card, options)
+ build_xml_request('TOKENIZE', options) do |xml|
+ add_payment(xml, credit_card)
+ add_addresses(xml, options)
+ add_customer_data(xml, options)
+
+ xml.target!
+ end
+ end
+
+ def commit(money, request, token = nil)
+ response = parse(ssl_post(url, request))
+
+ success = success?(response)
+ Response.new(success,
+ success ? 'APPROVED' : message_from(response),
+ response,
+ :test => test?,
+ :authorization => authorization_from(response, money, token),
+ :avs_result => AVSResult.new(:code => response[:avs]),
+ :cvv_result => CVVResult.new(response[:cvv2]),
+ :error_code => success ? nil : error_code_from(response)
+ )
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def parse(body)
+ return {} if body.blank?
+
+ xml = REXML::Document.new(body)
+
+ response = {}
+ xml.root.elements.to_a.each do |node|
+ parse_element(response, node)
+ end
+ response
+ end
+
+ def parse_element(response, node)
+ if node.has_elements?
+ node.elements.each { |element| parse_element(response, element) }
+ else
+ response[node.name.underscore.to_sym] = node.text
+ end
+ end
+
+ def format_exp(value)
+ format(value, :two_digits)
+ end
+
+ def success?(response)
+ response[:action_code] == '000'
+ end
+
+ def message_from(response)
+ ACTION_CODE_MESSAGES[response[:action_code]]
+ end
+
+ def authorization_from(response, money, previous_token)
+ original_amount = amount(money) if money
+ [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(';')
+ end
+
+ def error_code_from(response)
+ response[:action_code]
+ end
+
+ def add_payment(xml, payment)
+ return unless payment
+
+ if payment.is_a? String
+ token = payment
+ _, _, _, token = payment.split(';') if payment.include? ';'
+ xml.tag! 'Token', token if token
+ else
+ add_credit_card(xml, payment)
+ end
+ end
+
+ def add_credit_card(xml, credit_card)
+ xml.tag! 'CardNum', credit_card.number, 'CardPresent' => false, 'Tokenize' => true
+ xml.tag! 'CardExpMonth', format_exp(credit_card.month)
+ xml.tag! 'CardExpYear', format_exp(credit_card.year)
+
+ if credit_card.first_name || credit_card.last_name
+ xml.tag! 'CardName', [credit_card.first_name, credit_card.last_name].compact.join(' ')
+ end
+
+ unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0)
+ xml.tag! 'CVV2', credit_card.verification_value
+ end
+ end
+
+ def add_addresses(xml, options)
+ if billing_address = options[:billing_address] || options[:address]
+ xml.tag! 'Billing' do
+ xml.tag! 'Address', [billing_address[:address1], billing_address[:address2]].compact.join(' ')
+ xml.tag! 'City', billing_address[:city]
+ xml.tag! 'StateProv', billing_address[:state]
+ xml.tag! 'PostalCode', billing_address[:zip]
+ xml.tag! 'Country', lookup_country_code(billing_address[:country])
+ xml.tag! 'Phone', billing_address[:phone]
+ xml.tag! 'Email', options[:email] if options[:email]
+ end
+ end
+
+ if shipping_address = options[:shipping_address]
+ xml.tag! 'Shipping' do
+ xml.tag! 'Name', shipping_address[:name]
+ xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(' ')
+ xml.tag! 'City', shipping_address[:city]
+ xml.tag! 'StateProv', shipping_address[:state]
+ xml.tag! 'PostalCode', shipping_address[:zip]
+ xml.tag! 'Country', lookup_country_code(shipping_address[:country])
+ xml.tag! 'Phone', shipping_address[:phone]
+ end
+ end
+ end
+
+ def add_customer_data(xml, options)
+ xml.tag! 'UserIPAddress', options[:ip] if options[:ip]
+ end
+
+ def add_invoice_data(xml, options)
+ xml.tag! 'OrderNumber', options[:order_id] if options[:order_id]
+ if tax_amount = options[:tax_amount]
+ xml.tag! 'TaxAmount', tax_amount, {'ExemptInd' => options[:tax_exempt] || 'false'}
+ end
+ end
+
+ def add_purchase_order(xml, options)
+ if purchase_order = options[:purchase_order]
+ xml.tag! 'Billing' do
+ xml.tag! 'CustomerPO', purchase_order
+ end
+ end
+ end
+
+ def add_user_defined_fields(xml, options)
+ xml.tag! 'UDField1', options[:ud_field_1] if options[:ud_field_1]
+ xml.tag! 'UDField2', options[:ud_field_2] if options[:ud_field_2]
+ xml.tag! 'UDField3', options[:ud_field_3] if options[:ud_field_3]
+ end
+
+ def lookup_country_code(code)
+ country = Country.find(code) rescue nil
+ country&.code(:alpha3)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/komoju.rb b/lib/active_merchant/billing/gateways/komoju.rb
new file mode 100644
index 00000000000..d53ab5f5165
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/komoju.rb
@@ -0,0 +1,115 @@
+require 'json'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class KomojuGateway < Gateway
+ self.test_url = 'https://komoju.com/api/v1'
+ self.live_url = 'https://komoju.com/api/v1'
+ self.supported_countries = ['JP']
+ self.default_currency = 'JPY'
+ self.money_format = :cents
+ self.homepage_url = 'https://www.komoju.com/'
+ self.display_name = 'Komoju'
+ self.supported_cardtypes = [:visa, :master, :american_express, :jcb]
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ 'bad_verification_value' => 'incorrect_cvc',
+ 'card_expired' => 'expired_card',
+ 'card_declined' => 'card_declined',
+ 'invalid_number' => 'invalid_number'
+ }
+
+ def initialize(options = {})
+ requires!(options, :login)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ post = {}
+ post[:amount] = amount(money)
+ post[:description] = options[:description]
+ add_payment_details(post, payment, options)
+ post[:currency] = options[:currency] || default_currency
+ post[:external_order_num] = options[:order_id] if options[:order_id]
+ post[:tax] = options[:tax] if options[:tax]
+ add_fraud_details(post, options)
+
+ commit('/payments', post)
+ end
+
+ def refund(money, identification, options = {})
+ commit("/payments/#{identification}/refund", {})
+ end
+
+ private
+
+ def add_payment_details(post, payment, options)
+ details = {}
+
+ details[:type] = 'credit_card'
+ details[:number] = payment.number
+ details[:month] = payment.month
+ details[:year] = payment.year
+ details[:verification_value] = payment.verification_value
+ details[:given_name] = payment.first_name
+ details[:family_name] = payment.last_name
+ details[:email] = options[:email] if options[:email]
+
+ post[:payment_details] = details
+ end
+
+ def add_fraud_details(post, options)
+ details = {}
+
+ details[:customer_ip] = options[:ip] if options[:ip]
+ details[:customer_email] = options[:email] if options[:email]
+ details[:browser_language] = options[:browser_language] if options[:browser_language]
+ details[:browser_user_agent] = options[:browser_user_agent] if options[:browser_user_agent]
+
+ post[:fraud_details] = details unless details.empty?
+ end
+
+ def api_request(path, data)
+ raw_response = nil
+ begin
+ raw_response = ssl_post("#{url}#{path}", data, headers)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
+
+ JSON.parse(raw_response)
+ end
+
+ def commit(path, params)
+ response = api_request(path, params.to_json)
+ success = !response.key?('error')
+ message = (success ? 'Transaction succeeded' : response['error']['message'])
+ Response.new(
+ success,
+ message,
+ response,
+ test: test?,
+ error_code: (success ? nil : error_code(response['error']['code'])),
+ authorization: (success ? response['id'] : nil)
+ )
+ end
+
+ def error_code(code)
+ STANDARD_ERROR_CODE_MAPPING[code] || code
+ end
+
+ def url
+ test? ? self.test_url : self.live_url
+ end
+
+ def headers
+ {
+ 'Authorization' => 'Basic ' + Base64.encode64(@options[:login].to_s + ':').strip,
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => "Komoju/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/kushki.rb b/lib/active_merchant/billing/gateways/kushki.rb
new file mode 100644
index 00000000000..646df52166a
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/kushki.rb
@@ -0,0 +1,219 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class KushkiGateway < Gateway
+ self.display_name = 'Kushki'
+ self.homepage_url = 'https://www.kushkipagos.com'
+
+ self.test_url = 'https://api-uat.kushkipagos.com/v1/'
+ self.live_url = 'https://api.kushkipagos.com/v1/'
+
+ self.supported_countries = ['CL', 'CO', 'EC', 'MX', 'PE']
+ self.default_currency = 'USD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
+
+ def initialize(options={})
+ requires!(options, :public_merchant_id, :private_merchant_id)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ MultiResponse.run() do |r|
+ r.process { tokenize(amount, payment_method, options) }
+ r.process { charge(amount, r.authorization, options) }
+ end
+ end
+
+ def refund(amount, authorization, options={})
+ action = 'refund'
+
+ post = {}
+ post[:ticketNumber] = authorization
+
+ commit(action, post)
+ end
+
+ def void(authorization, options={})
+ action = 'void'
+
+ post = {}
+ post[:ticketNumber] = authorization
+
+ commit(action, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Private-Merchant-Id: )\d+), '\1[FILTERED]').
+ gsub(%r((\"card\\\":{\\\"number\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"cvv\\\":\\\")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def tokenize(amount, payment_method, options)
+ action = 'tokenize'
+
+ post = {}
+ add_invoice(action, post, amount, options)
+ add_payment_method(post, payment_method, options)
+
+ commit(action, post)
+ end
+
+ def charge(amount, authorization, options)
+ action = 'charge'
+
+ post = {}
+ add_reference(post, authorization, options)
+ add_invoice(action, post, amount, options)
+
+ commit(action, post)
+ end
+
+ def add_invoice(action, post, money, options)
+ if action == 'tokenize'
+ post[:totalAmount] = amount(money).to_f
+ post[:currency] = options[:currency] || currency(money)
+ post[:isDeferred] = false
+ else
+ sum = {}
+ sum[:currency] = options[:currency] || currency(money)
+ add_amount_defaults(sum, money, options)
+ add_amount_by_country(sum, options)
+ post[:amount] = sum
+ end
+ end
+
+ def add_amount_defaults(sum, money, options)
+ sum[:subtotalIva] = amount(money).to_f
+ sum[:iva] = 0
+ sum[:subtotalIva0] = 0
+
+ if sum[:currency] != 'COP'
+ sum[:ice] = 0
+ end
+ end
+
+ def add_amount_by_country(sum, options)
+ if amount = options[:amount]
+ sum[:subtotalIva] = amount[:subtotal_iva].to_f if amount[:subtotal_iva]
+ sum[:iva] = amount[:iva].to_f if amount[:iva]
+ sum[:subtotalIva0] = amount[:subtotal_iva_0].to_f if amount[:subtotal_iva_0]
+ sum[:ice] = amount[:ice].to_f if amount[:ice]
+ if (extra_taxes = amount[:extra_taxes]) && sum[:currency] == 'COP'
+ sum[:extraTaxes] ||= Hash.new
+ sum[:extraTaxes][:propina] = extra_taxes[:propina].to_f if extra_taxes[:propina]
+ sum[:extraTaxes][:tasaAeroportuaria] = extra_taxes[:tasa_aeroportuaria].to_f if extra_taxes[:tasa_aeroportuaria]
+ sum[:extraTaxes][:agenciaDeViaje] = extra_taxes[:agencia_de_viaje].to_f if extra_taxes[:agencia_de_viaje]
+ sum[:extraTaxes][:iac] = extra_taxes[:iac].to_f if extra_taxes[:iac]
+ end
+ end
+ end
+
+ def add_payment_method(post, payment_method, options)
+ card = {}
+ card[:number] = payment_method.number
+ card[:cvv] = payment_method.verification_value
+ card[:expiryMonth] = format(payment_method.month, :two_digits)
+ card[:expiryYear] = format(payment_method.year, :two_digits)
+ card[:name] = payment_method.name
+ post[:card] = card
+ end
+
+ def add_reference(post, authorization, options)
+ post[:token] = authorization
+ end
+
+ ENDPOINT = {
+ 'tokenize' => 'tokens',
+ 'charge' => 'charges',
+ 'void' => 'charges',
+ 'refund' => 'refund'
+ }
+
+ def commit(action, params)
+ response = begin
+ parse(ssl_invoke(action, params))
+ rescue ResponseError => e
+ parse(e.response.body)
+ end
+
+ success = success_from(response)
+
+ Response.new(
+ success,
+ message_from(success, response),
+ response,
+ authorization: success ? authorization_from(response) : nil,
+ error_code: success ? nil : error_from(response),
+ test: test?
+ )
+ end
+
+ def ssl_invoke(action, params)
+ if ['void', 'refund'].include?(action)
+ ssl_request(:delete, url(action, params), nil, headers(action))
+ else
+ ssl_post(url(action, params), post_data(params), headers(action))
+ end
+ end
+
+ def headers(action)
+ hfields = {}
+ hfields['Public-Merchant-Id'] = @options[:public_merchant_id] if action == 'tokenize'
+ hfields['Private-Merchant-Id'] = @options[:private_merchant_id] unless action == 'tokenize'
+ hfields['Content-Type'] = 'application/json'
+ hfields
+ end
+
+ def post_data(params)
+ params.to_json
+ end
+
+ def url(action, params)
+ base_url = test? ? test_url : live_url
+
+ if ['void', 'refund'].include?(action)
+ base_url + ENDPOINT[action] + '/' + params[:ticketNumber].to_s
+ else
+ base_url + ENDPOINT[action]
+ end
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ rescue JSON::ParserError
+ message = 'Invalid JSON response received from KushkiGateway. Please contact KushkiGateway if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{body.inspect})"
+ {
+ 'message' => message
+ }
+ end
+
+ def success_from(response)
+ return true if response['token'] || response['ticketNumber'] || response['code'] == 'K000'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response['message']
+ end
+ end
+
+ def authorization_from(response)
+ response['token'] || response['ticketNumber']
+ end
+
+ def error_from(response)
+ response['code']
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/latitude19.rb b/lib/active_merchant/billing/gateways/latitude19.rb
new file mode 100644
index 00000000000..d30b5e14a22
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/latitude19.rb
@@ -0,0 +1,411 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class Latitude19Gateway < Gateway
+ self.display_name = 'Latitude19 Gateway'
+ self.homepage_url = 'http://www.l19tech.com'
+
+ self.live_url = 'https://gateway.l19tech.com/payments/'
+ self.test_url = 'https://gateway-sb.l19tech.com/payments/'
+
+ self.supported_countries = ['US', 'CA']
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+
+ RESPONSE_CODE_MAPPING = {
+ '100' => 'Approved',
+ '101' => 'Local duplicate detected',
+ '102' => 'Accepted local capture with no match',
+ '103' => 'Auth succeeded but capture failed',
+ '104' => 'Auth succeeded but failed to save info',
+ '200' => STANDARD_ERROR_CODE[:card_declined],
+ '300' => 'Processor reject',
+ '301' => 'Local reject on user/password',
+ '302' => 'Local reject',
+ '303' => 'Processor unknown response',
+ '304' => 'Error parsing processor response',
+ '305' => 'Processor auth succeeded but settle failed',
+ '306' => 'Processor auth succeeded settle status unknown',
+ '307' => 'Processor settle status unknown',
+ '308' => 'Processor duplicate',
+ '400' => 'Not submitted',
+ '401' => 'Terminated before request submitted',
+ '402' => 'Local server busy',
+ '500' => 'Submitted not returned',
+ '501' => 'Terminated before response returned',
+ '502' => 'Processor returned timeout status',
+ '600' => 'Failed local capture with no match',
+ '601' => 'Failed local capture',
+ '700' => 'Failed local void (not in capture file)',
+ '701' => 'Failed local void',
+ '800' => 'Failed local refund (not authorized)',
+ '801' => 'Failed local refund'
+ }
+
+ BRAND_MAP = {
+ 'master' => 'MC',
+ 'visa' => 'VI',
+ 'american_express' => 'AX',
+ 'discover' => 'DS',
+ 'diners_club' => 'DC',
+ 'jcb' => 'JC'
+ }
+
+ def initialize(options={})
+ requires!(options, :account_number, :configuration_id, :secret)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ if payment_method.is_a?(String)
+ auth_or_sale('sale', payment_method, amount, nil, options)
+ else
+ MultiResponse.run() do |r|
+ r.process { get_session(options) }
+ r.process { get_token(r.authorization, payment_method, options) }
+ r.process { auth_or_sale('sale', r.authorization, amount, payment_method, options) }
+ end
+ end
+ end
+
+ def authorize(amount, payment_method, options={})
+ if payment_method.is_a?(String)
+ auth_or_sale('auth', payment_method, amount, nil, options)
+ else
+ MultiResponse.run() do |r|
+ r.process { get_session(options) }
+ r.process { get_token(r.authorization, payment_method, options) }
+ r.process { auth_or_sale('auth', r.authorization, amount, payment_method, options) }
+ end
+ end
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ post[:method] = 'deposit'
+ add_request_id(post)
+
+ params = {}
+
+ _, params[:pgwTID] = split_authorization(authorization)
+
+ add_invoice(params, amount, options)
+ add_credentials(params, post[:method])
+
+ post[:params] = [params]
+ commit('v1/', post)
+ end
+
+ def void(authorization, options={})
+ method, pgwTID = split_authorization(authorization)
+ case method
+ when 'auth'
+ reverse_or_void('reversal', pgwTID, options)
+ when 'deposit', 'sale'
+ reverse_or_void('void', pgwTID, options)
+ else
+ message = 'Unsupported operation: successful Purchase, Authorize and unsettled Capture transactions can only be voided.'
+ return Response.new(false, message)
+ end
+ end
+
+ def credit(amount, payment_method, options={})
+ if payment_method.is_a?(String)
+ refundWithCard(payment_method, amount, nil, options)
+ else
+ MultiResponse.run() do |r|
+ r.process { get_session(options) }
+ r.process { get_token(r.authorization, payment_method, options) }
+ r.process { refundWithCard(r.authorization, amount, payment_method, options) }
+ end
+ end
+ end
+
+ def verify(payment_method, options={}, action=nil)
+ if payment_method.is_a?(String)
+ verifyOnly(action, payment_method, nil, options)
+ else
+ MultiResponse.run() do |r|
+ r.process { get_session(options) }
+ r.process { get_token(r.authorization, payment_method, options) }
+ r.process { verifyOnly(action, r.authorization, payment_method, options) }
+ end
+ end
+ end
+
+ def store(payment_method, options={})
+ verify(payment_method, options, 'store')
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((\"cardNumber\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"cvv\\\":\\\")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_request_id(post)
+ post[:id] = SecureRandom.hex(16)
+ end
+
+ def add_timestamp
+ Time.now.getutc.strftime('%Y%m%d%H%M%S')
+ end
+
+ def add_hmac(params, method)
+ if method == 'getSession'
+ hmac_message = params[:pgwAccountNumber] + '|' + params[:pgwConfigurationId] + '|' + params[:requestTimeStamp] + '|' + method
+ else
+ hmac_message = params[:pgwAccountNumber] + '|' + params[:pgwConfigurationId] + '|' + (params[:orderNumber] || '') + '|' + method + '|' + (params[:amount] || '') + '|' + (params[:sessionToken] || '') + '|' + (params[:accountToken] || '')
+ end
+
+ OpenSSL::HMAC.hexdigest('sha512', @options[:secret], hmac_message)
+ end
+
+ def add_credentials(params, method)
+ params[:pgwAccountNumber] = @options[:account_number]
+ params[:pgwConfigurationId] = @options[:configuration_id]
+
+ params[:requestTimeStamp] = add_timestamp() if method == 'getSession'
+
+ params[:pgwHMAC] = add_hmac(params, method)
+ end
+
+ def add_invoice(params, money, options)
+ params[:amount] = amount(money)
+ params[:orderNumber] = options[:order_id]
+ params[:transactionClass] = options[:transaction_class] || 'eCommerce'
+ end
+
+ def add_payment_method(params, credit_card)
+ params[:cardExp] = format(credit_card.month, :two_digits).to_s + '/' + format(credit_card.year, :two_digits).to_s
+ params[:cardType] = BRAND_MAP[credit_card.brand.to_s]
+ params[:cvv] = credit_card.verification_value
+ params[:firstName] = credit_card.first_name
+ params[:lastName] = credit_card.last_name
+ end
+
+ def add_customer_data(params, options)
+ if (billing_address = options[:billing_address] || options[:address])
+ params[:address1] = billing_address[:address1]
+ params[:address2] = billing_address[:address2]
+ params[:city] = billing_address[:city]
+ params[:stateProvince] = billing_address[:state]
+ params[:zipPostalCode] = billing_address[:zip]
+ params[:countryCode] = billing_address[:country]
+ end
+ end
+
+ def get_session(options={})
+ post = {}
+ post[:method] = 'getSession'
+ add_request_id(post)
+
+ params = {}
+ add_credentials(params, post[:method])
+
+ post[:params] = [params]
+ commit('session', post)
+ end
+
+ def get_token(authorization, payment_method, options={})
+ post = {}
+ post[:method] = 'tokenize'
+ add_request_id(post)
+
+ params = {}
+ _, params[:sessionId] = split_authorization(authorization)
+ params[:cardNumber] = payment_method.number
+
+ post[:params] = [params]
+ commit('token', post)
+ end
+
+ def auth_or_sale(method, authorization, amount, credit_card, options={})
+ post = {}
+ post[:method] = method
+ add_request_id(post)
+
+ params = {}
+ if credit_card
+ _, params[:sessionToken] = split_authorization(authorization)
+ add_payment_method(params, credit_card)
+ add_customer_data(params, options)
+ else
+ _, params[:accountToken] = split_authorization(authorization)
+ end
+ add_invoice(params, amount, options)
+ add_credentials(params, post[:method])
+
+ post[:params] = [params]
+ commit('v1/', post)
+ end
+
+ def verifyOnly(action, authorization, credit_card, options={})
+ post = {}
+ post[:method] = 'verifyOnly'
+ add_request_id(post)
+
+ params = {}
+ if credit_card
+ _, params[:sessionToken] = split_authorization(authorization)
+ add_payment_method(params, credit_card)
+ add_customer_data(params, options)
+ else
+ _, params[:accountToken] = split_authorization(authorization)
+ end
+ params[:requestAccountToken] = '1' if action == 'store'
+ add_invoice(params, 0, options)
+ add_credentials(params, post[:method])
+
+ post[:params] = [params]
+ commit('v1/', post)
+ end
+
+ def refundWithCard(authorization, amount, credit_card, options={})
+ post = {}
+ post[:method] = 'refundWithCard'
+ add_request_id(post)
+
+ params = {}
+ if credit_card
+ _, params[:sessionToken] = split_authorization(authorization)
+ add_payment_method(params, credit_card)
+ else
+ _, params[:accountToken] = split_authorization(authorization)
+ end
+ add_invoice(params, amount, options)
+ add_credentials(params, post[:method])
+
+ post[:params] = [params]
+ commit('v1/', post)
+ end
+
+ def reverse_or_void(method, pgwTID, options={})
+ post = {}
+ post[:method] = method
+ add_request_id(post)
+
+ params = {}
+ params[:orderNumber] = options[:order_id]
+ params[:pgwTID] = pgwTID
+ add_credentials(params, post[:method])
+
+ post[:params] = [params]
+ commit('v1/', post)
+ end
+
+ def commit(endpoint, post)
+ raw_response = ssl_post(url() + endpoint, post_data(post), headers)
+ response = parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response_error(raw_response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ else
+ success = success_from(response)
+ Response.new(
+ success,
+ message_from(response),
+ response,
+ authorization: success ? authorization_from(response, post[:method]) : nil,
+ avs_result: success ? avs_from(response) : nil,
+ cvv_result: success ? cvv_from(response) : nil,
+ error_code: success ? nil : error_from(response),
+ test: test?
+ )
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def post_data(params)
+ params.to_json
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(response)
+ return false if response['result'].nil? || response['error']
+
+ if response['result'].key?('pgwResponseCode')
+ response['error'].nil? && response['result']['lastActionSucceeded'] == 1 && response['result']['pgwResponseCode'] == '100'
+ else
+ response['error'].nil? && response['result']['lastActionSucceeded'] == 1
+ end
+ end
+
+ def message_from(response)
+ return response['error'] if response['error']
+ return 'Failed' unless response.key?('result')
+
+ if response['result'].key?('pgwResponseCode')
+ RESPONSE_CODE_MAPPING[response['result']['pgwResponseCode']] || response['result']['responseText']
+ else
+ response['result']['lastActionSucceeded'] == 1 ? 'Succeeded' : 'Failed'
+ end
+ end
+
+ def error_from(response)
+ return response['error'] if response['error']
+ return 'Failed' unless response.key?('result')
+ return response['result']['pgwResponseCode'] || response['result']['processor']['responseCode'] || 'Failed'
+ end
+
+ def authorization_from(response, method)
+ method + '|' + (
+ response['result']['sessionId'] ||
+ response['result']['sessionToken'] ||
+ response['result']['pgwTID'] ||
+ response['result']['accountToken']
+ )
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def avs_from(response)
+ response['result'].key?('avsResponse') ? AVSResult.new(code: response['result']['avsResponse']) : nil
+ end
+
+ def cvv_from(response)
+ response['result'].key?('cvvResponse') ? CVVResult.new(response['result']['cvvResponse']) : nil
+ end
+
+ def response_error(raw_response)
+ response = parse(raw_response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ else
+ return Response.new(
+ false,
+ message_from(response),
+ response,
+ :test => test?
+ )
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from Latitude19Gateway. Please contact Latitude19Gateway if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/linkpoint.rb b/lib/active_merchant/billing/gateways/linkpoint.rb
index 10917c87532..4dd09c89800 100644
--- a/lib/active_merchant/billing/gateways/linkpoint.rb
+++ b/lib/active_merchant/billing/gateways/linkpoint.rb
@@ -2,7 +2,6 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
-
# Initialization Options
# :login Your store number
# :pem The text of your linkpoint PEM file. Note
@@ -144,6 +143,8 @@ def initialize(options = {})
}.update(options)
raise ArgumentError, "You need to pass in your pem file using the :pem parameter or set it globally using ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) or similar" if @options[:pem].blank?
+
+ @options[:pem].strip!
end
# Send a purchase request with periodic options
@@ -166,14 +167,16 @@ def initialize(options = {})
# :comments Uh... comments
#
def recurring(money, creditcard, options={})
- requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily], :installments, :order_id )
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
+ requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily], :installments, :order_id)
options.update(
- :ordertype => "SALE",
- :action => options[:action] || "SUBMIT",
+ :ordertype => 'SALE',
+ :action => options[:action] || 'SUBMIT',
:installments => options[:installments] || 12,
- :startdate => options[:startdate] || "immediate",
- :periodicity => options[:periodicity].to_s || "monthly",
+ :startdate => options[:startdate] || 'immediate',
+ :periodicity => options[:periodicity].to_s || 'monthly',
:comments => options[:comments] || nil,
:threshold => options[:threshold] || 3
)
@@ -184,7 +187,7 @@ def recurring(money, creditcard, options={})
def purchase(money, creditcard, options={})
requires!(options, :order_id)
options.update(
- :ordertype => "SALE"
+ :ordertype => 'SALE'
)
commit(money, creditcard, options)
end
@@ -197,7 +200,7 @@ def purchase(money, creditcard, options={})
def authorize(money, creditcard, options = {})
requires!(options, :order_id)
options.update(
- :ordertype => "PREAUTH"
+ :ordertype => 'PREAUTH'
)
commit(money, creditcard, options)
end
@@ -211,7 +214,7 @@ def authorize(money, creditcard, options = {})
def capture(money, authorization, options = {})
options.update(
:order_id => authorization,
- :ordertype => "POSTAUTH"
+ :ordertype => 'POSTAUTH'
)
commit(money, nil, options)
end
@@ -220,7 +223,7 @@ def capture(money, authorization, options = {})
def void(identification, options = {})
options.update(
:order_id => identification,
- :ordertype => "VOID"
+ :ordertype => 'VOID'
)
commit(nil, nil, options)
end
@@ -232,18 +235,30 @@ def void(identification, options = {})
#
def refund(money, identification, options = {})
options.update(
- :ordertype => "CREDIT",
+ :ordertype => 'CREDIT',
:order_id => identification
)
commit(money, nil, options)
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2')
+ end
+
private
+
# Commit the transaction by posting the XML file to the LinkPoint server
def commit(money, creditcard, options = {})
response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(money, creditcard, options)))
@@ -251,13 +266,13 @@ def commit(money, creditcard, options = {})
Response.new(successful?(response), response[:message], response,
:test => test?,
:authorization => response[:ordernum],
- :avs_result => { :code => response[:avs].to_s[2,1] },
- :cvv_result => response[:avs].to_s[3,1]
+ :avs_result => { :code => response[:avs].to_s[2, 1] },
+ :cvv_result => response[:avs].to_s[3, 1]
)
end
def successful?(response)
- response[:approved] == "APPROVED"
+ response[:approved] == 'APPROVED'
end
# Build the XML file
@@ -265,11 +280,11 @@ def post_data(money, creditcard, options)
params = parameters(money, creditcard, options)
xml = REXML::Document.new
- order = xml.add_element("order")
+ order = xml.add_element('order')
# Merchant Info
- merchantinfo = order.add_element("merchantinfo")
- merchantinfo.add_element("configfile").text = @options[:login]
+ merchantinfo = order.add_element('merchantinfo')
+ merchantinfo.add_element('configfile').text = @options[:login]
# Loop over the params hash to construct the XML string
for key, value in params
@@ -277,7 +292,7 @@ def post_data(money, creditcard, options)
if key == :items
build_items(elem, value)
else
- for k, v in params[key]
+ for k, _ in params[key]
elem.add_element(k.to_s).text = params[key][k].to_s if params[key][k]
end
end
@@ -290,14 +305,14 @@ def post_data(money, creditcard, options)
# adds LinkPoint's Items entity to the XML. Called from post_data
def build_items(element, items)
for item in items
- item_element = element.add_element("item")
+ item_element = element.add_element('item')
for key, value in item
if key == :options
- options_element = item_element.add_element("options")
+ options_element = item_element.add_element('options')
for option in value
- opt_element = options_element.add_element("option")
- opt_element.add_element("name").text = option[:name] unless option[:name].blank?
- opt_element.add_element("value").text = option[:value] unless option[:value].blank?
+ opt_element = options_element.add_element('option')
+ opt_element.add_element('name').text = option[:name] unless option[:name].blank?
+ opt_element.add_element('value').text = option[:value] unless option[:value].blank?
end
else
item_element.add_element(key.to_s).text = item[key].to_s unless item[key].blank?
@@ -309,7 +324,6 @@ def build_items(element, items)
# Set up the parameters hash just once so we don't have to do it
# for every action.
def parameters(money, creditcard, options = {})
-
params = {
:payment => {
:subtotal => amount(options[:subtotal]),
@@ -319,14 +333,14 @@ def parameters(money, creditcard, options = {})
:chargetotal => amount(money)
},
:transactiondetails => {
- :transactionorigin => options[:transactionorigin] || "ECI",
+ :transactionorigin => options[:transactionorigin] || 'ECI',
:oid => options[:order_id],
:ponumber => options[:ponumber],
:taxexempt => options[:taxexempt],
:terminaltype => options[:terminaltype],
:ip => options[:ip],
:reference_number => options[:reference_number],
- :recurring => options[:recurring] || "NO", #DO NOT USE if you are using the periodic billing option.
+ :recurring => options[:recurring] || 'NO', # DO NOT USE if you are using the periodic billing option.
:tdate => options[:tdate]
},
:orderoptions => {
@@ -403,7 +417,6 @@ def parameters(money, creditcard, options = {})
end
def parse(xml)
-
# For reference, a typical response...
#
#
@@ -418,29 +431,18 @@ def parse(xml)
# APPROVED
#
- response = {:message => "Global Error Receipt", :complete => false}
+ response = {:message => 'Global Error Receipt', :complete => false}
xml = REXML::Document.new("#{xml}")
- xml.root.elements.each do |node|
+ xml.root&.elements&.each do |node|
response[node.name.downcase.sub(/^r_/, '').to_sym] = normalize(node.text)
- end unless xml.root.nil?
+ end
response
end
- # Make a ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
- end
- end
-
def format_creditcard_expiry_year(year)
- sprintf("%.4i", year)[-2..-1]
+ sprintf('%.4i', year)[-2..-1]
end
end
end
diff --git a/lib/active_merchant/billing/gateways/litle.rb b/lib/active_merchant/billing/gateways/litle.rb
old mode 100755
new mode 100644
index 83ce2bc28dc..0cdfe62f9ef
--- a/lib/active_merchant/billing/gateways/litle.rb
+++ b/lib/active_merchant/billing/gateways/litle.rb
@@ -1,141 +1,167 @@
+require 'nokogiri'
+require 'active_merchant/billing/gateways/litle/paypage_registration'
+
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class LitleGateway < Gateway
- # Specific to Litle options:
- # * :merchant_id - Merchant Id assigned by Litle
- # * :user - Username assigned by Litle
- # * :password - Password assigned by Litle
- # * :version - The version of the api you are using (eg, '8.10')
- # * :proxy_addr - Proxy address - nil if not needed
- # * :proxy_port - Proxy port - nil if not needed
- # * :url - URL assigned by Litle (for testing, use the sandbox)
- #
- # Standard Active Merchant options
- # * :order_id - The order number
- # * :ip - The IP address of the customer making the purchase
- # * :customer - The name, customer number, or other information that identifies the customer
- # * :invoice - The invoice number
- # * :merchant - The name or description of the merchant offering the product
- # * :description - A description of the transaction
- # * :email - The email address of the customer
- # * :currency - The currency of the transaction. Only important when you are using a currency that is not the default with a gateway that supports multiple currencies.
- # * :billing_address - A hash containing the billing address of the customer.
- # * :shipping_address - A hash containing the shipping address of the customer.
- #
- # The :billing_address, and :shipping_address hashes can have the following keys:
- #
- # * :name - The full name of the customer.
- # * :company - The company name of the customer.
- # * :address1 - The primary street address of the customer.
- # * :address2 - Additional line of address information.
- # * :city - The city of the customer.
- # * :state - The state of the customer. The 2 digit code for US and Canadian addresses. The full name of the state or province for foreign addresses.
- # * :country - The [ISO 3166-1-alpha-2 code](http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm) for the customer.
- # * :zip - The zip or postal code of the customer.
- # * :phone - The phone number of the customer.
-
- self.test_url = 'https://www.testlitle.com/sandbox/communicator/online'
- self.live_url = 'https://payments.litle.com/vap/communicator/online'
-
- LITLE_SCHEMA_VERSION = '8.13'
-
- # The countries the gateway supports merchants from as 2 digit ISO country codes
- self.supported_countries = ['US']
+ SCHEMA_VERSION = '9.14'
- # The card types supported by the payment gateway
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+ self.test_url = 'https://www.testvantivcnp.com/sandbox/communicator/online'
+ self.live_url = 'https://payments.vantivcnp.com/vap/communicator/online'
- # The homepage URL of the gateway
- self.homepage_url = 'http://www.litle.com/'
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
- # The name of the gateway
- self.display_name = 'Litle & Co.'
+ self.homepage_url = 'http://www.vantiv.com/'
+ self.display_name = 'Vantiv eCommerce'
- self.default_currency = 'USD'
+ def initialize(options={})
+ requires!(options, :login, :password, :merchant_id)
+ super
+ end
- def initialize(options = {})
- begin
- require 'LitleOnline'
- rescue LoadError
- raise "Could not load the LitleOnline gem (> 08.15.0). Use `gem install LitleOnline` to install it."
+ def purchase(money, payment_method, options={})
+ request = build_xml_request do |doc|
+ add_authentication(doc)
+ if check?(payment_method)
+ doc.echeckSale(transaction_attributes(options)) do
+ add_echeck_purchase_params(doc, money, payment_method, options)
+ end
+ else
+ doc.sale(transaction_attributes(options)) do
+ add_auth_purchase_params(doc, money, payment_method, options)
+ end
+ end
end
+ check?(payment_method) ? commit(:echeckSales, request, money) : commit(:sale, request, money)
+ end
- if wiredump_device
- LitleOnline::Configuration.logger = ((Logger === wiredump_device) ? wiredump_device : Logger.new(wiredump_device))
- LitleOnline::Configuration.logger.level = Logger::DEBUG
- else
- LitleOnline::Configuration.logger = Logger.new(STDOUT)
- LitleOnline::Configuration.logger.level = Logger::WARN
+ def authorize(money, payment_method, options={})
+ request = build_xml_request do |doc|
+ add_authentication(doc)
+ if check?(payment_method)
+ doc.echeckVerification(transaction_attributes(options)) do
+ add_echeck_purchase_params(doc, money, payment_method, options)
+ end
+ else
+ doc.authorization(transaction_attributes(options)) do
+ add_auth_purchase_params(doc, money, payment_method, options)
+ end
+ end
end
+ check?(payment_method) ? commit(:echeckVerification, request, money) : commit(:authorization, request, money)
+ end
- @litle = LitleOnline::LitleOnlineRequest.new
-
- options[:version] ||= LITLE_SCHEMA_VERSION
- options[:merchant] ||= 'Default Report Group'
- options[:user] ||= options[:login]
+ def capture(money, authorization, options={})
+ transaction_id, _, _ = split_authorization(authorization)
- requires!(options, :merchant_id, :user, :password, :merchant, :version)
+ request = build_xml_request do |doc|
+ add_authentication(doc)
+ add_descriptor(doc, options)
+ doc.capture_(transaction_attributes(options)) do
+ doc.litleTxnId(transaction_id)
+ doc.amount(money) if money
+ end
+ end
- super
+ commit(:capture, request, money)
end
- def authorize(money, creditcard_or_token, options = {})
- to_pass = build_authorize_request(money, creditcard_or_token, options)
- build_response(:authorization, @litle.authorization(to_pass))
+ def credit(money, authorization, options = {})
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
+ refund(money, authorization, options)
end
- def purchase(money, creditcard_or_token, options = {})
- to_pass = build_purchase_request(money, creditcard_or_token, options)
- build_response(:sale, @litle.sale(to_pass))
+ def refund(money, payment, options={})
+ request = build_xml_request do |doc|
+ add_authentication(doc)
+ add_descriptor(doc, options)
+ doc.send(refund_type(payment), transaction_attributes(options)) do
+ if payment.is_a?(String)
+ transaction_id, _, _ = split_authorization(payment)
+ doc.litleTxnId(transaction_id)
+ doc.amount(money) if money
+ elsif check?(payment)
+ add_echeck_purchase_params(doc, money, payment, options)
+ else
+ add_auth_purchase_params(doc, money, payment, options)
+ end
+ end
+ end
+
+ commit(refund_type(payment), request)
end
- def capture(money, authorization, options = {})
- transaction_id, kind = split_authorization(authorization)
- to_pass = create_capture_hash(money, transaction_id, options)
- build_response(:capture, @litle.capture(to_pass))
+ def verify(creditcard, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(0, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
end
- # Note: Litle requires that authorization requests be voided via auth_reversal
- # and other requests via void. To maintain the same interface as the other
- # gateways the transaction_id and the kind of transaction are concatenated
- # together with a ; separator (e.g. 1234;authorization)
- #
- # A partial auth_reversal can be accomplished by passing :amount as an option
- def void(identification, options = {})
- transaction_id, kind = split_authorization(identification)
- if(kind == 'authorization')
- to_pass = create_auth_reversal_hash(transaction_id, options[:amount], options)
- build_response(:authReversal, @litle.auth_reversal(to_pass))
- else
- to_pass = create_void_hash(transaction_id, options)
- build_response(:void, @litle.void(to_pass))
+ def void(authorization, options={})
+ transaction_id, kind, money = split_authorization(authorization)
+
+ request = build_xml_request do |doc|
+ add_authentication(doc)
+ doc.send(void_type(kind), transaction_attributes(options)) do
+ doc.litleTxnId(transaction_id)
+ doc.amount(money) if void_type(kind) == :authReversal
+ end
end
+
+ commit(void_type(kind), request)
end
- def refund(money, authorization, options = {})
- to_pass = build_credit_request(money, authorization, options)
- build_response(:credit, @litle.credit(to_pass))
+ def store(payment_method, options = {})
+ request = build_xml_request do |doc|
+ add_authentication(doc)
+ doc.registerTokenRequest(transaction_attributes(options)) do
+ doc.orderId(truncate(options[:order_id], 24))
+ if payment_method.is_a?(String)
+ doc.paypageRegistrationId(payment_method)
+ elsif check?(payment_method)
+ doc.echeckForToken do
+ doc.accNum(payment_method.account_number)
+ doc.routingNum(payment_method.routing_number)
+ end
+ else
+ doc.accountNumber(payment_method.number)
+ doc.cardValidationNum(payment_method.verification_value) if payment_method.verification_value
+ end
+ end
+ end
+
+ commit(:registerToken, request)
end
- def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, authorization, options)
+ def supports_scrubbing?
+ true
end
- def store(creditcard_or_paypage_registration_id, options = {})
- to_pass = create_token_hash(creditcard_or_paypage_registration_id, options)
- build_response(:registerToken, @litle.register_token_request(to_pass), %w(000 801 802))
+ def scrub(transcript)
+ transcript.
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2')
end
private
CARD_TYPE = {
- 'visa' => 'VI',
- 'master' => 'MC',
- 'american_express' => 'AX',
- 'discover' => 'DI',
- 'jcb' => 'DI',
- 'diners_club' => 'DI'
+ 'visa' => 'VI',
+ 'master' => 'MC',
+ 'american_express' => 'AX',
+ 'discover' => 'DI',
+ 'jcb' => 'JC',
+ 'diners_club' => 'DC'
}
AVS_RESPONSE_CODE = {
@@ -156,403 +182,358 @@ def store(creditcard_or_paypage_registration_id, options = {})
'40' => 'E'
}
- def url
- return @options[:url] if @options[:url].present?
-
- test? ? self.test_url : self.live_url
- end
-
- def build_response(kind, litle_response, valid_responses=%w(000))
- response = Hash.from_xml(litle_response.raw_xml.to_s)['litleOnlineResponse']
-
- if response['response'] == "0"
- detail = response["#{kind}Response"]
- fraud = fraud_result(detail)
- Response.new(
- valid_responses.include?(detail['response']),
- detail['message'],
- { :litleOnlineResponse => response },
- :authorization => authorization_from(detail, kind),
- :avs_result => { :code => fraud['avs'] },
- :cvv_result => fraud['cvv'],
- :test => test?
- )
+ def void_type(kind)
+ if kind == 'authorization'
+ :authReversal
+ elsif kind == 'echeckSales'
+ :echeckVoid
else
- Response.new(false, response['message'], :litleOnlineResponse => response, :test => test?)
+ :void
end
end
- # Generates an authorization string of the appropriate id and the kind of transaction
- # See #void for how the kind is used
- def authorization_from(litle_response, kind)
- case kind
- when :registerToken
- authorization = litle_response['litleToken']
+ def refund_type(payment)
+ _, kind, _ = split_authorization(payment)
+ if check?(payment) || kind == 'echeckSales'
+ :echeckCredit
else
- authorization = [litle_response['litleTxnId'], kind.to_s].join(";")
+ :credit
end
end
- def split_authorization(authorization)
- transaction_id, kind = authorization.to_s.split(';')
- [transaction_id, kind]
+ def check?(payment_method)
+ return false if payment_method.is_a?(String)
+ card_brand(payment_method) == 'check'
end
- def build_authorize_request(money, creditcard_or_token, options)
- payment_method = build_payment_method(creditcard_or_token, options)
-
- hash = create_hash(money, options)
-
- add_creditcard_or_cardtoken_hash(hash, payment_method)
-
- hash
+ def add_authentication(doc)
+ doc.authentication do
+ doc.user(@options[:login])
+ doc.password(@options[:password])
+ end
end
- def build_purchase_request(money, creditcard_or_token, options)
- payment_method = build_payment_method(creditcard_or_token, options)
-
- hash = create_hash(money, options)
-
- add_creditcard_or_cardtoken_hash(hash, payment_method)
-
- hash
+ def add_auth_purchase_params(doc, money, payment_method, options)
+ doc.orderId(truncate(options[:order_id], 24))
+ doc.amount(money)
+ add_order_source(doc, payment_method, options)
+ add_billing_address(doc, payment_method, options)
+ add_shipping_address(doc, payment_method, options)
+ add_payment_method(doc, payment_method, options)
+ add_pos(doc, payment_method)
+ add_descriptor(doc, options)
+ add_processing_type(doc, options)
+ add_original_network_transaction(doc, options)
+ add_merchant_data(doc, options)
+ add_debt_repayment(doc, options)
+ add_stored_credential_params(doc, options)
end
- def build_credit_request(money, identification_or_token, options)
- payment_method = build_payment_method(identification_or_token, options)
-
- hash = create_hash(money, options)
-
- add_identification_or_cardtoken_hash(hash, payment_method)
-
- unless payment_method.is_a?(LitleCardToken)
- hash['orderSource'] = nil
- hash['orderId'] = nil
+ def add_merchant_data(doc, options={})
+ if options[:affiliate] || options[:campaign] || options[:merchant_grouping_id]
+ doc.merchantData do
+ doc.affiliate(options[:affiliate]) if options[:affiliate]
+ doc.campaign(options[:campaign]) if options[:campaign]
+ doc.merchantGroupingId(options[:merchant_grouping_id]) if options[:merchant_grouping_id]
+ end
end
-
- hash
end
- def build_payment_method(payment_method, options)
- result = payment_method
+ def add_echeck_purchase_params(doc, money, payment_method, options)
+ doc.orderId(truncate(options[:order_id], 24))
+ doc.amount(money)
+ add_order_source(doc, payment_method, options)
+ add_billing_address(doc, payment_method, options)
+ add_payment_method(doc, payment_method, options)
+ add_descriptor(doc, options)
+ add_processing_type(doc, options)
+ add_original_network_transaction(doc, options)
+ end
- # Build instance of the LitleCardToken class for internal use if this is a token request.
- if payment_method.is_a?(String) && options.has_key?(:token)
- result = LitleCardToken.new(:token => payment_method)
- result.month = options[:token][:month]
- result.year = options[:token][:year]
- result.verification_value = options[:token][:verification_value]
- result.brand = options[:token][:brand]
+ def add_descriptor(doc, options)
+ if options[:descriptor_name] || options[:descriptor_phone]
+ doc.customBilling do
+ doc.phone(options[:descriptor_phone]) if options[:descriptor_phone]
+ doc.descriptor(options[:descriptor_name]) if options[:descriptor_name]
+ end
end
+ end
- result
+ def add_debt_repayment(doc, options)
+ doc.debtRepayment(true) if options[:debt_repayment] == true
end
- def add_creditcard_or_cardtoken_hash(hash, creditcard_or_cardtoken)
- if creditcard_or_cardtoken.is_a?(LitleCardToken)
- add_cardtoken_hash(hash, creditcard_or_cardtoken)
+ def add_payment_method(doc, payment_method, options)
+ if payment_method.is_a?(String)
+ doc.token do
+ doc.litleToken(payment_method)
+ if options[:month].present? && options[:year].present?
+ doc.expDate("#{options[:month]}#{options[:year]}")
+ end
+ end
+ elsif payment_method.respond_to?(:litle_token)
+ doc.token do
+ doc.litleToken(payment_method.litle_token)
+ if payment_method.try(:month) && payment_method.try(:year)
+ doc.expDate("#{payment_method.month}#{payment_method.year}")
+ end
+ end
+ elsif payment_method.respond_to?(:paypage_registration_id)
+ doc.paypage do
+ doc.paypageRegistrationId(payment_method.paypage_registration_id)
+ if payment_method.try(:month) && payment_method.try(:year)
+ doc.expDate("#{payment_method.month}#{payment_method.year}")
+ end
+ if payment_method.try(:verification_value)
+ doc.cardValidationNum(payment_method.verification_value)
+ end
+ end
+ elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present?
+ doc.card do
+ doc.track(payment_method.track_data)
+ end
+ elsif check?(payment_method)
+ doc.echeck do
+ doc.accType(payment_method.account_type.capitalize)
+ doc.accNum(payment_method.account_number)
+ doc.routingNum(payment_method.routing_number)
+ doc.checkNum(payment_method.number)
+ end
else
- add_creditcard_hash(hash, creditcard_or_cardtoken)
+ doc.card do
+ doc.type_(CARD_TYPE[payment_method.brand])
+ doc.number(payment_method.number)
+ doc.expDate(exp_date(payment_method))
+ doc.cardValidationNum(payment_method.verification_value)
+ end
+ if payment_method.is_a?(NetworkTokenizationCreditCard)
+ doc.cardholderAuthentication do
+ doc.authenticationValue(payment_method.payment_cryptogram)
+ end
+ elsif options[:order_source]&.start_with?('3ds')
+ doc.cardholderAuthentication do
+ doc.authenticationValue(options[:cavv]) if options[:cavv]
+ doc.authenticationTransactionId(options[:xid]) if options[:xid]
+ end
+ end
end
end
- def add_identification_or_cardtoken_hash(hash, identification_or_cardtoken)
- if identification_or_cardtoken.is_a?(LitleCardToken)
- add_cardtoken_hash(hash, identification_or_cardtoken)
+ def add_stored_credential_params(doc, options={})
+ return unless options[:stored_credential]
+
+ if options[:stored_credential][:initial_transaction]
+ add_stored_credential_params_initial(doc, options)
else
- transaction_id, kind = split_authorization(identification_or_cardtoken)
- hash['litleTxnId'] = transaction_id
+ add_stored_credential_params_used(doc, options)
end
end
- def add_cardtoken_hash(hash, cardtoken)
- token_info = {}
- token_info['litleToken'] = cardtoken.token
- token_info['expDate'] = cardtoken.exp_date if cardtoken.exp_date?
- token_info['cardValidationNum'] = cardtoken.verification_value unless cardtoken.verification_value.blank?
- token_info['type'] = cardtoken.type unless cardtoken.type.blank?
-
- hash['token'] = token_info
- hash
+ def add_stored_credential_params_initial(doc, options)
+ case options[:stored_credential][:reason_type]
+ when 'unscheduled'
+ doc.processingType('initialCOF')
+ when 'installment'
+ doc.processingType('initialInstallment')
+ when 'recurring'
+ doc.processingType('initialRecurring')
+ end
end
- def add_creditcard_hash(hash, creditcard)
- cc_type = CARD_TYPE[creditcard.brand]
- exp_date_yr = creditcard.year.to_s[2..3]
- exp_date_mo = '%02d' % creditcard.month.to_i
- exp_date = exp_date_mo + exp_date_yr
+ def add_stored_credential_params_used(doc, options)
+ if options[:stored_credential][:reason_type] == 'unscheduled'
+ if options[:stored_credential][:initiator] == 'merchant'
+ doc.processingType('merchantInitiatedCOF')
+ else
+ doc.processingType('cardholderInitiatedCOF')
+ end
+ end
+ doc.originalNetworkTransactionId(options[:stored_credential][:network_transaction_id])
+ end
- card_info = {
- 'type' => cc_type,
- 'number' => creditcard.number,
- 'expDate' => exp_date,
- 'cardValidationNum' => creditcard.verification_value
- }
+ def add_billing_address(doc, payment_method, options)
+ return if payment_method.is_a?(String)
- hash['card'] = card_info
- hash
- end
+ doc.billToAddress do
+ if check?(payment_method)
+ doc.name(payment_method.name)
+ doc.firstName(payment_method.first_name)
+ doc.lastName(payment_method.last_name)
+ else
+ doc.name(payment_method.name)
+ end
+ doc.email(options[:email]) if options[:email]
- def create_capture_hash(money, authorization, options)
- hash = create_hash(money, options)
- hash['litleTxnId'] = authorization
- hash
+ add_address(doc, options[:billing_address])
+ end
end
- def create_token_hash(creditcard_or_paypage_registration_id, options)
- hash = create_hash(0, options)
+ def add_shipping_address(doc, payment_method, options)
+ return if payment_method.is_a?(String)
- if creditcard_or_paypage_registration_id.is_a?(String)
- hash['paypageRegistrationId'] = creditcard_or_paypage_registration_id
- else
- hash['accountNumber'] = creditcard_or_paypage_registration_id.number
+ doc.shipToAddress do
+ add_address(doc, options[:shipping_address])
end
-
- hash
end
- def create_void_hash(identification, options)
- hash = create_hash(nil, options)
- hash['litleTxnId'] = identification
- hash
+ def add_address(doc, address)
+ return unless address
+
+ doc.companyName(address[:company]) unless address[:company].blank?
+ doc.addressLine1(address[:address1]) unless address[:address1].blank?
+ doc.addressLine2(address[:address2]) unless address[:address2].blank?
+ doc.city(address[:city]) unless address[:city].blank?
+ doc.state(address[:state]) unless address[:state].blank?
+ doc.zip(address[:zip]) unless address[:zip].blank?
+ doc.country(address[:country]) unless address[:country].blank?
+ doc.phone(address[:phone]) unless address[:phone].blank?
end
- def create_auth_reversal_hash(identification, money, options)
- hash = create_hash(money, options)
- hash['litleTxnId'] = identification
- hash
+ def add_order_source(doc, payment_method, options)
+ order_source = order_source(options)
+ if order_source
+ doc.orderSource(order_source)
+ elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :apple_pay
+ doc.orderSource('applepay')
+ elsif payment_method.is_a?(NetworkTokenizationCreditCard) && payment_method.source == :android_pay
+ doc.orderSource('androidpay')
+ elsif payment_method.respond_to?(:track_data) && payment_method.track_data.present?
+ doc.orderSource('retail')
+ else
+ doc.orderSource('ecommerce')
+ end
end
- def create_hash(money, options)
- fraud_check_type = {}
- if options[:ip]
- fraud_check_type['customerIpAddress'] = options[:ip]
+ def order_source(options={})
+ return options[:order_source] unless options[:stored_credential]
+ order_source = nil
+
+ case options[:stored_credential][:reason_type]
+ when 'unscheduled'
+ if options[:stored_credential][:initiator] == 'merchant'
+ # For merchant-initiated, we should always set order source to
+ # 'ecommerce'
+ order_source = 'ecommerce'
+ else
+ # For cardholder-initiated, we rely on #add_order_source's
+ # default logic to set orderSource appropriately
+ order_source = options[:order_source]
+ end
+ when 'installment'
+ order_source = 'installment'
+ when 'recurring'
+ order_source = 'recurring'
end
- enhanced_data = {}
- if options[:invoice]
- enhanced_data['invoiceReferenceNumber'] = options[:invoice]
- end
+ order_source
+ end
- if options[:description]
- enhanced_data['customerReference'] = options[:description]
- end
+ def add_pos(doc, payment_method)
+ return unless payment_method.respond_to?(:track_data) && payment_method.track_data.present?
- if options[:billing_address]
- bill_to_address = {
- 'name' => options[:billing_address][:name],
- 'companyName' => options[:billing_address][:company],
- 'addressLine1' => options[:billing_address][:address1],
- 'addressLine2' => options[:billing_address][:address2],
- 'city' => options[:billing_address][:city],
- 'state' => options[:billing_address][:state],
- 'zip' => options[:billing_address][:zip],
- 'country' => options[:billing_address][:country],
- 'email' => options[:email],
- 'phone' => options[:billing_address][:phone]
- }
- end
- if options[:shipping_address]
- ship_to_address = {
- 'name' => options[:shipping_address][:name],
- 'companyName' => options[:shipping_address][:company],
- 'addressLine1' => options[:shipping_address][:address1],
- 'addressLine2' => options[:shipping_address][:address2],
- 'city' => options[:shipping_address][:city],
- 'state' => options[:shipping_address][:state],
- 'zip' => options[:shipping_address][:zip],
- 'country' => options[:shipping_address][:country],
- 'email' => options[:email],
- 'phone' => options[:shipping_address][:phone]
- }
+ doc.pos do
+ doc.capability('magstripe')
+ doc.entryMode('completeread')
+ doc.cardholderId('signature')
end
+ end
- hash = {
- 'billToAddress' => bill_to_address,
- 'shipToAddress' => ship_to_address,
- 'orderId' => (options[:order_id] || @options[:order_id]),
- 'customerId' => options[:customer],
- 'reportGroup' => (options[:merchant] || @options[:merchant]),
- 'merchantId' => (options[:merchant_id] || @options[:merchant_id]),
- 'orderSource' => (options[:order_source] || 'ecommerce'),
- 'enhancedData' => enhanced_data,
- 'fraudCheckType' => fraud_check_type,
- 'user' => (options[:user] || @options[:user]),
- 'password' => (options[:password] || @options[:password]),
- 'version' => (options[:version] || @options[:version]),
- 'url' => (options[:url] || url),
- 'proxy_addr' => (options[:proxy_addr] || @options[:proxy_addr]),
- 'proxy_port' => (options[:proxy_port] || @options[:proxy_port]),
- 'id' => (options[:id] || options[:order_id] || @options[:order_id])
- }
+ def add_processing_type(doc, options)
+ doc.processingType(options[:processing_type]) unless options[:processing_type].blank?
+ end
- if (!money.nil? && money.to_s.length > 0)
- hash.merge!({ 'amount' => money })
- end
- hash
+ def add_original_network_transaction(doc, options)
+ doc.originalNetworkTransactionId(options[:original_network_transaction_id]) unless options[:original_network_transaction_id].blank?
end
- def fraud_result(authorization_response)
- if result = authorization_response['fraudResult']
- if result.key?('cardValidationResult')
- cvv_to_pass = result['cardValidationResult'].blank? ? "P" : result['cardValidationResult']
- end
+ def exp_date(payment_method)
+ "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}"
+ end
- avs_to_pass = AVS_RESPONSE_CODE[result['avsResult']] unless result['avsResult'].blank?
- end
- { 'cvv' => cvv_to_pass, 'avs' => avs_to_pass }
- end
-
- # A +LitleCardToken+ object represents a tokenized credit card, and is capable of validating the various
- # data associated with these.
- #
- # == Example Usage
- # token = LitleCardToken.new(
- # :token => '1234567890123456',
- # :month => '9',
- # :year => '2010',
- # :brand => 'visa',
- # :verification_value => '123'
- # )
- #
- # token.valid? # => true
- # cc.exp_date # => 0910
- #
- class LitleCardToken
- include Validateable
-
- # Returns or sets the token. (required)
- #
- # @return [String]
- attr_accessor :token
-
- # Returns or sets the expiry month for the card associated with token. (optional)
- #
- # @return [Integer]
- attr_accessor :month
-
- # Returns or sets the expiry year for the card associated with token. (optional)
- #
- # @return [Integer]
- attr_accessor :year
-
- # Returns or sets the card verification value. (optional)
- #
- # @return [String] the verification value
- attr_accessor :verification_value
-
- # Returns or sets the credit card brand. (optional)
- #
- # Valid card types are
- #
- # * +'visa'+
- # * +'master'+
- # * +'discover'+
- # * +'american_express'+
- # * +'diners_club'+
- # * +'jcb'+
- # * +'switch'+
- # * +'solo'+
- # * +'dankort'+
- # * +'maestro'+
- # * +'forbrugsforeningen'+
- # * +'laser'+
- #
- # @return (String) the credit card brand
- attr_accessor :brand
-
- # Returns the Litle credit card type identifier.
- #
- # @return (String) the credit card type identifier
- def type
- CARD_TYPE[brand] unless brand.blank?
+ def parse(kind, xml)
+ parsed = {}
+
+ doc = Nokogiri::XML(xml).remove_namespaces!
+ doc.xpath("//litleOnlineResponse/#{kind}Response/*").each do |node|
+ if node.elements.empty?
+ parsed[node.name.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name}_#{childnode.name}"
+ parsed[name.to_sym] = childnode.text
+ end
+ end
end
- # Returns true if the expiration date is set.
- #
- # @return (Boolean)
- def exp_date?
- !month.to_i.zero? && !year.to_i.zero?
+ if parsed.empty?
+ %w(response message).each do |attribute|
+ parsed[attribute.to_sym] = doc.xpath('//litleOnlineResponse').attribute(attribute).value
+ end
end
- # Returns the card token expiration date in MMYY format.
- #
- # @return (String) the expiration date in MMYY format
- def exp_date
- result = ''
- if exp_date?
- exp_date_yr = year.to_s[2..3]
- exp_date_mo = '%02d' % month.to_i
+ parsed
+ end
- result = exp_date_mo + exp_date_yr
- end
- result
- end
+ def commit(kind, request, money=nil)
+ parsed = parse(kind, ssl_post(url, request, headers))
- # Validates the card token details.
- #
- # Any validation errors are added to the {#errors} attribute.
- def validate
- validate_card_token
- validate_expiration_date
- validate_card_brand
- end
+ options = {
+ authorization: authorization_from(kind, parsed, money),
+ test: test?,
+ :avs_result => { :code => AVS_RESPONSE_CODE[parsed[:fraudResult_avsResult]] },
+ :cvv_result => parsed[:fraudResult_cardValidationResult]
+ }
- def check?
- false
- end
+ Response.new(success_from(kind, parsed), parsed[:message], parsed, options)
+ end
- private
+ def success_from(kind, parsed)
+ return (parsed[:response] == '000') unless kind == :registerToken
+ %w(000 801 802).include?(parsed[:response])
+ end
- CARD_TYPE = {
- 'visa' => 'VI',
- 'master' => 'MC',
- 'american_express' => 'AX',
- 'discover' => 'DI',
- 'jcb' => 'DI',
- 'diners_club' => 'DI'
- }
+ def authorization_from(kind, parsed, money)
+ kind == :registerToken ? parsed[:litleToken] : "#{parsed[:litleTxnId]};#{kind};#{money}"
+ end
- def before_validate #:nodoc:
- self.month = month.to_i
- self.year = year.to_i
- end
+ def split_authorization(authorization)
+ transaction_id, kind, money = authorization.to_s.split(';')
+ [transaction_id, kind, money]
+ end
- # Litle XML Reference Guide 1.8.2
- #
- # The length of the original card number is reflected in the token, so a
- # submitted 16-digit number results in a 16-digit token. Also, all tokens
- # use only numeric characters, so you do not have to change your
- # systems to accept alpha-numeric characters.
- #
- # The credit card token numbers themselves have two parts.
- # The last four digits match the last four digits of the card number.
- # The remaining digits (length can vary based upon original card number
- # length) are a randomly generated.
- def validate_card_token #:nodoc:
- if token.to_s.length < 12 || token.to_s.match(/\A\d+\Z/).nil?
- errors.add :token, "is not a valid card token"
- end
- end
+ def transaction_attributes(options)
+ attributes = {}
+ attributes[:id] = truncate(options[:id] || options[:order_id], 24)
+ attributes[:reportGroup] = options[:merchant] || 'Default Report Group'
+ attributes[:customerId] = options[:customer]
+ attributes.delete_if { |key, value| value == nil }
+ attributes
+ end
- def validate_expiration_date #:nodoc:
- if !month.to_i.zero? || !year.to_i.zero?
- errors.add :month, "is not a valid month" unless valid_month?(month)
- errors.add :year, "is not a valid year" unless valid_expiry_year?(year)
- end
- end
+ def root_attributes
+ {
+ merchantId: @options[:merchant_id],
+ version: SCHEMA_VERSION,
+ xmlns: 'http://www.litle.com/schema'
+ }
+ end
- def validate_card_brand #:nodoc:
- errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand)
+ def build_xml_request
+ builder = Nokogiri::XML::Builder.new
+ builder.__send__('litleOnlineRequest', root_attributes) do |doc|
+ yield(doc)
end
+ builder.doc.root.to_xml
+ end
- def valid_month?(month)
- (1..12).include?(month.to_i)
- end
+ def url
+ test? ? test_url : live_url
+ end
- def valid_expiry_year?(year)
- year.to_s =~ /\A\d{4}\Z/ && year.to_i > 1987
- end
+ def headers
+ {
+ 'Content-Type' => 'text/xml'
+ }
end
end
end
diff --git a/lib/active_merchant/billing/gateways/litle/paypage_registration.rb b/lib/active_merchant/billing/gateways/litle/paypage_registration.rb
new file mode 100644
index 00000000000..f8f4613295a
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/litle/paypage_registration.rb
@@ -0,0 +1,28 @@
+module ActiveMerchant
+ module Billing
+ # Litle provides functionality to make authorization and sale calls
+ # with a Paypage Registration Id instead of a Litle Token.
+ # This class wraps the required data for such a request.
+ #
+ # The first parameter is the paypage_registration_id and is required.
+ #
+ # The second parameter optionally takes a month, year, verification_value, and name.
+ # These parameters are allowed by the Litle endpoints but not necessary for
+ # a successful request.
+ #
+ # The name parameter is allowed by Vantiv as a member of the billToAddress element.
+ # It is passed in here to be consistent with the rest of the Litle gateway and Activemerchant.
+ class LitlePaypageRegistration
+ attr_reader :paypage_registration_id, :month, :year, :verification_value, :name, :type
+
+ def initialize(paypage_registration_id, options = {})
+ @paypage_registration_id = paypage_registration_id
+ @month = options[:month]
+ @year = options[:year]
+ @verification_value = options[:verification_value]
+ @name = options[:name]
+ @type = options[:type]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/mastercard.rb b/lib/active_merchant/billing/gateways/mastercard.rb
new file mode 100644
index 00000000000..609039cb9e3
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/mastercard.rb
@@ -0,0 +1,267 @@
+module ActiveMerchant
+ module Billing
+ module MastercardGateway
+ def initialize(options={})
+ requires!(options, :userid, :password)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ MultiResponse.run do |r|
+ r.process { authorize(amount, payment_method, options) }
+ r.process { capture(amount, r.authorization, options) }
+ end
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = new_post
+ add_invoice(post, amount, options)
+ add_reference(post, *new_authorization)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, payment_method, options)
+ add_3dsecure_id(post, options)
+
+ commit('authorize', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = new_post
+ add_invoice(post, amount, options, :transaction)
+ add_reference(post, *next_authorization(authorization))
+ add_customer_data(post, nil, options)
+ add_3dsecure_id(post, options)
+
+ commit('capture', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = new_post
+ add_invoice(post, amount, options, :transaction)
+ add_reference(post, *next_authorization(authorization))
+ add_customer_data(post, nil, options)
+
+ commit('refund', post)
+ end
+
+ def void(authorization, options={})
+ post = new_post
+ add_reference(post, *next_authorization(authorization), :targetTransactionId)
+
+ 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 verify_credentials
+ url = build_url(SecureRandom.uuid, 'nonexistent')
+ begin
+ ssl_get(url, headers)
+ rescue ResponseError => e
+ return false if e.response.code.to_i == 401
+ end
+
+ true
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic ).*\\r\\n), '\1[FILTERED]').
+ gsub(%r(("number"?\\?":"?\\?")\d*), '\1[FILTERED]').
+ gsub(%r(("securityCode"?\\?":"?\\?")\d*), '\1[FILTERED]')
+ end
+
+ private
+
+ def new_post
+ {
+ order: {},
+ sourceOfFunds: {
+ provided: {
+ card: {
+ }
+ }
+ },
+ customer: {},
+ billing: {},
+ device: {},
+ shipping: {},
+ transaction: {},
+ }
+ end
+
+ def add_invoice(post, amount, options, node=:order)
+ post[node][:amount] = amount(amount)
+ post[node][:currency] = (options[:currency] || currency(amount))
+ end
+
+ def add_reference(post, orderid, transactionid, transaction_reference, reference_key=:reference)
+ post[:orderid] = orderid
+ post[:transactionid] = transactionid
+ post[:transaction][reference_key] = transaction_reference if transaction_reference
+ end
+
+ def add_payment_method(post, payment_method)
+ card = {}
+ card[:expiry] = {}
+ card[:number] = payment_method.number
+ card[:securityCode] = payment_method.verification_value
+ card[:expiry][:year] = format(payment_method.year, :two_digits)
+ card[:expiry][:month] = format(payment_method.month, :two_digits)
+
+ post[:sourceOfFunds][:type] = 'CARD'
+ post[:sourceOfFunds][:provided][:card].merge!(card)
+ end
+
+ def add_customer_data(post, payment_method, options)
+ billing = {}
+ shipping = {}
+ customer = {}
+ device = {}
+
+ customer[:firstName] = payment_method.first_name if payment_method
+ customer[:lastName] = payment_method.last_name if payment_method
+ customer[:email] = options[:email] if options[:email]
+ device[:ipAddress] = options[:ip] if options[:ip]
+
+ if (billing_address = options[:billing_address])
+ billing[:address] = {}
+ billing[:address][:street] = billing_address[:address1]
+ billing[:address][:street2] = billing_address[:address2]
+ billing[:address][:city] = billing_address[:city]
+ billing[:address][:stateProvince] = billing_address[:state]
+ billing[:address][:postcodeZip] = billing_address[:zip]
+ billing[:address][:country] = country_code(billing_address[:country])
+ customer[:phone] = billing_address[:phone]
+ end
+
+ if (shipping_address = options[:shipping_address])
+ shipping[:address] = {}
+ shipping[:address][:street] = shipping_address[:address1]
+ shipping[:address][:street2] = shipping_address[:address2]
+ shipping[:address][:city] = shipping_address[:city]
+ shipping[:address][:stateProvince] = shipping_address[:state]
+ shipping[:address][:postcodeZip] = shipping_address[:zip]
+ shipping[:address][:shipcountry] = country_code(shipping_address[:country])
+
+ first_name, last_name = split_names(shipping_address[:name])
+ shipping[:firstName] = first_name if first_name
+ shipping[:lastName] = last_name if last_name
+ end
+ post[:billing].merge!(billing)
+ post[:shipping].merge!(shipping)
+ post[:device].merge!(device)
+ post[:customer].merge!(customer)
+ end
+
+ def add_3dsecure_id(post, options)
+ return unless options[:threed_secure_id]
+ post.merge!({'3DSecureId' => options[:threed_secure_id]})
+ end
+
+ def country_code(country)
+ if country
+ country = ActiveMerchant::Country.find(country)
+ country.code(:alpha3).value
+ end
+ rescue InvalidCountryCodeError
+ end
+
+ def headers
+ {
+ 'Authorization' => 'Basic ' + Base64.encode64("merchant.#{@options[:userid]}:#{@options[:password]}").strip.delete("\r\n"),
+ 'Content-Type' => 'application/json',
+ }
+ end
+
+ def commit(action, post)
+ url = build_url(post.delete(:orderid), post.delete(:transactionid))
+ post[:apiOperation] = action.upcase
+ begin
+ raw = parse(ssl_request(:put, url, build_request(post), headers))
+ rescue ResponseError => e
+ raw = parse(e.response.body)
+ end
+ succeeded = success_from(raw)
+ Response.new(
+ succeeded,
+ message_from(succeeded, raw),
+ raw,
+ :authorization => authorization_from(post, raw),
+ :test => test?
+ )
+ end
+
+ def build_url(orderid, transactionid)
+ "#{base_url}merchant/#{@options[:userid]}/order/#{orderid}/transaction/#{transactionid}"
+ end
+
+ def base_url
+ if test?
+ @options[:region] == 'asia_pacific' ? test_ap_url : test_na_url
+ else
+ @options[:region] == 'asia_pacific' ? live_ap_url : live_na_url
+ end
+ end
+
+ def build_request(post = {})
+ post.to_json
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(response)
+ response['result'] == 'SUCCESS'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ [
+ response['result'],
+ response['response'] && response['response']['gatewayCode'],
+ response['error'] && response['error']['cause'],
+ response['error'] && response['error']['explanation']
+ ].compact.join(' - ')
+ end
+ end
+
+ def authorization_from(request, response)
+ [response['order']['id'], response['transaction']['id']].join('|') if response['order']
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def new_authorization
+ # Must be unique within a merchant id.
+ orderid = SecureRandom.uuid
+
+ # Must be unique within an order id.
+ transactionid = '1'
+
+ # New transactions have no previous reference.
+ transaction_reference = nil
+ [orderid, transactionid, transaction_reference]
+ end
+
+ def next_authorization(authorization)
+ orderid, prev_transactionid = split_authorization(authorization)
+ next_transactionid = SecureRandom.uuid
+ [orderid, next_transactionid, prev_transactionid]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/maxipago.rb b/lib/active_merchant/billing/gateways/maxipago.rb
new file mode 100644
index 00000000000..b4ca346327b
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/maxipago.rb
@@ -0,0 +1,220 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class MaxipagoGateway < Gateway
+ API_VERSION = '3.1.1.15'
+
+ self.live_url = 'https://api.maxipago.net/UniversalAPI/postXML'
+ self.test_url = 'https://testapi.maxipago.net/UniversalAPI/postXML'
+
+ self.supported_countries = ['BR']
+ self.default_currency = 'BRL'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club]
+ self.homepage_url = 'http://www.maxipago.com/'
+ self.display_name = 'maxiPago!'
+
+ def initialize(options = {})
+ requires!(options, :login, :password)
+ super
+ end
+
+ def purchase(money, creditcard, options = {})
+ commit(:sale) do |xml|
+ add_auth_purchase(xml, money, creditcard, options)
+ end
+ end
+
+ def authorize(money, creditcard, options = {})
+ commit(:auth) do |xml|
+ add_auth_purchase(xml, money, creditcard, options)
+ end
+ end
+
+ def capture(money, authorization, options = {})
+ commit(:capture) do |xml|
+ add_order_id(xml, authorization)
+ add_reference_num(xml, options)
+ xml.payment do
+ add_amount(xml, money, options)
+ end
+ end
+ end
+
+ def void(authorization, options = {})
+ _, transaction_id = split_authorization(authorization)
+ commit(:void) do |xml|
+ xml.transactionID transaction_id
+ end
+ end
+
+ def refund(money, authorization, options = {})
+ commit(:return) do |xml|
+ add_order_id(xml, authorization)
+ add_reference_num(xml, options)
+ xml.payment do
+ add_amount(xml, money, options)
+ end
+ end
+ end
+
+ def verify(creditcard, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def commit(action)
+ request = build_xml_request(action) { |doc| yield(doc) }
+ response = parse(ssl_post(url, request, 'Content-Type' => 'text/xml'))
+
+ Response.new(
+ success?(response),
+ message_from(response),
+ response,
+ test: test?,
+ authorization: authorization_from(response)
+ )
+ end
+
+ def url
+ test? ? self.test_url : self.live_url
+ end
+
+ def build_xml_request(action)
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8')
+ builder.send('transaction-request') do |xml|
+ xml.version '3.1.1.15'
+ xml.verification do
+ xml.merchantId @options[:login]
+ xml.merchantKey @options[:password]
+ end
+ xml.order do
+ xml.send("#{action}!") do
+ yield(xml)
+ end
+ end
+ end
+
+ builder.to_xml(indent: 2)
+ end
+
+ def success?(response)
+ response[:response_code] == '0'
+ end
+
+ def message_from(response)
+ response[:error_message] || response[:response_message] || response[:processor_message] || response[:error_msg]
+ end
+
+ def authorization_from(response)
+ "#{response[:order_id]}|#{response[:transaction_id]}"
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def parse(body)
+ xml = REXML::Document.new(body)
+
+ response = {}
+ xml.root.elements.to_a.each do |node|
+ parse_element(response, node)
+ end
+ response
+ end
+
+ def parse_element(response, node)
+ if node.has_elements?
+ node.elements.each { |element| parse_element(response, element) }
+ else
+ response[node.name.underscore.to_sym] = node.text
+ end
+ end
+
+ def add_auth_purchase(xml, money, creditcard, options)
+ add_processor_id(xml)
+ xml.fraudCheck('N')
+ add_reference_num(xml, options)
+ xml.transactionDetail do
+ xml.payType do
+ xml.creditCard do
+ xml.number(creditcard.number)
+ xml.expMonth(creditcard.month)
+ xml.expYear(creditcard.year)
+ xml.cvvNumber(creditcard.verification_value)
+ end
+ end
+ end
+ xml.payment do
+ add_amount(xml, money, options)
+ add_installments(xml, options)
+ end
+ add_billing_address(xml, creditcard, options)
+ end
+
+ def add_reference_num(xml, options)
+ xml.referenceNum(options[:order_id] || generate_unique_id)
+ end
+
+ def add_amount(xml, money, options)
+ xml.chargeTotal(amount(money))
+ xml.currencyCode(options[:currency] || currency(money) || default_currency)
+ end
+
+ def add_processor_id(xml)
+ if test?
+ xml.processorID(1)
+ else
+ xml.processorID(@options[:processor_id] || 4)
+ end
+ end
+
+ def add_installments(xml, options)
+ if options.has_key?(:installments) && options[:installments] > 1
+ xml.creditInstallment do
+ xml.numberOfInstallments options[:installments]
+ xml.chargeInterest 'N'
+ end
+ end
+ end
+
+ def add_billing_address(xml, creditcard, options)
+ address = options[:billing_address]
+ return unless address
+
+ xml.billing do
+ xml.name creditcard.name
+ xml.address address[:address1] if address[:address1]
+ xml.address2 address[:address2] if address[:address2]
+ xml.city address[:city] if address[:city]
+ xml.state address[:state] if address[:state]
+ xml.postalcode address[:zip] if address[:zip]
+ xml.country address[:country] if address[:country]
+ xml.phone address[:phone] if address[:phone]
+ end
+ end
+
+ def add_order_id(xml, authorization)
+ order_id, _ = split_authorization(authorization)
+ xml.orderID order_id
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/mercado_pago.rb b/lib/active_merchant/billing/gateways/mercado_pago.rb
new file mode 100644
index 00000000000..98cc40bb374
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/mercado_pago.rb
@@ -0,0 +1,277 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class MercadoPagoGateway < Gateway
+ self.live_url = self.test_url = 'https://api.mercadopago.com/v1'
+
+ self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PE', 'UY']
+ self.supported_cardtypes = [:visa, :master, :american_express, :elo, :cabal, :naranja]
+
+ self.homepage_url = 'https://www.mercadopago.com/'
+ self.display_name = 'Mercado Pago'
+ self.money_format = :dollars
+
+ def initialize(options={})
+ requires!(options, :access_token)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ MultiResponse.run do |r|
+ r.process { commit('tokenize', 'card_tokens', card_token_request(money, payment, options)) }
+ options[:card_token] = r.authorization.split('|').first
+ r.process { commit('purchase', 'payments', purchase_request(money, payment, options)) }
+ end
+ end
+
+ def authorize(money, payment, options={})
+ MultiResponse.run do |r|
+ r.process { commit('tokenize', 'card_tokens', card_token_request(money, payment, options)) }
+ options[:card_token] = r.authorization.split('|').first
+ r.process { commit('authorize', 'payments', authorize_request(money, payment, options)) }
+ end
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ authorization, _ = authorization.split('|')
+ post[:capture] = true
+ post[:transaction_amount] = amount(money).to_f
+ commit('capture', "payments/#{authorization}", post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ authorization, original_amount = authorization.split('|')
+ post[:amount] = amount(money).to_f if original_amount && original_amount.to_f > amount(money).to_f
+ commit('refund', "payments/#{authorization}/refunds", post)
+ end
+
+ def void(authorization, options={})
+ authorization, _ = authorization.split('|')
+ post = { status: 'cancelled' }
+ commit('void', "payments/#{authorization}", 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((access_token=).*?([^\s]+)), '\1[FILTERED]').
+ gsub(%r((\"card_number\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"security_code\\\":\\\")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def card_token_request(money, payment, options = {})
+ post = {}
+ post[:card_number] = payment.number
+ post[:security_code] = payment.verification_value
+ post[:expiration_month] = payment.month
+ post[:expiration_year] = payment.year
+ post[:cardholder] = {
+ name: payment.name,
+ identification: {
+ type: options[:cardholder_identification_type],
+ number: options[:cardholder_identification_number]
+ }
+ }
+ post
+ end
+
+ def purchase_request(money, payment, options = {})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, options)
+ add_additional_data(post, options)
+ add_customer_data(post, payment, options)
+ add_address(post, options)
+ add_processing_mode(post, options)
+ post[:binary_mode] = (options[:binary_mode].nil? ? true : options[:binary_mode])
+ post
+ end
+
+ def authorize_request(money, payment, options = {})
+ post = purchase_request(money, payment, options)
+ post[:capture] = false
+ post
+ end
+
+ def add_processing_mode(post, options)
+ return unless options[:processing_mode]
+ post[:processing_mode] = options[:processing_mode]
+ post[:merchant_account_id] = options[:merchant_account_id] if options[:merchant_account_id]
+ add_merchant_services(post, options)
+ end
+
+ def add_merchant_services(post, options)
+ return unless options[:fraud_scoring] || options[:fraud_manual_review]
+ merchant_services = {}
+ merchant_services[:fraud_scoring] = options[:fraud_scoring] if options[:fraud_scoring]
+ merchant_services[:fraud_manual_review] = options[:fraud_manual_review] if options[:fraud_manual_review]
+ post[:merchant_services] = merchant_services
+ end
+
+ def add_additional_data(post, options)
+ post[:sponsor_id] = options[:sponsor_id]
+ post[:device_id] = options[:device_id] if options[:device_id]
+ post[:additional_info] = {
+ ip_address: options[:ip_address]
+ }.merge(options[:additional_info] || {})
+
+ add_address(post, options)
+ add_shipping_address(post, options)
+ end
+
+ def add_customer_data(post, payment, options)
+ post[:payer] = {
+ email: options[:email],
+ first_name: payment.first_name,
+ last_name: payment.last_name
+ }
+ end
+
+ def add_address(post, options)
+ if address = (options[:billing_address] || options[:address])
+
+ post[:additional_info].merge!({
+ payer: {
+ address: {
+ zip_code: address[:zip],
+ street_name: "#{address[:address1]} #{address[:address2]}"
+ }
+ }
+ })
+ end
+ end
+
+ def add_shipping_address(post, options)
+ if address = options[:shipping_address]
+
+ post[:additional_info].merge!({
+ shipments: {
+ receiver_address: {
+ zip_code: address[:zip],
+ street_name: "#{address[:address1]} #{address[:address2]}"
+ }
+ }
+ })
+ end
+ end
+
+ def split_street_address(address1)
+ street_number = address1.split(' ').first
+
+ if street_name = address1.split(' ')[1..-1]
+ street_name = street_name.join(' ')
+ else
+ nil
+ end
+
+ [street_number, street_name]
+ end
+
+ def add_invoice(post, money, options)
+ post[:transaction_amount] = amount(money).to_f
+ post[:description] = options[:description]
+ post[:installments] = options[:installments] ? options[:installments].to_i : 1
+ post[:statement_descriptor] = options[:statement_descriptor] if options[:statement_descriptor]
+ post[:external_reference] = options[:order_id] || SecureRandom.hex(16)
+ end
+
+ def add_payment(post, options)
+ post[:token] = options[:card_token]
+ post[:issuer_id] = options[:issuer_id] if options[:issuer_id]
+ post[:payment_method_id] = options[:payment_method_id] if options[:payment_method_id]
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ rescue JSON::ParserError
+ {
+ 'status' => 'error',
+ 'status_detail' => 'json_parse_error',
+ 'message' => "A non-JSON response was received from Mercado Pago where one was expected. The raw response was:\n\n#{body}"
+ }
+ end
+
+ def commit(action, path, parameters)
+ if ['capture', 'void'].include?(action)
+ response = parse(ssl_request(:put, url(path), post_data(parameters), headers))
+ else
+ response = parse(ssl_post(url(path), post_data(parameters), headers(parameters)))
+ end
+
+ Response.new(
+ success_from(action, response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, parameters),
+ test: test?,
+ error_code: error_code_from(action, response)
+ )
+ end
+
+ def success_from(action, response)
+ if action == 'refund'
+ response['status'] != 404 && response['error'].nil?
+ else
+ ['active', 'approved', 'authorized', 'cancelled', 'in_process'].include?(response['status'])
+ end
+ end
+
+ def message_from(response)
+ (response['status_detail']) || (response['message'])
+ end
+
+ def authorization_from(response, params)
+ [response['id'], params[:transaction_amount]].join('|')
+ end
+
+ def post_data(parameters = {})
+ parameters.clone.tap { |p| p.delete(:device_id) }.to_json
+ end
+
+ def error_code_from(action, response)
+ unless success_from(action, response)
+ if cause = response['cause']
+ cause.empty? ? nil : cause.first['code']
+ else
+ response['status']
+ end
+ end
+ end
+
+ def url(action)
+ full_url = (test? ? test_url : live_url)
+ full_url + "/#{action}?access_token=#{CGI.escape(@options[:access_token])}"
+ end
+
+ def headers(options = {})
+ headers = {
+ 'Content-Type' => 'application/json'
+ }
+ headers['X-Device-Session-ID'] = options[:device_id] if options[:device_id]
+ headers
+ end
+
+ def handle_response(response)
+ case response.code.to_i
+ when 200..499
+ response.body
+ else
+ raise ResponseError.new(response)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
index 7e5d8db0e4a..bad8070e2d1 100644
--- a/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
+++ b/lib/active_merchant/billing/gateways/merchant_e_solutions.rb
@@ -1,6 +1,8 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class MerchantESolutionsGateway < Gateway
+ include Empty
+
self.test_url = 'https://cert.merchante-solutions.com/mes-api/tridentApi'
self.live_url = 'https://api.merchante-solutions.com/mes-api/tridentApi'
@@ -28,6 +30,7 @@ def authorize(money, creditcard_or_card_id, options = {})
add_invoice(post, options)
add_payment_source(post, creditcard_or_card_id, options)
add_address(post, options)
+ add_3dsecure_params(post, options)
commit('P', money, post)
end
@@ -38,6 +41,7 @@ def purchase(money, creditcard_or_card_id, options = {})
add_invoice(post, options)
add_payment_source(post, creditcard_or_card_id, options)
add_address(post, options)
+ add_3dsecure_params(post, options)
commit('D', money, post)
end
@@ -45,6 +49,8 @@ def capture(money, transaction_id, options = {})
post ={}
post[:transaction_id] = transaction_id
post[:client_reference_number] = options[:customer] if options.has_key?(:customer)
+ add_invoice(post, options)
+ add_3dsecure_params(post, options)
commit('S', money, post)
end
@@ -88,6 +94,17 @@ def void(transaction_id, options = {})
commit('V', nil, options.merge(post))
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?profile_key=)\w*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?card_number=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?cvv2=)\d*(&?)), '\1[FILTERED]\2')
+ end
+
private
def add_address(post, options)
@@ -99,7 +116,8 @@ def add_address(post, options)
def add_invoice(post, options)
if options.has_key? :order_id
- post[:invoice_number] = options[:order_id].to_s.gsub(/[^\w.]/, '')
+ order_id = options[:order_id].to_s.gsub(/[^\w.]/, '')
+ post[:invoice_number] = truncate(order_id, 17)
end
end
@@ -120,10 +138,17 @@ def add_creditcard(post, creditcard, options)
post[:card_exp_date] = expdate(creditcard)
end
+ def add_3dsecure_params(post, options)
+ post[:xid] = options[:xid] unless empty?(options[:xid])
+ post[:cavv] = options[:cavv] unless empty?(options[:cavv])
+ post[:ucaf_collection_ind] = options[:ucaf_collection_ind] unless empty?(options[:ucaf_collection_ind])
+ post[:ucaf_auth_data] = options[:ucaf_auth_data] unless empty?(options[:ucaf_auth_data])
+ end
+
def parse(body)
results = {}
body.split(/&/).each do |pair|
- key,val = pair.split(/=/)
+ key, val = pair.split(/=/)
results[key] = val
end
results
@@ -133,32 +158,25 @@ def commit(action, money, parameters)
url = test? ? self.test_url : self.live_url
parameters[:transaction_amount] = amount(money) if money unless action == 'V'
-
response = begin
- parse( ssl_post(url, post_data(action,parameters)) )
+ parse(ssl_post(url, post_data(action, parameters)))
rescue ActiveMerchant::ResponseError => e
- { "error_code" => "404", "auth_response_text" => e.to_s }
+ { 'error_code' => '404', 'auth_response_text' => e.to_s }
end
- Response.new(response["error_code"] == "000", message_from(response), response,
- :authorization => response["transaction_id"],
+ Response.new(response['error_code'] == '000', message_from(response), response,
+ :authorization => response['transaction_id'],
:test => test?,
- :cvv_result => response["cvv2_result"],
- :avs_result => { :code => response["avs_result"] }
+ :cvv_result => response['cvv2_result'],
+ :avs_result => { :code => response['avs_result'] }
)
end
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
- "#{month}#{year[-2..-1]}"
- end
-
def message_from(response)
- if response["error_code"] == "000"
- "This transaction has been approved"
+ if response['error_code'] == '000'
+ 'This transaction has been approved'
else
- response["auth_response_text"]
+ response['auth_response_text']
end
end
@@ -168,7 +186,7 @@ def post_data(action, parameters = {})
post[:profile_key] = @options[:password]
post[:transaction_type] = action if action
- request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&")
+ request = post.merge(parameters).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
request
end
end
diff --git a/lib/active_merchant/billing/gateways/merchant_one.rb b/lib/active_merchant/billing/gateways/merchant_one.rb
index b85844c7bdd..20f60dd0cd2 100644
--- a/lib/active_merchant/billing/gateways/merchant_one.rb
+++ b/lib/active_merchant/billing/gateways/merchant_one.rb
@@ -1,4 +1,4 @@
-require "cgi"
+require 'cgi'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -8,7 +8,6 @@ class MerchantOneSslConnection < ActiveMerchant::Connection
def configure_ssl(http)
super(http)
http.use_ssl = true
- http.ssl_version = :SSLv3
end
end
@@ -47,7 +46,7 @@ def purchase(money, creditcard, options = {})
def capture(money, authorization, options = {})
post = {}
- post.merge!(:transactionid => authorization)
+ post[:transactionid] = authorization
add_amount(post, money, options)
commit('capture', money, post)
end
@@ -76,40 +75,39 @@ def add_address(post, creditcard, options)
end
def add_creditcard(post, creditcard)
- post['cvv'] = creditcard.verification_value
- post['ccnumber'] = creditcard.number
- post['ccexp'] = "#{sprintf("%02d", creditcard.month)}#{"#{creditcard.year}"[-2, 2]}"
+ post['cvv'] = creditcard.verification_value
+ post['ccnumber'] = creditcard.number
+ post['ccexp'] = "#{sprintf("%02d", creditcard.month)}#{creditcard.year.to_s[-2, 2]}"
end
def commit(action, money, parameters={})
parameters['username'] = @options[:username]
parameters['password'] = @options[:password]
- parse(ssl_post(BASE_URL,post_data(action, parameters)))
+ parse(ssl_post(BASE_URL, post_data(action, parameters)))
end
def post_data(action, parameters = {})
- parameters.merge!({:type => action})
- ret = ""
+ parameters[:type] = action
+ ret = ''
for key in parameters.keys
ret += "#{key}=#{CGI.escape(parameters[key].to_s)}"
if key != parameters.keys.last
- ret += "&"
+ ret += '&'
end
end
ret.to_s
end
def parse(data)
- responses = CGI.parse(data).inject({}){|h,(k, v)| h[k] = v.first; h}
+ responses = CGI.parse(data).inject({}) { |h, (k, v)| h[k] = v.first; h }
Response.new(
- (responses["response"].to_i == 1),
- responses["responsetext"],
+ (responses['response'].to_i == 1),
+ responses['responsetext'],
responses,
:test => test?,
- :authorization => responses["transactionid"]
+ :authorization => responses['transactionid']
)
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/merchant_partners.rb b/lib/active_merchant/billing/gateways/merchant_partners.rb
new file mode 100644
index 00000000000..e4630211a5d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/merchant_partners.rb
@@ -0,0 +1,245 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class MerchantPartnersGateway < Gateway
+ self.display_name = 'Merchant Partners'
+ self.homepage_url = 'http://www.merchantpartners.com/'
+
+ self.live_url = 'https://trans.merchantpartners.com/cgi-bin/ProcessXML.cgi'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
+
+ def initialize(options={})
+ requires!(options, :account_id, :merchant_pin)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit(payment_method.is_a?(String) ? :stored_purchase : :purchase, post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit(:authorize, post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit(:capture, post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+
+ commit(:void, post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit(:refund, post)
+ end
+
+ def credit(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+
+ commit(payment_method.is_a?(String) ? :stored_credit : :credit, 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_method, options = {})
+ post = {}
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ post[:profileactiontype] = options[:profileactiontype] || STORE_TX_TYPES[:store_only]
+
+ commit(:store, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2')
+ end
+
+ def test?
+ @options[:account_id].eql?('TEST0')
+ end
+
+ private
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:merchantordernumber] = options[:order_id]
+ post[:currency] = options[:currency] || currency(money)
+ end
+
+ def add_payment_method(post, payment_method)
+ if(payment_method.is_a?(String))
+ user_profile_id, last_4 = split_authorization(payment_method)
+ post[:userprofileid] = user_profile_id
+ post[:last4digits] = last_4
+ else
+ post[:ccname] = payment_method.name
+ post[:ccnum] = payment_method.number
+ post[:cvv2] = payment_method.verification_value
+ post[:expmon] = format(payment_method.month, :two_digits)
+ post[:expyear] = format(payment_method.year, :four_digits)
+ post[:swipedata] = payment_method.track_data if payment_method.track_data
+ end
+ end
+
+ def add_customer_data(post, options)
+ post[:email] = options[:email] if options[:email]
+ post[:ipaddress] = options[:ip] if options[:ip]
+ if(billing_address = options[:billing_address])
+ post[:billaddr1] = billing_address[:address1]
+ post[:billaddr2] = billing_address[:address2]
+ post[:billcity] = billing_address[:city]
+ post[:billstate] = billing_address[:state]
+ post[:billcountry] = billing_address[:country]
+ post[:bilzip] = billing_address[:zip]
+ post[:phone] = billing_address[:phone]
+ end
+ end
+
+ def add_reference(post, authorization)
+ post[:historykeyid] = authorization
+ end
+
+ ACTIONS = {
+ purchase: '2',
+ authorize: '1',
+ capture: '3',
+ void: '5',
+ refund: '4',
+ credit: '6',
+ store: '7',
+ stored_purchase: '8',
+ stored_credit: '13'
+ }
+
+ STORE_TX_TYPES = {
+ store_only: '3'
+ }
+
+ def commit(action, post)
+ post[:acctid] = @options[:account_id]
+ post[:merchantpin] = @options[:merchant_pin]
+ post[:service] = ACTIONS[action] if ACTIONS[action]
+
+ data = build_request(post)
+ response_data = parse(ssl_post(live_url, data, headers))
+ succeeded = success_from(response_data)
+
+ Response.new(
+ succeeded,
+ message_from(succeeded, response_data),
+ response_data,
+ authorization: authorization_from(post, response_data),
+ :avs_result => AVSResult.new(code: response_data['avs_response']),
+ :cvv_result => CVVResult.new(response_data['cvv2_response']),
+ test: test?
+ )
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/xml'
+ }
+ end
+
+ def build_request(post)
+ Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml|
+ xml.interface_driver {
+ xml.trans_catalog {
+ xml.transaction(name: 'creditcard') {
+ xml.inputs {
+ post.each do |field, value|
+ xml.send(field, value)
+ end
+ }
+ }
+ }
+ }
+ end.to_xml
+ end
+
+ def parse(body)
+ response = {}
+ Nokogiri::XML(CGI.unescapeHTML(body)).xpath('//trans_catalog/transaction/outputs').children.each do |node|
+ parse_element(response, node)
+ end
+ response
+ end
+
+ def parse_element(response, node)
+ if node.elements.size == 0
+ response[node.name.downcase.underscore.to_sym] = node.text
+ else
+ node.elements.each { |element| parse_element(response, element) }
+ end
+ end
+
+ def success_from(response)
+ response[:status] == 'Approved'
+ end
+
+ def message_from(succeeded, response)
+ succeeded ? 'Succeeded' : error_message_from(response)
+ end
+
+ def authorization_from(request, response)
+ request[:service] == ACTIONS[:store] ?
+ "#{response[:userprofileid]}|#{response[:last4digits]}" :
+ response[:historyid]
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def error_message_from(response)
+ if(response[:status] == 'Declined')
+ match = response[:result].match(/DECLINED:\d{10}:(.+):/)
+ match[1] if match
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/merchant_ware.rb b/lib/active_merchant/billing/gateways/merchant_ware.rb
index a7609f458b8..d028024ccb0 100644
--- a/lib/active_merchant/billing/gateways/merchant_ware.rb
+++ b/lib/active_merchant/billing/gateways/merchant_ware.rb
@@ -11,25 +11,25 @@ class MerchantWareGateway < Gateway
self.homepage_url = 'http://merchantwarehouse.com/merchantware'
self.display_name = 'MerchantWARE'
- ENV_NAMESPACES = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
- "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
- "xmlns:env" => "http://schemas.xmlsoap.org/soap/envelope/"
+ ENV_NAMESPACES = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
+ 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/'
}
- ENV_NAMESPACES_V4 = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
- "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
- "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/"
+ ENV_NAMESPACES_V4 = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'
}
- TX_NAMESPACE = "http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail"
- TX_NAMESPACE_V4 = "http://schemas.merchantwarehouse.com/merchantware/40/Credit/"
+ TX_NAMESPACE = 'http://merchantwarehouse.com/MerchantWARE/Client/TransactionRetail'
+ TX_NAMESPACE_V4 = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/'
ACTIONS = {
- :purchase => "IssueKeyedSale",
- :authorize => "IssueKeyedPreAuth",
- :capture => "IssuePostAuth",
- :void => "VoidPreAuthorization",
- :credit => "IssueKeyedRefund",
- :reference_credit => "IssueRefundByReference"
+ :purchase => 'IssueKeyedSale',
+ :authorize => 'IssueKeyedPreAuth',
+ :capture => 'IssuePostAuth',
+ :void => 'VoidPreAuthorization',
+ :credit => 'IssueKeyedRefund',
+ :reference_credit => 'IssueRefundByReference'
}
# Creates a new MerchantWareGateway
@@ -105,7 +105,7 @@ def void(authorization, options = {})
# * :order_id - A unique reference for this order (required when performing a non-referenced credit)
def credit(money, identification, options = {})
if identification.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
else
perform_credit(money, identification, options)
@@ -121,9 +121,9 @@ def refund(money, reference, options = {})
def soap_request(action)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! "env:Envelope", ENV_NAMESPACES do
- xml.tag! "env:Body" do
- xml.tag! ACTIONS[action], "xmlns" => TX_NAMESPACE do
+ xml.tag! 'env:Envelope', ENV_NAMESPACES do
+ xml.tag! 'env:Body' do
+ xml.tag! ACTIONS[action], 'xmlns' => TX_NAMESPACE do
add_credentials(xml)
yield xml
end
@@ -135,12 +135,12 @@ def soap_request(action)
def v4_soap_request(action)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! "soap:Envelope", ENV_NAMESPACES_V4 do
- xml.tag! "soap:Body" do
- xml.tag! ACTIONS[:void], "xmlns" => TX_NAMESPACE_V4 do
- xml.tag! "merchantName", @options[:name]
- xml.tag! "merchantSiteId", @options[:login]
- xml.tag! "merchantKey", @options[:password]
+ xml.tag! 'soap:Envelope', ENV_NAMESPACES_V4 do
+ xml.tag! 'soap:Body' do
+ xml.tag! ACTIONS[:void], 'xmlns' => TX_NAMESPACE_V4 do
+ xml.tag! 'merchantName', @options[:name]
+ xml.tag! 'merchantSiteId', @options[:login]
+ xml.tag! 'merchantKey', @options[:password]
yield xml
end
end
@@ -151,7 +151,7 @@ def v4_soap_request(action)
def build_purchase_request(action, money, credit_card, options)
requires!(options, :order_id)
- request = soap_request(action) do |xml|
+ soap_request(action) do |xml|
add_invoice(xml, options)
add_amount(xml, money)
add_credit_card(xml, credit_card)
@@ -162,7 +162,7 @@ def build_purchase_request(action, money, credit_card, options)
def build_capture_request(action, money, identification, options)
reference, options[:order_id] = split_reference(identification)
- request = soap_request(action) do |xml|
+ soap_request(action) do |xml|
add_reference(xml, reference)
add_invoice(xml, options)
add_amount(xml, money)
@@ -175,7 +175,7 @@ def perform_reference_credit(money, identification, options)
request = soap_request(:reference_credit) do |xml|
add_reference(xml, reference)
add_invoice(xml, options)
- add_amount(xml, money, "strOverrideAmount")
+ add_amount(xml, money, 'strOverrideAmount')
end
commit(:reference_credit, request)
@@ -194,50 +194,47 @@ def perform_credit(money, credit_card, options)
end
def add_credentials(xml)
- xml.tag! "strSiteId", @options[:login]
- xml.tag! "strKey", @options[:password]
- xml.tag! "strName", @options[:name]
- end
-
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
+ xml.tag! 'strSiteId', @options[:login]
+ xml.tag! 'strKey', @options[:password]
+ xml.tag! 'strName', @options[:name]
end
def add_invoice(xml, options)
- xml.tag! "strOrderNumber", options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25)
+ xml.tag! 'strOrderNumber', options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25)
end
- def add_amount(xml, money, tag = "strAmount")
+ def add_amount(xml, money, tag = 'strAmount')
xml.tag! tag, amount(money)
end
def add_reference(xml, reference)
- xml.tag! "strReferenceCode", reference
+ xml.tag! 'strReferenceCode', reference
end
def add_reference_token(xml, reference)
- xml.tag! "token", reference
+ xml.tag! 'token', reference
end
def add_address(xml, options)
if address = options[:billing_address] || options[:address]
- xml.tag! "strAVSStreetAddress", address[:address1]
- xml.tag! "strAVSZipCode", address[:zip]
+ xml.tag! 'strAVSStreetAddress', address[:address1]
+ xml.tag! 'strAVSZipCode', address[:zip]
end
end
def add_credit_card(xml, credit_card)
- xml.tag! "strPAN", credit_card.number
- xml.tag! "strExpDate", expdate(credit_card)
- xml.tag! "strCardHolder", credit_card.name
- xml.tag! "strCVCode", credit_card.verification_value if credit_card.verification_value?
+ if credit_card.respond_to?(:track_data) && credit_card.track_data.present?
+ xml.tag! 'trackData', credit_card.track_data
+ else
+ xml.tag! 'strPAN', credit_card.number
+ xml.tag! 'strExpDate', expdate(credit_card)
+ xml.tag! 'strCardHolder', credit_card.name
+ xml.tag! 'strCVCode', credit_card.verification_value if credit_card.verification_value?
+ end
end
def split_reference(reference)
- reference.to_s.split(";")
+ reference.to_s.split(';')
end
def parse(action, data)
@@ -250,10 +247,10 @@ def parse(action, data)
response[element.name] = element.text
end
- status, code, message = response["ApprovalStatus"].split(";")
+ status, code, message = response['ApprovalStatus'].split(';')
response[:status] = status
- if response[:success] = status == "APPROVED"
+ if response[:success] = status == 'APPROVED'
response[:message] = status
else
response[:message] = message
@@ -271,17 +268,17 @@ def parse_error(http_response)
document = REXML::Document.new(http_response.body)
- node = REXML::XPath.first(document, "//soap:Fault")
+ node = REXML::XPath.first(document, '//soap:Fault')
node.elements.each do |element|
response[element.name] = element.text
end
- response[:message] = response["faultstring"].to_s.gsub("\n", " ")
+ response[:message] = response['faultstring'].to_s.gsub("\n", ' ')
response
- rescue REXML::ParseException => e
+ rescue REXML::ParseException
response[:http_body] = http_response.body
- response[:message] = "Failed to parse the failed response"
+ response[:message] = 'Failed to parse the failed response'
response
end
@@ -296,9 +293,9 @@ def url(v4 = false)
def commit(action, request, v4 = false)
begin
data = ssl_post(url(v4), request,
- "Content-Type" => 'text/xml; charset=utf-8',
- "SOAPAction" => soap_action(action, v4)
- )
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'SOAPAction' => soap_action(action, v4)
+ )
response = parse(action, data)
rescue ActiveMerchant::ResponseError => e
response = parse_error(e.response)
@@ -307,14 +304,14 @@ def commit(action, request, v4 = false)
Response.new(response[:success], response[:message], response,
:test => test?,
:authorization => authorization_from(response),
- :avs_result => { :code => response["AVSResponse"] },
- :cvv_result => response["CVResponse"]
+ :avs_result => { :code => response['AVSResponse'] },
+ :cvv_result => response['CVResponse']
)
end
def authorization_from(response)
if response[:success]
- [ response["ReferenceID"], response["OrderNumber"] ].join(";")
+ [ response['ReferenceID'], response['OrderNumber'] ].join(';')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
index 88421ca91c9..4b1667334a4 100644
--- a/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
+++ b/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb
@@ -2,26 +2,26 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class MerchantWareVersionFourGateway < Gateway
self.live_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx'
- self.test_url = 'https://staging.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx'
+ self.test_url = 'https://ps1.merchantware.net/Merchantware/ws/RetailTransaction/v4/Credit.asmx'
self.supported_countries = ['US']
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.homepage_url = 'http://merchantwarehouse.com/merchantware'
self.display_name = 'MerchantWARE'
- ENV_NAMESPACES = { "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
- "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
- "xmlns:soap" => "http://schemas.xmlsoap.org/soap/envelope/" }
+ ENV_NAMESPACES = { 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/' }
- TX_NAMESPACE = "http://schemas.merchantwarehouse.com/merchantware/40/Credit/"
+ TX_NAMESPACE = 'http://schemas.merchantwarehouse.com/merchantware/40/Credit/'
ACTIONS = {
- :purchase => "SaleKeyed",
+ :purchase => 'SaleKeyed',
:reference_purchase => 'RepeatSale',
- :authorize => "PreAuthorizationKeyed",
- :capture => "PostAuthorization",
- :void => "VoidPreAuthorization",
- :refund => "Refund"
+ :authorize => 'PreAuthorizationKeyed',
+ :capture => 'PostAuthorization',
+ :void => 'Void',
+ :refund => 'Refund'
}
# Creates a new MerchantWareVersionFourGateway
@@ -102,23 +102,41 @@ def refund(money, identification, options = {})
request = soap_request(:refund) do |xml|
add_reference_token(xml, reference)
add_invoice(xml, options)
- add_amount(xml, money, "overrideAmount")
+ add_amount(xml, money, 'overrideAmount')
end
commit(:refund, 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(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2')
+ end
+
private
def soap_request(action)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
- xml.tag! "soap:Envelope", ENV_NAMESPACES do
- xml.tag! "soap:Body" do
- xml.tag! ACTIONS[action], "xmlns" => TX_NAMESPACE do
- xml.tag! "merchantName", @options[:name]
- xml.tag! "merchantSiteId", @options[:login]
- xml.tag! "merchantKey", @options[:password]
+ xml.tag! 'soap:Envelope', ENV_NAMESPACES do
+ xml.tag! 'soap:Body' do
+ xml.tag! ACTIONS[action], 'xmlns' => TX_NAMESPACE do
+ xml.tag! 'merchantName', @options[:name]
+ xml.tag! 'merchantSiteId', @options[:login]
+ xml.tag! 'merchantKey', @options[:password]
yield xml
end
end
@@ -129,7 +147,7 @@ def soap_request(action)
def build_purchase_request(action, money, payment_source, options)
requires!(options, :order_id)
- request = soap_request(action) do |xml|
+ soap_request(action) do |xml|
add_invoice(xml, options)
add_amount(xml, money)
add_payment_source(xml, payment_source)
@@ -140,36 +158,29 @@ def build_purchase_request(action, money, payment_source, options)
def build_capture_request(action, money, identification, options)
reference, options[:order_id] = split_reference(identification)
- request = soap_request(action) do |xml|
+ soap_request(action) do |xml|
add_reference_token(xml, reference)
add_invoice(xml, options)
add_amount(xml, money)
end
end
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
def add_invoice(xml, options)
- xml.tag! "invoiceNumber", options[:order_id].to_s.gsub(/[^\w]/, '').slice(0, 25)
+ xml.tag! 'invoiceNumber', truncate(options[:order_id].to_s.gsub(/[^\w]/, ''), 8)
end
- def add_amount(xml, money, tag = "amount")
+ def add_amount(xml, money, tag = 'amount')
xml.tag! tag, amount(money)
end
def add_reference_token(xml, reference)
- xml.tag! "token", reference
+ xml.tag! 'token', reference
end
def add_address(xml, options)
address = options[:billing_address] || options[:address] || {}
- xml.tag! "avsStreetAddress", address[:address1]
- xml.tag! "avsStreetZipCode", address[:zip]
+ xml.tag! 'avsStreetAddress', address[:address1]
+ xml.tag! 'avsStreetZipCode', address[:zip]
end
def add_payment_source(xml, source)
@@ -181,14 +192,14 @@ def add_payment_source(xml, source)
end
def add_credit_card(xml, credit_card)
- xml.tag! "cardNumber", credit_card.number
- xml.tag! "expirationDate", expdate(credit_card)
- xml.tag! "cardholder", credit_card.name
- xml.tag! "cardSecurityCode", credit_card.verification_value if credit_card.verification_value?
+ xml.tag! 'cardNumber', credit_card.number
+ xml.tag! 'expirationDate', expdate(credit_card)
+ xml.tag! 'cardholder', credit_card.name
+ xml.tag! 'cardSecurityCode', credit_card.verification_value if credit_card.verification_value?
end
def split_reference(reference)
- reference.to_s.split(";")
+ reference.to_s.split(';')
end
def parse(action, data)
@@ -201,14 +212,14 @@ def parse(action, data)
response[element.name] = element.text
end
- if response["ErrorMessage"].present?
- response[:message] = response["ErrorMessage"]
+ if response['ErrorMessage'].present?
+ response[:message] = response['ErrorMessage']
response[:success] = false
else
- status, code, message = response["ApprovalStatus"].split(";")
+ status, code, message = response['ApprovalStatus'].split(';')
response[:status] = status
- if response[:success] = status == "APPROVED"
+ if response[:success] = status == 'APPROVED'
response[:message] = status
else
response[:message] = message
@@ -232,11 +243,11 @@ def parse_error(http_response, action)
response[element.name] = element.text
end
- response[:message] = response["ErrorMessage"].to_s.gsub("\n", " ")
+ response[:message] = response['ErrorMessage'].to_s.gsub("\n", ' ')
response
- rescue REXML::ParseException => e
+ rescue REXML::ParseException
response[:http_body] = http_response.body
- response[:message] = "Failed to parse the failed response"
+ response[:message] = 'Failed to parse the failed response'
response
end
@@ -251,9 +262,9 @@ def url
def commit(action, request)
begin
data = ssl_post(url, request,
- "Content-Type" => 'text/xml; charset=utf-8',
- "SOAPAction" => soap_action(action)
- )
+ 'Content-Type' => 'text/xml; charset=utf-8',
+ 'SOAPAction' => soap_action(action)
+ )
response = parse(action, data)
rescue ActiveMerchant::ResponseError => e
response = parse_error(e.response, action)
@@ -262,8 +273,8 @@ def commit(action, request)
Response.new(response[:success], response[:message], response,
:test => test?,
:authorization => authorization_from(response),
- :avs_result => { :code => response["AvsResponse"] },
- :cvv_result => response["CvResponse"]
+ :avs_result => { :code => response['AvsResponse'] },
+ :cvv_result => response['CvResponse']
)
end
diff --git a/lib/active_merchant/billing/gateways/merchant_warrior.rb b/lib/active_merchant/billing/gateways/merchant_warrior.rb
index 9ee22a3c3b1..8e92e21811e 100644
--- a/lib/active_merchant/billing/gateways/merchant_warrior.rb
+++ b/lib/active_merchant/billing/gateways/merchant_warrior.rb
@@ -12,9 +12,9 @@ class MerchantWarriorGateway < Gateway
self.supported_countries = ['AU']
self.supported_cardtypes = [:visa, :master, :american_express,
- :diners_club, :discover]
- self.homepage_url = 'http://www.merchantwarrior.com/'
- self.display_name = 'MerchantWarrior'
+ :diners_club, :discover, :jcb]
+ self.homepage_url = 'https://www.merchantwarrior.com/'
+ self.display_name = 'Merchant Warrior'
self.money_format = :dollars
self.default_currency = 'AUD'
@@ -27,7 +27,7 @@ def initialize(options = {})
def authorize(money, payment_method, options = {})
post = {}
add_amount(post, money, options)
- add_product(post, options)
+ add_order_id(post, options)
add_address(post, options)
add_payment_method(post, payment_method)
commit('processAuth', post)
@@ -36,21 +36,21 @@ def authorize(money, payment_method, options = {})
def purchase(money, payment_method, options = {})
post = {}
add_amount(post, money, options)
- add_product(post, options)
+ add_order_id(post, options)
add_address(post, options)
add_payment_method(post, payment_method)
commit('processCard', post)
end
- def capture(money, identification)
+ def capture(money, identification, options = {})
post = {}
add_amount(post, money, options)
add_transaction(post, identification)
- post.merge!('captureAmount' => amount(money))
+ post['captureAmount'] = amount(money)
commit('processCapture', post)
end
- def refund(money, identification)
+ def refund(money, identification, options = {})
post = {}
add_amount(post, money, options)
add_transaction(post, identification)
@@ -60,7 +60,7 @@ def refund(money, identification)
def store(creditcard, options = {})
post = {
- 'cardName' => creditcard.name,
+ 'cardName' => scrub_name(creditcard.name),
'cardNumber' => creditcard.number,
'cardExpiryMonth' => format(creditcard.month, :two_digits),
'cardExpiryYear' => format(creditcard.year, :two_digits)
@@ -68,6 +68,18 @@ def store(creditcard, options = {})
commit('addCard', post)
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?paymentCardNumber=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((CardNumber=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?paymentCardCSC=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?apiKey=)[^&]*)i, '\1[FILTERED]')
+ end
+
private
def add_transaction(post, identification)
@@ -75,18 +87,21 @@ def add_transaction(post, identification)
end
def add_address(post, options)
- return unless(address = options[:address])
+ return unless(address = (options[:billing_address] || options[:address]))
- post['customerName'] = address[:name]
+ post['customerName'] = scrub_name(address[:name])
post['customerCountry'] = address[:country]
- post['customerState'] = address[:state]
+ post['customerState'] = address[:state] || 'N/A'
post['customerCity'] = address[:city]
post['customerAddress'] = address[:address1]
post['customerPostCode'] = address[:zip]
+ post['customerIP'] = address[:ip]
+ post['customerPhone'] = address[:phone]
+ post['customerEmail'] = address[:email]
end
- def add_product(post, options)
- post['transactionProduct'] = options[:transaction_product]
+ def add_order_id(post, options)
+ post['transactionProduct'] = truncate(options[:order_id], 34) || SecureRandom.hex(15)
end
def add_payment_method(post, payment_method)
@@ -103,11 +118,15 @@ def add_token(post, token)
def add_creditcard(post, creditcard)
post['paymentCardNumber'] = creditcard.number
- post['paymentCardName'] = creditcard.name
- post['paymentCardExpiry'] = creditcard.expiry_date.expiration.strftime("%m%y")
+ post['paymentCardName'] = scrub_name(creditcard.name)
+ post['paymentCardExpiry'] = creditcard.expiry_date.expiration.strftime('%m%y')
post['paymentCardCSC'] = creditcard.verification_value if creditcard.verification_value?
end
+ def scrub_name(name)
+ name.gsub(/[^a-zA-Z\. -]/, '')
+ end
+
def add_amount(post, money, options)
currency = (options[:currency] || currency(money))
@@ -139,7 +158,7 @@ def parse(body)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|element| parse_element(response, element)}
+ node.elements.each { |element| parse_element(response, element) }
else
response[node.name.underscore.to_sym] = node.text
end
@@ -169,14 +188,14 @@ def add_auth(action, post)
def url_for(action, post)
if token?(post)
- [(test? ? TOKEN_TEST_URL : TOKEN_LIVE_URL), action].join("/")
+ [(test? ? TOKEN_TEST_URL : TOKEN_LIVE_URL), action].join('/')
else
(test? ? POST_TEST_URL : POST_LIVE_URL)
end
end
def token?(post)
- (post["cardID"] || post["cardName"])
+ (post['cardID'] || post['cardName'])
end
def success?(response)
@@ -184,7 +203,7 @@ def success?(response)
end
def post_data(post)
- post.collect{|k,v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
+ post.collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/mercury.rb b/lib/active_merchant/billing/gateways/mercury.rb
index 4f969015904..6fea6dd9197 100644
--- a/lib/active_merchant/billing/gateways/mercury.rb
+++ b/lib/active_merchant/billing/gateways/mercury.rb
@@ -12,16 +12,22 @@ module Billing #:nodoc:
# and +refund+ will become mandatory.
class MercuryGateway < Gateway
URLS = {
- :test => 'https://w1.mercurydev.net/ws/ws.asmx',
+ :test => 'https://w1.mercurycert.net/ws/ws.asmx',
:live => 'https://w1.mercurypay.com/ws/ws.asmx'
}
self.homepage_url = 'http://www.mercurypay.com'
self.display_name = 'Mercury'
- self.supported_countries = ['US']
+ self.supported_countries = ['US', 'CA']
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
self.default_currency = 'USD'
+ STANDARD_ERROR_CODE_MAPPING = {
+ '100204' => STANDARD_ERROR_CODE[:invalid_number],
+ '100205' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '000000' => STANDARD_ERROR_CODE[:card_declined]
+ }
+
def initialize(options = {})
requires!(options, :login, :password)
@use_tokenization = (!options.has_key?(:tokenization) || options[:tokenization])
@@ -66,35 +72,46 @@ def refund(money, authorization, options = {})
def void(authorization, options={})
requires!(options, :credit_card) unless @use_tokenization
- if options[:try_reversal]
- request = build_authorized_request('VoidSale', nil, authorization, options[:credit_card], options.merge(:reversal => true))
- response = commit('VoidSale', request)
-
- return response if response.success?
- end
-
request = build_authorized_request('VoidSale', nil, authorization, options[:credit_card], options)
commit('VoidSale', request)
end
+ def store(credit_card, options={})
+ request = build_card_lookup_request(credit_card, options)
+ commit('CardLookup', request)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(<), '<').
+ gsub(%r(>), '>').
+ gsub(%r(().*())i, '\1[FILTERED]\2').
+ gsub(%r(()(\d|x)*())i, '\1[FILTERED]\3').
+ gsub(%r(()\d*())i, '\1[FILTERED]\2')
+ end
+
private
def build_non_authorized_request(action, money, credit_card, options)
xml = Builder::XmlMarkup.new
- xml.tag! "TStream" do
- xml.tag! "Transaction" do
+ xml.tag! 'TStream' do
+ xml.tag! 'Transaction' do
xml.tag! 'TranType', 'Credit'
xml.tag! 'TranCode', action
- if action == 'PreAuth' || action == 'Sale'
- xml.tag! "PartialAuth", "Allow"
+ if options[:allow_partial_auth] && ['PreAuth', 'Sale'].include?(action)
+ xml.tag! 'PartialAuth', 'Allow'
end
add_invoice(xml, options[:order_id], nil, options)
- add_reference(xml, "RecordNumberRequested")
+ add_reference(xml, 'RecordNumberRequested')
add_customer_data(xml, options)
add_amount(xml, money, options)
add_credit_card(xml, credit_card, action)
- add_address(xml, options)
+ add_address(xml, options) unless credit_card.track_data.present?
end
end
xml = xml.target!
@@ -104,15 +121,15 @@ def build_authorized_request(action, money, authorization, credit_card, options)
xml = Builder::XmlMarkup.new
invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount = split_authorization(authorization)
- ref_no = invoice_no if options[:reversal]
+ ref_no = '1' if ref_no.blank?
- xml.tag! "TStream" do
- xml.tag! "Transaction" do
+ xml.tag! 'TStream' do
+ xml.tag! 'Transaction' do
xml.tag! 'TranType', 'Credit'
- if action == 'PreAuthCapture'
- xml.tag! "PartialAuth", "Allow"
+ if options[:allow_partial_auth] && (action == 'PreAuthCapture')
+ xml.tag! 'PartialAuth', 'Allow'
end
- xml.tag! 'TranCode', (@use_tokenization ? (action + "ByRecordNo") : action)
+ xml.tag! 'TranCode', (@use_tokenization ? (action + 'ByRecordNo') : action)
add_invoice(xml, invoice_no, ref_no, options)
add_reference(xml, record_no)
add_customer_data(xml, options)
@@ -120,20 +137,33 @@ def build_authorized_request(action, money, authorization, credit_card, options)
add_credit_card(xml, credit_card, action) if credit_card
add_address(xml, options)
xml.tag! 'TranInfo' do
- xml.tag! "AuthCode", auth_code
- xml.tag! "AcqRefData", acq_ref_data if options[:reversal]
- xml.tag! "ProcessData", process_data if options[:reversal]
+ xml.tag! 'AuthCode', auth_code
+ xml.tag! 'AcqRefData', acq_ref_data
+ xml.tag! 'ProcessData', process_data
end
end
end
xml = xml.target!
end
- def add_invoice(xml, invoice_no, ref_no, options)
- if /^\d+$/ !~ invoice_no.to_s
- raise ArgumentError.new("order_id '#{invoice_no}' is not numeric as required by Mercury")
+ def build_card_lookup_request(credit_card, options)
+ xml = Builder::XmlMarkup.new
+
+ xml.tag! 'TStream' do
+ xml.tag! 'Transaction' do
+ xml.tag! 'TranType', 'CardLookup'
+ xml.tag! 'RecordNo', 'RecordNumberRequested'
+ xml.tag! 'Frequency', 'OneTime'
+
+ xml.tag! 'Memo', options[:description]
+ add_customer_data(xml, options)
+ add_credit_card(xml, credit_card, options)
+ end
end
+ xml.target!
+ end
+ def add_invoice(xml, invoice_no, ref_no, options)
xml.tag! 'InvoiceNo', invoice_no
xml.tag! 'RefNo', (ref_no || invoice_no)
xml.tag! 'OperatorID', options[:merchant] if options[:merchant]
@@ -142,15 +172,15 @@ def add_invoice(xml, invoice_no, ref_no, options)
def add_reference(xml, record_no)
if @use_tokenization
- xml.tag! "Frequency", "OneTime"
- xml.tag! "RecordNo", record_no
+ xml.tag! 'Frequency', 'OneTime'
+ xml.tag! 'RecordNo', record_no
end
end
def add_customer_data(xml, options)
xml.tag! 'IpAddress', options[:ip] if options[:ip]
if options[:customer]
- xml.tag! "TranInfo" do
+ xml.tag! 'TranInfo' do
xml.tag! 'CustomerCode', options[:customer]
end
end
@@ -177,22 +207,36 @@ def add_amount(xml, money, options = {})
def add_credit_card(xml, credit_card, action)
xml.tag! 'Account' do
- xml.tag! 'AcctNo', credit_card.number
- xml.tag! 'ExpDate', expdate(credit_card)
+ if credit_card.track_data.present?
+ # Track 1 has a start sentinel (STX) of '%' and track 2 is ';'
+ # Track 1 and 2 have identical end sentinels (ETX) of '?'
+ # Tracks may or may not have checksum (LRC) after the ETX
+ # If the track has no STX or is corrupt, we send it as track 1, to let Mercury
+ # handle with the validation error as it sees fit.
+ # Track 2 requires having the STX and ETX stripped. Track 1 does not.
+ # Max-length track 1s require having the STX and ETX stripped. Max is 79 bytes including LRC.
+ is_track_2 = credit_card.track_data[0] == ';'
+ etx_index = credit_card.track_data.rindex('?') || credit_card.track_data.length
+ is_max_track1 = etx_index >= 77
+
+ if is_track_2
+ xml.tag! 'Track2', credit_card.track_data[1...etx_index]
+ elsif is_max_track1
+ xml.tag! 'Track1', credit_card.track_data[1...etx_index]
+ else
+ xml.tag! 'Track1', credit_card.track_data
+ end
+ else
+ xml.tag! 'AcctNo', credit_card.number
+ xml.tag! 'ExpDate', expdate(credit_card)
+ end
end
xml.tag! 'CardType', CARD_CODES[credit_card.brand] if credit_card.brand
- include_cvv = !%w(Return PreAuthCapture).include?(action)
+ include_cvv = !%w(Return PreAuthCapture).include?(action) && !credit_card.track_data.present?
xml.tag! 'CVVData', credit_card.verification_value if(include_cvv && credit_card.verification_value)
end
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
def add_address(xml, options)
if billing_address = options[:billing_address] || options[:address]
xml.tag! 'AVS' do
@@ -211,12 +255,12 @@ def parse(action, body)
def hashify_xml!(xml, response)
xml = REXML::Document.new(xml)
- xml.elements.each("//CmdResponse/*") do |node|
+ xml.elements.each('//CmdResponse/*') do |node|
response[node.name.underscore.to_sym] = node.text
end
- xml.elements.each("//TranResponse/*") do |node|
- if node.name.to_s == "Amount"
+ xml.elements.each('//TranResponse/*') do |node|
+ if node.name.to_s == 'Amount'
node.elements.each do |amt|
response[amt.name.underscore.to_sym] = amt.text
end
@@ -249,8 +293,8 @@ def build_soap_request(body)
def build_header
{
- "SOAPAction" => "http://www.mercurypay.com/CreditTransaction",
- "Content-Type" => "text/xml; charset=utf-8"
+ 'SOAPAction' => 'http://www.mercurypay.com/CreditTransaction',
+ 'Content-Type' => 'text/xml; charset=utf-8'
}
end
@@ -266,7 +310,8 @@ def commit(action, request)
:test => test?,
:authorization => authorization_from(response),
:avs_result => { :code => response[:avs_result] },
- :cvv_result => response[:cvv_result])
+ :cvv_result => response[:cvv_result],
+ :error_code => success ? nil : STANDARD_ERROR_CODE_MAPPING[response[:dsix_return_code]])
end
def message_from(response)
@@ -274,7 +319,7 @@ def message_from(response)
end
def authorization_from(response)
- dollars, cents = (response[:purchase] || "").split(".").collect{|e| e.to_i}
+ dollars, cents = (response[:purchase] || '').split('.').collect(&:to_i)
dollars ||= 0
cents ||= 0
[
@@ -285,17 +330,17 @@ def authorization_from(response)
response[:process_data],
response[:record_no],
((dollars * 100) + cents).to_s
- ].join(";")
+ ].join(';')
end
def split_authorization(authorization)
- invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount = authorization.split(";")
+ invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount = authorization.split(';')
[invoice_no, ref_no, auth_code, acq_ref_data, process_data, record_no, amount]
end
ENVELOPE_NAMESPACES = {
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
- 'xmlns:soap' => "http://schemas.xmlsoap.org/soap/envelope/",
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
}
@@ -304,7 +349,7 @@ def escape_xml(xml)
end
def unescape_xml(escaped_xml)
- escaped_xml.gsub(/\>/,'>').gsub(/\</,'<')
+ escaped_xml.gsub(/\>/, '>').gsub(/\</, '<')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/metrics_global.rb b/lib/active_merchant/billing/gateways/metrics_global.rb
index fd7bf764352..314f403c83c 100644
--- a/lib/active_merchant/billing/gateways/metrics_global.rb
+++ b/lib/active_merchant/billing/gateways/metrics_global.rb
@@ -20,8 +20,8 @@ class MetricsGlobalGateway < Gateway
class_attribute :test_url, :live_url
- self.test_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll?testing=true"
- self.live_url = "https://secure.metricsglobalgateway.com/gateway/transact.dll"
+ self.test_url = 'https://secure.metricsglobalgateway.com/gateway/transact.dll?testing=true'
+ self.live_url = 'https://secure.metricsglobalgateway.com/gateway/transact.dll'
class_attribute :duplicate_window
@@ -150,7 +150,7 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -213,14 +213,14 @@ def post_data(action, parameters = {})
post[:version] = API_VERSION
post[:login] = @options[:login]
post[:tran_key] = @options[:password]
- post[:relay_response] = "FALSE"
+ post[:relay_response] = 'FALSE'
post[:type] = action
- post[:delim_data] = "TRUE"
- post[:delim_char] = ","
- post[:encap_char] = "$"
- post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant"
+ post[:delim_data] = 'TRUE'
+ post[:delim_char] = ','
+ post[:encap_char] = '$'
+ post[:solution_ID] = application_id if application_id
- request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join('&')
request
end
@@ -284,39 +284,20 @@ def add_address(post, options)
end
end
- # Make a ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
- end
- end
-
def message_from(results)
if results[:response_code] == DECLINED
- return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code])
+ return CVVResult.messages[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code])
if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code])
- return AVSResult.messages[ results[:avs_result_code] ]
+ return AVSResult.messages[results[:avs_result_code]]
end
end
(results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '')
end
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
def split(response)
response[1..-2].split(/\$,\$/)
end
-
end
end
end
diff --git a/lib/active_merchant/billing/gateways/micropayment.rb b/lib/active_merchant/billing/gateways/micropayment.rb
new file mode 100644
index 00000000000..fc416311865
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/micropayment.rb
@@ -0,0 +1,183 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class MicropaymentGateway < Gateway
+
+ self.display_name = 'micropayment'
+ self.homepage_url = 'https://www.micropayment.de/'
+
+ self.test_url = self.live_url = 'https://sipg.micropayment.de/public/creditcardpsp/v1/nvp/'
+
+ self.supported_countries = %w(DE)
+ self.default_currency = 'EUR'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express]
+
+ def initialize(options={})
+ requires!(options, :access_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ add_address(post, options)
+ commit('purchase', post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ add_address(post, options)
+ commit('authorize', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_invoice(post, amount, options)
+ commit('capture', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ commit('void', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_invoice(post, amount, options)
+ commit('refund', post)
+ end
+
+ def verify(credit_card, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(250, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((accessKey=)\w+), '\1[FILTERED]').
+ gsub(%r((number=)\d+), '\1[FILTERED]').
+ gsub(%r((cvc2=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_invoice(post, money, options)
+ if money
+ post[:amount] = amount(money)
+ post[:currency] = options[:currency] || currency(money)
+ end
+ post[:project] = options[:project] if options[:project]
+ post['params[title]'] = options[:description] if options[:description]
+ end
+
+ def add_payment_method(post, payment_method, options={})
+ post[:number] = payment_method.number
+ post[:recurring] = 1 if options[:recurring] == true
+ post[:cvc2] = payment_method.verification_value
+ post[:expiryYear] = format(payment_method.year, :four_digits)
+ post[:expiryMonth] = format(payment_method.month, :two_digits)
+
+ post['params[firstname]'] = payment_method.first_name
+ post['params[surname]'] = payment_method.last_name
+ end
+
+ def add_customer_data(post, options)
+ post['params[email]'] = options[:email] if options[:email]
+ post['params[ip]'] = options[:ip] || '1.1.1.1'
+ post['params[sendMail]'] = options[:send_mail] || 'false'
+ end
+
+ def add_address(post, options)
+ address = options[:billing_address]
+ return unless address
+
+ post['params[address]'] = address[:address1] if address[:address1]
+ post['params[zipcode]'] = address[:zip] if address[:zip]
+ post['params[town]'] = address[:city] if address[:city]
+ post['params[country]'] = address[:country] if address[:country]
+ end
+
+ def add_reference(post, authorization)
+ session_id, transaction_id = split_authorization(authorization)
+ post[:sessionId] = session_id
+ post[:transactionId] = transaction_id
+ end
+
+ def commit(action, params)
+ params[:testMode] = 1 if test?
+ params[:accessKey] = @options[:access_key]
+ params[:apiKey] = @options[:api_key] || 'af1fd841af792f4c50131414ff76e004'
+
+ response = parse(ssl_post(url(action), post_data(action, params), headers))
+
+ Response.new(
+ succeeded = success_from(response),
+ message_from(succeeded, response),
+ response,
+ authorization: authorization_from(response, params),
+ avs_result: AVSResult.new(code: response['some_avs_result_key']),
+ cvv_result: CVVResult.new(response['some_cvv_result_key']),
+ test: test?
+ )
+ end
+
+ def headers
+ { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' }
+ end
+
+ def post_data(action, params)
+ params.map { |k, v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def url(action)
+ action_url = test? ? test_url : live_url
+ "#{action_url}?action=#{action}"
+ end
+
+ def parse(body)
+ body.split(/\r?\n/).inject({}) do |acc, pair|
+ key, value = pair.split('=')
+ acc[key] = CGI.unescape(value)
+ acc
+ end
+ end
+
+ def success_from(response)
+ response['error'] == '0' &&
+ response['transactionResultCode'] == '00' &&
+ response['transactionStatus'] == 'SUCCESS'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response['errorMessage'] || response['transactionResultMessage']
+ end
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def authorization_from(response, request_params)
+ session_id = response['sessionId'] || request_params[:sessionId]
+ "#{session_id}|#{response["transactionId"]}"
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/migs.rb b/lib/active_merchant/billing/gateways/migs.rb
index 7eabdfdfa75..e1ae2922682 100644
--- a/lib/active_merchant/billing/gateways/migs.rb
+++ b/lib/active_merchant/billing/gateways/migs.rb
@@ -1,6 +1,4 @@
-require File.dirname(__FILE__) + '/migs/migs_codes'
-
-require 'digest/md5' # Used in add_secure_hash
+require 'active_merchant/billing/gateways/migs/migs_codes'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -19,12 +17,13 @@ class MigsGateway < Gateway
# MiGS is supported throughout Asia Pacific, Middle East and Africa
# MiGS is used in Australia (AU) by ANZ (eGate), CBA (CommWeb) and more
# Source of Country List: http://www.scribd.com/doc/17811923
- self.supported_countries = %w(AU AE BD BN EG HK ID IN JO KW LB LK MU MV MY NZ OM PH QA SA SG TT VN)
+ self.supported_countries = %w(AU AE BD BN EG HK ID JO KW LB LK MU MV MY NZ OM PH QA SA SG TT VN)
# The card types supported by the payment gateway
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
self.money_format = :cents
+ self.currencies_without_fractions = %w(IDR)
# The homepage URL of the gateway
self.homepage_url = 'http://mastercard.com/mastercardsps'
@@ -58,10 +57,13 @@ def purchase(money, creditcard, options = {})
requires!(options, :order_id)
post = {}
- post[:Amount] = amount(money)
+
+ add_amount(post, money, options)
add_invoice(post, options)
add_creditcard(post, creditcard)
add_standard_parameters('pay', post, options[:unique_id])
+ add_3ds(post, options)
+ add_tx_source(post, options)
commit(post)
end
@@ -78,9 +80,11 @@ def capture(money, authorization, options = {})
requires!(@options, :advanced_login, :advanced_password)
post = options.merge(:TransNo => authorization)
- post[:Amount] = amount(money)
+
+ add_amount(post, money, options)
add_advanced_user(post)
add_standard_parameters('capture', post, options[:unique_id])
+ add_tx_source(post, options)
commit(post)
end
@@ -93,18 +97,39 @@ def refund(money, authorization, options = {})
requires!(@options, :advanced_login, :advanced_password)
post = options.merge(:TransNo => authorization)
- post[:Amount] = amount(money)
+
+ add_amount(post, money, options)
add_advanced_user(post)
add_standard_parameters('refund', post, options[:unique_id])
+ add_tx_source(post, options)
+
+ commit(post)
+ end
+
+ def void(authorization, options = {})
+ requires!(@options, :advanced_login, :advanced_password)
+
+ post = options.merge(:TransNo => authorization)
+
+ add_advanced_user(post)
+ add_standard_parameters('voidAuthorisation', post, options[:unique_id])
+ add_tx_source(post, options)
commit(post)
end
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
+ def verify(credit_card, options={})
+ MultiResponse.run do |r|
+ r.process { authorize(100, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
# Checks the status of a previous transaction
# This can be useful when a response is not received due to network issues
#
@@ -143,14 +168,13 @@ def purchase_offsite_url(money, options = {})
requires!(@options, :secure_hash)
post = {}
- post[:Amount] = amount(money)
+
+ add_amount(post, money, options)
add_invoice(post, options)
add_creditcard_type(post, options[:card_type]) if options[:card_type]
- post.merge!(
- :Locale => options[:locale] || 'en',
- :ReturnURL => options[:return_url]
- )
+ post[:Locale] = options[:locale] || 'en'
+ post[:ReturnURL] = options[:return_url]
add_standard_parameters('pay', post, options[:unique_id])
@@ -170,9 +194,9 @@ def purchase_offsite_response(data)
response_hash = parse(data)
- expected_secure_hash = calculate_secure_hash(response_hash.reject{|k, v| k == :SecureHash}, @options[:secure_hash])
+ expected_secure_hash = calculate_secure_hash(response_hash, @options[:secure_hash])
unless response_hash[:SecureHash] == expected_secure_hash
- raise SecurityError, "Secure Hash mismatch, response may be tampered with"
+ raise SecurityError, 'Secure Hash mismatch, response may be tampered with'
end
response_object(response_hash)
@@ -182,8 +206,27 @@ def test?
@options[:login].start_with?('TEST')
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?CardNum=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?CardSecurityCode=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?AccessCode=)[^&]*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?Password=)[^&]*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?3DSXID=)[^&]*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?VerToken=)[^&]*(&?)), '\1[FILTERED]\2')
+ end
+
private
+ def add_amount(post, money, options)
+ post[:Amount] = localized_amount(money, options[:currency])
+ post[:Currency] = options[:currency] if options[:currency]
+ end
+
def add_advanced_user(post)
post[:User] = @options[:advanced_login]
post[:Password] = @options[:advanced_password]
@@ -193,6 +236,19 @@ def add_invoice(post, options)
post[:OrderInfo] = options[:order_id]
end
+ def add_3ds(post, options)
+ post[:VerType] = options[:ver_type] if options[:ver_type]
+ post[:VerToken] = options[:ver_token] if options[:ver_token]
+ post['3DSXID'] = options[:three_ds_xid] if options[:three_ds_xid]
+ post['3DSECI'] = options[:three_ds_eci] if options[:three_ds_eci]
+ post['3DSenrolled'] = options[:three_ds_enrolled] if options[:three_ds_enrolled]
+ post['3DSstatus'] = options[:three_ds_status] if options[:three_ds_status]
+ end
+
+ def add_tx_source(post, options)
+ post[:TxSource] = options[:tx_source] if options[:tx_source]
+ end
+
def add_creditcard(post, creditcard)
post[:CardNum] = creditcard.number
post[:CardSecurityCode] = creditcard.verification_value if creditcard.verification_value?
@@ -201,7 +257,7 @@ def add_creditcard(post, creditcard)
def add_creditcard_type(post, card_type)
post[:Gateway] = 'ssl'
- post[:card] = CARD_TYPES.detect{|ct| ct.am_code == card_type}.migs_long_code
+ post[:card] = CARD_TYPES.detect { |ct| ct.am_code == card_type }.migs_long_code
end
def parse(body)
@@ -214,18 +270,25 @@ def parse(body)
end
def commit(post)
+ add_secure_hash(post) if @options[:secure_hash]
data = ssl_post self.merchant_hosted_url, post_data(post)
response_hash = parse(data)
response_object(response_hash)
end
def response_object(response)
+ avs_response_code = response[:AVSResultCode]
+ avs_response_code = 'S' if avs_response_code == 'Unsupported'
+
+ cvv_result_code = response[:CSCResultCode]
+ cvv_result_code = 'P' if cvv_result_code == 'Unsupported'
+
Response.new(success?(response), response[:Message], response,
:test => test?,
:authorization => response[:TransactionNo],
:fraud_review => fraud_review?(response),
- :avs_result => { :code => response[:AVSResultCode] },
- :cvv_result => response[:CSCResultCode]
+ :avs_result => { :code => avs_response_code },
+ :cvv_result => cvv_result_code
)
end
@@ -248,17 +311,21 @@ def add_standard_parameters(action, post, unique_id = nil)
end
def post_data(post)
- post.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ post.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
def add_secure_hash(post)
post[:SecureHash] = calculate_secure_hash(post, @options[:secure_hash])
+ post[:SecureHashType] = 'SHA256'
end
def calculate_secure_hash(post, secure_hash)
- sorted_values = post.sort_by(&:to_s).map(&:last)
- input = secure_hash + sorted_values.join
- Digest::MD5.hexdigest(input).upcase
+ input = post.
+ reject { |k| %i[SecureHash SecureHashType].include?(k) }.
+ sort.
+ map { |(k, v)| "vpc_#{k}=#{v}" }.
+ join('&')
+ OpenSSL::HMAC.hexdigest('SHA256', [secure_hash].pack('H*'), input).upcase
end
end
end
diff --git a/lib/active_merchant/billing/gateways/modern_payments.rb b/lib/active_merchant/billing/gateways/modern_payments.rb
index 91f059017a9..c5846f7a079 100644
--- a/lib/active_merchant/billing/gateways/modern_payments.rb
+++ b/lib/active_merchant/billing/gateways/modern_payments.rb
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/modern_payments_cim'
+require 'active_merchant/billing/gateways/modern_payments_cim'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -19,7 +19,7 @@ def purchase(money, credit_card, options = {})
customer_response = cim.create_customer(options)
return customer_response unless customer_response.success?
- customer_id = customer_response.params["create_customer_result"]
+ customer_id = customer_response.params['create_customer_result']
card_response = cim.modify_customer_credit_card(customer_id, credit_card)
return card_response unless card_response.success?
@@ -28,10 +28,10 @@ def purchase(money, credit_card, options = {})
end
private
+
def cim
@cim ||= ModernPaymentsCimGateway.new(@options)
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/modern_payments_cim.rb b/lib/active_merchant/billing/gateways/modern_payments_cim.rb
index 756055ad791..b1626434d38 100644
--- a/lib/active_merchant/billing/gateways/modern_payments_cim.rb
+++ b/lib/active_merchant/billing/gateways/modern_payments_cim.rb
@@ -1,20 +1,20 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class ModernPaymentsCimGateway < Gateway #:nodoc:
- self.test_url = "https://secure.modpay.com/netservices/test/ModpayTest.asmx"
+ self.test_url = 'https://secure.modpay.com/netservices/test/ModpayTest.asmx'
self.live_url = 'https://secure.modpay.com/ws/modpay.asmx'
- LIVE_XMLNS = "https://secure.modpay.com/ws/"
- TEST_XMLNS = "https://secure.modpay.com/netservices/test/"
+ LIVE_XMLNS = 'https://secure.modpay.com/ws/'
+ TEST_XMLNS = 'https://secure.modpay.com/netservices/test/'
self.supported_countries = ['US']
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.homepage_url = 'http://www.modpay.com'
self.display_name = 'Modern Payments'
- SUCCESS_MESSAGE = "Transaction accepted"
- FAILURE_MESSAGE = "Transaction failed"
- ERROR_MESSAGE = "Transaction error"
+ SUCCESS_MESSAGE = 'Transaction accepted'
+ FAILURE_MESSAGE = 'Transaction failed'
+ ERROR_MESSAGE = 'Transaction error'
PAYMENT_METHOD = {
:check => 1,
@@ -35,7 +35,7 @@ def create_customer(options = {})
end
def modify_customer_credit_card(customer_id, credit_card)
- raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank?
+ raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank?
post = {}
add_customer_id(post, customer_id)
@@ -45,7 +45,7 @@ def modify_customer_credit_card(customer_id, credit_card)
end
def authorize_credit_card_payment(customer_id, amount)
- raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank?
+ raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank?
post = {}
add_customer_id(post, customer_id)
@@ -55,7 +55,7 @@ def authorize_credit_card_payment(customer_id, amount)
end
def create_payment(customer_id, amount, options = {})
- raise ArgumentError, "The customer_id cannot be blank" if customer_id.blank?
+ raise ArgumentError, 'The customer_id cannot be blank' if customer_id.blank?
post = {}
add_customer_id(post, customer_id)
@@ -66,8 +66,9 @@ def create_payment(customer_id, amount, options = {})
end
private
+
def add_payment_details(post, options)
- post[:pmtDate] = (options[:payment_date] || Time.now.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
+ post[:pmtDate] = (options[:payment_date] || Time.now.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
post[:pmtType] = PAYMENT_METHOD[options[:payment_method] || :credit_card]
end
@@ -87,9 +88,7 @@ def add_address(post, options)
address = options[:billing_address] || options[:address] || {}
if name = address[:name]
- segments = name.split(' ')
- post[:lastName] = segments.pop
- post[:firstName] = segments.join(' ')
+ post[:firstName], post[:lastName] = split_names(name)
else
post[:firstName] = address[:first_name]
post[:lastName] = address[:last_name]
@@ -120,10 +119,10 @@ def build_request(action, params)
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance' } do
xml.tag! 'env:Body' do
- xml.tag! action, { "xmlns" => xmlns(action) } do
- xml.tag! "clientId", @options[:login]
- xml.tag! "clientCode", @options[:password]
- params.each {|key, value| xml.tag! key, value }
+ xml.tag! action, { 'xmlns' => xmlns(action) } do
+ xml.tag! 'clientId', @options[:login]
+ xml.tag! 'clientCode', @options[:password]
+ params.each { |key, value| xml.tag! key, value }
end
end
end
@@ -148,9 +147,9 @@ def url(action)
def commit(action, params)
data = ssl_post(url(action), build_request(action, params),
- { 'Content-Type' =>'text/xml; charset=utf-8',
- 'SOAPAction' => "#{xmlns(action)}#{action}" }
- )
+ { 'Content-Type' =>'text/xml; charset=utf-8',
+ 'SOAPAction' => "#{xmlns(action)}#{action}" }
+ )
response = parse(action, data)
Response.new(successful?(action, response), message_from(action, response), response,
@@ -165,14 +164,14 @@ def authorization_from(action, response)
end
def authorization_key(action)
- action == "AuthorizeCreditCardPayment" ? :trans_id : "#{action.underscore}_result".to_sym
+ action == 'AuthorizeCreditCardPayment' ? :trans_id : "#{action.underscore}_result".to_sym
end
def successful?(action, response)
key = authorization_key(action)
if key == :trans_id
- response[:approved] == "true"
+ response[:approved] == 'true'
else
response[key].to_i > 0
end
@@ -197,7 +196,7 @@ def parse(action, xml)
root.elements.to_a.each do |node|
parse_element(response, node)
end
- elsif root = REXML::XPath.first(xml, "//soap:Fault")
+ elsif root = REXML::XPath.first(xml, '//soap:Fault')
root.elements.to_a.each do |node|
response[node.name.underscore.to_sym] = node.text
end
@@ -208,7 +207,7 @@ def parse(action, xml)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|e| parse_element(response, e) }
+ node.elements.each { |e| parse_element(response, e) }
else
response[node.name.underscore.to_sym] = node.text.to_s.strip
end
@@ -216,4 +215,3 @@ def parse_element(response, node)
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/monei.rb b/lib/active_merchant/billing/gateways/monei.rb
new file mode 100755
index 00000000000..37247721a6a
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/monei.rb
@@ -0,0 +1,338 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ #
+ # == Monei gateway
+ # This class implements Monei gateway for Active Merchant. For more information about Monei
+ # gateway please go to http://www.monei.net
+ #
+ # === Setup
+ # In order to set-up the gateway you need four paramaters: sender_id, channel_id, login and pwd.
+ # Request that data to Monei.
+ class MoneiGateway < Gateway
+ self.test_url = 'https://test.monei-api.net/payment/ctpe'
+ self.live_url = 'https://monei-api.net/payment/ctpe'
+
+ self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CA', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FO', 'FR', 'GB', 'GI', 'GR', 'HU', 'IE', 'IL', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'TR', 'US', 'VA']
+ self.default_currency = 'EUR'
+ self.supported_cardtypes = [:visa, :master, :maestro, :jcb, :american_express]
+
+ self.homepage_url = 'http://www.monei.net/'
+ self.display_name = 'Monei'
+
+ # Constructor
+ #
+ # options - Hash containing the gateway credentials, ALL MANDATORY
+ # :sender_id Sender ID
+ # :channel_id Channel ID
+ # :login User login
+ # :pwd User password
+ #
+ def initialize(options={})
+ requires!(options, :sender_id, :channel_id, :login, :pwd)
+ super
+ end
+
+ # Public: Performs purchase operation
+ #
+ # money - Amount of purchase
+ # credit_card - Credit card
+ # options - Hash containing purchase options
+ # :order_id Merchant created id for the purchase
+ # :billing_address Hash with billing address information
+ # :description Merchant created purchase description (optional)
+ # :currency Sale currency to override money object or default (optional)
+ #
+ # Returns Active Merchant response object
+ def purchase(money, credit_card, options={})
+ execute_new_order(:purchase, money, credit_card, options)
+ end
+
+ # Public: Performs authorization operation
+ #
+ # money - Amount to authorize
+ # credit_card - Credit card
+ # options - Hash containing authorization options
+ # :order_id Merchant created id for the authorization
+ # :billing_address Hash with billing address information
+ # :description Merchant created authorization description (optional)
+ # :currency Sale currency to override money object or default (optional)
+ #
+ # Returns Active Merchant response object
+ def authorize(money, credit_card, options={})
+ execute_new_order(:authorize, money, credit_card, options)
+ end
+
+ # Public: Performs capture operation on previous authorization
+ #
+ # money - Amount to capture
+ # authorization - Reference to previous authorization, obtained from response object returned by authorize
+ # options - Hash containing capture options
+ # :order_id Merchant created id for the authorization (optional)
+ # :description Merchant created authorization description (optional)
+ # :currency Sale currency to override money object or default (optional)
+ #
+ # Note: you should pass either order_id or description
+ #
+ # Returns Active Merchant response object
+ def capture(money, authorization, options={})
+ execute_dependant(:capture, money, authorization, options)
+ end
+
+ # Public: Refunds from previous purchase
+ #
+ # money - Amount to refund
+ # authorization - Reference to previous purchase, obtained from response object returned by purchase
+ # options - Hash containing refund options
+ # :order_id Merchant created id for the authorization (optional)
+ # :description Merchant created authorization description (optional)
+ # :currency Sale currency to override money object or default (optional)
+ #
+ # Note: you should pass either order_id or description
+ #
+ # Returns Active Merchant response object
+ def refund(money, authorization, options={})
+ execute_dependant(:refund, money, authorization, options)
+ end
+
+ # Public: Voids previous authorization
+ #
+ # authorization - Reference to previous authorization, obtained from response object returned by authorize
+ # options - Hash containing capture options
+ # :order_id Merchant created id for the authorization (optional)
+ #
+ # Returns Active Merchant response object
+ def void(authorization, options={})
+ execute_dependant(:void, nil, authorization, options)
+ end
+
+ # Public: Verifies credit card. Does this by doing a authorization of 1.00 Euro and then voiding it.
+ #
+ # credit_card - Credit card
+ # options - Hash containing authorization options
+ # :order_id Merchant created id for the authorization
+ # :billing_address Hash with billing address information
+ # :description Merchant created authorization description (optional)
+ # :currency Sale currency to override money object or default (optional)
+ #
+ # Returns Active Merchant response object of Authorization operation
+ 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
+
+ private
+
+ # Private: Execute purchase or authorize operation
+ def execute_new_order(action, money, credit_card, options)
+ request = build_request do |xml|
+ add_identification_new_order(xml, options)
+ add_payment(xml, action, money, options)
+ add_account(xml, credit_card)
+ add_customer(xml, credit_card, options)
+ add_three_d_secure(xml, options)
+ end
+
+ commit(request)
+ end
+
+ # Private: Execute operation that depends on authorization code from previous purchase or authorize operation
+ def execute_dependant(action, money, authorization, options)
+ request = build_request do |xml|
+ add_identification_authorization(xml, authorization, options)
+ add_payment(xml, action, money, options)
+ end
+
+ commit(request)
+ end
+
+ # Private: Build XML wrapping code yielding to code to fill the transaction information
+ def build_request
+ builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
+ xml.Request(:version => '1.0') do
+ xml.Header { xml.Security(:sender => @options[:sender_id]) }
+ xml.Transaction(:mode => test? ? 'CONNECTOR_TEST' : 'LIVE', :response => 'SYNC', :channel => @options[:channel_id]) do
+ xml.User(:login => @options[:login], :pwd => @options[:pwd])
+ yield xml
+ end
+ end
+ end
+ builder.to_xml
+ end
+
+ # Private: Add identification part to XML for new orders
+ def add_identification_new_order(xml, options)
+ requires!(options, :order_id)
+ xml.Identification do
+ xml.TransactionID options[:order_id]
+ end
+ end
+
+ # Private: Add identification part to XML for orders that depend on authorization from previous operation
+ def add_identification_authorization(xml, authorization, options)
+ xml.Identification do
+ xml.ReferenceID authorization
+ xml.TransactionID options[:order_id]
+ end
+ end
+
+ # Private: Add payment part to XML
+ def add_payment(xml, action, money, options)
+ code = tanslate_payment_code(action)
+
+ xml.Payment(:code => code) do
+ xml.Presentation do
+ xml.Amount amount(money)
+ xml.Currency options[:currency] || currency(money)
+ xml.Usage options[:description] || options[:order_id]
+ end unless money.nil?
+ end
+ end
+
+ # Private: Add account part to XML
+ def add_account(xml, credit_card)
+ xml.Account do
+ xml.Holder credit_card.name
+ xml.Number credit_card.number
+ xml.Brand credit_card.brand.upcase
+ xml.Expiry(:month => credit_card.month, :year => credit_card.year)
+ xml.Verification credit_card.verification_value
+ end
+ end
+
+ # Private: Add customer part to XML
+ def add_customer(xml, credit_card, options)
+ requires!(options, :billing_address)
+ address = options[:billing_address]
+ xml.Customer do
+ xml.Name do
+ xml.Given credit_card.first_name
+ xml.Family credit_card.last_name
+ end
+ xml.Address do
+ xml.Street address[:address1].to_s
+ xml.Zip address[:zip].to_s
+ xml.City address[:city].to_s
+ xml.State address[:state].to_s if address.has_key? :state
+ xml.Country address[:country].to_s
+ end
+ xml.Contact do
+ xml.Email options[:email] || 'noemail@monei.net'
+ xml.Ip options[:ip] || '0.0.0.0'
+ end
+ end
+ end
+
+ # Private : Convert ECI to ResultIndicator
+ # Possible ECI values:
+ # 02 or 05 - Fully Authenticated Transaction
+ # 00 or 07 - Non 3D Secure Transaction
+ # Possible ResultIndicator values:
+ # 01 = MASTER_3D_ATTEMPT
+ # 02 = MASTER_3D_SUCCESS
+ # 05 = VISA_3D_SUCCESS
+ # 06 = VISA_3D_ATTEMPT
+ # 07 = DEFAULT_E_COMMERCE
+ def eci_to_result_indicator(eci)
+ case eci
+ when '02', '05'
+ return eci
+ else
+ return '07'
+ end
+ end
+
+ # Private : Add the 3DSecure infos to XML
+ def add_three_d_secure(xml, options)
+ if options[:three_d_secure]
+ xml.Authentication(:type => '3DSecure') do
+ xml.ResultIndicator eci_to_result_indicator options[:three_d_secure][:eci]
+ xml.Parameter(:name => 'VERIFICATION_ID') { xml.text options[:three_d_secure][:cavv] }
+ xml.Parameter(:name => 'XID') { xml.text options[:three_d_secure][:xid] }
+ end
+ end
+ end
+
+ # Private: Parse XML response from Monei servers
+ def parse(body)
+ xml = Nokogiri::XML(body)
+ {
+ :unique_id => xml.xpath('//Response/Transaction/Identification/UniqueID').text,
+ :status => translate_status_code(xml.xpath('//Response/Transaction/Processing/Status/@code').text),
+ :reason => translate_status_code(xml.xpath('//Response/Transaction/Processing/Reason/@code').text),
+ :message => xml.xpath('//Response/Transaction/Processing/Return').text
+ }
+ end
+
+ # Private: Send XML transaction to Monei servers and create AM response
+ def commit(xml)
+ url = (test? ? test_url : live_url)
+
+ response = parse(ssl_post(url, post_data(xml), 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ # Private: Decide success from servers response
+ def success_from(response)
+ response[:status] == :success || response[:status] == :new
+ end
+
+ # Private: Get message from servers response
+ def message_from(response)
+ response[:message]
+ end
+
+ # Private: Get error code from servers response
+ def error_code_from(response)
+ success_from(response) ? nil : STANDARD_ERROR_CODE[:card_declined]
+ end
+
+ # Private: Get authorization code from servers response
+ def authorization_from(response)
+ response[:unique_id]
+ end
+
+ # Private: Encode POST parameters
+ def post_data(xml)
+ "load=#{CGI.escape(xml)}"
+ end
+
+ # Private: Translate Monei status code to native ruby symbols
+ def translate_status_code(code)
+ {
+ '00' => :success,
+ '40' => :neutral,
+ '59' => :waiting_bank,
+ '60' => :rejected_bank,
+ '64' => :waiting_risk,
+ '65' => :rejected_risk,
+ '70' => :rejected_validation,
+ '80' => :waiting,
+ '90' => :new
+ }[code]
+ end
+
+ # Private: Translate AM operations to Monei operations codes
+ def tanslate_payment_code(action)
+ {
+ :purchase => 'CC.DB',
+ :authorize => 'CC.PA',
+ :capture => 'CC.CP',
+ :refund => 'CC.RF',
+ :void => 'CC.RV'
+ }[action]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/moneris.rb b/lib/active_merchant/billing/gateways/moneris.rb
index c501f0911d1..f17f0e28acd 100644
--- a/lib/active_merchant/billing/gateways/moneris.rb
+++ b/lib/active_merchant/billing/gateways/moneris.rb
@@ -2,7 +2,6 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
-
# To learn more about the Moneris gateway, please contact
# eselectplus@moneris.com for a copy of their integration guide. For
# information on remote testing, please see "Test Environment Penny Value
@@ -33,7 +32,8 @@ class MonerisGateway < Gateway
def initialize(options = {})
requires!(options, :login, :password)
@cvv_enabled = options[:cvv_enabled]
- options = { :crypt_type => 7 }.merge(options)
+ @avs_enabled = options[:avs_enabled]
+ options[:crypt_type] = 7 unless options.has_key?(:crypt_type)
super
end
@@ -45,12 +45,19 @@ def initialize(options = {})
def authorize(money, creditcard_or_datakey, options = {})
requires!(options, :order_id)
post = {}
- add_payment_source(post, creditcard_or_datakey)
- post[:amount] = amount(money)
- post[:order_id] = options[:order_id]
- post[:cust_id] = options[:customer]
+ add_payment_source(post, creditcard_or_datakey, options)
+ post[:amount] = amount(money)
+ post[:order_id] = options[:order_id]
+ post[:address] = options[:billing_address] || options[:address]
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
- action = (post[:data_key].blank?) ? 'preauth' : 'res_preauth_cc'
+ add_cof(post, options)
+ action = if post[:cavv]
+ 'cavv_preauth'
+ elsif post[:data_key].blank?
+ 'preauth'
+ else
+ 'res_preauth_cc'
+ end
commit(action, post)
end
@@ -61,12 +68,19 @@ def authorize(money, creditcard_or_datakey, options = {})
def purchase(money, creditcard_or_datakey, options = {})
requires!(options, :order_id)
post = {}
- add_payment_source(post, creditcard_or_datakey)
- post[:amount] = amount(money)
- post[:order_id] = options[:order_id]
- post[:cust_id] = options[:customer]
+ add_payment_source(post, creditcard_or_datakey, options)
+ post[:amount] = amount(money)
+ post[:order_id] = options[:order_id]
+ post[:address] = options[:billing_address] || options[:address]
post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
- action = (post[:data_key].blank?) ? 'purchase' : 'res_purchase_cc'
+ add_cof(post, options)
+ action = if post[:cavv]
+ 'cavv_purchase'
+ elsif post[:data_key].blank?
+ 'purchase'
+ else
+ 'res_purchase_cc'
+ end
commit(action, post)
end
@@ -81,13 +95,28 @@ def capture(money, authorization, options = {})
commit 'completion', crediting_params(authorization, :comp_amount => amount(money))
end
- # Voiding cancels an open authorization.
+ # Voiding requires the original transaction ID and order ID of some open
+ # transaction. Closed transactions must be refunded.
+ #
+ # Moneris supports the voiding of an unsettled capture or purchase via
+ # its purchasecorrection command. This action can only occur
+ # on the same day as the capture/purchase prior to 22:00-23:00 EST. If
+ # you want to do this, pass :purchasecorrection => true as
+ # an option.
+ #
+ # Fun, Historical Trivia:
+ # Voiding an authorization in Moneris is a relatively new feature
+ # (September, 2011). It is actually done by doing a $0 capture.
#
# Concatenate your transaction number and order_id by using a semicolon
# (';'). This is to keep the Moneris interface consistent with other
# gateways. (See +capture+ for details.)
def void(authorization, options = {})
- capture(0, authorization, options)
+ if options[:purchasecorrection]
+ commit 'purchasecorrection', crediting_params(authorization)
+ else
+ capture(0, authorization, options)
+ end
end
# Performs a refund. This method requires that the original transaction
@@ -96,7 +125,7 @@ def void(authorization, options = {})
# Moneris interface consistent with other gateways. (See +capture+ for
# details.)
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
@@ -104,6 +133,13 @@ def refund(money, authorization, options = {})
commit 'refund', crediting_params(authorization, :amount => amount(money))
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(credit_card, options = {})
post = {}
post[:pan] = credit_card.number
@@ -127,22 +163,51 @@ def update(data_key, credit_card, options = {})
commit('res_update_cc', post)
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').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2')
+ end
+
private # :nodoc: all
def expdate(creditcard)
- sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month)
+ sprintf('%.4i', creditcard.year)[-2..-1] + sprintf('%.2i', creditcard.month)
end
- def add_payment_source(post, source)
- if source.is_a?(String)
- post[:data_key] = source
+ def add_payment_source(post, payment_method, options)
+ if payment_method.is_a?(String)
+ post[:data_key] = payment_method
+ post[:cust_id] = options[:customer]
else
- post[:pan] = source.number
- post[:expdate] = expdate(source)
- post[:cvd_value] = source.verification_value if source.verification_value?
+ if payment_method.respond_to?(:track_data) && payment_method.track_data.present?
+ post[:pos_code] = '00'
+ post[:track2] = payment_method.track_data
+ else
+ post[:pan] = payment_method.number
+ post[:expdate] = expdate(payment_method)
+ post[:cvd_value] = payment_method.verification_value if payment_method.verification_value?
+ post[:cavv] = payment_method.payment_cryptogram if payment_method.is_a?(NetworkTokenizationCreditCard)
+ post[:wallet_indicator] = wallet_indicator(payment_method.source.to_s) if payment_method.is_a?(NetworkTokenizationCreditCard)
+ post[:crypt_type] = (payment_method.eci || 7) if payment_method.is_a?(NetworkTokenizationCreditCard)
+ end
+ post[:cust_id] = options[:customer] || payment_method.name
end
end
+ def add_cof(post, options)
+ post[:issuer_id] = options[:issuer_id] if options[:issuer_id]
+ post[:payment_indicator] = options[:payment_indicator] if options[:payment_indicator]
+ post[:payment_information] = options[:payment_information] if options[:payment_information]
+ end
+
# Common params used amongst the +credit+, +void+ and +capture+ methods
def crediting_params(authorization, options = {})
{
@@ -152,7 +217,7 @@ def crediting_params(authorization, options = {})
}.merge(options)
end
- # Splits an +authorization+ param and retrives the order id and
+ # Splits an +authorization+ param and retrieves the order id and
# transaction number in that order.
def split_authorization(authorization)
if authorization.nil? || authorization.empty? || authorization !~ /;/
@@ -163,13 +228,19 @@ def split_authorization(authorization)
end
def commit(action, parameters = {})
- response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters)))
-
- Response.new(successful?(response), message_from(response[:message]), response,
- :test => test?,
- :cvv_result => response[:cvd_result_code].try(:last),
- :authorization => authorization_from(response)
- )
+ data = post_data(action, parameters)
+ url = test? ? self.test_url : self.live_url
+ raw = ssl_post(url, data)
+ response = parse(raw)
+
+ Response.new(
+ successful?(response),
+ message_from(response[:message]),
+ response,
+ :test => test?,
+ :avs_result => {:code => response[:avs_result_code]},
+ :cvv_result => response[:cvd_result_code] && response[:cvd_result_code][-1, 1],
+ :authorization => authorization_from(response))
end
# Generates a Moneris authorization string of the form 'trans_id;receipt_id'.
@@ -183,11 +254,11 @@ def authorization_from(response = {})
def successful?(response)
response[:response_code] &&
response[:complete] &&
- (0..49).include?(response[:response_code].to_i)
+ (0..49).cover?(response[:response_code].to_i)
end
def parse(xml)
- response = { :message => "Global Error Receipt", :complete => false }
+ response = { :message => 'Global Error Receipt', :complete => false }
hashify_xml!(xml, response)
response
end
@@ -201,10 +272,10 @@ def hashify_xml!(xml, response)
end
def post_data(action, parameters = {})
- xml = REXML::Document.new
- root = xml.add_element("request")
- root.add_element("store_id").text = options[:login]
- root.add_element("api_token").text = options[:password]
+ xml = REXML::Document.new
+ root = xml.add_element('request')
+ root.add_element('store_id').text = options[:login]
+ root.add_element('api_token').text = options[:password]
root.add_element(transaction_element(action, parameters))
xml.to_s
@@ -215,8 +286,13 @@ def transaction_element(action, parameters)
# Must add the elements in the correct order
actions[action].each do |key|
- if((key == :cvd_info) && @cvv_enabled)
- transaction.add_element(cvd_element(parameters[:cvd_value]))
+ case key
+ when :avs_info
+ transaction.add_element(avs_element(parameters[:address])) if @avs_enabled && parameters[:address]
+ when :cvd_info
+ transaction.add_element(cvd_element(parameters[:cvd_value])) if @cvv_enabled
+ when :cof_info
+ transaction.add_element(credential_on_file(parameters)) if cof_details_present?(parameters)
else
transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank?
end
@@ -225,52 +301,75 @@ def transaction_element(action, parameters)
transaction
end
+ def avs_element(address)
+ full_address = "#{address[:address1]} #{address[:address2]}"
+ tokens = full_address.split(/\s+/)
+
+ element = REXML::Element.new('avs_info')
+ element.add_element('avs_street_number').text = tokens.select { |x| x =~ /\d/ }.join(' ')
+ element.add_element('avs_street_name').text = tokens.reject { |x| x =~ /\d/ }.join(' ')
+ element.add_element('avs_zipcode').text = address[:zip]
+ element
+ end
+
def cvd_element(cvd_value)
element = REXML::Element.new('cvd_info')
if cvd_value
- element.add_element('cvd_indicator').text = "1"
+ element.add_element('cvd_indicator').text = '1'
element.add_element('cvd_value').text = cvd_value
else
- element.add_element('cvd_indicator').text = "0"
+ element.add_element('cvd_indicator').text = '0'
end
element
end
- def message_from(message)
- return 'Unspecified error' if message.blank?
- message.gsub(/[^\w]/, ' ').split.join(" ").capitalize
+ def cof_details_present?(parameters)
+ parameters[:issuer_id] && parameters[:payment_indicator] && parameters[:payment_information]
end
- # Make a Ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when '', "null" then nil
- else field
- end
+ def credential_on_file(parameters)
+ issuer_id = parameters[:issuer_id]
+ payment_indicator = parameters[:payment_indicator]
+ payment_information = parameters[:payment_information]
+
+ cof_info = REXML::Element.new('cof_info')
+ cof_info.add_element('issuer_id').text = issuer_id
+ cof_info.add_element('payment_indicator').text = payment_indicator
+ cof_info.add_element('payment_information').text = payment_information
+ cof_info
+ end
+
+ def wallet_indicator(token_source)
+ return 'APP' if token_source == 'apple_pay'
+ return 'ANP' if token_source == 'android_pay'
+ nil
+ end
+
+ def message_from(message)
+ return 'Unspecified error' if message.blank?
+ message.gsub(/[^\w]/, ' ').split.join(' ').capitalize
end
def actions
{
- "purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :cvd_info],
- "preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :cvd_info],
- "command" => [:order_id],
- "refund" => [:order_id, :amount, :txn_number, :crypt_type],
- "indrefund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
- "completion" => [:order_id, :comp_amount, :txn_number, :crypt_type],
- "purchasecorrection" => [:order_id, :txn_number, :crypt_type],
- "cavvpurcha" => [:order_id, :cust_id, :amount, :pan, :expdate, :cav],
- "cavvpreaut" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
- "transact" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
- "Batchcloseall" => [],
- "opentotals" => [:ecr_number],
- "batchclose" => [:ecr_number],
- "res_add_cc" => [:pan, :expdate, :crypt_type],
- "res_delete" => [:data_key],
- "res_update_cc" => [:data_key, :pan, :expdate, :crypt_type],
- "res_purchase_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type],
- "res_preauth_cc" => [:data_key, :order_id, :cust_id, :amount, :crypt_type]
+ 'purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code, :cof_info],
+ 'preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info, :track2, :pos_code, :cof_info],
+ 'command' => [:order_id],
+ 'refund' => [:order_id, :amount, :txn_number, :crypt_type],
+ 'indrefund' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
+ 'completion' => [:order_id, :comp_amount, :txn_number, :crypt_type],
+ 'purchasecorrection' => [:order_id, :txn_number, :crypt_type],
+ 'cavv_preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv, :crypt_type, :wallet_indicator],
+ 'cavv_purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv, :crypt_type, :wallet_indicator],
+ 'transact' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
+ 'Batchcloseall' => [],
+ 'opentotals' => [:ecr_number],
+ 'batchclose' => [:ecr_number],
+ 'res_add_cc' => [:pan, :expdate, :crypt_type, :cof_info],
+ 'res_delete' => [:data_key],
+ 'res_update_cc' => [:data_key, :pan, :expdate, :crypt_type],
+ 'res_purchase_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type, :cof_info],
+ 'res_preauth_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type, :cof_info]
}
end
end
diff --git a/lib/active_merchant/billing/gateways/moneris_us.rb b/lib/active_merchant/billing/gateways/moneris_us.rb
index 92feec27397..28c06ea91d0 100644
--- a/lib/active_merchant/billing/gateways/moneris_us.rb
+++ b/lib/active_merchant/billing/gateways/moneris_us.rb
@@ -2,7 +2,6 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
-
# To learn more about the Moneris (US) gateway, please contact
# ussales@moneris.com for a copy of their integration guide. For
# information on remote testing, please see "Test Environment Penny Value
@@ -18,29 +17,70 @@ class MonerisUsGateway < Gateway
self.homepage_url = 'http://www.monerisusa.com/'
self.display_name = 'Moneris (US)'
- # login is your Store ID
- # password is your API Token
+ # Initialize the Gateway
+ #
+ # The gateway requires that a valid login and password be passed
+ # in the +options+ hash.
+ #
+ # ==== Options
+ #
+ # * :login -- Your Store ID
+ # * :password -- Your API Token
+ # * :cvv_enabled -- Specify that you would like the CVV passed to the gateway.
+ # Only particular account types at Moneris will allow this.
+ # Defaults to false. (optional)
def initialize(options = {})
requires!(options, :login, :password)
+ @cvv_enabled = options[:cvv_enabled]
+ @avs_enabled = options[:avs_enabled]
options = { :crypt_type => 7 }.merge(options)
super
end
+ def verify(creditcard_or_datakey, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, creditcard_or_datakey, options) }
+ r.process(:ignore_result) { capture(0, r.authorization) }
+ end
+ end
+
# Referred to as "PreAuth" in the Moneris integration guide, this action
# verifies and locks funds on a customer's card, which then must be
# captured at a later date.
#
# Pass in +order_id+ and optionally a +customer+ parameter.
- def authorize(money, creditcard, options = {})
- debit_commit 'us_preauth', money, creditcard, options
+ def authorize(money, creditcard_or_datakey, options = {})
+ requires!(options, :order_id)
+ post = {}
+ add_payment_source(post, creditcard_or_datakey, options)
+ post[:amount] = amount(money)
+ post[:order_id] = options[:order_id]
+ post[:address] = options[:billing_address] || options[:address]
+ post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
+ action = post[:data_key].blank? ? 'us_preauth' : 'us_res_preauth_cc'
+ commit(action, post)
end
- # This action verifies funding on a customer's card, and readies them for
+ # This action verifies funding on a customer's card and readies them for
# deposit in a merchant's account.
#
# Pass in order_id and optionally a customer parameter
- def purchase(money, creditcard, options = {})
- debit_commit 'us_purchase', money, creditcard, options
+ def purchase(money, creditcard_or_datakey, options = {})
+ requires!(options, :order_id)
+ post = {}
+ add_payment_source(post, creditcard_or_datakey, options)
+ post[:amount] = amount(money)
+ post[:order_id] = options[:order_id]
+ add_address(post, creditcard_or_datakey, options)
+ post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
+ action = if creditcard_or_datakey.is_a?(String)
+ 'us_res_purchase_cc'
+ elsif card_brand(creditcard_or_datakey) == 'check'
+ 'us_ach_debit'
+ elsif post[:data_key].blank?
+ 'us_purchase'
+ end
+ commit(action, post)
end
# This method retrieves locked funds from a customer's account (from a
@@ -71,7 +111,7 @@ def void(authorization, options = {})
# Moneris interface consistent with other gateways. (See +capture+ for
# details.)
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
@@ -79,27 +119,80 @@ def refund(money, authorization, options = {})
commit 'us_refund', crediting_params(authorization, :amount => amount(money))
end
+ def store(payment_source, options = {})
+ post = {}
+ add_payment_source(post, payment_source, options)
+ post[:crypt_type] = options[:crypt_type] || @options[:crypt_type]
+ card_brand(payment_source) == 'check' ? commit('us_res_add_ach', post) : commit('us_res_add_cc', post)
+ end
+
+ def unstore(data_key, options = {})
+ post = {}
+ post[:data_key] = data_key
+ commit('us_res_delete', post)
+ end
+
+ def update(data_key, payment_source, options = {})
+ post = {}
+ add_payment_source(post, payment_source, options)
+ post[:data_key] = data_key
+ card_brand(payment_source) == 'check' ? commit('us_res_update_ach', post) : commit('us_res_update_cc', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2')
+ end
+
private # :nodoc: all
def expdate(creditcard)
- sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month)
+ sprintf('%.4i', creditcard.year)[-2..-1] + sprintf('%.2i', creditcard.month)
end
- def debit_commit(commit_type, money, creditcard, options)
- requires!(options, :order_id)
- commit(commit_type, debit_params(money, creditcard, options))
+ def add_address(post, payment_method, options)
+ if !payment_method.is_a?(String) && card_brand(payment_method) == 'check'
+ post[:ach_info][:cust_first_name] = payment_method.first_name if payment_method.first_name
+ post[:ach_info][:cust_last_name] = payment_method.last_name if payment_method.last_name
+ if address = options[:billing_address] || options[:address]
+ post[:ach_info][:cust_address1] = address[:address1] if address[:address1]
+ post[:ach_info][:cust_address2] = address[:address2] if address[:address2]
+ post[:ach_info][:city] = address[:city] if address[:city]
+ post[:ach_info][:state] = address[:state] if address[:state]
+ post[:ach_info][:zip] = address[:zip] if address[:zip]
+ end
+ else
+ post[:address] = options[:billing_address] || options[:address]
+ end
end
- # Common params used amongst the +purchase+ and +authorization+ methods
- def debit_params(money, creditcard, options = {})
- {
- :order_id => options[:order_id],
- :cust_id => options[:customer],
- :amount => amount(money),
- :pan => creditcard.number,
- :expdate => expdate(creditcard),
- :crypt_type => options[:crypt_type] || @options[:crypt_type]
- }
+ def add_payment_source(post, source, options)
+ if source.is_a?(String)
+ post[:data_key] = source
+ post[:cust_id] = options[:customer]
+ elsif card_brand(source) == 'check'
+ ach_info = {}
+ ach_info[:sec] = 'web'
+ ach_info[:routing_num] = source.routing_number
+ ach_info[:account_num] = source.account_number
+ ach_info[:account_type] = source.account_type
+ ach_info[:check_num] = source.number if source.number
+ post[:ach_info] = ach_info
+ else
+ post[:pan] = source.number
+ post[:expdate] = expdate(source)
+ post[:cvd_value] = source.verification_value if source.verification_value?
+ if crypt_type = options[:crypt_type] || @options[:crypt_type]
+ post[:crypt_type] = crypt_type
+ end
+ post[:cust_id] = options[:customer] || source.name
+ end
end
# Common params used amongst the +credit+, +void+ and +capture+ methods
@@ -111,7 +204,7 @@ def crediting_params(authorization, options = {})
}.merge(options)
end
- # Splits an +authorization+ param and retrives the order id and
+ # Splits an +authorization+ param and retrieves the order id and
# transaction number in that order.
def split_authorization(authorization)
if authorization.nil? || authorization.empty? || authorization !~ /;/
@@ -122,10 +215,15 @@ def split_authorization(authorization)
end
def commit(action, parameters = {})
- response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(action, parameters)))
+ data = post_data(action, parameters)
+ url = test? ? self.test_url : self.live_url
+ raw = ssl_post(url, data)
+ response = parse(raw)
Response.new(successful?(response), message_from(response[:message]), response,
:test => test?,
+ :avs_result => { :code => response[:avs_result_code] },
+ :cvv_result => response[:cvd_result_code] && response[:cvd_result_code][-1, 1],
:authorization => authorization_from(response)
)
end
@@ -141,11 +239,11 @@ def authorization_from(response = {})
def successful?(response)
response[:response_code] &&
response[:complete] &&
- (0..49).include?(response[:response_code].to_i)
+ (0..49).cover?(response[:response_code].to_i)
end
def parse(xml)
- response = { :message => "Global Error Receipt", :complete => false }
+ response = { :message => 'Global Error Receipt', :complete => false }
hashify_xml!(xml, response)
response
end
@@ -160,47 +258,93 @@ def hashify_xml!(xml, response)
def post_data(action, parameters = {})
xml = REXML::Document.new
- root = xml.add_element("request")
- root.add_element("store_id").text = options[:login]
- root.add_element("api_token").text = options[:password]
- transaction = root.add_element(action)
+ root = xml.add_element('request')
+ root.add_element('store_id').text = options[:login]
+ root.add_element('api_token').text = options[:password]
+ root.add_element(transaction_element(action, parameters))
+
+ xml.to_s
+ end
+
+ def transaction_element(action, parameters)
+ transaction = REXML::Element.new(action)
# Must add the elements in the correct order
actions[action].each do |key|
- transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank?
+ case key
+ when :avs_info
+ transaction.add_element(avs_element(parameters[:address])) if @avs_enabled && parameters[:address]
+ when :cvd_info
+ transaction.add_element(cvd_element(parameters[:cvd_value])) if @cvv_enabled
+ when :ach_info
+ transaction.add_element(ach_element(parameters[:ach_info]))
+ else
+ transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank?
+ end
end
- xml.to_s
+ transaction
end
- def message_from(message)
- return 'Unspecified error' if message.blank?
- message.gsub(/[^\w]/, ' ').split.join(" ").capitalize
+ def avs_element(address)
+ full_address = "#{address[:address1]} #{address[:address2]}"
+ tokens = full_address.split(/\s+/)
+
+ element = REXML::Element.new('avs_info')
+ element.add_element('avs_street_number').text = tokens.select { |x| x =~ /\d/ }.join(' ')
+ element.add_element('avs_street_name').text = tokens.reject { |x| x =~ /\d/ }.join(' ')
+ element.add_element('avs_zipcode').text = address[:zip]
+ element
end
- # Make a Ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when '', "null" then nil
- else field
+ def cvd_element(cvd_value)
+ element = REXML::Element.new('cvd_info')
+ if cvd_value
+ element.add_element('cvd_indicator').text = '1'
+ element.add_element('cvd_value').text = cvd_value
+ else
+ element.add_element('cvd_indicator').text = '0'
end
+ element
+ end
+
+ def ach_element(ach_info)
+ element = REXML::Element.new('ach_info')
+ actions['ach_info'].each do |key|
+ element.add_element(key.to_s).text = ach_info[key] unless ach_info[key].blank?
+ end
+ element
+ end
+
+ def message_from(message)
+ return 'Unspecified error' if message.blank?
+ message.gsub(/[^\w]/, ' ').split.join(' ').capitalize
end
def actions
{
- "us_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
- "us_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
- "us_refund" => [:order_id, :amount, :txn_number, :crypt_type],
- "us_ind_refund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
- "us_completion" => [:order_id, :comp_amount, :txn_number, :crypt_type],
- "us_purchasecorrection" => [:order_id, :txn_number, :crypt_type],
- "us_cavv_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
- "us_cavv_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
- "us_batchcloseall" => [],
- "us_opentotals" => [:ecr_number],
- "us_batchclose" => [:ecr_number]
+ 'us_purchase' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info],
+ 'us_preauth' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type, :avs_info, :cvd_info],
+ 'us_command' => [:order_id],
+ 'us_refund' => [:order_id, :amount, :txn_number, :crypt_type],
+ 'us_indrefund' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
+ 'us_completion' => [:order_id, :comp_amount, :txn_number, :crypt_type],
+ 'us_purchasecorrection' => [:order_id, :txn_number, :crypt_type],
+ 'us_cavvpurcha' => [:order_id, :cust_id, :amount, :pan, :expdate, :cav],
+ 'us_cavvpreaut' => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
+ 'us_transact' => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
+ 'us_Batchcloseall' => [],
+ 'us_opentotals' => [:ecr_number],
+ 'us_batchclose' => [:ecr_number],
+ 'us_res_add_cc' => [:pan, :expdate, :crypt_type],
+ 'us_res_delete' => [:data_key],
+ 'us_res_update_cc' => [:data_key, :pan, :expdate, :crypt_type],
+ 'us_res_purchase_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type],
+ 'us_res_preauth_cc' => [:data_key, :order_id, :cust_id, :amount, :crypt_type],
+ 'us_ach_debit' => [:order_id, :cust_id, :amount, :ach_info],
+ 'us_res_add_ach' => [:order_id, :cust_id, :amount, :ach_info],
+ 'us_res_update_ach' => [:order_id, :data_key, :cust_id, :amount, :ach_info],
+ 'ach_info' => [:sec, :cust_first_name, :cust_last_name, :cust_address1, :cust_address2, :cust_city, :cust_state, :cust_zip, :routing_num, :account_num, :check_num, :account_type]
}
end
end
diff --git a/lib/active_merchant/billing/gateways/money_movers.rb b/lib/active_merchant/billing/gateways/money_movers.rb
new file mode 100644
index 00000000000..8a7a1e9e9a4
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/money_movers.rb
@@ -0,0 +1,151 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class MoneyMoversGateway < Gateway
+ self.live_url = self.test_url = 'https://secure.mmoagateway.com/api/transact.php'
+
+ APPROVED, DECLINED, ERROR = 1, 2, 3
+
+ self.homepage_url = 'http://mmoa.us/'
+ self.display_name = 'MoneyMovers'
+ self.supported_countries = ['US']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ def initialize(options = {})
+ requires!(options, :login, :password)
+ @options = options
+ super
+ end
+
+ def purchase(money, creditcard, options = {})
+ post = {}
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_address(post, options)
+ add_customer_data(post, options)
+ commit('sale', money, post)
+ end
+
+ def authorize(money, creditcard, options = {})
+ post = {}
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_address(post, options)
+ add_customer_data(post, options)
+ commit('auth', money, post)
+ end
+
+ def capture(money, authorization, options = {})
+ options[:transactionid] = authorization
+ commit('capture', money, options)
+ end
+
+ def void(authorization, options = {})
+ options[:transactionid] = authorization
+ commit('void', nil, options)
+ end
+
+ def refund(money, authorization, options = {})
+ commit('refund', money, options.merge(:transactionid => authorization))
+ end
+
+ def credit(money, authorization, options = {})
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
+ refund(money, authorization, options)
+ end
+
+ private
+
+ def add_customer_data(post, options)
+ post[:firstname] = options[:first_name]
+ post[:lastname] = options[:last_name]
+
+ post[:email] = options[:email]
+ end
+
+ def add_address(post, options)
+ if address = (options[:billing_address] || options[:address])
+ post[:company] = address[:company]
+ post[:address1] = address[:address1]
+ post[:address2] = address[:address2]
+ post[:city] = address[:city]
+ post[:state] = address[:state]
+ post[:zip] = address[:zip]
+ post[:country] = address[:country]
+ post[:phone] = address[:phone]
+ end
+ if address = options[:shipping_address]
+ post[:shipping_firstname] = address[:first_name]
+ post[:shipping_lastname] = address[:last_name]
+ post[:shipping_company] = address[:company]
+ post[:shipping_address1] = address[:address1]
+ post[:shipping_address2] = address[:address2]
+ post[:shipping_city] = address[:city]
+ post[:shipping_state] = address[:state]
+ post[:shipping_zip] = address[:zip]
+ post[:shipping_country] = address[:country]
+ post[:shipping_email] = address[:email]
+ end
+ end
+
+ def add_invoice(post, options)
+ post[:orderid] = options[:order_id]
+ post[:orderdescription] = options[:description]
+ end
+
+ def add_creditcard(post, creditcard)
+ post[:ccnumber] = creditcard.number
+ post[:ccexp] = expdate(creditcard)
+ post[:cvv] = creditcard.verification_value
+ end
+
+ def parse(body)
+ body.split('&').inject({}) do |memo, x|
+ k, v = x.split('=')
+ memo[k] = v
+ memo
+ end
+ end
+
+ def commit(action, money, parameters)
+ parameters[:amount] = amount(money)
+
+ data = ssl_post(self.live_url, post_data(action, parameters))
+ response = parse(data)
+ message = message_from(response)
+
+ Response.new(success?(response), message, response,
+ :test => test?,
+ :authorization => response['transactionid'],
+ :avs_result => {:code => response['avsresponse']},
+ :cvv_result => response['cvvresponse']
+ )
+ end
+
+ def success?(response)
+ response['response'] == '1'
+ end
+
+ def test?
+ @options[:login].eql?('demo') && @options[:password].eql?('password')
+ end
+
+ def message_from(response)
+ case response['response'].to_i
+ when APPROVED
+ 'Transaction Approved'
+ when DECLINED
+ 'Transaction Declined'
+ else
+ 'Error in transaction data or system error'
+ end
+ end
+
+ def post_data(action, parameters = {})
+ parameters[:type] = action
+ parameters[:username] = @options[:login]
+ parameters[:password] = @options[:password]
+ parameters.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/mundipagg.rb b/lib/active_merchant/billing/gateways/mundipagg.rb
new file mode 100644
index 00000000000..e66a6b94680
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/mundipagg.rb
@@ -0,0 +1,293 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class MundipaggGateway < Gateway
+ self.live_url = 'https://api.mundipagg.com/core/v1'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :alelo]
+
+ self.homepage_url = 'https://www.mundipagg.com/'
+ self.display_name = 'Mundipagg'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '400' => STANDARD_ERROR_CODE[:processing_error],
+ '401' => STANDARD_ERROR_CODE[:config_error],
+ '404' => STANDARD_ERROR_CODE[:processing_error],
+ '412' => STANDARD_ERROR_CODE[:processing_error],
+ '422' => STANDARD_ERROR_CODE[:processing_error],
+ '500' => STANDARD_ERROR_CODE[:processing_error]
+ }
+
+ STANDARD_ERROR_MESSAGE_MAPPING = {
+ '400' => 'Invalid request;',
+ '401' => 'Invalid API key;',
+ '404' => 'The requested resource does not exist;',
+ '412' => 'Valid parameters but request failed;',
+ '422' => 'Invalid parameters;',
+ '500' => 'An internal error occurred;'
+ }
+
+ def initialize(options={})
+ requires!(options, :api_key)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_customer_data(post, options) unless payment.is_a?(String)
+ add_shipping_address(post, options)
+ add_payment(post, payment, options)
+
+ commit('sale', post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_customer_data(post, options) unless payment.is_a?(String)
+ add_shipping_address(post, options)
+ add_payment(post, payment, options)
+ add_capture_flag(post, payment)
+ commit('authonly', post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ post[:code] = authorization
+ add_invoice(post, money, options)
+ commit('capture', post, authorization)
+ end
+
+ def refund(money, authorization, options={})
+ add_invoice(post={}, money, options)
+ commit('refund', post, authorization)
+ end
+
+ def void(authorization, options={})
+ commit('void', nil, authorization)
+ end
+
+ def store(payment, options={})
+ post = {}
+ options.update(name: payment.name)
+ options = add_customer(options) unless options[:customer_id]
+ add_payment(post, payment, options)
+ commit('store', post, options[:customer_id])
+ 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((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(("cvv\\":\\")\d*), '\1[FILTERED]').
+ gsub(%r((card\\":{\\"number\\":\\")\d*), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_customer(options)
+ post = {}
+ post[:name] = options[:name]
+ customer = commit('customer', post)
+ options.update(customer_id: customer.authorization)
+ end
+
+ def add_customer_data(post, options)
+ post[:customer] = {}
+ post[:customer][:email] = options[:email]
+ end
+
+ def add_billing_address(post, type, options)
+ if address = (options[:billing_address] || options[:address])
+ billing = {}
+ address = options[:billing_address] || options[:address]
+ billing[:street] = address[:address1].match(/\D+/)[0].strip if address[:address1]
+ billing[:number] = address[:address1].match(/\d+/)[0] if address[:address1]
+ billing[:compliment] = address[:address2] if address[:address2]
+ billing[:city] = address[:city] if address[:city]
+ billing[:state] = address[:state] if address[:state]
+ billing[:country] = address[:country] if address[:country]
+ billing[:zip_code] = address[:zip] if address[:zip]
+ billing[:neighborhood] = address[:neighborhood]
+ post[:payment][type.to_sym][:card][:billing_address] = billing
+ end
+ end
+
+ def add_shipping_address(post, options)
+ if address = options[:shipping_address]
+ post[:address] = {}
+ post[:address][:street] = address[:address1].match(/\D+/)[0].strip if address[:address1]&.match(/\D+/)
+ post[:address][:number] = address[:address1].match(/\d+/)[0] if address[:address1]&.match(/\d+/)
+ post[:address][:compliment] = address[:address2] if address[:address2]
+ post[:address][:city] = address[:city] if address[:city]
+ post[:address][:state] = address[:state] if address[:state]
+ post[:address][:country] = address[:country] if address[:country]
+ post[:address][:zip_code] = address[:zip] if address[:zip]
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = money
+ post[:currency] = (options[:currency] || currency(money))
+ end
+
+ def add_capture_flag(post, payment)
+ if voucher?(payment)
+ post[:payment][:voucher][:capture] = false
+ else
+ post[:payment][:credit_card][:capture] = false
+ end
+ end
+
+ def add_payment(post, payment, options)
+ post[:customer][:name] = payment.name if post[:customer]
+ post[:customer_id] = parse_auth(payment)[0] if payment.is_a?(String)
+ post[:payment] = {}
+ affiliation = options[:gateway_affiliation_id] || @options[:gateway_id]
+ post[:payment][:gateway_affiliation_id] = affiliation if affiliation
+ post[:payment][:metadata] = { mundipagg_payment_method_code: '1' } if test?
+ if voucher?(payment)
+ add_voucher(post, payment, options)
+ else
+ add_credit_card(post, payment, options)
+ end
+ end
+
+ def add_credit_card(post, payment, options)
+ post[:payment][:payment_method] = 'credit_card'
+ post[:payment][:credit_card] = {}
+ if payment.is_a?(String)
+ post[:payment][:credit_card][:card_id] = parse_auth(payment)[1]
+ else
+ post[:payment][:credit_card][:card] = {}
+ post[:payment][:credit_card][:card][:number] = payment.number
+ post[:payment][:credit_card][:card][:holder_name] = payment.name
+ post[:payment][:credit_card][:card][:exp_month] = payment.month
+ post[:payment][:credit_card][:card][:exp_year] = payment.year
+ post[:payment][:credit_card][:card][:cvv] = payment.verification_value
+ post[:payment][:credit_card][:card][:holder_document] = options[:holder_document] if options[:holder_document]
+ add_billing_address(post, 'credit_card', options)
+ end
+ end
+
+ def add_voucher(post, payment, options)
+ post[:currency] = 'BRL'
+ post[:payment][:payment_method] = 'voucher'
+ post[:payment][:voucher] = {}
+ post[:payment][:voucher][:card] = {}
+ post[:payment][:voucher][:card][:number] = payment.number
+ post[:payment][:voucher][:card][:holder_name] = payment.name
+ post[:payment][:voucher][:card][:holder_document] = options[:holder_document]
+ post[:payment][:voucher][:card][:exp_month] = payment.month
+ post[:payment][:voucher][:card][:exp_year] = payment.year
+ post[:payment][:voucher][:card][:cvv] = payment.verification_value
+ add_billing_address(post, 'voucher', options)
+ end
+
+ def voucher?(payment)
+ return false if payment.is_a?(String)
+ %w[sodexo vr].include? card_brand(payment)
+ end
+
+ def headers
+ {
+ 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:api_key]}:"),
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json'
+ }
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def url_for(action, auth = nil)
+ url = live_url
+ case action
+ when 'store'
+ "#{url}/customers/#{auth}/cards/"
+ when 'customer'
+ "#{url}/customers/"
+ when 'refund', 'void'
+ "#{url}/charges/#{auth}/"
+ when 'capture'
+ "#{url}/charges/#{auth}/capture/"
+ else
+ "#{url}/charges/"
+ end
+ end
+
+ def commit(action, parameters, auth = nil)
+ url = url_for(action, auth)
+ parameters.merge!(parameters[:payment][:credit_card].delete(:card)).delete(:payment) if action == 'store'
+ response = if %w[refund void].include? action
+ parse(ssl_request(:delete, url, post_data(parameters), headers))
+ else
+ parse(ssl_post(url, post_data(parameters), headers))
+ end
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, action),
+ avs_result: AVSResult.new(code: response['some_avs_response_key']),
+ cvv_result: CVVResult.new(response['some_cvv_response_key']),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ rescue ResponseError => e
+ message = get_error_message(e)
+ return Response.new(
+ false,
+ "#{STANDARD_ERROR_MESSAGE_MAPPING[e.response.code]} #{message}",
+ parse(e.response.body),
+ test: test?,
+ error_code: STANDARD_ERROR_CODE_MAPPING[e.response.code]
+ )
+ end
+
+ def success_from(response)
+ %w[pending paid processing canceled active].include? response['status']
+ end
+
+ def get_error_message(error)
+ JSON.parse(error.response.body)['message']
+ end
+
+ def message_from(response)
+ return response['message'] if response['message']
+ return response['last_transaction']['acquirer_message'] if response['last_transaction']
+ end
+
+ def authorization_from(response, action)
+ return "#{response['customer']['id']}|#{response['id']}" if action == 'store'
+ response['id']
+ end
+
+ def parse_auth(auth)
+ auth.split('|')
+ end
+
+ def post_data(parameters = {})
+ parameters.to_json
+ end
+
+ def error_code_from(response)
+ STANDARD_ERROR_CODE[:processing_error] unless success_from(response)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/nab_transact.rb b/lib/active_merchant/billing/gateways/nab_transact.rb
index 456d582b343..6715c0e1afb 100644
--- a/lib/active_merchant/billing/gateways/nab_transact.rb
+++ b/lib/active_merchant/billing/gateways/nab_transact.rb
@@ -6,50 +6,41 @@ module Billing #:nodoc:
# be a rebadged Securepay Australia service, though some differences exist.
class NabTransactGateway < Gateway
API_VERSION = 'xml-4.2'
- PERIODIC_API_VERSION = "spxml-4.2"
+ PERIODIC_API_VERSION = 'spxml-4.2'
class_attribute :test_periodic_url, :live_periodic_url
- self.test_url = 'https://transact.nab.com.au/test/xmlapi/payment'
+ self.test_url = 'https://demo.transact.nab.com.au/xmlapi/payment'
self.live_url = 'https://transact.nab.com.au/live/xmlapi/payment'
- self.test_periodic_url = 'https://transact.nab.com.au/xmlapidemo/periodic'
+ self.test_periodic_url = 'https://demo.transact.nab.com.au/xmlapi/periodic'
self.live_periodic_url = 'https://transact.nab.com.au/xmlapi/periodic'
self.supported_countries = ['AU']
-
- # The card types supported by the payment gateway
- # Note that support for Diners, Amex, and JCB require extra
- # steps in setting up your account, as detailed in the NAB Transact API
self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
self.homepage_url = 'http://transact.nab.com.au'
self.display_name = 'NAB Transact'
-
- cattr_accessor :request_timeout
- self.request_timeout = 60
-
self.money_format = :cents
self.default_currency = 'AUD'
- #Transactions currently accepted by NAB Transact XML API
+ # Transactions currently accepted by NAB Transact XML API
TRANSACTIONS = {
- :purchase => 0, #Standard Payment
- :refund => 4, #Refund
- :void => 6, #Client Reversal (Void)
- :authorization => 10, #Preauthorise
- :capture => 11 #Preauthorise Complete (Advice)
+ :purchase => 0, # Standard Payment
+ :refund => 4, # Refund
+ :void => 6, # Client Reversal (Void)
+ :unmatched_refund => 666, # Unmatched Refund
+ :authorization => 10, # Preauthorise
+ :capture => 11 # Preauthorise Complete (Advice)
}
PERIODIC_TYPES = {
:addcrn => 5,
- :editcrn => 5,
:deletecrn => 5,
:trigger => 8
}
SUCCESS_CODES = [ '00', '08', '11', '16', '77' ]
-
def initialize(options = {})
requires!(options, :login, :password)
super
@@ -57,15 +48,16 @@ def initialize(options = {})
def purchase(money, credit_card_or_stored_id, options = {})
if credit_card_or_stored_id.respond_to?(:number)
- #Credit card for instant payment
commit :purchase, build_purchase_request(money, credit_card_or_stored_id, options)
else
- #Triggered payment for an existing stored credit card
- options[:billing_id] = credit_card_or_stored_id.to_s
- commit_periodic build_periodic_item(:trigger, money, nil, options)
+ commit_periodic(:trigger, build_purchase_using_stored_card_request(money, credit_card_or_stored_id, options))
end
end
+ def credit(money, credit_card, options = {})
+ commit :unmatched_refund, build_purchase_request(money, credit_card, options)
+ end
+
def refund(money, authorization, options = {})
commit :refund, build_reference_request(money, authorization, options)
end
@@ -78,14 +70,24 @@ def capture(money, authorization, options = {})
commit :capture, build_reference_request(money, authorization, options)
end
- def store(creditcard, options = {})
- requires!(options, :billing_id, :amount)
- commit_periodic(build_periodic_item(:addcrn, options[:amount], creditcard, options))
+ def store(credit_card, options = {})
+ commit_periodic(:addcrn, build_store_request(credit_card, options))
end
def unstore(identification, options = {})
- options[:billing_id] = identification
- commit_periodic(build_periodic_item(:deletecrn, options[:amount], nil, options))
+ commit_periodic(:deletecrn, build_unstore_request(identification, options))
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ return '' if transcript.blank?
+ transcript.
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2')
end
private
@@ -101,7 +103,7 @@ def add_metadata(xml, options)
def build_purchase_request(money, credit_card, options)
xml = Builder::XmlMarkup.new
- xml.tag! 'amount', amount(money)
+ xml.tag! 'amount', localized_amount(money, options[:currency] || currency(money))
xml.tag! 'currency', options[:currency] || currency(money)
xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '')
@@ -121,24 +123,26 @@ def build_reference_request(money, reference, options)
transaction_id, order_id, preauth_id, original_amount = reference.split('*')
- xml.tag! 'amount', (money ? amount(money) : original_amount)
+ xml.tag! 'amount', (money ? localized_amount(money, options[:currency] || currency(money)) : original_amount)
xml.tag! 'currency', options[:currency] || currency(money)
xml.tag! 'txnID', transaction_id
xml.tag! 'purchaseOrderNo', order_id
xml.tag! 'preauthID', preauth_id
+ add_metadata(xml, options)
+
xml.target!
end
- #Generate payment request XML
- # - API is set to allow multiple Txn's but currentlu only allows one
+ # Generate payment request XML
+ # - API is set to allow multiple Txn's but currently only allows one
# - txnSource = 23 - (XML)
def build_request(action, body)
xml = Builder::XmlMarkup.new
xml.instruct!
xml.tag! 'NABTransactMessage' do
xml.tag! 'MessageInfo' do
- xml.tag! 'messageID', Utils.generate_unique_id.slice(0, 30)
+ xml.tag! 'messageID', SecureRandom.hex(15)
xml.tag! 'messageTimestamp', generate_timestamp
xml.tag! 'timeoutValue', request_timeout
xml.tag! 'apiVersion', API_VERSION
@@ -151,8 +155,8 @@ def build_request(action, body)
xml.tag! 'RequestType', 'Payment'
xml.tag! 'Payment' do
- xml.tag! 'TxnList', "count" => 1 do
- xml.tag! 'Txn', "ID" => 1 do
+ xml.tag! 'TxnList', 'count' => 1 do
+ xml.tag! 'Txn', 'ID' => 1 do
xml.tag! 'txnType', TRANSACTIONS[action]
xml.tag! 'txnSource', 23
xml << body
@@ -164,32 +168,12 @@ def build_request(action, body)
xml.target!
end
- def build_periodic_item(action, money, credit_card, options)
- xml = Builder::XmlMarkup.new
-
- xml.tag! 'actionType', action.to_s
- xml.tag! 'periodicType', PERIODIC_TYPES[action] if PERIODIC_TYPES[action]
- xml.tag! 'currency', options[:currency] || currency(money)
- xml.tag! 'crn', options[:billing_id]
-
- if credit_card
- xml.tag! 'CreditCardInfo' do
- xml.tag! 'cardNumber', credit_card.number
- xml.tag! 'expiryDate', expdate(credit_card)
- xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value?
- end
- end
- xml.tag! 'amount', amount(money)
-
- xml.target!
- end
-
- def build_periodic_request(body)
+ def build_periodic_request(action, body)
xml = Builder::XmlMarkup.new
xml.instruct!
xml.tag! 'NABTransactMessage' do
xml.tag! 'MessageInfo' do
- xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30)
+ xml.tag! 'messageID', SecureRandom.hex(15)
xml.tag! 'messageTimestamp', generate_timestamp
xml.tag! 'timeoutValue', request_timeout
xml.tag! 'apiVersion', PERIODIC_API_VERSION
@@ -202,8 +186,10 @@ def build_periodic_request(body)
xml.tag! 'RequestType', 'Periodic'
xml.tag! 'Periodic' do
- xml.tag! 'PeriodicList', "count" => 1 do
- xml.tag! 'PeriodicItem', "ID" => 1 do
+ xml.tag! 'PeriodicList', 'count' => 1 do
+ xml.tag! 'PeriodicItem', 'ID' => 1 do
+ xml.tag! 'actionType', action.to_s
+ xml.tag! 'periodicType', PERIODIC_TYPES[action] if PERIODIC_TYPES[action]
xml << body
end
end
@@ -213,20 +199,50 @@ def build_periodic_request(body)
xml.target!
end
+ def build_purchase_using_stored_card_request(money, identification, options)
+ xml = Builder::XmlMarkup.new
+
+ xml.tag! 'crn', identification
+ xml.tag! 'currency', options[:currency] || currency(money)
+ xml.tag! 'amount', localized_amount(money, options[:currency] || currency(money))
+
+ xml.target!
+ end
+
+ def build_store_request(credit_card, options)
+ xml = Builder::XmlMarkup.new
+
+ xml.tag! 'crn', options[:billing_id] || SecureRandom.hex(10)
+ xml.tag! 'CreditCardInfo' do
+ xml.tag! 'cardNumber', credit_card.number
+ xml.tag! 'expiryDate', expdate(credit_card)
+ xml.tag! 'cvv', credit_card.verification_value if credit_card.verification_value?
+ end
+
+ xml.target!
+ end
+
+ def build_unstore_request(identification, options)
+ xml = Builder::XmlMarkup.new
+
+ xml.tag! 'crn', identification
+ xml.target!
+ end
+
def commit(action, request)
response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(action, request)))
Response.new(success?(response), message_from(response), response,
:test => test?,
- :authorization => authorization_from(response)
+ :authorization => authorization_from(action, response)
)
end
- def commit_periodic(request)
- response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, build_periodic_request(request)))
+ def commit_periodic(action, request)
+ response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, build_periodic_request(action, request)))
Response.new(success?(response), message_from(response), response,
:test => test?,
- :authorization => authorization_from(response)
+ :authorization => authorization_from(action, response)
)
end
@@ -234,8 +250,12 @@ def success?(response)
SUCCESS_CODES.include?(response[:response_code])
end
- def authorization_from(response)
- [response[:txn_id], response[:purchase_order_no], response[:preauth_id], response[:amount]].join('*')
+ def authorization_from(action, response)
+ if action == :addcrn
+ response[:crn]
+ else
+ [response[:txn_id], response[:purchase_order_no], response[:preauth_id], response[:amount]].join('*')
+ end
end
def message_from(response)
@@ -260,7 +280,7 @@ def parse(body)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|element| parse_element(response, element) }
+ node.elements.each { |element| parse_element(response, element) }
else
response[node.name.underscore.to_sym] = node.text
end
@@ -272,6 +292,10 @@ def generate_timestamp
time.strftime("%Y%d%m%H%M%S#{time.usec}+000")
end
+ def request_timeout
+ @options[:request_timeout] || 60
+ end
+
end
end
end
diff --git a/lib/active_merchant/billing/gateways/ncr_secure_pay.rb b/lib/active_merchant/billing/gateways/ncr_secure_pay.rb
new file mode 100644
index 00000000000..1a595dd196d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/ncr_secure_pay.rb
@@ -0,0 +1,165 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class NcrSecurePayGateway < Gateway
+ self.test_url = 'https://testbox.monetra.com:8665/'
+ self.live_url = 'https://portal.ncrsecurepay.com:8444/'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'http://www.ncrretailonline.com'
+ self.display_name = 'NCR Secure Pay'
+
+ def initialize(options={})
+ requires!(options, :username, :password)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+
+ commit('sale', post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+
+ commit('preauth', post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_invoice(post, money, options)
+
+ commit('preauthcomplete', post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_invoice(post, money, options)
+
+ commit('credit', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_reference(post, reference)
+ post[:ttid] = reference
+ end
+
+ def add_address(post, payment, options)
+ address = options[:billing_address] || options[:address]
+ post[:zip] = address[:zip]
+ post[:street] = address[:address1]
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:descmerch] = options[:merchant] if options[:merchant]
+ post[:ordernum] = options[:order_id] if options[:order_id]
+ post[:comments] = options[:description] if options[:description]
+ end
+
+ def add_payment(post, payment)
+ post[:cardholdername] = payment.name
+ post[:account] = payment.number
+ post[:cv] = payment.verification_value
+ post[:expdate] = expdate(payment)
+ end
+
+ def parse(body)
+ doc = Nokogiri::XML(body)
+ doc.remove_namespaces!
+ response = doc.xpath('/MonetraResp/Resp')[0]
+ resp_params = {}
+
+ response.elements.each do |node|
+ resp_params[node.name.downcase.to_sym] = node.text
+ end
+ resp_params
+ end
+
+ def commit(action, parameters)
+ url = (test? ? test_url : live_url)
+ response = parse(ssl_post(url, request_body(action, parameters)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def success_from(response)
+ response[:code] == 'AUTH'
+ end
+
+ def message_from(response)
+ response[:verbiage]
+ end
+
+ def authorization_from(response)
+ response[:ttid]
+ end
+
+ def request_body(action, parameters = {})
+ Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml|
+ xml.MonetraTrans do
+ xml.Trans(identifier: parameters.delete(:identifier) || '1') do
+ xml.username(options[:username])
+ xml.password(options[:password])
+ xml.action(action)
+ parameters.each do |name, value|
+ xml.send(name, value)
+ end
+ end
+ end
+ end.to_xml
+ end
+
+ def error_code_from(response)
+ unless success_from(response)
+ response[:msoft_code] || response[:phard_code]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/net_registry.rb b/lib/active_merchant/billing/gateways/net_registry.rb
index fddad0d1629..8f199a88ca8 100644
--- a/lib/active_merchant/billing/gateways/net_registry.rb
+++ b/lib/active_merchant/billing/gateways/net_registry.rb
@@ -22,7 +22,7 @@ module Billing
# response will contain a 'receipt' parameter
# (response.params['receipt']) if a receipt was issued by the gateway.
class NetRegistryGateway < Gateway
- self.live_url = self.test_url = 'https://4tknox.au.com/cgi-bin/themerchant.au.com/ecom/external2.pl'
+ self.live_url = self.test_url = 'https://paygate.ssllock.net/external2.pl'
FILTERED_PARAMS = [ 'card_no', 'card_expiry', 'receipt_array' ]
@@ -104,7 +104,7 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -122,6 +122,7 @@ def status(identification)
end
private
+
def add_request_details(params, options)
params['COMMENT'] = options[:description] unless options[:description].blank?
end
@@ -130,7 +131,7 @@ def add_request_details(params, options)
# format for a command.
def expiry(credit_card)
month = format(credit_card.month, :two_digits)
- year = format(credit_card.year , :two_digits)
+ year = format(credit_card.year, :two_digits)
"#{month}/#{year}"
end
@@ -141,7 +142,7 @@ def expiry(credit_card)
# omitted if nil.
def commit(action, params)
# get gateway response
- response = parse( ssl_post(self.live_url, post_data(action, params)) )
+ response = parse(ssl_post(self.live_url, post_data(action, params)))
Response.new(response['status'] == 'approved', message_from(response), response,
:authorization => authorization_from(response, action)
@@ -151,7 +152,12 @@ def commit(action, params)
def post_data(action, params)
params['COMMAND'] = TRANSACTIONS[action]
params['LOGIN'] = "#{@options[:login]}/#{@options[:password]}"
- URI.encode(params.map{|k,v| "#{k}=#{v}"}.join('&'))
+ escape_uri(params.map { |k, v| "#{k}=#{v}" }.join('&'))
+ end
+
+ # The upstream is picky and so we can't use CGI.escape like we want to
+ def escape_uri(uri)
+ URI::DEFAULT_PARSER.escape(uri)
end
def parse(response)
diff --git a/lib/active_merchant/billing/gateways/netaxept.rb b/lib/active_merchant/billing/gateways/netaxept.rb
index 3ee6443f5a9..6681259e657 100644
--- a/lib/active_merchant/billing/gateways/netaxept.rb
+++ b/lib/active_merchant/billing/gateways/netaxept.rb
@@ -31,8 +31,8 @@ def purchase(money, creditcard, options = {})
requires!(options, :order_id)
MultiResponse.run do |r|
- r.process{authorize(money, creditcard, options)}
- r.process{capture(money, r.authorization, options)}
+ r.process { authorize(money, creditcard, options) }
+ r.process { capture(money, r.authorization, options) }
end
end
@@ -40,9 +40,9 @@ def authorize(money, creditcard, options = {})
requires!(options, :order_id)
MultiResponse.run do |r|
- r.process{setup_transaction(money, options)}
- r.process{add_and_auth_credit_card(r.authorization, creditcard, options)}
- r.process{query_transaction(r.authorization, options)}
+ r.process { setup_transaction(money, options) }
+ r.process { add_and_auth_credit_card(r.authorization, creditcard, options) }
+ r.process { query_transaction(r.authorization, options) }
end
end
@@ -50,24 +50,24 @@ def capture(money, authorization, options = {})
post = {}
add_credentials(post, options)
add_authorization(post, authorization, money)
- post[:operation] = "Capture"
- commit("Netaxept/process.aspx", post)
+ post[:operation] = 'Capture'
+ commit('Netaxept/process.aspx', post)
end
def refund(money, authorization, options = {})
post = {}
add_credentials(post, options)
add_authorization(post, authorization, money)
- post[:operation] = "Credit"
- commit("Netaxept/process.aspx", post)
+ post[:operation] = 'Credit'
+ commit('Netaxept/process.aspx', post)
end
def void(authorization, options = {})
post = {}
add_credentials(post, options)
add_authorization(post, authorization)
- post[:operation] = "Annul"
- commit("Netaxept/process.aspx", post)
+ post[:operation] = 'Annul'
+ commit('Netaxept/process.aspx', post)
end
private
@@ -76,7 +76,7 @@ def setup_transaction(money, options)
post = {}
add_credentials(post, options)
add_order(post, money, options)
- commit("Netaxept/Register.aspx", post)
+ commit('Netaxept/Register.aspx', post)
end
def add_and_auth_credit_card(authorization, creditcard, options)
@@ -84,14 +84,14 @@ def add_and_auth_credit_card(authorization, creditcard, options)
add_credentials(post, options, false)
add_authorization(post, authorization)
add_creditcard(post, creditcard)
- commit("terminal/default.aspx", post, false)
+ commit('terminal/default.aspx', post, false)
end
def query_transaction(authorization, options)
post = {}
add_credentials(post, options)
add_authorization(post, authorization)
- commit("Netaxept/query.aspx", post)
+ commit('Netaxept/query.aspx', post)
end
def add_credentials(post, options, secure=true)
@@ -109,7 +109,7 @@ def add_order(post, money, options)
post[:orderNumber] = options[:order_id]
post[:amount] = amount(money)
post[:currencyCode] = (options[:currency] || currency(money))
- post[:autoAuth] = "true"
+ post[:autoAuth] = 'true'
end
def add_creditcard(post, options)
@@ -122,13 +122,13 @@ def commit(path, parameters, xml=true)
raw = parse(ssl_get(build_url(path, parameters)), xml)
success = false
- authorization = (raw["TransactionId"] || parameters[:transactionId])
+ authorization = (raw['TransactionId'] || parameters[:transactionId])
if raw[:container] =~ /Exception|Error/
- message = (raw["Message"] || raw["Error"]["Message"])
- elsif raw["Error"] && !raw["Error"].empty?
- message = (raw["Error"]["ResponseText"] || raw["Error"]["ResponseCode"])
+ message = (raw['Message'] || raw['Error']['Message'])
+ elsif raw['Error'] && !raw['Error'].empty?
+ message = (raw['Error']['ResponseText'] || raw['Error']['ResponseCode'])
else
- message = (raw["ResponseText"] || raw["ResponseCode"] || "OK")
+ message = (raw['ResponseText'] || raw['ResponseCode'] || 'OK')
success = true
end
@@ -173,9 +173,8 @@ def build_url(base, parameters=nil)
end
def encode(hash)
- hash.collect{|(k,v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join('&')
+ hash.collect { |(k, v)| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/netbanx.rb b/lib/active_merchant/billing/gateways/netbanx.rb
new file mode 100644
index 00000000000..88e53fe1298
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/netbanx.rb
@@ -0,0 +1,294 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class NetbanxGateway < Gateway
+ # Netbanx is the new REST based API for Optimal Payments / Paysafe
+ self.test_url = 'https://api.test.netbanx.com/'
+ self.live_url = 'https://api.netbanx.com/'
+
+ self.supported_countries = %w(AF AX AL DZ AS AD AO AI AQ AG AR AM AW AU AT AZ BS BH BD BB BY BE BZ BJ BM BT BO BQ BA BW BV BR IO BN BG BF BI KH CM CA CV KY CF TD CL CN CX CC CO KM CG CD CK CR CI HR CU CW CY CZ DK DJ DM DO EC EG SV GQ ER EE ET FK FO FJ FI FR GF PF TF GA GM GE DE GH GI GR GL GD GP GU GT GG GN GW GY HT HM HN HK HU IS IN ID IR IQ IE IM IL IT JM JP JE JO KZ KE KI KP KR KW KG LA LV LB LS LR LY LI LT LU MO MK MG MW MY MV ML MT MH MQ MR MU YT MX FM MD MC MN ME MS MA MZ MM NA NR NP NC NZ NI NE NG NU NF MP NO OM PK PW PS PA PG PY PE PH PN PL PT PR QA RE RO RU RW BL SH KN LC MF VC WS SM ST SA SN RS SC SL SG SX SK SI SB SO ZA GS SS ES LK PM SD SR SJ SZ SE CH SY TW TJ TZ TH NL TL TG TK TO TT TN TR TM TC TV UG UA AE GB US UM UY UZ VU VA VE VN VG VI WF EH YE ZM ZW)
+ self.default_currency = 'CAD'
+ self.supported_cardtypes = [
+ :american_express,
+ :diners_club,
+ :discover,
+ :jcb,
+ :master,
+ :maestro,
+ :visa
+ ]
+
+ self.money_format = :cents
+
+ self.homepage_url = 'https://processing.paysafe.com/'
+ self.display_name = 'Netbanx by PaySafe'
+
+ def initialize(options={})
+ requires!(options, :account_number, :api_key)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_settle_with_auth(post)
+ add_payment(post, payment, options)
+
+ commit(:post, 'auths', post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment, options)
+
+ commit(:post, 'auths', post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ add_invoice(post, money, options)
+
+ commit(:post, "auths/#{authorization}/settlements", post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_invoice(post, money, options)
+
+ # Setting merchantRefNumber to a unique id for each refund
+ # This is to support multiple partial refunds for the same order
+ post[:merchantRefNum] = SecureRandom.uuid
+
+ commit(:post, "settlements/#{authorization}/refunds", post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_order_id(post, options)
+
+ commit(:post, "auths/#{authorization}/voidauths", post)
+ end
+
+ def verify(credit_card, options={})
+ post = {}
+ add_payment(post, credit_card)
+ add_order_id(post, options)
+
+ commit(:post, 'verifications', post)
+ end
+
+ # note: when passing options[:customer] we only attempt to add the
+ # card to the profile_id passed as the options[:customer]
+ def store(credit_card, options={})
+ # locale can only be one of en_US, fr_CA, en_GB
+ requires!(options, :locale)
+ post = {}
+ add_credit_card(post, credit_card, options)
+ add_customer_data(post, options)
+
+ commit(:post, 'customervault/v1/profiles', post)
+ end
+
+ def unstore(identification, options = {})
+ customer_id, card_id = identification.split('|')
+
+ if card_id.nil?
+ # deleting the profile
+ commit(:delete, "customervault/v1/profiles/#{CGI.escape(customer_id)}", nil)
+ else
+ # deleting the card from the profile
+ commit(:delete, "customervault/v1/profiles/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil)
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(("card\\?":{\\?"cardNum\\?":\\?")\d+), '\1[FILTERED]').
+ gsub(%r(("cvv\\?":\\?")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_settle_with_auth(post)
+ post[:settleWithAuth] = true
+ end
+
+ def add_customer_data(post, options)
+ post[:merchantCustomerId] = (options[:merchant_customer_id] || SecureRandom.uuid)
+ post[:locale] = options[:locale]
+ end
+
+ def add_credit_card(post, credit_card, options = {})
+ post[:card] ||= {}
+ post[:card][:cardNum] = credit_card.number
+ post[:card][:holderName] = credit_card.name
+ post[:card][:cvv] = credit_card.verification_value
+ post[:card][:cardExpiry] = expdate(credit_card)
+ if options[:billing_address]
+ post[:card][:billingAddress] = map_address(options[:billing_address])
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ add_order_id(post, options)
+ end
+
+ def add_payment(post, credit_card_or_reference, options = {})
+ post[:card] ||= {}
+ if credit_card_or_reference.is_a?(String)
+ post[:card][:paymentToken] = credit_card_or_reference
+ else
+ post[:card][:cardNum] = credit_card_or_reference.number
+ post[:card][:cvv] = credit_card_or_reference.verification_value
+ post[:card][:cardExpiry] = expdate(credit_card_or_reference)
+ end
+
+ post[:currencyCode] = options[:currency] if options[:currency]
+ post[:billingDetails] = map_address(options[:billing_address]) if options[:billing_address]
+ end
+
+ def expdate(credit_card)
+ year = format(credit_card.year, :four_digits)
+ month = format(credit_card.month, :two_digits)
+
+ # returns a hash (necessary in the card JSON object)
+ { :month => month, :year => year }
+ end
+
+ def add_order_id(post, options)
+ post[:merchantRefNum] = (options[:order_id] || SecureRandom.uuid)
+ end
+
+ def map_address(address)
+ return {} if address.nil?
+ country = Country.find(address[:country]) if address[:country]
+ mapped = {
+ :street => address[:address1],
+ :city => address[:city],
+ :zip => address[:zip],
+ :state => address[:state],
+ }
+ mapped[:country] = country.code(:alpha2).value unless country.blank?
+
+ mapped
+ end
+
+ def parse(body)
+ body.blank? ? {} : JSON.parse(body)
+ end
+
+ def commit(method, uri, parameters)
+ params = parameters.to_json unless parameters.nil?
+ response = begin
+ parse(ssl_request(method, get_url(uri), params, headers))
+ rescue ResponseError => e
+ return Response.new(false, 'Invalid Login') if(e.response.code == '401')
+ parse(e.response.body)
+ end
+
+ success = success_from(response)
+ Response.new(
+ success,
+ message_from(success, response),
+ response,
+ :test => test?,
+ :error_code => error_code_from(response),
+ :authorization => authorization_from(success, get_url(uri), method, response)
+ )
+ end
+
+ def get_url(uri)
+ url = (test? ? test_url : live_url)
+ if uri =~ /^customervault/
+ "#{url}#{uri}"
+ else
+ "#{url}cardpayments/v1/accounts/#{@options[:account_number]}/#{uri}"
+ end
+ end
+
+ def success_from(response)
+ response.blank? || !response.key?('error')
+ end
+
+ def message_from(success, response)
+ success ? 'OK' : (response['error']['message'] || 'Unknown error - please contact Netbanx-Paysafe')
+ end
+
+ def authorization_from(success, url, method, response)
+ if success && response.present? && url.match(/cardpayments\/v1\/accounts\/.*\//)
+ response['id']
+ elsif method == :post && url.match(/customervault\/.*\//)
+ # auth for tokenised customer vault is returned as
+ # customer_profile_id|card_id|payment_method_token
+ #
+ # customer_profile_id is the uuid that identifies the customer
+ # card_id is the uuid that identifies the card
+ # payment_method_token is the token that needs to be used when
+ # calling purchase with a token
+ #
+ # both id's are used to unstore, the payment token is only used for
+ # purchase transactions
+ [response['id'], response['cards'].first['id'], response['cards'].first['paymentToken']].join('|')
+ end
+ end
+
+ # Builds the auth and U-A headers for the request
+ def headers
+ {
+ 'Accept' => 'application/json',
+ 'Content-type' => 'application/json',
+ 'Authorization' => "Basic #{Base64.strict_encode64(@options[:api_key].to_s)}",
+ 'User-Agent' => "Netbanx-Paysafe v1.0/ActiveMerchant #{ActiveMerchant::VERSION}"
+ }
+ end
+
+ def error_code_from(response)
+ unless success_from(response)
+ case response['errorCode']
+ when '3002' then STANDARD_ERROR_CODE[:invalid_number] # You submitted an invalid card number or brand or combination of card number and brand with your request.
+ when '3004' then STANDARD_ERROR_CODE[:incorrect_zip] # The zip/postal code must be provided for an AVS check request.
+ when '3005' then STANDARD_ERROR_CODE[:incorrect_cvc] # You submitted an incorrect CVC value with your request.
+ when '3006' then STANDARD_ERROR_CODE[:expired_card] # You submitted an expired credit card number with your request.
+ when '3009' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank.
+ when '3011' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank because the card used is a restricted card. Contact the cardholder's credit card company for further investigation.
+ when '3012' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank because the credit card expiry date submitted is invalid.
+ when '3013' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank due to problems with the credit card account.
+ when '3014' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined - the issuing bank has returned an unknown response. Contact the card holder's credit card company for further investigation.
+ when '3015' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you process the transaction manually by calling the cardholder's credit card company.
+ when '3016' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – it may be a lost or stolen card.
+ when '3017' then STANDARD_ERROR_CODE[:invalid_number] # You submitted an invalid credit card number with your request.
+ when '3022' then STANDARD_ERROR_CODE[:card_declined] # The card has been declined due to insufficient funds.
+ when '3023' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank due to its proprietary card activity regulations.
+ when '3024' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined because the issuing bank does not permit the transaction for this card.
+ when '3032' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined by the issuing bank or external gateway because the card is probably in one of their negative databases.
+ when '3035' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to exceeded PIN attempts.
+ when '3036' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to an invalid issuer.
+ when '3037' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined because it is invalid.
+ when '3038' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to customer cancellation.
+ when '3039' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to an invalid authentication value.
+ when '3040' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined because the request type is not permitted on the card.
+ when '3041' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to a timeout.
+ when '3042' then STANDARD_ERROR_CODE[:card_declined] # Your request has been declined due to a cryptographic error.
+ when '3045' then STANDARD_ERROR_CODE[:invalid_expiry_date] # You submitted an invalid date format for this request.
+ when '3046' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined because the amount was set to zero.
+ when '3047' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined because the amount exceeds the floor limit.
+ when '3048' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined because the amount is less than the floor limit.
+ when '3049' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – the credit card has expired.
+ when '3050' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – fraudulent activity is suspected.
+ when '3051' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – contact the acquirer for more information.
+ when '3052' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – the credit card is restricted.
+ when '3053' then STANDARD_ERROR_CODE[:card_declined] # The bank has requested that you retrieve the card from the cardholder – please call the acquirer.
+ when '3054' then STANDARD_ERROR_CODE[:card_declined] # The transaction was declined due to suspected fraud.
+ else STANDARD_ERROR_CODE[:processing_error]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/netbilling.rb b/lib/active_merchant/billing/gateways/netbilling.rb
index 6488d54d14f..296d9e198ad 100644
--- a/lib/active_merchant/billing/gateways/netbilling.rb
+++ b/lib/active_merchant/billing/gateways/netbilling.rb
@@ -1,5 +1,15 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
+ # To perform PCI Compliant Repeat Billing
+ #
+ # Ensure that PCI Compliant Repeat Billing is enabled on your merchant account:
+ # "Enable PCI Compliant Repeat Billing, Up-selling and Cross-selling" in Step 6 of the Credit Cards setup page
+ #
+ # Instead of passing a credit_card to authorize or purchase, pass the transaction id (res.authorization)
+ # of a past Netbilling transaction
+ #
+ # To store billing information without performing an operation, use the 'store' method
+ # which invokes the tran_type 'Q' (Quasi) operation and returns a transaction id to use in future Repeat Billing operations
class NetbillingGateway < Gateway
self.live_url = self.test_url = 'https://secure.netbilling.com:1402/gw/sas/direct3.1'
@@ -9,7 +19,8 @@ class NetbillingGateway < Gateway
:refund => 'R',
:credit => 'C',
:capture => 'D',
- :void => 'U'
+ :void => 'U',
+ :quasi => 'Q'
}
SUCCESS_CODES = [ '1', 'T' ]
@@ -27,24 +38,26 @@ def initialize(options = {})
super
end
- def authorize(money, credit_card, options = {})
+ def authorize(money, payment_source, options = {})
post = {}
add_amount(post, money)
add_invoice(post, options)
- add_credit_card(post, credit_card)
- add_address(post, credit_card, options)
+ add_payment_source(post, payment_source)
+ add_address(post, payment_source, options)
add_customer_data(post, options)
+ add_user_data(post, options)
commit(:authorization, post)
end
- def purchase(money, credit_card, options = {})
+ def purchase(money, payment_source, options = {})
post = {}
add_amount(post, money)
add_invoice(post, options)
- add_credit_card(post, credit_card)
- add_address(post, credit_card, options)
+ add_payment_source(post, payment_source)
+ add_address(post, payment_source, options)
add_customer_data(post, options)
+ add_user_data(post, options)
commit(:purchase, post)
end
@@ -69,6 +82,7 @@ def credit(money, credit_card, options = {})
add_credit_card(post, credit_card)
add_address(post, credit_card, options)
add_customer_data(post, options)
+ add_user_data(post, options)
commit(:credit, post)
end
@@ -79,10 +93,31 @@ def void(source, options = {})
commit(:void, post)
end
+ def store(credit_card, options = {})
+ post = {}
+ add_amount(post, 0)
+ add_payment_source(post, credit_card)
+ add_address(post, credit_card, options)
+ add_customer_data(post, options)
+
+ commit(:quasi, post)
+ end
+
def test?
(@options[:login] == TEST_LOGIN || super)
end
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?card_number=)[^&]*), '\1[FILTERED]').
+ gsub(%r((&?card_cvv2=)[^&]*), '\1[FILTERED]')
+ end
+
private
def add_amount(post, money)
@@ -108,23 +143,38 @@ def add_address(post, credit_card, options)
post[:bill_state] = billing_address[:state]
end
- if shipping_address = options[:shipping_address]
- first_name, last_name = parse_first_and_last_name(shipping_address[:name])
-
- post[:ship_name1] = first_name
- post[:ship_name2] = last_name
- post[:ship_street] = shipping_address[:address1]
- post[:ship_zip] = shipping_address[:zip]
- post[:ship_city] = shipping_address[:city]
- post[:ship_country] = shipping_address[:country]
- post[:ship_state] = shipping_address[:state]
- end
+ if shipping_address = options[:shipping_address]
+ post[:ship_name1], post[:ship_name2] = split_names(shipping_address[:name])
+ post[:ship_street] = shipping_address[:address1]
+ post[:ship_zip] = shipping_address[:zip]
+ post[:ship_city] = shipping_address[:city]
+ post[:ship_country] = shipping_address[:country]
+ post[:ship_state] = shipping_address[:state]
+ end
end
def add_invoice(post, options)
post[:description] = options[:description]
end
+ def add_payment_source(params, source)
+ if source.is_a?(String)
+ add_transaction_id(params, source)
+ else
+ add_credit_card(params, source)
+ end
+ end
+
+ def add_user_data(post, options)
+ if options[:order_id]
+ post[:user_data] = "order_id:#{options[:order_id]}"
+ end
+ end
+
+ def add_transaction_id(post, transaction_id)
+ post[:card_number] = 'CS:' + transaction_id
+ end
+
def add_credit_card(post, credit_card)
post[:bill_name1] = credit_card.first_name
post[:bill_name2] = credit_card.last_name
@@ -136,7 +186,7 @@ def add_credit_card(post, credit_card)
def parse(body)
results = {}
body.split(/&/).each do |pair|
- key,val = pair.split(/\=/)
+ key, val = pair.split(/\=/)
results[key.to_sym] = CGI.unescape(val)
end
results
@@ -168,30 +218,15 @@ def message_from(response)
success?(response) ? SUCCESS_MESSAGE : (response[:auth_msg] || FAILURE_MESSAGE)
end
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
def post_data(action, parameters = {})
parameters[:account_id] = @options[:login]
parameters[:site_tag] = @options[:site_tag] if @options[:site_tag].present?
parameters[:pay_type] = 'C'
parameters[:tran_type] = TRANSACTIONS[action]
- parameters.reject{|k,v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ parameters.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
- def parse_first_and_last_name(value)
- name = value.to_s.split(' ')
-
- last_name = name.pop || ''
- first_name = name.join(' ')
- [ first_name, last_name ]
- end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/netpay.rb b/lib/active_merchant/billing/gateways/netpay.rb
index babf9542502..48e2a0a14d5 100644
--- a/lib/active_merchant/billing/gateways/netpay.rb
+++ b/lib/active_merchant/billing/gateways/netpay.rb
@@ -18,7 +18,7 @@ module Billing #:nodoc:
# transaction. After this, a refund should be performed instead.
#
# In addition to the regular ActiveMerchant transaction options, NETPAY
- # also supports a `:mode` parameter. This allows testing to be peformed
+ # also supports a `:mode` parameter. This allows testing to be performed
# in production and force specific results.
#
# * 'P' - Production
@@ -51,7 +51,7 @@ class NetpayGateway < Gateway
self.display_name = 'NETPAY Gateway'
CURRENCY_CODES = {
- "MXN" => '484'
+ 'MXN' => '484'
}
# The header keys that we will provide in the response params hash
@@ -110,7 +110,6 @@ def refund(money, authorization, options = {})
add_order_id(post, order_id_from(authorization))
add_amount(post, money, options)
- #commit('Refund', post, options)
commit('Credit', post, options)
end
@@ -157,7 +156,7 @@ def build_authorization(request_params, response_params)
end
def split_authorization(authorization)
- order_id, amount, currency = authorization.split("|")
+ order_id, amount, currency = authorization.split('|')
[order_id, amount, currency]
end
@@ -166,8 +165,8 @@ def order_id_from(authorization)
end
def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
+ year = sprintf('%.4i', credit_card.year)
+ month = sprintf('%.2i', credit_card.month)
"#{month}/#{year[-2..-1]}"
end
@@ -191,7 +190,7 @@ def commit(action, parameters, options)
add_login_data(parameters)
add_action(parameters, action, options)
- post = parameters.collect{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ post = parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
parse(ssl_post(url, post), parameters)
end
diff --git a/lib/active_merchant/billing/gateways/network_merchants.rb b/lib/active_merchant/billing/gateways/network_merchants.rb
new file mode 100644
index 00000000000..aadc09d425f
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/network_merchants.rb
@@ -0,0 +1,241 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class NetworkMerchantsGateway < Gateway
+ self.live_url = self.test_url = 'https://secure.networkmerchants.com/api/transact.php'
+
+ self.supported_countries = ['US']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'http://www.nmi.com/'
+ self.display_name = 'Network Merchants (NMI)'
+
+ self.money_format = :dollars
+ self.default_currency = 'USD'
+
+ def initialize(options = {})
+ requires!(options, :login, :password)
+ super
+ end
+
+ def authorize(money, creditcard_or_vault_id, options = {})
+ post = build_auth_post(money, creditcard_or_vault_id, options)
+ commit('auth', post)
+ end
+
+ def purchase(money, creditcard_or_vault_id, options = {})
+ post = build_purchase_post(money, creditcard_or_vault_id, options)
+ commit('sale', post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = build_capture_post(money, authorization, options)
+ commit('capture', post)
+ end
+
+ def void(authorization, options = {})
+ post = build_void_post(authorization, options)
+ commit('void', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = build_refund_post(money, authorization, options)
+ commit('refund', post)
+ end
+
+ def store(creditcard, options = {})
+ post = build_store_post(creditcard, options)
+ commit_vault('add_customer', post)
+ end
+
+ def unstore(customer_vault_id, options = {})
+ post = build_unstore_post(customer_vault_id, options)
+ commit_vault('delete_customer', post)
+ end
+
+ private
+
+ def build_auth_post(money, creditcard_or_vault_id, options)
+ post = {}
+ add_order(post, options)
+ add_address(post, options)
+ add_shipping_address(post, options)
+ add_payment_method(post, creditcard_or_vault_id, options)
+ add_amount(post, money, options)
+ post
+ end
+
+ def build_purchase_post(money, creditcard, options)
+ build_auth_post(money, creditcard, options)
+ end
+
+ def build_capture_post(money, authorization, options)
+ post = {}
+ post[:transactionid] = authorization
+ add_amount(post, money, options)
+ post
+ end
+
+ def build_void_post(authorization, options)
+ post = {}
+ post[:transactionid] = authorization
+ post
+ end
+
+ def build_refund_post(money, authorization, options)
+ post = {}
+ post[:transactionid] = authorization
+ add_amount(post, money, options)
+ post
+ end
+
+ def build_store_post(creditcard_or_check, options)
+ post = {}
+ add_address(post, options)
+ add_shipping_address(post, options)
+ add_payment_method(post, creditcard_or_check, options)
+ post
+ end
+
+ def build_unstore_post(customer_vault_id, options)
+ post = {}
+ post['customer_vault_id'] = customer_vault_id
+ post
+ end
+
+ def add_order(post, options)
+ post[:orderid] = options[:order_id]
+ post[:orderdescription] = options[:description]
+ end
+
+ def add_address(post, options)
+ post[:email] = options[:email]
+ post[:ipaddress] = options[:ip]
+
+ address = options[:billing_address] || options[:address] || {}
+ post[:address1] = address[:address1]
+ post[:address2] = address[:address2]
+ post[:city] = address[:city]
+ post[:state] = address[:state].blank? ? 'n/a' : address[:state]
+ post[:zip] = address[:zip]
+ post[:country] = address[:country]
+ post[:phone] = address[:phone]
+ end
+
+ def add_shipping_address(post, options)
+ shipping_address = options[:shipping_address] || {}
+ post[:shipping_address1] = shipping_address[:address1]
+ post[:shipping_address2] = shipping_address[:address2]
+ post[:shipping_city] = shipping_address[:city]
+ post[:shipping_state] = shipping_address[:state]
+ post[:shipping_zip] = shipping_address[:zip]
+ post[:shipping_country] = shipping_address[:country]
+ end
+
+ def add_swipe_data(post, creditcard, options)
+ # unencrypted tracks
+ if creditcard.respond_to?(:track_data) && creditcard.track_data.present?
+ post[:track_1] = creditcard.track_data
+ else
+ post[:track_1] = options[:track_1]
+ post[:track_2] = options[:track_2]
+ post[:track_3] = options[:track_3]
+ end
+
+ # encrypted tracks
+ post[:magnesafe_track_1] = options[:magnesafe_track_1]
+ post[:magnesafe_track_2] = options[:magnesafe_track_2]
+ post[:magnesafe_track_3] = options[:magnesafe_track_3]
+ post[:magnesafe_magneprint] = options[:magnesafe_magneprint]
+ post[:magnesafe_ksn] = options[:magnesafe_ksn]
+ post[:magnesafe_magneprint_status] = options[:magnesafe_magneprint_status]
+ end
+
+ def add_payment_method(post, payment_source, options)
+ post[:processor_id] = options[:processor_id]
+ post[:customer_vault] = 'add_customer' if options[:store]
+
+ add_swipe_data(post, payment_source, options)
+
+ if payment_source.is_a?(Check)
+ check = payment_source
+ post[:firstname] = check.first_name
+ post[:lastname] = check.last_name
+ post[:checkname] = check.name
+ post[:checkaba] = check.routing_number
+ post[:checkaccount] = check.account_number
+ post[:account_type] = check.account_type
+ post[:account_holder_type] = check.account_holder_type
+ post[:payment] = 'check'
+ elsif payment_source.respond_to?(:number)
+ creditcard = payment_source
+ post[:firstname] = creditcard.first_name
+ post[:lastname] = creditcard.last_name
+ post[:ccnumber] = creditcard.number
+ post[:ccexp] = format(creditcard.month, :two_digits) + format(creditcard.year, :two_digits)
+ post[:cvv] = creditcard.verification_value
+ post[:payment] = 'creditcard'
+ else
+ post[:customer_vault_id] = payment_source
+ end
+ end
+
+ def add_login(post)
+ post[:username] = @options[:login]
+ post[:password] = @options[:password]
+ end
+
+ def add_amount(post, money, options)
+ post[:currency] = options[:currency] || currency(money)
+ post[:amount] = amount(money)
+ end
+
+ def commit_vault(action, parameters)
+ commit(nil, parameters.merge(:customer_vault => action))
+ end
+
+ def commit(action, parameters)
+ raw = parse(ssl_post(self.live_url, build_request(action, parameters)))
+
+ success = (raw['response'] == ResponseCodes::APPROVED)
+
+ authorization = authorization_from(success, parameters, raw)
+
+ Response.new(success, raw['responsetext'], raw,
+ :test => test?,
+ :authorization => authorization,
+ :avs_result => { :code => raw['avsresponse']},
+ :cvv_result => raw['cvvresponse']
+ )
+ end
+
+ def build_request(action, parameters)
+ parameters[:type] = action if action
+ add_login(parameters)
+ parameters.to_query
+ end
+
+ def authorization_from(success, parameters, response)
+ return nil unless success
+
+ authorization = response['transactionid']
+ if(parameters[:customer_vault] && (authorization.nil? || authorization.empty?))
+ authorization = response['customer_vault_id']
+ end
+
+ authorization
+ end
+
+ class ResponseCodes
+ APPROVED = '1'
+ DENIED = '2'
+ ERROR = '3'
+ end
+
+ def parse(raw_response)
+ rsp = CGI.parse(raw_response)
+ rsp.keys.each { |k| rsp[k] = rsp[k].first } # flatten out the values
+ rsp
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/nmi.rb b/lib/active_merchant/billing/gateways/nmi.rb
index 39fc1e8af34..7d5339d6d70 100644
--- a/lib/active_merchant/billing/gateways/nmi.rb
+++ b/lib/active_merchant/billing/gateways/nmi.rb
@@ -1,20 +1,324 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- class NmiGateway < AuthorizeNetGateway
- self.test_url = 'https://secure.networkmerchants.com/gateway/transact.dll'
- self.live_url = 'https://secure.networkmerchants.com/gateway/transact.dll'
- self.homepage_url = 'http://nmi.com/'
- self.display_name = 'NMI'
+ class NmiGateway < Gateway
+ include Empty
+
+ DUP_WINDOW_DEPRECATION_MESSAGE = 'The class-level duplicate_window variable is deprecated. Please use the :dup_seconds transaction option instead.'
+
+ self.test_url = self.live_url = 'https://secure.nmi.com/api/transact.php'
+ self.default_currency = 'USD'
+ self.money_format = :dollars
self.supported_countries = ['US']
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+ self.homepage_url = 'http://nmi.com/'
+ self.display_name = 'NMI'
- private
- def add_creditcard(post, creditcard, options={})
+ def self.duplicate_window=(seconds)
+ ActiveMerchant.deprecated(DUP_WINDOW_DEPRECATION_MESSAGE)
+ @dup_seconds = seconds
+ end
+
+ def self.duplicate_window
+ instance_variable_defined?(:@dup_seconds) ? @dup_seconds : nil
+ end
+
+ def initialize(options = {})
+ requires!(options, :login, :password)
super
- post[:recurring_billing] = "TRUE" if options[:recurring]
end
- end
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method, options)
+ add_stored_credential(post, options)
+ add_customer_data(post, options)
+ add_vendor_data(post, options)
+ add_merchant_defined_fields(post, options)
+ add_level3_fields(post, options)
+
+ commit('sale', post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method, options)
+ add_stored_credential(post, options)
+ add_customer_data(post, options)
+ add_vendor_data(post, options)
+ add_merchant_defined_fields(post, options)
+ add_level3_fields(post, options)
+
+ commit('auth', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_merchant_defined_fields(post, options)
+
+ commit('capture', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+ add_payment_type(post, authorization)
+
+ commit('void', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_payment_type(post, authorization)
+
+ commit('refund', post)
+ end
+
+ def credit(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ add_vendor_data(post, options)
+ add_level3_fields(post, options)
+
+ commit('credit', post)
+ end
+
+ def verify(payment_method, options={})
+ post = {}
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ add_vendor_data(post, options)
+ add_merchant_defined_fields(post, options)
+ add_level3_fields(post, options)
+
+ commit('validate', post)
+ end
+
+ def store(payment_method, options = {})
+ post = {}
+ add_invoice(post, nil, options)
+ add_payment_method(post, payment_method, options)
+ add_customer_data(post, options)
+ add_vendor_data(post, options)
+ add_merchant_defined_fields(post, options)
+
+ commit('add_customer', post)
+ end
+
+ def verify_credentials
+ response = void('0')
+ response.message != 'Authentication Failed'
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((password=)[^&\n]*), '\1[FILTERED]').
+ gsub(%r((ccnumber=)\d+), '\1[FILTERED]').
+ gsub(%r((cvv=)\d+), '\1[FILTERED]').
+ gsub(%r((checkaba=)\d+), '\1[FILTERED]').
+ gsub(%r((checkaccount=)\d+), '\1[FILTERED]').
+ gsub(%r((cryptogram=)[^&]+(&?)), '\1[FILTERED]\2')
+ end
+
+ def supports_network_tokenization?
+ true
+ end
+
+ private
+
+ def add_level3_fields(post, options)
+ add_fields_to_post_if_present(post, options, [:tax, :shipping, :ponumber])
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:orderid] = options[:order_id]
+ post[:orderdescription] = options[:description]
+ post[:currency] = options[:currency] || currency(money)
+ post[:billing_method] = 'recurring' if options[:recurring]
+ if (dup_seconds = (options[:dup_seconds] || self.class.duplicate_window))
+ post[:dup_seconds] = dup_seconds
+ end
+ end
+
+ def add_payment_method(post, payment_method, options)
+ if(payment_method.is_a?(String))
+ customer_vault_id, _ = split_authorization(payment_method)
+ post[:customer_vault_id] = customer_vault_id
+ elsif payment_method.is_a?(NetworkTokenizationCreditCard)
+ post[:ccnumber] = payment_method.number
+ post[:ccexp] = exp_date(payment_method)
+ post[:token_cryptogram] = payment_method.payment_cryptogram
+ elsif(card_brand(payment_method) == 'check')
+ post[:payment] = 'check'
+ post[:firstname] = payment_method.first_name
+ post[:lastname] = payment_method.last_name
+ post[:checkname] = payment_method.name
+ post[:checkaba] = payment_method.routing_number
+ post[:checkaccount] = payment_method.account_number
+ post[:account_holder_type] = payment_method.account_holder_type
+ post[:account_type] = payment_method.account_type
+ post[:sec_code] = options[:sec_code] || 'WEB'
+ else
+ post[:payment] = 'creditcard'
+ post[:firstname] = payment_method.first_name
+ post[:lastname] = payment_method.last_name
+ post[:ccnumber] = payment_method.number
+ post[:cvv] = payment_method.verification_value unless empty?(payment_method.verification_value)
+ post[:ccexp] = exp_date(payment_method)
+ end
+ end
+
+ def add_stored_credential(post, options)
+ return unless (stored_credential = options[:stored_credential])
+
+ if stored_credential[:initiator] == 'cardholder'
+ post[:initiated_by] = 'customer'
+ else
+ post[:initiated_by] = 'merchant'
+ end
+
+ # :reason_type, when provided, overrides anything previously set in
+ # post[:billing_method] (see `add_invoice` and the :recurring) option
+ case stored_credential[:reason_type]
+ when 'recurring'
+ post[:billing_method] = 'recurring'
+ when 'installment'
+ post[:billing_method] = 'installment'
+ when 'unscheduled'
+ post.delete(:billing_method)
+ end
+
+ if stored_credential[:initial_transaction]
+ post[:stored_credential_indicator] = 'stored'
+ else
+ post[:stored_credential_indicator] = 'used'
+ post[:initial_transaction_id] = stored_credential[:network_transaction_id]
+ end
+ end
+
+ def add_customer_data(post, options)
+ post[:email] = options[:email]
+ post[:ipaddress] = options[:ip]
+ post[:customer_id] = options[:customer_id] || options[:customer]
+
+ if(billing_address = options[:billing_address] || options[:address])
+ post[:company] = billing_address[:company]
+ post[:address1] = billing_address[:address1]
+ post[:address2] = billing_address[:address2]
+ post[:city] = billing_address[:city]
+ post[:state] = billing_address[:state]
+ post[:country] = billing_address[:country]
+ post[:zip] = billing_address[:zip]
+ post[:phone] = billing_address[:phone]
+ end
+
+ if(shipping_address = options[:shipping_address])
+ post[:shipping_company] = shipping_address[:company]
+ post[:shipping_address1] = shipping_address[:address1]
+ post[:shipping_address2] = shipping_address[:address2]
+ post[:shipping_city] = shipping_address[:city]
+ post[:shipping_state] = shipping_address[:state]
+ post[:shipping_country] = shipping_address[:country]
+ post[:shipping_zip] = shipping_address[:zip]
+ post[:shipping_phone] = shipping_address[:phone]
+ end
+ end
+
+ def add_vendor_data(post, options)
+ post[:vendor_id] = options[:vendor_id] if options[:vendor_id]
+ post[:processor_id] = options[:processor_id] if options[:processor_id]
+ end
+
+ def add_merchant_defined_fields(post, options)
+ (1..20).each do |each|
+ key = "merchant_defined_field_#{each}".to_sym
+ post[key] = options[key] if options[key]
+ end
+ end
+
+ def add_reference(post, authorization)
+ transaction_id, _ = split_authorization(authorization)
+ post[:transactionid] = transaction_id
+ end
+
+ def add_payment_type(post, authorization)
+ _, payment_type = split_authorization(authorization)
+ post[:payment] = payment_type if payment_type
+ end
+
+ def exp_date(payment_method)
+ "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}"
+ end
+
+ def commit(action, params)
+ params[action == 'add_customer' ? :customer_vault : :type] = action
+ params[:username] = @options[:login]
+ params[:password] = @options[:password]
+
+ raw_response = ssl_post(url, post_data(action, params), headers)
+ response = parse(raw_response)
+ succeeded = success_from(response)
+
+ Response.new(
+ succeeded,
+ message_from(succeeded, response),
+ response,
+ authorization: authorization_from(response, params[:payment], action),
+ avs_result: AVSResult.new(code: response[:avsresponse]),
+ cvv_result: CVVResult.new(response[:cvvresponse]),
+ test: test?
+ )
+ end
+
+ def authorization_from(response, payment_type, action)
+ authorization = (action == 'add_customer' ? response[:customer_vault_id] : response[:transactionid])
+ [ authorization, payment_type ].join('#')
+ end
+
+ def split_authorization(authorization)
+ authorization.split('#')
+ end
+
+ def headers
+ { 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8' }
+ end
+
+ def post_data(action, params)
+ params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def parse(body)
+ Hash[CGI::parse(body).map { |k, v| [k.intern, v.first] }]
+ end
+
+ def success_from(response)
+ response[:response] == '1'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response[:responsetext]
+ end
+ end
+
+ end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/ogone.rb b/lib/active_merchant/billing/gateways/ogone.rb
index 973faa5c4ec..883ac94a05a 100644
--- a/lib/active_merchant/billing/gateways/ogone.rb
+++ b/lib/active_merchant/billing/gateways/ogone.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+
require 'rexml/document'
module ActiveMerchant #:nodoc:
@@ -32,13 +33,13 @@ module Billing #:nodoc:
# == Usage
#
# gateway = ActiveMerchant::Billing::OgoneGateway.new(
- # :login => "my_ogone_psp_id",
- # :user => "my_ogone_user_id",
- # :password => "my_ogone_pswd",
- # :signature => "my_ogone_sha_signature", # Only if you configured your Ogone environment so.
- # :signature_encryptor => "sha512", # Can be "none" (default), "sha1", "sha256" or "sha512".
+ # :login => "my_ogone_psp_id",
+ # :user => "my_ogone_user_id",
+ # :password => "my_ogone_pswd",
+ # :signature => "my_ogone_sha_signature", # Only if you configured your Ogone environment so.
+ # :signature_encryptor => "sha512" # Can be "none" (default), "sha1", "sha256" or "sha512".
# # Must be the same as the one configured in your Ogone account.
- # )
+ # )
#
# # set up credit card object as in main ActiveMerchant example
# creditcard = ActiveMerchant::Billing::CreditCard.new(
@@ -75,7 +76,19 @@ module Billing #:nodoc:
#
# # When using store, you can also let Ogone generate the alias for you
# response = gateway.store(creditcard)
- # puts response.billing_id # Retrieve the generated alias
+ # puts response.billing_id # Retrieve the generated alias
+ #
+ # # By default, Ogone tries to authorize 0.01 EUR but you can change this
+ # # amount using the :store_amount option when creating the gateway object:
+ # gateway = ActiveMerchant::Billing::OgoneGateway.new(
+ # :login => "my_ogone_psp_id",
+ # :user => "my_ogone_user_id",
+ # :password => "my_ogone_pswd",
+ # :signature => "my_ogone_sha_signature",
+ # :signature_encryptor => "sha512",
+ # :store_amount => 100 # The store method will try to authorize 1 EUR instead of 0.01 EUR
+ # )
+ # response = gateway.store(creditcard) # authorize 1 EUR and void the authorization right away
#
# == 3-D Secure feature
#
@@ -107,20 +120,18 @@ class OgoneGateway < Gateway
'KO' => 'N',
'NO' => 'R' }
- SUCCESS_MESSAGE = "The transaction was successful"
+ SUCCESS_MESSAGE = 'The transaction was successful'
- THREE_D_SECURE_DISPLAY_WAYS = { :main_window => 'MAINW', # display the identification page in the main window
- # (default value).
- :pop_up => 'POPUP', # display the identification page in a pop-up window
- # and return to the main window at the end.
- :pop_ix => 'POPIX' } # display the identification page in a pop-up window
- # and remain in the pop-up window.
+ THREE_D_SECURE_DISPLAY_WAYS = { :main_window => 'MAINW', # display the identification page in the main window (default value).
- OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE = "Signature usage will be the default for a future release of ActiveMerchant. You should either begin using it, or update your configuration to explicitly disable it (signature_encryptor: none)"
+ :pop_up => 'POPUP', # display the identification page in a pop-up window and return to the main window at the end.
+ :pop_ix => 'POPIX' } # display the identification page in a pop-up window and remain in the pop-up window.
+
+ OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE = 'Signature usage will be the default for a future release of ActiveMerchant. You should either begin using it, or update your configuration to explicitly disable it (signature_encryptor: none)'
OGONE_STORE_OPTION_DEPRECATION_MESSAGE = "The 'store' option has been renamed to 'billing_id', and its usage is deprecated."
- self.test_url = "https://secure.ogone.com/ncol/test/"
- self.live_url = "https://secure.ogone.com/ncol/prod/"
+ self.test_url = 'https://secure.ogone.com/ncol/test/'
+ self.live_url = 'https://secure.ogone.com/ncol/prod/'
self.supported_countries = ['BE', 'DE', 'FR', 'NL', 'AT', 'CH']
# also supports Airplus and UATP
@@ -138,12 +149,13 @@ def initialize(options = {})
# Verify and reserve the specified amount on the account, without actually doing the transaction.
def authorize(money, payment_source, options = {})
post = {}
+ action = payment_source.brand == 'mastercard' ? 'PAU' : 'RES'
add_invoice(post, options)
add_payment_source(post, payment_source, options)
add_address(post, payment_source, options)
add_customer_data(post, options)
add_money(post, money, options)
- commit('RES', post)
+ commit(action, post)
end
# Verify and transfer the specified amount.
@@ -179,7 +191,7 @@ def void(identification, options = {})
# Credit the specified account by a specific amount.
def credit(money, identification_or_credit_card, options = {})
if reference_transaction?(identification_or_credit_card)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
# Referenced credit: refund of a settled transaction
refund(money, identification_or_credit_card, options)
else # must be a credit card or card reference
@@ -192,23 +204,42 @@ def refund(money, reference, options = {})
perform_reference_credit(money, reference, options)
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
+
# Store a credit card by creating an Ogone Alias
def store(payment_source, options = {})
- options.merge!(:alias_operation => 'BYPSP') unless(options.has_key?(:billing_id) || options.has_key?(:store))
- response = authorize(1, payment_source, options)
+ options[:alias_operation] = 'BYPSP' unless(options.has_key?(:billing_id) || options.has_key?(:store))
+ response = authorize(@options[:store_amount] || 1, payment_source, options)
void(response.authorization) if response.success?
response
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?cardno=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?cvc=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?pswd=)[^&]*)i, '\1[FILTERED]')
+ end
+
private
def reference_from(authorization)
- authorization.split(";").first
+ authorization.split(';').first
end
def reference_transaction?(identifier)
return false unless identifier.is_a?(String)
- _, action = identifier.split(";")
+ _, action = identifier.split(';')
!action.nil?
end
@@ -231,17 +262,18 @@ def perform_non_referenced_credit(money, payment_target, options = {})
end
def add_payment_source(post, payment_source, options)
+ add_d3d(post, options) if options[:d3d]
+
if payment_source.is_a?(String)
add_alias(post, payment_source, options[:alias_operation])
add_eci(post, options[:eci] || '9')
else
if options.has_key?(:store)
- deprecated OGONE_STORE_OPTION_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated OGONE_STORE_OPTION_DEPRECATION_MESSAGE
options[:billing_id] ||= options[:store]
end
add_alias(post, options[:billing_id], options[:alias_operation])
add_eci(post, options[:eci] || '7')
- add_d3d(post, options) if options[:d3d]
add_creditcard(post, payment_source)
end
end
@@ -253,11 +285,13 @@ def add_d3d(post, options)
THREE_D_SECURE_DISPLAY_WAYS[:main_window]
add_pair post, 'WIN3DS', win_3ds
- add_pair post, 'HTTP_ACCEPT', options[:http_accept] || "*/*"
+ add_pair post, 'HTTP_ACCEPT', options[:http_accept] || '*/*'
add_pair post, 'HTTP_USER_AGENT', options[:http_user_agent] if options[:http_user_agent]
add_pair post, 'ACCEPTURL', options[:accept_url] if options[:accept_url]
add_pair post, 'DECLINEURL', options[:decline_url] if options[:decline_url]
add_pair post, 'EXCEPTIONURL', options[:exception_url] if options[:exception_url]
+ add_pair post, 'CANCELURL', options[:cancel_url] if options[:cancel_url]
+ add_pair post, 'PARAMVAR', options[:paramvar] if options[:paramvar]
add_pair post, 'PARAMPLUS', options[:paramplus] if options[:paramplus]
add_pair post, 'COMPLUS', options[:complus] if options[:complus]
add_pair post, 'LANGUAGE', options[:language] if options[:language]
@@ -267,8 +301,8 @@ def add_eci(post, eci)
add_pair post, 'ECI', eci.to_s
end
- def add_alias(post, _alias, alias_operation = nil)
- add_pair post, 'ALIAS', _alias
+ def add_alias(post, alias_name, alias_operation = nil)
+ add_pair post, 'ALIAS', alias_name
add_pair post, 'ALIASOPERATION', alias_operation unless alias_operation.nil?
end
@@ -298,12 +332,13 @@ def add_address(post, creditcard, options)
def add_invoice(post, options)
add_pair post, 'orderID', options[:order_id] || generate_unique_id[0...30]
add_pair post, 'COM', options[:description]
+ add_pair post, 'ORIG', options[:origin] if options[:origin]
end
def add_creditcard(post, creditcard)
add_pair post, 'CN', creditcard.name
add_pair post, 'CARDNO', creditcard.number
- add_pair post, 'ED', "%02d%02s" % [creditcard.month, creditcard.year.to_s[-2..-1]]
+ add_pair post, 'ED', '%02d%02s' % [creditcard.month, creditcard.year.to_s[-2..-1]]
add_pair post, 'CVC', creditcard.verification_value
end
@@ -313,14 +348,15 @@ def parse(body)
# Add HTML_ANSWER element (3-D Secure specific to the response's params)
# Note: HTML_ANSWER is not an attribute so we add it "by hand" to the response
- if html_answer = REXML::XPath.first(xml_root, "//HTML_ANSWER")
- response["HTML_ANSWER"] = html_answer.text
+ if html_answer = REXML::XPath.first(xml_root, '//HTML_ANSWER')
+ response['HTML_ANSWER'] = html_answer.text
end
response
end
def commit(action, parameters)
+ add_pair parameters, 'RTIMEOUT', @options[:timeout] if @options[:timeout]
add_pair parameters, 'PSPID', @options[:login]
add_pair parameters, 'USERID', @options[:user]
add_pair parameters, 'PSWD', @options[:password]
@@ -328,27 +364,27 @@ def commit(action, parameters)
response = parse(ssl_post(url(parameters['PAYID']), post_data(action, parameters)))
options = {
- :authorization => [response["PAYID"], action].join(";"),
+ :authorization => [response['PAYID'], action].join(';'),
:test => test?,
- :avs_result => { :code => AVS_MAPPING[response["AAVCheck"]] },
- :cvv_result => CVV_MAPPING[response["CVCCheck"]]
+ :avs_result => { :code => AVS_MAPPING[response['AAVCheck']] },
+ :cvv_result => CVV_MAPPING[response['CVCCheck']]
}
OgoneResponse.new(successful?(response), message_from(response), response, options)
end
def url(payid)
- (test? ? test_url : live_url) + (payid ? "maintenancedirect.asp" : "orderdirect.asp")
+ (test? ? test_url : live_url) + (payid ? 'maintenancedirect.asp' : 'orderdirect.asp')
end
def successful?(response)
- response["NCERROR"] == "0"
+ response['NCERROR'] == '0'
end
def message_from(response)
if successful?(response)
SUCCESS_MESSAGE
else
- format_error_message(response["NCERRORPLUS"])
+ format_error_message(response['NCERRORPLUS'])
end
end
@@ -356,9 +392,9 @@ def format_error_message(message)
raw_message = message.to_s.strip
case raw_message
when /\|/
- raw_message.split("|").join(", ").capitalize
+ raw_message.split('|').join(', ').capitalize
when /\//
- raw_message.split("/").first.to_s.capitalize
+ raw_message.split('/').first.to_s.capitalize
else
raw_message.to_s.capitalize
end
@@ -372,27 +408,48 @@ def post_data(action, parameters = {})
def add_signature(parameters)
if @options[:signature].blank?
- deprecated(OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) unless(@options[:signature_encryptor] == "none")
- return
+ ActiveMerchant.deprecated(OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE) unless(@options[:signature_encryptor] == 'none')
+ return
end
- sha_encryptor = case @options[:signature_encryptor]
- when 'sha256'
- Digest::SHA256
- when 'sha512'
- Digest::SHA512
- else
- Digest::SHA1
- end
-
- string_to_digest = if @options[:signature_encryptor]
- parameters.sort { |a, b| a[0].upcase <=> b[0].upcase }.map { |k, v| "#{k.upcase}=#{v}" }.join(@options[:signature])
+ add_pair parameters, 'SHASign', calculate_signature(parameters, @options[:signature_encryptor], @options[:signature])
+ end
+
+ def calculate_signature(signed_parameters, algorithm, secret)
+ return legacy_calculate_signature(signed_parameters, secret) unless algorithm
+
+ sha_encryptor = case algorithm
+ when 'sha256'
+ Digest::SHA256
+ when 'sha512'
+ Digest::SHA512
+ when 'sha1'
+ Digest::SHA1
else
- %w[orderID amount currency CARDNO PSPID Operation ALIAS].map { |key| parameters[key] }.join
+ raise "Unknown signature algorithm #{algorithm}"
end
- string_to_digest << @options[:signature]
- add_pair parameters, 'SHASign', sha_encryptor.hexdigest(string_to_digest).upcase
+ filtered_params = signed_parameters.select { |k, v| !v.blank? }
+ sha_encryptor.hexdigest(
+ filtered_params.sort_by { |k, v| k.upcase }.map { |k, v| "#{k.upcase}=#{v}#{secret}" }.join('')
+ ).upcase
+ end
+
+ def legacy_calculate_signature(parameters, secret)
+ Digest::SHA1.hexdigest(
+ (
+ %w(
+ orderID
+ amount
+ currency
+ CARDNO
+ PSPID
+ Operation
+ ALIAS
+ ).map { |key| parameters[key] } +
+ [secret]
+ ).join('')
+ ).upcase
end
def add_pair(post, key, value)
diff --git a/lib/active_merchant/billing/gateways/omise.rb b/lib/active_merchant/billing/gateways/omise.rb
new file mode 100644
index 00000000000..218b099590f
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/omise.rb
@@ -0,0 +1,324 @@
+require 'active_merchant/billing/rails'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class OmiseGateway < Gateway
+ API_URL = 'https://api.omise.co/'
+ VAULT_URL = 'https://vault.omise.co/'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ 'invalid_security_code' => STANDARD_ERROR_CODE[:invalid_cvc],
+ 'failed_capture' => STANDARD_ERROR_CODE[:card_declined]
+ }
+
+ self.live_url = self.test_url = API_URL
+
+ # Currency supported by Omise
+ # * Thai Baht with Satang, 50000 (THB500.00)
+ # * Japanese Yen, 500 (JPY500)
+ self.default_currency = 'THB'
+ self.money_format = :cents
+
+ # Country supported by Omise
+ # * Thailand
+ self.supported_countries = %w( TH JP )
+
+ # Credit cards supported by Omise
+ # * VISA
+ # * MasterCard
+ # * JCB
+ self.supported_cardtypes = [:visa, :master, :jcb]
+
+ # Omise main page
+ self.homepage_url = 'https://www.omise.co/'
+ self.display_name = 'Omise'
+
+ # Creates a new OmiseGateway.
+ #
+ # Omise requires public_key for token creation.
+ # And it requires secret_key for other transactions.
+ # These keys can be found in https://dashboard.omise.co/test/api-keys
+ #
+ # ==== Options
+ #
+ # * :public_key -- Omise's public key (REQUIRED).
+ # * :secret_key -- Omise's secret key (REQUIRED).
+ # * :api_version -- Omise's API Version (OPTIONAL), default version is '2014-07-27'
+ # See version at page https://dashboard.omise.co/api-version/edit
+
+ def initialize(options={})
+ requires!(options, :public_key, :secret_key)
+ @public_key = options[:public_key]
+ @secret_key = options[:secret_key]
+ @api_version = options[:api_version]
+ super
+ end
+
+ # Perform a purchase (with auto capture)
+ #
+ # ==== Parameters
+ #
+ # * money -- The purchasing amount in Thai Baht Satang
+ # * payment_method -- The CreditCard object
+ # * options -- An optional parameters, such as token from Omise.js
+ #
+ # ==== Options
+ # * token_id -- token id, use Omise.js library to retrieve a token id
+ # if this is passed as an option, it will ignore tokenizing via Omisevaultgateway object
+ #
+ # === Example
+ # To create a charge on a card
+ #
+ # purchase(money, Creditcard_object)
+ #
+ # To create a charge on a token
+ #
+ # purchase(money, nil, { :token_id => token_id, ... })
+ #
+ # To create a charge on a customer
+ #
+ # purchase(money, nil, { :customer_id => customer_id })
+
+ def purchase(money, payment_method, options={})
+ create_charge(money, payment_method, options)
+ end
+
+ # Authorize a charge.
+ #
+ # ==== Parameters
+ #
+ # * money -- The purchasing amount in Thai Baht Satang
+ # * payment_method -- The CreditCard object
+ # * options -- An optional parameters, such as token or capture
+
+ def authorize(money, payment_method, options={})
+ options[:capture] = 'false'
+ create_charge(money, payment_method, options)
+ end
+
+ # Capture an authorized charge.
+ #
+ # ==== Parameters
+ #
+ # * money -- An amount in Thai Baht Satang
+ # * charge_id -- The CreditCard object
+ # * options -- An optional parameters, such as token or capture
+
+ def capture(money, charge_id, options={})
+ post = {}
+ add_amount(post, money, options)
+ commit(:post, "charges/#{CGI.escape(charge_id)}/capture", post, options)
+ end
+
+ # Refund a charge.
+ #
+ # ==== Parameters
+ #
+ # * money -- An amount of money to charge in Satang.
+ # * charge_id -- The CreditCard object
+ # * options -- An optional parameters, such as token or capture
+
+ def refund(money, charge_id, options={})
+ options[:amount] = money if money
+ commit(:post, "charges/#{CGI.escape(charge_id)}/refunds", options)
+ end
+
+ # Store a card details as customer
+ #
+ # ==== Parameters
+ #
+ # * payment_method -- The CreditCard.
+ # * options -- Optional Customer information:
+ # 'email' (A customer email)
+ # 'description' (A customer description)
+
+ def store(payment_method, options={})
+ post, card_params = {}, {}
+ add_customer_data(post, options)
+ add_token(card_params, payment_method, options)
+ commit(:post, 'customers', post.merge(card_params), options)
+ end
+
+ # Delete a customer and all associated credit cards.
+ #
+ # ==== Parameters
+ #
+ # * customer_id -- The Customer identifier (REQUIRED).
+
+ def unstore(customer_id, options={})
+ commit(:delete, "customers/#{CGI.escape(customer_id)}")
+ end
+
+ # Enable scrubbing sensitive information
+ def supports_scrubbing?
+ true
+ end
+
+ # Scrub sensitive information out of HTTP transcripts
+ #
+ # ==== Parameters
+ #
+ # * transcript -- The HTTP transcripts
+
+ def scrub(transcript)
+ transcript.
+ gsub(/(Authorization: Basic )\w+/i, '\1[FILTERED]').
+ gsub(/(\\"number\\":)\\"\d+\\"/, '\1[FILTERED]').
+ gsub(/(\\"security_code\\":)\\"\d+\\"/, '\1[FILTERED]')
+ end
+
+ private
+
+ def create_charge(money, payment_method, options)
+ post = {}
+ add_token(post, payment_method, options)
+ add_amount(post, money, options)
+ add_customer(post, options)
+ post[:capture] = options[:capture] if options[:capture]
+ commit(:post, 'charges', post, options)
+ end
+
+ def headers(options={})
+ key = options[:key] || @secret_key
+ {
+ 'Content-Type' => 'application/json;utf-8',
+ 'Omise-Version' => @api_version || '2014-07-27',
+ 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION} Ruby/#{RUBY_VERSION}",
+ 'Authorization' => 'Basic ' + Base64.encode64(key.to_s + ':').strip,
+ 'Accept-Encoding' => 'utf-8'
+ }
+ end
+
+ def url_for(endpoint)
+ (endpoint == 'tokens' ? VAULT_URL : API_URL) + endpoint
+ end
+
+ def post_data(parameters)
+ parameters.present? ? parameters.to_json : nil
+ end
+
+ def https_request(method, endpoint, parameters=nil, options={})
+ raw_response = response = nil
+ begin
+ raw_response = ssl_request(method, url_for(endpoint), post_data(parameters), headers(options))
+ response = parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response = parse(raw_response)
+ rescue JSON::ParserError
+ response = json_error(raw_response)
+ end
+ response
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def json_error(raw_response)
+ msg = 'Invalid response received from Omise API. Please contact support@omise.co if you continue to receive this message.'
+ msg += "The raw response returned by the API was #{raw_response.inspect})"
+ { message: msg }
+ end
+
+ def commit(method, endpoint, params=nil, options={})
+ response = https_request(method, endpoint, params, options)
+ Response.new(
+ successful?(response),
+ message_from(response),
+ response,
+ {
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: successful?(response) ? nil : standard_error_code_mapping(response)
+ }
+ )
+ end
+
+ def standard_error_code_mapping(response)
+ STANDARD_ERROR_CODE_MAPPING[error_code_from(response)] || message_to_standard_error_code_from(response)
+ end
+
+ def error_code_from(response)
+ error?(response) ? response['code'] : response['failure_code']
+ end
+
+ def message_to_standard_error_code_from(response)
+ message = response['message'] if response['code'] == 'invalid_card'
+ case message
+ when /brand not supported/
+ STANDARD_ERROR_CODE[:invalid_number]
+ when /number is invalid/
+ STANDARD_ERROR_CODE[:incorrect_number]
+ when /expiration date cannot be in the past/
+ STANDARD_ERROR_CODE[:expired_card]
+ when /expiration \w+ is invalid/
+ STANDARD_ERROR_CODE[:invalid_expiry_date]
+ else
+ STANDARD_ERROR_CODE[:processing_error]
+ end
+ end
+
+ def message_from(response)
+ if successful?(response)
+ 'Success'
+ else
+ response['message'] || response['failure_message']
+ end
+ end
+
+ def authorization_from(response)
+ response['id'] if successful?(response)
+ end
+
+ def successful?(response)
+ !error?(response) && response['failure_code'].nil?
+ end
+
+ def error?(response)
+ response.key?('object') && (response['object'] == 'error')
+ end
+
+ def get_token(post, credit_card)
+ add_creditcard(post, credit_card) if credit_card
+ commit(:post, 'tokens', post, { key: @public_key })
+ end
+
+ def add_token(post, credit_card, options={})
+ if options[:token_id].present?
+ post[:card] = options[:token_id]
+ else
+ response = get_token(post, credit_card)
+ response.authorization ? (post[:card] = response.authorization) : response
+ end
+ end
+
+ def add_creditcard(post, payment_method)
+ card = {
+ number: payment_method.number,
+ name: payment_method.name,
+ security_code: payment_method.verification_value,
+ expiration_month: payment_method.month,
+ expiration_year: payment_method.year
+ }
+ post[:card] = card
+ end
+
+ def add_customer(post, options={})
+ post[:customer] = options[:customer_id] if options[:customer_id]
+ end
+
+ def add_customer_data(post, options={})
+ post[:description] = options[:description] if options[:description]
+ post[:email] = options[:email] if options[:email]
+ end
+
+ def add_amount(post, money, options)
+ post[:amount] = amount(money)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:description] = options[:description] if options.key?(:description)
+ end
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/openpay.rb b/lib/active_merchant/billing/gateways/openpay.rb
new file mode 100644
index 00000000000..166f1f44b43
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/openpay.rb
@@ -0,0 +1,228 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class OpenpayGateway < Gateway
+ self.live_url = 'https://api.openpay.mx/v1/'
+ self.test_url = 'https://sandbox-api.openpay.mx/v1/'
+
+ self.supported_countries = ['MX']
+ self.supported_cardtypes = [:visa, :master, :american_express, :carnet]
+ self.homepage_url = 'http://www.openpay.mx/'
+ self.display_name = 'Openpay'
+ self.default_currency = 'MXN'
+
+ # Instantiate a instance of OpenpayGateway by passing through your
+ # merchant id and private api key.
+ #
+ # === To obtain your own credentials
+ # 1. Visit http://openpay.mx
+ # 2. Sign up
+ # 3. Activate your account clicking on the email confirmation
+ def initialize(options = {})
+ requires!(options, :key, :merchant_id)
+ @api_key = options[:key]
+ @merchant_id = options[:merchant_id]
+ super
+ end
+
+ def purchase(money, creditcard, options = {})
+ post = create_post_for_auth_or_purchase(money, creditcard, options)
+ commit(:post, 'charges', post, options)
+ end
+
+ def authorize(money, creditcard, options = {})
+ post = create_post_for_auth_or_purchase(money, creditcard, options)
+ post[:capture] = false
+ commit(:post, 'charges', post, options)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ post[:amount] = amount(money) if money
+ post[:payments] = options[:payments] if options[:payments]
+ commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options)
+ end
+
+ def void(identification, options = {})
+ commit(:post, "charges/#{CGI.escape(identification)}/refund", nil, options)
+ end
+
+ def refund(money, identification, options = {})
+ post = {}
+ post[:description] = options[:description]
+ post[:amount] = amount(money)
+ commit(:post, "charges/#{CGI.escape(identification)}/refund", post, options)
+ 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(creditcard, options = {})
+ card_params = {}
+ add_creditcard(card_params, creditcard, options)
+ card = card_params[:card]
+
+ if options[:customer].present?
+ commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", card, options)
+ else
+ requires!(options, :email, :name)
+ post = {}
+ post[:name] = options[:name]
+ post[:email] = options[:email]
+ MultiResponse.run(:first) do |r|
+ r.process { commit(:post, 'customers', post, options) }
+
+ if(r.success? && !r.params['id'].blank?)
+ customer_id = r.params['id']
+ r.process { commit(:post, "customers/#{customer_id}/cards", card, options) }
+ end
+ end
+ end
+ end
+
+ def unstore(customer_id, card_id = nil, options = {})
+ if card_id.nil?
+ commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, options)
+ else
+ commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil, options)
+ end
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((card_number\\?":\\?")[^"\\]*)i, '\1[FILTERED]').
+ gsub(%r((cvv2\\?":\\?")\d+[^"\\]*)i, '\1[FILTERED]').
+ gsub(%r((cvv2\\?":)null), '\1[BLANK]').
+ gsub(%r((cvv2\\?":\\?")\\?"), '\1[BLANK]"').
+ gsub(%r((cvv2\\?":\\?")\s+), '\1[BLANK]')
+ end
+
+ private
+
+ def create_post_for_auth_or_purchase(money, creditcard, options)
+ post = {}
+ post[:amount] = amount(money)
+ post[:method] = 'card'
+ post[:description] = options[:description]
+ post[:order_id] = options[:order_id]
+ post[:device_session_id] = options[:device_session_id]
+ post[:currency] = (options[:currency] || currency(money)).upcase
+ post[:use_card_points] = options[:use_card_points] if options[:use_card_points]
+ post[:payment_plan] = {payments: options[:payments]} if options[:payments]
+ add_creditcard(post, creditcard, options)
+ post
+ end
+
+ def add_creditcard(post, creditcard, options)
+ if creditcard.kind_of?(String)
+ post[:source_id] = creditcard
+ elsif creditcard.respond_to?(:number)
+ card = {
+ card_number: creditcard.number,
+ expiration_month: sprintf('%02d', creditcard.month),
+ expiration_year: creditcard.year.to_s[-2, 2],
+ cvv2: creditcard.verification_value,
+ holder_name: creditcard.name
+ }
+ add_address(card, options)
+ add_customer_data(post, creditcard, options)
+ post[:card] = card
+ end
+ end
+
+ def add_customer_data(post, creditcard, options)
+ if options[:email]
+ customer = {
+ name: creditcard.name || options[:name],
+ email: options[:email]
+ }
+ post[:customer] = customer
+ end
+ post
+ end
+
+ def add_address(card, options)
+ return unless card.kind_of?(Hash)
+ if address = (options[:billing_address] || options[:address])
+ card[:address] = {
+ line1: address[:address1],
+ line2: address[:address2],
+ line3: address[:company],
+ city: address[:city],
+ postal_code: address[:zip],
+ state: address[:state],
+ country_code: address[:country]
+ }
+ end
+ end
+
+ def headers(options = {})
+ {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => 'Basic ' + Base64.strict_encode64(@api_key.to_s + ':').strip,
+ 'User-Agent' => "Openpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ 'X-Openpay-Client-User-Agent' => user_agent
+ }
+ end
+
+ def parse(body)
+ return {} unless body
+ JSON.parse(body)
+ end
+
+ def commit(method, resource, parameters, options = {})
+ response = http_request(method, resource, parameters, options)
+ success = !error?(response)
+
+ Response.new(success,
+ (success ? response['error_code'] : response['description']),
+ response,
+ :test => test?,
+ :authorization => response['id']
+ )
+ end
+
+ def http_request(method, resource, parameters={}, options={})
+ url = (test? ? self.test_url : self.live_url) + @merchant_id + '/' + resource
+ raw_response = nil
+ begin
+ raw_response = ssl_request(method, url, (parameters ? parameters.to_json : nil), headers(options))
+ parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response_error(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+ end
+
+ def error?(response)
+ response['error_code'] && !response['error_code'].blank?
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def json_error(raw_response)
+ msg = 'Invalid response received from the Openpay API. Please contact soporte@openpay.mx if you continue to receive this message.'
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
+ {
+ 'category' => 'request',
+ 'error_code' => '9999',
+ 'description' => msg
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/opp.rb b/lib/active_merchant/billing/gateways/opp.rb
new file mode 100644
index 00000000000..cdd4900e729
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/opp.rb
@@ -0,0 +1,388 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class OppGateway < Gateway
+ # = Open Payment Platform
+ #
+ # The Open Payment Platform includes a powerful omni-channel transaction processing API,
+ # enabling you to quickly and flexibly build new applications and services on the platform.
+ #
+ # This plugin enables connectivity to the Open Payment Platform for activemerchant.
+ #
+ # For any questions or comments please contact support@payon.com
+ #
+ # == Usage
+ #
+ # gateway = ActiveMerchant::Billing::OppGateway.new(
+ # user_id: 'merchant user id',
+ # password: 'password',
+ # entity_id: 'entity id',
+ # )
+ #
+ # # set up credit card object as in main ActiveMerchant example
+ # creditcard = ActiveMerchant::Billing::CreditCard.new(
+ # :type => 'visa',
+ # :number => '4242424242424242',
+ # :month => 8,
+ # :year => 2009,
+ # :first_name => 'Bob',
+ # :last_name => 'Bobsen'
+ # :verification_value: '123')
+ #
+ # # Request: complete example, including address, billing address, shipping address
+ # complete_request_options = {
+ # order_id: "your merchant/shop order id", # alternative is to set merchantInvoiceId
+ # merchant_transaction_id: "your merchant/shop transaction id",
+ # address: address,
+ # description: 'Store Purchase - Books',
+ # risk_workflow: false,
+ # test_mode: 'EXTERNAL' # or 'INTERNAL', valid only for test system
+ # create_registration: false, # payment details will be stored on the server an latter can be referenced
+ #
+ # billing_address: {
+ # address1: '123 Test Street',
+ # city: 'Test',
+ # state: 'TE',
+ # zip: 'AB12CD',
+ # country: 'GB',
+ # },
+ # shipping_address: {
+ # name: 'Muton DeMicelis',
+ # address1: 'My Street On Upiter, Apt 3.14/2.78',
+ # city: 'Munich',
+ # state: 'Bov',
+ # zip: '81675',
+ # country: 'DE',
+ # },
+ # customer: {
+ # merchant_customer_id: "your merchant/customer id",
+ # givenname: 'Jane',
+ # surname: 'Jones',
+ # birth_date: '1965-05-01',
+ # phone: '(?!?)555-5555',
+ # mobile: '(?!?)234-23423',
+ # email: 'jane@jones.com',
+ # company_name: 'JJ Ltd.',
+ # identification_doctype: 'PASSPORT',
+ # identification_docid: 'FakeID2342431234123',
+ # ip: 101.102.103.104,
+ # },
+ # }
+ #
+ # # Request: minimal example
+ # minimal_request_options = {
+ # order_id: "your merchant/shop order id", # alternative is to set merchantInvoiceId
+ # description: 'Store Purchase - Books',
+ # }
+ #
+ # options =
+ # # run request
+ # response = gateway.purchase(754, creditcard, options) # charge 7,54 EUR
+ #
+ # response.success? # Check whether the transaction was successful
+ # response.error_code # Retrieve the error message - it's mapped to Gateway::STANDARD_ERROR_CODE
+ # response.message # Retrieve the message returned by opp
+ # response.authorization # Retrieve the unique transaction ID returned by opp
+ # response.params['result']['code'] # Retrieve original return code returned by opp server
+ #
+ # == Errors
+ # If transaction is not successful, response.error_code contains mapped to Gateway::STANDARD_ERROR_CODE error message.
+ # Complete list of opp error codes can be viewed on https://docs.oppwa.com/
+ # Because this list is much bigger than Gateway::STANDARD_ERROR_CODE, only fraction is mapped to Gateway::STANDARD_ERROR_CODE.
+ # All other codes are mapped as Gateway::STANDARD_ERROR_CODE[:processing_error], so if this is the case,
+ # you may check the original result code from OPP that can be found in response.params['result']['code']
+ #
+ # == Special features
+ # For purchase method risk check can be forced when options[:risk_workflow] = true
+ # This will split (on OPP server side) the transaction into two separate transactions: authorize and capture,
+ # but capture will be executed only if risk checks are successful.
+ #
+ # For testing you may use the test account details listed fixtures.yml under opp. It is important to note that there are two test modes available:
+ # options[:test_mode]='EXTERNAL' causes test transactions to be forwarded to the processor's test system for 'end-to-end' testing
+ # options[:test_mode]='INTERNAL' causes transactions to be sent to opp simulators, which is useful when switching to the live endpoint for connectivity testing.
+ # If no test_mode parameter is sent, test_mode=INTERNAL is the default behaviour.
+ #
+ # Billing Address, Shipping Address, Custom Parameters are supported as described under https://docs.oppwa.com/parameters
+ # See complete example above for details.
+ #
+ # == Tokenization
+ # When create_registration is set to true, the payment details will be stored and a token will be returned in registrationId response field,
+ # which can subsequently be used to reference the stored payment.
+
+ self.test_url = 'https://test.oppwa.com/v1/payments'
+ self.live_url = 'https://oppwa.com/v1/payments'
+
+ self.supported_countries = %w(AD AI AG AR AU AT BS BB BE BZ BM BR BN BG CA HR CY CZ DK DM EE FI FR DE GR GD GY HK HU IS IN IL IT JP LV LI LT LU MY MT MX MC MS NL PA PL PT KN LC MF VC SM SG SK SI ZA ES SR SE CH TR GB US UY)
+ self.default_currency = 'EUR'
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro, :dankort]
+
+ self.homepage_url = 'https://docs.oppwa.com'
+ self.display_name = 'Open Payment Platform'
+
+ def initialize(options={})
+ requires!(options, :user_id, :password, :entity_id)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ # debit
+ if payment.is_a?(String)
+ options[:registrationId] = payment
+ end
+ execute_dbpa(options[:risk_workflow] ? 'PA.CP': 'DB',
+ money, payment, options)
+ end
+
+ def authorize(money, payment, options={})
+ # preauthorization PA
+ execute_dbpa('PA', money, payment, options)
+ end
+
+ def capture(money, authorization, options={})
+ # capture CP
+ execute_referencing('CP', money, authorization, options)
+ end
+
+ def refund(money, authorization, options={})
+ # refund RF
+ execute_referencing('RF', money, authorization, options)
+ end
+
+ def void(authorization, options={})
+ # reversal RV
+ execute_referencing('RV', nil, authorization, options)
+ 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(credit_card, options = {})
+ execute_store(credit_card, options.merge(store: true))
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((authentication\.password=)\w+), '\1[FILTERED]').
+ gsub(%r((card\.number=)\d+), '\1[FILTERED]').
+ gsub(%r((card\.cvv=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def execute_store(payment, options)
+ post = {}
+ add_payment_method(post, payment, options)
+ add_address(post, options)
+ add_options(post, options)
+ add_3d_secure(post, options)
+ commit(post, nil, options)
+ end
+
+ def execute_dbpa(txtype, money, payment, options)
+ post = {}
+ post[:paymentType] = txtype
+ add_invoice(post, money, options)
+ add_payment_method(post, payment, options)
+ add_address(post, options)
+ add_customer_data(post, payment, options)
+ add_options(post, options)
+ add_3d_secure(post, options)
+ commit(post, nil, options)
+ end
+
+ def execute_referencing(txtype, money, authorization, options)
+ post = {}
+ post[:paymentType] = txtype
+ add_invoice(post, money, options)
+ commit(post, authorization, options)
+ end
+
+ def add_authentication(post)
+ post[:authentication] = { entityId: @options[:entity_id], password: @options[:password], userId: @options[:user_id]}
+ end
+
+ def add_customer_data(post, payment, options)
+ if options[:customer]
+ post[:customer] = {
+ merchantCustomerId: options[:customer][:merchant_customer_id],
+ givenName: options[:customer][:givenname] || payment.first_name,
+ surname: options[:customer][:surname] || payment.last_name,
+ birthDate: options[:customer][:birth_date],
+ phone: options[:customer][:phone],
+ mobile: options[:customer][:mobile],
+ email: options[:customer][:email] || options[:email],
+ companyName: options[:customer][:company_name],
+ identificationDocType: options[:customer][:identification_doctype],
+ identificationDocId: options[:customer][:identification_docid],
+ ip: options[:customer][:ip] || options[:ip]
+ }
+ end
+ end
+
+ def add_address(post, options)
+ if billing_address = options[:billing_address] || options[:address]
+ address(post, billing_address, 'billing')
+ end
+ if shipping_address = options[:shipping_address]
+ address(post, shipping_address, 'shipping')
+ if shipping_address[:name]
+ firstname, lastname = shipping_address[:name].split(' ')
+ post[:shipping] = { givenName: firstname, surname: lastname }
+ end
+ end
+ end
+
+ def address(post, address, prefix)
+ post[prefix] = {
+ street1: address[:address1],
+ street2: address[:address2],
+ city: address[:city],
+ state: address[:state],
+ postcode: address[:zip],
+ country: address[:country],
+ }
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:currency] = options[:currency] || currency(money) unless post[:paymentType] == 'RV'
+ post[:descriptor] = options[:description] || options[:descriptor]
+ post[:merchantInvoiceId] = options[:merchantInvoiceId] || options[:order_id]
+ post[:merchantTransactionId] = options[:merchant_transaction_id] || generate_unique_id
+ end
+
+ def add_payment_method(post, payment, options)
+ return if payment.is_a?(String)
+ if options[:registrationId]
+ post[:card] = {
+ cvv: payment.verification_value,
+ }
+ else
+ post[:paymentBrand] = payment.brand.upcase
+ post[:card] = {
+ holder: payment.name,
+ number: payment.number,
+ expiryMonth: '%02d' % payment.month,
+ expiryYear: payment.year,
+ cvv: payment.verification_value,
+ }
+ end
+ end
+
+ def add_3d_secure(post, options)
+ return unless options[:eci] && options[:cavv] && options[:xid]
+
+ post[:threeDSecure] = {
+ eci: options[:eci],
+ verificationId: options[:cavv],
+ xid: options[:xid]
+ }
+ end
+
+ def add_options(post, options)
+ post[:createRegistration] = options[:create_registration] if options[:create_registration] && !options[:registrationId]
+ post[:testMode] = options[:test_mode] if test? && options[:test_mode]
+ options.each { |key, value| post[key] = value if key.to_s.match('customParameters\[[a-zA-Z0-9\._]{3,64}\]') }
+ post['customParameters[SHOPPER_pluginId]'] = 'activemerchant'
+ post['customParameters[custom_disable3DSecure]'] = options[:disable_3d_secure] if options[:disable_3d_secure]
+ end
+
+ def build_url(url, authorization, options)
+ if options[:store]
+ url.gsub(/payments/, 'registrations')
+ elsif options[:registrationId]
+ "#{url.gsub(/payments/, 'registrations')}/#{options[:registrationId]}/payments"
+ elsif authorization
+ "#{url}/#{authorization}"
+ else
+ url
+ end
+ end
+
+ def commit(post, authorization, options)
+ url = build_url(test? ? test_url : live_url, authorization, options)
+ add_authentication(post)
+ post = flatten_hash(post)
+
+ response = begin
+ parse(
+ ssl_post(
+ url,
+ post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&'),
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
+ )
+ )
+ rescue ResponseError => e
+ parse(e.response.body)
+ end
+
+ success = success_from(response)
+
+ Response.new(
+ success,
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: success ? nil : error_code_from(response)
+ )
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ rescue JSON::ParserError
+ json_error(body)
+ end
+
+ def json_error(body)
+ message = "Invalid response received #{body.inspect}"
+ { 'result' => {'description' => message, 'code' => 'unknown' } }
+ end
+
+ def success_from(response)
+ return false unless response['result']
+
+ success_regex = /^(000\.000\.|000\.100\.1|000\.[36])/
+
+ if success_regex =~ response['result']['code']
+ true
+ else
+ false
+ end
+ end
+
+ def message_from(response)
+ return 'Failed' unless response['result']
+
+ response['result']['description']
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def error_code_from(response)
+ response['result']['code']
+ end
+
+ def flatten_hash(hash)
+ hash.each_with_object({}) do |(k, v), h|
+ if v.is_a? Hash
+ flatten_hash(v).map do |h_k, h_v|
+ h["#{k}.#{h_k}".to_sym] = h_v
+ end
+ else
+ h[k] = v
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/optimal_payment.rb b/lib/active_merchant/billing/gateways/optimal_payment.rb
index 604eefa18f5..30f7bf57e02 100644
--- a/lib/active_merchant/billing/gateways/optimal_payment.rb
+++ b/lib/active_merchant/billing/gateways/optimal_payment.rb
@@ -5,10 +5,12 @@ class OptimalPaymentGateway < Gateway
self.live_url = 'https://webservices.optimalpayments.com/creditcardWS/CreditCardServlet/v1'
# The countries the gateway supports merchants from as 2 digit ISO country codes
- self.supported_countries = ['CA', 'US', 'GB']
+ self.supported_countries = ['CA', 'US', 'GB', 'AU', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK',
+ 'EE', 'FI', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT',
+ 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH']
# The card types supported by the payment gateway
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :solo] # :switch?
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
# The homepage URL of the gateway
self.homepage_url = 'http://www.optimalpayments.com/'
@@ -17,7 +19,17 @@ class OptimalPaymentGateway < Gateway
self.display_name = 'Optimal Payments'
def initialize(options = {})
- #requires!(options, :login, :password)
+ if(options[:login])
+ ActiveMerchant.deprecated("The 'login' option is deprecated in favor of 'store_id' and will be removed in a future version.")
+ options[:store_id] = options[:login]
+ end
+
+ if(options[:account])
+ ActiveMerchant.deprecated("The 'account' option is deprecated in favor of 'account_number' and will be removed in a future version.")
+ options[:account_number] = options[:account]
+ end
+
+ requires!(options, :account_number, :store_id, :password)
super
end
@@ -48,15 +60,31 @@ def capture(money, authorization, options = {})
commit('ccSettlement', money, options)
end
+ def verify(credit_card, options = {})
+ parse_card_or_auth(credit_card, options)
+ commit('ccVerification', 0, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((%3CstorePwd%3E).*(%3C(%2F|/)storePwd%3E))i, '\1[FILTERED]\2').
+ gsub(%r((%3CcardNum%3E)\d*(%3C(%2F|/)cardNum%3E))i, '\1[FILTERED]\2').
+ gsub(%r((%3Ccvd%3E)\d*(%3C(%2F|/)cvd%3E))i, '\1[FILTERED]\2')
+ end
+
private
def parse_card_or_auth(card_or_auth, options)
if card_or_auth.respond_to?(:number)
@credit_card = card_or_auth
- @stored_data = ""
+ @stored_data = ''
else
options[:confirmationNumber] = card_or_auth
- @stored_data = "StoredData"
+ @stored_data = 'StoredData'
end
end
@@ -76,16 +104,16 @@ def commit(action, money, post)
cc_stored_data_request(money, post)
when 'ccAuthorizeReversal'
cc_auth_reversal_request(post)
- #when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment'
+ # when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment'
# cc_cancel_request(money, post)
- #when 'ccPayment'
+ # when 'ccPayment'
# cc_payment_request(money, post)
- #when 'ccAuthenticate'
+ # when 'ccAuthenticate'
# cc_authenticate_request(money, post)
else
raise 'Unknown Action'
end
- txnRequest = URI.encode(xml)
+ txnRequest = escape_uri(xml)
response = parse(ssl_post(test? ? self.test_url : self.live_url, "txnMode=#{action}&txnRequest=#{txnRequest}"))
Response.new(successful?(response), message_from(response), hash_from_xml(response),
@@ -96,6 +124,11 @@ def commit(action, money, post)
)
end
+ # The upstream is picky and so we can't use CGI.escape like we want to
+ def escape_uri(uri)
+ URI::DEFAULT_PARSER.escape(uri)
+ end
+
def successful?(response)
REXML::XPath.first(response, '//decision').text == 'ACCEPTED' rescue false
end
@@ -150,23 +183,24 @@ def xml_document(root_tag)
def get_text_from_document(document, node)
node = REXML::XPath.first(document, node)
- node && node.text
+ node&.text
end
def cc_auth_request(money, opts)
xml_document('ccAuthRequestV1') do |xml|
- build_merchant_account(xml, @options)
+ build_merchant_account(xml)
xml.merchantRefNum opts[:order_id]
xml.amount(money/100.0)
build_card(xml, opts)
build_billing_details(xml, opts)
build_shipping_details(xml, opts)
+ xml.customerIP opts[:ip] if opts[:ip]
end
end
def cc_auth_reversal_request(opts)
xml_document('ccAuthReversalRequestV1') do |xml|
- build_merchant_account(xml, @options)
+ build_merchant_account(xml)
xml.confirmationNumber opts[:confirmationNumber]
xml.merchantRefNum opts[:order_id]
end
@@ -174,7 +208,7 @@ def cc_auth_reversal_request(opts)
def cc_post_auth_request(money, opts)
xml_document('ccPostAuthRequestV1') do |xml|
- build_merchant_account(xml, @options)
+ build_merchant_account(xml)
xml.confirmationNumber opts[:confirmationNumber]
xml.merchantRefNum opts[:order_id]
xml.amount(money/100.0)
@@ -183,7 +217,7 @@ def cc_post_auth_request(money, opts)
def cc_stored_data_request(money, opts)
xml_document('ccStoredDataRequestV1') do |xml|
- build_merchant_account(xml, @options)
+ build_merchant_account(xml)
xml.merchantRefNum opts[:order_id]
xml.confirmationNumber opts[:confirmationNumber]
xml.amount(money/100.0)
@@ -194,14 +228,14 @@ def cc_stored_data_request(money, opts)
#
# def cc_cancel_request(opts)
# xml_document('ccCancelRequestV1') do |xml|
- # build_merchant_account(xml, @options)
+ # build_merchant_account(xml)
# xml.confirmationNumber opts[:confirmationNumber]
# end
# end
#
# def cc_payment_request(money, opts)
# xml_document('ccPaymentRequestV1') do |xml|
- # build_merchant_account(xml, @options)
+ # build_merchant_account(xml)
# xml.merchantRefNum opts[:order_id]
# xml.amount(money/100.0)
# build_card(xml, opts)
@@ -211,7 +245,7 @@ def cc_stored_data_request(money, opts)
#
# def cc_authenticate_request(opts)
# xml_document('ccAuthenticateRequestV1') do |xml|
- # build_merchant_account(xml, @options)
+ # build_merchant_account(xml)
# xml.confirmationNumber opts[:confirmationNumber]
# xml.paymentResponse 'myPaymentResponse'
# end
@@ -224,27 +258,29 @@ def schema
}
end
- def build_merchant_account(xml, opts)
+ def build_merchant_account(xml)
xml.tag! 'merchantAccount' do
- xml.tag! 'accountNum' , opts[:account]
- xml.tag! 'storeID' , opts[:login]
- xml.tag! 'storePwd' , opts[:password]
+ xml.tag! 'accountNum', @options[:account_number]
+ xml.tag! 'storeID', @options[:store_id]
+ xml.tag! 'storePwd', @options[:password]
end
end
def build_card(xml, opts)
xml.tag! 'card' do
- xml.tag! 'cardNum' , @credit_card.number
+ xml.tag! 'cardNum', @credit_card.number
xml.tag! 'cardExpiry' do
- xml.tag! 'month' , @credit_card.month
- xml.tag! 'year' , @credit_card.year
+ xml.tag! 'month', @credit_card.month
+ xml.tag! 'year', @credit_card.year
end
if brand = card_type(@credit_card.brand)
- xml.tag! 'cardType' , brand
+ xml.tag! 'cardType', brand
end
- if @credit_card.verification_value
- xml.tag! 'cvdIndicator' , '1' # Value Provided
- xml.tag! 'cvd' , @credit_card.verification_value
+ if @credit_card.verification_value?
+ xml.tag! 'cvdIndicator', '1' # Value Provided
+ xml.tag! 'cvd', @credit_card.verification_value
+ else
+ xml.tag! 'cvdIndicator', '0'
end
end
end
@@ -252,32 +288,34 @@ def build_card(xml, opts)
def build_billing_details(xml, opts)
xml.tag! 'billingDetails' do
xml.tag! 'cardPayMethod', 'WEB'
- build_address(xml, opts[:billing_address], opts[:email])
+ build_address(xml, opts[:billing_address]) if opts[:billing_address]
+ xml.tag! 'email', opts[:email] if opts[:email]
end
end
def build_shipping_details(xml, opts)
xml.tag! 'shippingDetails' do
- build_address(xml, opts[:shipping_address], opts[:email])
+ build_address(xml, opts[:shipping_address])
+ xml.tag! 'email', opts[:email] if opts[:email]
end if opts[:shipping_address].present?
end
- def build_address(xml, addr, email=nil)
+ def build_address(xml, addr)
if addr[:name]
- xml.tag! 'firstName', CGI.escape(addr[:name].split(' ').first) # TODO: parse properly
- xml.tag! 'lastName' , CGI.escape(addr[:name].split(' ').last )
+ first_name, last_name = split_names(addr[:name])
+ xml.tag! 'firstName', first_name
+ xml.tag! 'lastName', last_name
end
- xml.tag! 'street' , CGI.escape(addr[:address1]) if addr[:address1].present?
- xml.tag! 'street2', CGI.escape(addr[:address2]) if addr[:address2].present?
- xml.tag! 'city' , CGI.escape(addr[:city] ) if addr[:city].present?
+ xml.tag! 'street', addr[:address1] if addr[:address1].present?
+ xml.tag! 'street2', addr[:address2] if addr[:address2].present?
+ xml.tag! 'city', addr[:city] if addr[:city].present?
if addr[:state].present?
state_tag = %w(US CA).include?(addr[:country]) ? 'state' : 'region'
- xml.tag! state_tag, CGI.escape(addr[:state])
+ xml.tag! state_tag, addr[:state]
end
- xml.tag! 'country', CGI.escape(addr[:country] ) if addr[:country].present?
- xml.tag! 'zip' , CGI.escape(addr[:zip] ) if addr[:zip].present?
- xml.tag! 'phone' , CGI.escape(addr[:phone] ) if addr[:phone].present?
- xml.tag! 'email', CGI.escape(email) if email
+ xml.tag! 'country', addr[:country] if addr[:country].present?
+ xml.tag! 'zip', addr[:zip] if addr[:zip].present?
+ xml.tag! 'phone', addr[:phone] if addr[:phone].present?
end
def card_type(key)
@@ -286,12 +324,9 @@ def card_type(key)
'american_express'=> 'AM',
'discover' => 'DI',
'diners_club' => 'DC',
- #'switch' => '',
- 'solo' => 'SO'
}[key]
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/orbital.rb b/lib/active_merchant/billing/gateways/orbital.rb
index c26ebbac4a9..aad16855ea2 100644
--- a/lib/active_merchant/billing/gateways/orbital.rb
+++ b/lib/active_merchant/billing/gateways/orbital.rb
@@ -1,6 +1,5 @@
-require File.dirname(__FILE__) + '/orbital/orbital_soft_descriptors'
-require File.dirname(__FILE__) + '/orbital/avs_result'
-require "rexml/document"
+require 'active_merchant/billing/gateways/orbital/orbital_soft_descriptors'
+require 'rexml/document'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -29,29 +28,51 @@ module Billing #:nodoc:
# Company will automatically be affiliated.
class OrbitalGateway < Gateway
- API_VERSION = "5.6"
+ include Empty
+
+ API_VERSION = '7.7'
POST_HEADERS = {
- "MIME-Version" => "1.1",
- "Content-Type" => "application/PTI56",
- "Content-transfer-encoding" => "text",
- "Request-number" => '1',
- "Document-type" => "Request",
- "Interface-Version" => "Ruby|ActiveMerchant|Proprietary Gateway"
+ 'MIME-Version' => '1.1',
+ 'Content-Type' => "application/PTI#{API_VERSION.gsub(/\./, '')}",
+ 'Content-transfer-encoding' => 'text',
+ 'Request-number' => '1',
+ 'Document-type' => 'Request',
+ 'Interface-Version' => 'Ruby|ActiveMerchant|Proprietary Gateway'
}
- SUCCESS, APPROVED = '0', '00'
+ SUCCESS = '0'
+
+ APPROVED = [
+ '00', # Approved
+ '08', # Approved authorization, honor with ID
+ '11', # Approved authorization, VIP approval
+ '24', # Validated
+ '26', # Pre-noted
+ '27', # No reason to decline
+ '28', # Received and stored
+ '29', # Provided authorization
+ '31', # Request received
+ '32', # BIN alert
+ '34', # Approved for partial
+ '91', # Approved low fraud
+ '92', # Approved medium fraud
+ '93', # Approved high fraud
+ '94', # Approved fraud service unavailable
+ 'E7', # Stored
+ 'PA' # Partial approval
+ ]
class_attribute :secondary_test_url, :secondary_live_url
- self.test_url = "https://orbitalvar1.paymentech.net/authorize"
- self.secondary_test_url = "https://orbitalvar2.paymentech.net/authorize"
+ self.test_url = 'https://orbitalvar1.chasepaymentech.com/authorize'
+ self.secondary_test_url = 'https://orbitalvar2.chasepaymentech.com/authorize'
- self.live_url = "https://orbital1.paymentech.net/authorize"
- self.secondary_live_url = "https://orbital2.paymentech.net/authorize"
+ self.live_url = 'https://orbital1.chasepaymentech.com/authorize'
+ self.secondary_live_url = 'https://orbital2.chasepaymentech.com/authorize'
- self.supported_countries = ["US", "CA"]
- self.default_currency = "CAD"
+ self.supported_countries = ['US', 'CA']
+ self.default_currency = 'CAD'
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
self.display_name = 'Orbital Paymentech'
@@ -62,22 +83,45 @@ class OrbitalGateway < Gateway
AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB']
CURRENCY_CODES = {
- "AUD" => '036',
- "CAD" => '124',
- "CZK" => '203',
- "DKK" => '208',
- "HKD" => '344',
- "ICK" => '352',
- "JPY" => '392',
- "MXN" => '484',
- "NZD" => '554',
- "NOK" => '578',
- "SGD" => '702',
- "SEK" => '752',
- "CHF" => '756',
- "GBP" => '826',
- "USD" => '840',
- "EUR" => '978'
+ 'AUD' => '036',
+ 'BRL' => '986',
+ 'CAD' => '124',
+ 'CLP' => '152',
+ 'CZK' => '203',
+ 'DKK' => '208',
+ 'HKD' => '344',
+ 'ICK' => '352',
+ 'JPY' => '392',
+ 'MXN' => '484',
+ 'NZD' => '554',
+ 'NOK' => '578',
+ 'SGD' => '702',
+ 'SEK' => '752',
+ 'CHF' => '756',
+ 'GBP' => '826',
+ 'USD' => '840',
+ 'EUR' => '978'
+ }
+
+ CURRENCY_EXPONENTS = {
+ 'AUD' => '2',
+ 'BRL' => '2',
+ 'CAD' => '2',
+ 'CLP' => '2',
+ 'CZK' => '2',
+ 'DKK' => '2',
+ 'HKD' => '2',
+ 'ICK' => '2',
+ 'JPY' => '0',
+ 'MXN' => '2',
+ 'NZD' => '2',
+ 'NOK' => '2',
+ 'SGD' => '2',
+ 'SEK' => '2',
+ 'CHF' => '2',
+ 'GBP' => '2',
+ 'USD' => '2',
+ 'EUR' => '2'
}
# INDUSTRY TYPES
@@ -137,32 +181,42 @@ class OrbitalGateway < Gateway
USE_ORDER_ID = 'O' # Use OrderID field
USE_COMMENTS = 'D' # Use Comments field
+ SENSITIVE_FIELDS = [:account_num, :cc_account_num]
+
def initialize(options = {})
requires!(options, :merchant_id)
requires!(options, :login, :password) unless options[:ip_authentication]
super
+ @options[:merchant_id] = @options[:merchant_id].to_s
end
# A – Authorization request
def authorize(money, creditcard, options = {})
- order = build_new_order_xml(AUTH_ONLY, money, options) do |xml|
- add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn]
+ order = build_new_order_xml(AUTH_ONLY, money, creditcard, options) do |xml|
+ add_creditcard(xml, creditcard, options[:currency])
add_address(xml, creditcard, options)
if @options[:customer_profiles]
- add_customer_data(xml, options)
+ add_customer_data(xml, creditcard, options)
add_managed_billing(xml, options)
end
end
commit(order, :authorize, options[:trace_number])
end
+ def verify(creditcard, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization) }
+ end
+ end
+
# AC – Authorization and Capture
def purchase(money, creditcard, options = {})
- order = build_new_order_xml(AUTH_AND_CAPTURE, money, options) do |xml|
- add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn]
+ order = build_new_order_xml(AUTH_AND_CAPTURE, money, creditcard, options) do |xml|
+ add_creditcard(xml, creditcard, options[:currency])
add_address(xml, creditcard, options)
if @options[:customer_profiles]
- add_customer_data(xml, options)
+ add_customer_data(xml, creditcard, options)
add_managed_billing(xml, options)
end
end
@@ -176,7 +230,7 @@ def capture(money, authorization, options = {})
# R – Refund request
def refund(money, authorization, options = {})
- order = build_new_order_xml(REFUND, money, options.merge(:authorization => authorization)) do |xml|
+ order = build_new_order_xml(REFUND, money, nil, options.merge(:authorization => authorization)) do |xml|
add_refund(xml, options[:currency])
xml.tag! :CustomerRefNum, options[:customer_ref_num] if @options[:customer_profiles] && options[:profile_txn]
end
@@ -184,13 +238,13 @@ def refund(money, authorization, options = {})
end
def credit(money, authorization, options= {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
def void(authorization, options = {}, deprecated = {})
if(!options.kind_of?(Hash))
- deprecated("Calling the void method with an amount parameter is deprecated and will be removed in a future version.")
+ ActiveMerchant.deprecated('Calling the void method with an amount parameter is deprecated and will be removed in a future version.')
return void(options, deprecated.merge(:amount => authorization))
end
@@ -198,9 +252,8 @@ def void(authorization, options = {}, deprecated = {})
commit(order, :void, options[:trace_number])
end
-
# ==== Customer Profiles
- # :customer_ref_num should be set unless your happy with Orbital providing one
+ # :customer_ref_num should be set unless you're happy with Orbital providing one
#
# :customer_profile_order_override_ind can be set to map
# the CustomerRefNum to OrderID or Comments. Defaults to 'NO' - no mapping
@@ -221,13 +274,13 @@ def void(authorization, options = {}, deprecated = {})
# 'MS' - Manual Suspend
def add_customer_profile(creditcard, options = {})
- options.merge!(:customer_profile_action => CREATE)
+ options[:customer_profile_action] = CREATE
order = build_customer_request_xml(creditcard, options)
commit(order, :add_customer_profile)
end
def update_customer_profile(creditcard, options = {})
- options.merge!(:customer_profile_action => UPDATE)
+ options[:customer_profile_action] = UPDATE
order = build_customer_request_xml(creditcard, options)
commit(order, :update_customer_profile)
end
@@ -244,22 +297,39 @@ def delete_customer_profile(customer_ref_num)
commit(order, :delete_customer_profile)
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').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2')
+ end
+
private
def authorization_string(*args)
- args.compact.join(";")
+ args.compact.join(';')
end
def split_authorization(authorization)
authorization.split(';')
end
- def add_customer_data(xml, options)
+ def add_customer_data(xml, creditcard, options)
if options[:profile_txn]
xml.tag! :CustomerRefNum, options[:customer_ref_num]
else
if options[:customer_ref_num]
- xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM
+ if creditcard
+ xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM
+ end
xml.tag! :CustomerRefNum, options[:customer_ref_num]
else
xml.tag! :CustomerProfileFromOrderInd, AUTO_GENERATE
@@ -277,21 +347,58 @@ def add_soft_descriptors(xml, soft_desc)
xml.tag! :SDMerchantEmail, soft_desc.merchant_email if soft_desc.merchant_email
end
+ def add_soft_descriptors_from_hash(xml, soft_desc)
+ xml.tag! :SDMerchantName, soft_desc[:merchant_name] || nil
+ xml.tag! :SDProductDescription, soft_desc[:product_description] || nil
+ xml.tag! :SDMerchantCity, soft_desc[:merchant_city] || nil
+ xml.tag! :SDMerchantPhone, soft_desc[:merchant_phone] || nil
+ xml.tag! :SDMerchantURL, soft_desc[:merchant_url] || nil
+ xml.tag! :SDMerchantEmail, soft_desc[:merchant_email] || nil
+ end
+
+ def add_level_2_tax(xml, options={})
+ if (level_2 = options[:level_2_data])
+ xml.tag! :TaxInd, level_2[:tax_indicator] if [TAX_NOT_PROVIDED, TAX_INCLUDED, NON_TAXABLE_TRANSACTION].include?(level_2[:tax_indicator].to_i)
+ xml.tag! :Tax, level_2[:tax].to_i if level_2[:tax]
+ end
+ end
+
+ def add_level_2_advice_addendum(xml, options={})
+ if (level_2 = options[:level_2_data])
+ xml.tag! :AMEXTranAdvAddn1, byte_limit(level_2[:advice_addendum_1], 40) if level_2[:advice_addendum_1]
+ xml.tag! :AMEXTranAdvAddn2, byte_limit(level_2[:advice_addendum_2], 40) if level_2[:advice_addendum_2]
+ xml.tag! :AMEXTranAdvAddn3, byte_limit(level_2[:advice_addendum_3], 40) if level_2[:advice_addendum_3]
+ xml.tag! :AMEXTranAdvAddn4, byte_limit(level_2[:advice_addendum_4], 40) if level_2[:advice_addendum_4]
+ end
+ end
+
+ def add_level_2_purchase(xml, options={})
+ if (level_2 = options[:level_2_data])
+ xml.tag! :PCOrderNum, byte_limit(level_2[:purchase_order], 17) if level_2[:purchase_order]
+ xml.tag! :PCDestZip, byte_limit(format_address_field(level_2[:zip]), 10) if level_2[:zip]
+ xml.tag! :PCDestName, byte_limit(format_address_field(level_2[:name]), 30) if level_2[:name]
+ xml.tag! :PCDestAddress1, byte_limit(format_address_field(level_2[:address1]), 30) if level_2[:address1]
+ xml.tag! :PCDestAddress2, byte_limit(format_address_field(level_2[:address2]), 30) if level_2[:address2]
+ xml.tag! :PCDestCity, byte_limit(format_address_field(level_2[:city]), 20) if level_2[:city]
+ xml.tag! :PCDestState, byte_limit(format_address_field(level_2[:state]), 2) if level_2[:state]
+ end
+ end
+
def add_address(xml, creditcard, options)
if(address = (options[:billing_address] || options[:address]))
- avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
+ avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s) || empty?(address[:country])
if avs_supported
- xml.tag! :AVSzip, (address[:zip] ? address[:zip].to_s[0..9] : nil)
- xml.tag! :AVSaddress1, (address[:address1] ? address[:address1][0..29] : nil)
- xml.tag! :AVSaddress2, (address[:address2] ? address[:address2][0..29] : nil)
- xml.tag! :AVScity, (address[:city] ? address[:city][0..19] : nil)
- xml.tag! :AVSstate, address[:state]
- xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil)
+ xml.tag! :AVSzip, byte_limit(format_address_field(address[:zip]), 10)
+ xml.tag! :AVSaddress1, byte_limit(format_address_field(address[:address1]), 30)
+ xml.tag! :AVSaddress2, byte_limit(format_address_field(address[:address2]), 30)
+ xml.tag! :AVScity, byte_limit(format_address_field(address[:city]), 20)
+ xml.tag! :AVSstate, byte_limit(format_address_field(address[:state]), 2)
+ xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil)
end
- # can't look in billing address?
- xml.tag! :AVSname, ((creditcard && creditcard.name) ? creditcard.name[0..29] : nil)
- xml.tag! :AVScountryCode, (avs_supported ? address[:country] : '')
+
+ xml.tag! :AVSname, (creditcard&.name ? creditcard.name[0..29] : nil)
+ xml.tag! :AVScountryCode, (avs_supported ? byte_limit(format_address_field(address[:country]), 2) : '')
# Needs to come after AVScountryCode
add_destination_address(xml, address) if avs_supported
@@ -300,38 +407,44 @@ def add_address(xml, creditcard, options)
def add_destination_address(xml, address)
if address[:dest_zip]
- xml.tag! :AVSDestzip, (address[:dest_zip] ? address[:dest_zip].to_s[0..9] : nil)
- xml.tag! :AVSDestaddress1, (address[:dest_address1] ? address[:dest_address1][0..29] : nil)
- xml.tag! :AVSDestaddress2, (address[:dest_address2] ? address[:dest_address2][0..29] : nil)
- xml.tag! :AVSDestcity, (address[:dest_city] ? address[:dest_city][0..19] : nil)
- xml.tag! :AVSDeststate, address[:dest_state]
+ avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:dest_country].to_s)
+
+ xml.tag! :AVSDestzip, byte_limit(format_address_field(address[:dest_zip]), 10)
+ xml.tag! :AVSDestaddress1, byte_limit(format_address_field(address[:dest_address1]), 30)
+ xml.tag! :AVSDestaddress2, byte_limit(format_address_field(address[:dest_address2]), 30)
+ xml.tag! :AVSDestcity, byte_limit(format_address_field(address[:dest_city]), 20)
+ xml.tag! :AVSDeststate, byte_limit(format_address_field(address[:dest_state]), 2)
xml.tag! :AVSDestphoneNum, (address[:dest_phone] ? address[:dest_phone].scan(/\d/).join.to_s[0..13] : nil)
- xml.tag! :AVSDestname, (address[:dest_name] ? address[:dest_name][0..29] : nil)
- xml.tag! :AVSDestcountryCode, address[:dest_country]
+ xml.tag! :AVSDestname, byte_limit(address[:dest_name], 30)
+ xml.tag! :AVSDestcountryCode, (avs_supported ? address[:dest_country] : '')
end
end
# For Profile requests
def add_customer_address(xml, options)
if(address = (options[:billing_address] || options[:address]))
- xml.tag! :CustomerAddress1, (address[:address1] ? address[:address1][0..29] : nil)
- xml.tag! :CustomerAddress2, (address[:address2] ? address[:address2][0..29] : nil)
- xml.tag! :CustomerCity, (address[:city] ? address[:city][0..19] : nil)
- xml.tag! :CustomerState, address[:state]
- xml.tag! :CustomerZIP, (address[:zip] ? address[:zip].to_s[0..9] : nil)
- xml.tag! :CustomerEmail, address[:email].to_s[0..49] if address[:email]
+ avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
+
+ xml.tag! :CustomerAddress1, byte_limit(format_address_field(address[:address1]), 30)
+ xml.tag! :CustomerAddress2, byte_limit(format_address_field(address[:address2]), 30)
+ xml.tag! :CustomerCity, byte_limit(format_address_field(address[:city]), 20)
+ xml.tag! :CustomerState, byte_limit(format_address_field(address[:state]), 2)
+ xml.tag! :CustomerZIP, byte_limit(format_address_field(address[:zip]), 10)
+ xml.tag! :CustomerEmail, byte_limit(address[:email], 50) if address[:email]
xml.tag! :CustomerPhone, (address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil)
- xml.tag! :CustomerCountryCode, (address[:country] ? address[:country][0..1] : nil)
+ xml.tag! :CustomerCountryCode, (avs_supported ? address[:country] : '')
end
end
def add_creditcard(xml, creditcard, currency=nil)
- xml.tag! :AccountNum, creditcard.number
- xml.tag! :Exp, expiry_date(creditcard)
+ unless creditcard.nil?
+ xml.tag! :AccountNum, creditcard.number
+ xml.tag! :Exp, expiry_date(creditcard)
+ end
xml.tag! :CurrencyCode, currency_code(currency)
- xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
+ xml.tag! :CurrencyExponent, currency_exponents(currency)
# If you are trying to collect a Card Verification Number
# (CardSecVal) for a Visa or Discover transaction, pass one of these values:
@@ -342,21 +455,83 @@ def add_creditcard(xml, creditcard, currency=nil)
# Null-fill this attribute OR
# Do not submit the attribute at all.
# - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
- if %w( visa discover ).include?(creditcard.brand)
- xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9')
+ unless creditcard.nil?
+ if creditcard.verification_value?
+ if %w( visa discover ).include?(creditcard.brand)
+ xml.tag! :CardSecValInd, '1'
+ end
+ xml.tag! :CardSecVal, creditcard.verification_value
+ end
end
- xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value?
+ end
+
+ def add_eci(xml, creditcard, three_d_secure)
+ eci = if three_d_secure
+ three_d_secure[:eci]
+ elsif creditcard.is_a?(NetworkTokenizationCreditCard)
+ creditcard.eci
+ end
+
+ xml.tag!(:AuthenticationECIInd, eci) if eci
+ end
+
+ def add_xid(xml, creditcard, three_d_secure)
+ xid = if three_d_secure && creditcard.brand == 'visa'
+ three_d_secure[:xid]
+ elsif creditcard.is_a?(NetworkTokenizationCreditCard)
+ creditcard.transaction_id
+ end
+
+ xml.tag!(:XID, xid) if xid
+ end
+
+ def add_cavv(xml, creditcard, three_d_secure)
+ return unless three_d_secure && creditcard.brand == 'visa'
+
+ xml.tag!(:CAVV, three_d_secure[:cavv])
+ end
+
+ def add_aav(xml, creditcard, three_d_secure)
+ return unless three_d_secure && creditcard.brand == 'master'
+
+ xml.tag!(:AAV, three_d_secure[:cavv])
+ end
+
+ def add_dpanind(xml, creditcard)
+ return unless creditcard.is_a?(NetworkTokenizationCreditCard)
+
+ xml.tag! :DPANInd, 'Y'
+ end
+
+ def add_digital_token_cryptogram(xml, creditcard)
+ return unless creditcard.is_a?(NetworkTokenizationCreditCard)
+
+ xml.tag! :DigitalTokenCryptogram, creditcard.payment_cryptogram
+ end
+
+ def add_aevv(xml, creditcard, three_d_secure)
+ return unless three_d_secure && creditcard.brand == 'american_express'
+
+ xml.tag!(:AEVV, three_d_secure[:cavv])
+ end
+
+ def add_pymt_brand_program_code(xml, creditcard, three_d_secure)
+ return unless three_d_secure && creditcard.brand == 'american_express'
+
+ xml.tag!(:PymtBrandProgramCode, 'ASK')
end
def add_refund(xml, currency=nil)
xml.tag! :AccountNum, nil
xml.tag! :CurrencyCode, currency_code(currency)
- xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
+ xml.tag! :CurrencyExponent, currency_exponents(currency)
end
def add_managed_billing(xml, options)
if mb = options[:managed_billing]
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
# default to recurring (R). Other option is deferred (D).
xml.tag! :MBType, mb[:type] || RECURRING
# default to Customer Reference Number
@@ -377,32 +552,65 @@ def add_managed_billing(xml, options)
end
end
+ def add_stored_credentials(xml, parameters)
+ return unless parameters[:mit_stored_credential_ind] == 'Y' || parameters[:stored_credential] && !parameters[:stored_credential].values.all?(&:nil?)
+ if msg_type = get_msg_type(parameters)
+ xml.tag! :MITMsgType, msg_type
+ end
+ xml.tag! :MITStoredCredentialInd, 'Y'
+ if parameters[:mit_submitted_transaction_id]
+ xml.tag! :MITSubmittedTransactionID, parameters[:mit_submitted_transaction_id]
+ elsif parameters.dig(:stored_credential, :network_transaction_id) && parameters.dig(:stored_credential, :initiator) == 'merchant'
+ xml.tag! :MITSubmittedTransactionID, parameters[:stored_credential][:network_transaction_id]
+ end
+ end
+
+ def get_msg_type(parameters)
+ return parameters[:mit_msg_type] if parameters[:mit_msg_type]
+ return 'CSTO' if parameters[:stored_credential][:initial_transaction]
+ return unless parameters[:stored_credential][:initiator] && parameters[:stored_credential][:reason_type]
+ initiator = case parameters[:stored_credential][:initiator]
+ when 'customer' then 'C'
+ when 'merchant' then 'M'
+ end
+ reason = case parameters[:stored_credential][:reason_type]
+ when 'recurring' then 'REC'
+ when 'installment' then 'INS'
+ when 'unscheduled' then 'USE'
+ end
+
+ "#{initiator}#{reason}"
+ end
+
def parse(body)
response = {}
xml = REXML::Document.new(body)
- root = REXML::XPath.first(xml, "//Response") ||
- REXML::XPath.first(xml, "//ErrorResponse")
+ root = REXML::XPath.first(xml, '//Response') ||
+ REXML::XPath.first(xml, '//ErrorResponse')
if root
root.elements.to_a.each do |node|
recurring_parse_element(response, node)
end
end
- response
+
+ response.delete_if { |k, _| SENSITIVE_FIELDS.include?(k) }
end
def recurring_parse_element(response, node)
if node.has_elements?
- node.elements.each{|e| recurring_parse_element(response, e) }
+ node.elements.each { |e| recurring_parse_element(response, e) }
else
response[node.name.underscore.to_sym] = node.text
end
end
def commit(order, message_type, trace_number=nil)
- headers = POST_HEADERS.merge("Content-length" => order.size.to_s)
- headers.merge!( "Trace-number" => trace_number.to_s,
- "Merchant-Id" => @options[:merchant_id] ) if @options[:retry_logic] && trace_number
- request = lambda{|url| parse(ssl_post(url, order, headers))}
+ headers = POST_HEADERS.merge('Content-length' => order.size.to_s)
+ if @options[:retry_logic] && trace_number
+ headers['Trace-number'] = trace_number.to_s
+ headers['Merchant-Id'] = @options[:merchant_id]
+ end
+ request = ->(url) { parse(ssl_post(url, order, headers)) }
# Failover URL will be attempted in the event of a connection error
response = begin
@@ -416,7 +624,7 @@ def commit(order, message_type, trace_number=nil)
:authorization => authorization_string(response[:tx_ref_num], response[:order_id]),
:test => self.test?,
:avs_result => OrbitalGateway::AVSResult.new(response[:avs_resp_code]),
- :cvv_result => response[:cvv2_resp_code]
+ :cvv_result => OrbitalGateway::CVVResult.new(response[:cvv2_resp_code])
}
)
end
@@ -436,7 +644,7 @@ def success?(response, message_type)
response[:profile_proc_status] == SUCCESS
else
response[:proc_status] == SUCCESS &&
- response[:resp_code] == APPROVED
+ APPROVED.include?(response[:resp_code])
end
end
@@ -448,7 +656,7 @@ def ip_authentication?
@options[:ip_authentication] == true
end
- def build_new_order_xml(action, money, parameters = {})
+ def build_new_order_xml(action, money, creditcard, parameters = {})
requires!(parameters, :order_id)
xml = xml_envelope
xml.tag! :Request do
@@ -471,14 +679,30 @@ def build_new_order_xml(action, money, parameters = {})
yield xml if block_given?
+ three_d_secure = parameters[:three_d_secure]
+
+ add_eci(xml, creditcard, three_d_secure)
+ add_cavv(xml, creditcard, three_d_secure)
+ add_xid(xml, creditcard, three_d_secure)
+
xml.tag! :OrderID, format_order_id(parameters[:order_id])
xml.tag! :Amount, amount(money)
xml.tag! :Comments, parameters[:comments] if parameters[:comments]
+ add_level_2_tax(xml, parameters)
+ add_level_2_advice_addendum(xml, parameters)
+
+ add_aav(xml, creditcard, three_d_secure)
# CustomerAni, AVSPhoneType and AVSDestPhoneType could be added here.
+ add_dpanind(xml, creditcard)
+ add_aevv(xml, creditcard, three_d_secure)
+ add_digital_token_cryptogram(xml, creditcard)
+
if parameters[:soft_descriptors].is_a?(OrbitalSoftDescriptors)
add_soft_descriptors(xml, parameters[:soft_descriptors])
+ elsif parameters[:soft_descriptors].is_a?(Hash)
+ add_soft_descriptors_from_hash(xml, parameters[:soft_descriptors])
end
set_recurring_ind(xml, parameters)
@@ -488,6 +712,10 @@ def build_new_order_xml(action, money, parameters = {})
tx_ref_num, _ = split_authorization(parameters[:authorization])
xml.tag! :TxRefNum, tx_ref_num
end
+
+ add_level_2_purchase(xml, parameters)
+ add_stored_credentials(xml, parameters)
+ add_pymt_brand_program_code(xml, creditcard, three_d_secure)
end
end
xml.target!
@@ -498,7 +726,7 @@ def build_new_order_xml(action, money, parameters = {})
# RS - Subsequent Recurring Transactions
def set_recurring_ind(xml, parameters)
if parameters[:recurring_ind]
- raise "RecurringInd must be set to either \"RF\" or \"RS\"" unless %w(RF RS).include?(parameters[:recurring_ind])
+ raise 'RecurringInd must be set to either "RF" or "RS"' unless %w(RF RS).include?(parameters[:recurring_ind])
xml.tag! :RecurringInd, parameters[:recurring_ind]
end
end
@@ -511,8 +739,11 @@ def build_mark_for_capture_xml(money, authorization, parameters = {})
add_xml_credentials(xml)
xml.tag! :OrderID, format_order_id(order_id)
xml.tag! :Amount, amount(money)
+ add_level_2_tax(xml, parameters)
add_bin_merchant_and_terminal(xml, parameters)
xml.tag! :TxRefNum, tx_ref_num
+ add_level_2_purchase(xml, parameters)
+ add_level_2_advice_addendum(xml, parameters)
end
end
xml.target!
@@ -540,6 +771,10 @@ def currency_code(currency)
CURRENCY_CODES[(currency || self.default_currency)].to_s
end
+ def currency_exponents(currency)
+ CURRENCY_EXPONENTS[(currency || self.default_currency)].to_s
+ end
+
def expiry_date(credit_card)
"#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
end
@@ -577,13 +812,34 @@ def salem_mid?
# 2. - , $ @ & and a space character, though the space character cannot be the leading character
# 3. PINless Debit transactions can only use uppercase and lowercase alpha (A-Z, a-z) and numeric (0-9)
def format_order_id(order_id)
- illegal_characters = /[^,$@\- \w]/
+ illegal_characters = /[^,$@&\- \w]/
order_id = order_id.to_s.gsub(/\./, '-')
order_id.gsub!(illegal_characters, '')
+ order_id.lstrip!
order_id[0...22]
end
+ # Address-related fields cannot contain % | ^ \ /
+ # Returns the value with these characters removed, or nil
+ def format_address_field(value)
+ value.gsub(/[%\|\^\\\/]/, '') if value.respond_to?(:gsub)
+ end
+
+ # Field lengths should be limited by byte count instead of character count
+ # Returns the truncated value or nil
+ def byte_limit(value, byte_length)
+ limited_value = ''
+
+ value.to_s.each_char do |c|
+ break if((limited_value.bytesize + c.bytesize) > byte_length)
+ limited_value << c
+ end
+
+ limited_value
+ end
+
def build_customer_request_xml(creditcard, options = {})
+ ActiveMerchant.deprecated 'Customer Profile support in Orbital is non-conformant to the ActiveMerchant API and will be removed in its current form in a future version. Please contact the ActiveMerchant maintainers if you have an interest in modifying it to conform to the store/unstore/update API.'
xml = xml_envelope
xml.tag! :Request do
xml.tag! :Profile do
@@ -620,7 +876,7 @@ def build_customer_request_xml(creditcard, options = {})
end
xml.tag! :CCAccountNum, creditcard.number if creditcard
- xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime("%m%y") if creditcard
+ xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime('%m%y') if creditcard
# This has to come after CCExpireDate.
add_managed_billing(xml, options)
@@ -628,6 +884,123 @@ def build_customer_request_xml(creditcard, options = {})
end
xml.target!
end
+
+ # Unfortunately, Orbital uses their own special codes for AVS responses
+ # that are different than the standard codes defined in
+ # ActiveMerchant::Billing::AVSResult.
+ #
+ # This class encapsulates the response codes shown on page 240 of their spec:
+ # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
+ #
+ class AVSResult < ActiveMerchant::Billing::AVSResult
+ CODES = {
+ '1' => 'No address supplied',
+ '2' => 'Bill-to address did not pass Auth Host edit checks',
+ '3' => 'AVS not performed',
+ '4' => 'Issuer does not participate in AVS',
+ '5' => 'Edit-error - AVS data is invalid',
+ '6' => 'System unavailable or time-out',
+ '7' => 'Address information unavailable',
+ '8' => 'Transaction Ineligible for AVS',
+ '9' => 'Zip Match/Zip 4 Match/Locale match',
+ 'A' => 'Zip Match/Zip 4 Match/Locale no match',
+ 'B' => 'Zip Match/Zip 4 no Match/Locale match',
+ 'C' => 'Zip Match/Zip 4 no Match/Locale no match',
+ 'D' => 'Zip No Match/Zip 4 Match/Locale match',
+ 'E' => 'Zip No Match/Zip 4 Match/Locale no match',
+ 'F' => 'Zip No Match/Zip 4 No Match/Locale match',
+ 'G' => 'No match at all',
+ 'H' => 'Zip Match/Locale match',
+ 'J' => 'Issuer does not participate in Global AVS',
+ 'JA' => 'International street address and postal match',
+ 'JB' => 'International street address match. Postal code not verified',
+ 'JC' => 'International street address and postal code not verified',
+ 'JD' => 'International postal code match. Street address not verified',
+ 'M1' => 'Cardholder name matches',
+ 'M2' => 'Cardholder name, billing address, and postal code matches',
+ 'M3' => 'Cardholder name and billing code matches',
+ 'M4' => 'Cardholder name and billing address match',
+ 'M5' => 'Cardholder name incorrect, billing address and postal code match',
+ 'M6' => 'Cardholder name incorrect, billing postal code matches',
+ 'M7' => 'Cardholder name incorrect, billing address matches',
+ 'M8' => 'Cardholder name, billing address and postal code are all incorrect',
+ 'N3' => 'Address matches, ZIP not verified',
+ 'N4' => 'Address and ZIP code not verified due to incompatible formats',
+ 'N5' => 'Address and ZIP code match (International only)',
+ 'N6' => 'Address not verified (International only)',
+ 'N7' => 'ZIP matches, address not verified',
+ 'N8' => 'Address and ZIP code match (International only)',
+ 'N9' => 'Address and ZIP code match (UK only)',
+ 'R' => 'Issuer does not participate in AVS',
+ 'UK' => 'Unknown',
+ 'X' => 'Zip Match/Zip 4 Match/Address Match',
+ 'Z' => 'Zip Match/Locale no match',
+ }
+
+ # Map vendor's AVS result code to a postal match code
+ ORBITAL_POSTAL_MATCH_CODE = {
+ 'Y' => %w( 9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z ),
+ 'N' => %w( D E F G M8 ),
+ 'X' => %w( 4 J R ),
+ nil => %w( 1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UK )
+ }.inject({}) do |map, (type, codes)|
+ codes.each { |code| map[code] = type }
+ map
+ end
+
+ # Map vendor's AVS result code to a street match code
+ ORBITAL_STREET_MATCH_CODE = {
+ 'Y' => %w( 9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X ),
+ 'N' => %w( A C E G M8 Z ),
+ 'X' => %w( 4 J R ),
+ nil => %w( 1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UK )
+ }.inject({}) do |map, (type, codes)|
+ codes.each { |code| map[code] = type }
+ map
+ end
+
+ def self.messages
+ CODES
+ end
+
+ def initialize(code)
+ @code = (code.blank? ? nil : code.to_s.strip.upcase)
+ if @code
+ @message = CODES[@code]
+ @postal_match = ORBITAL_POSTAL_MATCH_CODE[@code]
+ @street_match = ORBITAL_STREET_MATCH_CODE[@code]
+ end
+ end
+ end
+
+ # Unfortunately, Orbital uses their own special codes for CVV responses
+ # that are different than the standard codes defined in
+ # ActiveMerchant::Billing::CVVResult.
+ #
+ # This class encapsulates the response codes shown on page 255 of their spec:
+ # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
+ #
+ class CVVResult < ActiveMerchant::Billing::CVVResult
+ MESSAGES = {
+ 'M' => 'Match',
+ 'N' => 'No match',
+ 'P' => 'Not processed',
+ 'S' => 'Should have been present',
+ 'U' => 'Unsupported by issuer/Issuer unable to process request',
+ 'I' => 'Invalid',
+ 'Y' => 'Invalid',
+ '' => 'Not applicable'
+ }
+
+ def self.messages
+ MESSAGES
+ end
+
+ def initialize(code)
+ @code = code.blank? ? '' : code.upcase
+ @message = MESSAGES[@code]
+ end
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/orbital/avs_result.rb b/lib/active_merchant/billing/gateways/orbital/avs_result.rb
deleted file mode 100644
index 264030a2347..00000000000
--- a/lib/active_merchant/billing/gateways/orbital/avs_result.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class OrbitalGateway < Gateway
- # Unfortunately, Orbital uses their own special codes for AVS responses
- # that are different than the standard codes defined in
- # ActiveMerchant::Billing::AVSResult.
- #
- # This class encapsulates the response codes shown on page 240 of their spec:
- # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
- #
- class AVSResult < ActiveMerchant::Billing::AVSResult
- CODES = {
- '1' => 'No address supplied',
- '2' => 'Bill-to address did not pass Auth Host edit checks',
- '3' => 'AVS not performed',
- '4' => 'Issuer does not participate in AVS',
- '5' => 'Edit-error - AVS data is invalid',
- '6' => 'System unavailable or time-out',
- '7' => 'Address information unavailable',
- '8' => 'Transaction Ineligible for AVS',
- '9' => 'Zip Match/Zip 4 Match/Locale match',
- 'A' => 'Zip Match/Zip 4 Match/Locale no match',
- 'B' => 'Zip Match/Zip 4 no Match/Locale match',
- 'C' => 'Zip Match/Zip 4 no Match/Locale no match',
- 'D' => 'Zip No Match/Zip 4 Match/Locale match',
- 'E' => 'Zip No Match/Zip 4 Match/Locale no match',
- 'F' => 'Zip No Match/Zip 4 No Match/Locale match',
- 'G' => 'No match at all',
- 'H' => 'Zip Match/Locale match',
- 'J' => 'Issuer does not participate in Global AVS',
- 'JA' => 'International street address and postal match',
- 'JB' => 'International street address match. Postal code not verified',
- 'JC' => 'International street address and postal code not verified',
- 'JD' => 'International postal code match. Street address not verified',
- 'M1' => 'Merchant Override Decline',
- 'M2' => 'Cardholder name, billing address, and postal code matches',
- 'M3' => 'Cardholder name and billing code matches',
- 'M4' => 'Cardholder name and billing address matches',
- 'M5' => 'Cardholder name incorrect, billing address and postal code match',
- 'M6' => 'Cardholder name incorrect, billing address matches',
- 'M7' => 'Cardholder name incorrect, billing address matches',
- 'M8' => 'Cardholder name, billing address and postal code are all incorrect',
- 'N3' => 'Address matches, ZIP not verified',
- 'N4' => 'Address and ZIP code not verified due to incompatible formats',
- 'N5' => 'Address and ZIP code match (International only)',
- 'N6' => 'Address not verified (International only)',
- 'N7' => 'ZIP matches, address not verified',
- 'N8' => 'Address and ZIP code match (International only)',
- 'N9' => 'Address and ZIP code match (UK only)',
- 'R' => 'Issuer does not participate in AVS',
- 'UX' => 'Unknown',
- 'X' => 'Zip Match/Zip 4 Match/Address Match',
- 'Z' => 'Zip Match/Locale no match',
- }
-
- # Map vendor's AVS result code to a postal match code
- ORBITAL_POSTAL_MATCH_CODE = {
- 'Y' => %w( 9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z ),
- 'N' => %w( D E F G M8 ),
- 'X' => %w( 4 J R ),
- nil => %w( 1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UX )
- }.inject({}) do |map, (type, codes)|
- codes.each { |code| map[code] = type }
- map
- end
-
- # Map vendor's AVS result code to a street match code
- ORBITAL_STREET_MATCH_CODE = {
- 'Y' => %w( 9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X ),
- 'N' => %w( A C E G M8 Z ),
- 'X' => %w( 4 J R ),
- nil => %w( 1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UX )
- }.inject({}) do |map, (type, codes)|
- codes.each { |code| map[code] = type }
- map
- end
-
- def self.messages
- CODES
- end
-
- def initialize(code)
- @code = code.to_s.strip.upcase unless code.blank?
- if @code
- @message = CODES[@code]
- @postal_match = ORBITAL_POSTAL_MATCH_CODE[@code]
- @street_match = ORBITAL_STREET_MATCH_CODE[@code]
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb b/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb
index 010af391187..da1d82d0ca0 100644
--- a/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb
+++ b/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb
@@ -1,23 +1,21 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- class OrbitalSoftDescriptors
- include Validateable
-
+ class OrbitalSoftDescriptors < Model
PHONE_FORMAT_1 = /\A\d{3}-\d{3}-\d{4}\z/
PHONE_FORMAT_2 = /\A\d{3}-\w{7}\z/
-
+
# ==== Tampa PNS Soft Descriptors
- # The support for Soft Descriptors via the PNS Host is only for customers processing through Chase
- # Paymentech Canada.
+ # The support for Soft Descriptors via the PNS Host is only for customers processing through Chase
+ # Paymentech Canada.
- # Unlike Salem, the only value that gets passed on the cardholder statement is the Merchant Name field.
- # And for these customers, it is a maximum of 25 bytes of data.
- #
- # All other Soft Descriptor fields can optionally be sent, but will not be submitted to the settlement host
+ # Unlike Salem, the only value that gets passed on the cardholder statement is the Merchant Name field.
+ # And for these customers, it is a maximum of 25 bytes of data.
+ #
+ # All other Soft Descriptor fields can optionally be sent, but will not be submitted to the settlement host
# and will not display on the cardholder statement.
-
+
attr_accessor :merchant_name, :product_description, :merchant_city, :merchant_phone, :merchant_url, :merchant_email
-
+
def initialize(options = {})
self.merchant_name = options[:merchant_name]
self.merchant_city = options[:merchant_city]
@@ -25,22 +23,25 @@ def initialize(options = {})
self.merchant_url = options[:merchant_url]
self.merchant_email = options[:merchant_email]
end
-
+
def validate
- errors.add(:merchant_name, "is required") if self.merchant_name.blank?
- errors.add(:merchant_name, "is required to be 25 bytes or less") if self.merchant_name.bytesize > 25
-
- unless self.merchant_phone.blank? || self.merchant_phone.match(PHONE_FORMAT_1) || self.merchant_phone.match(PHONE_FORMAT_2)
- errors.add(:merchant_phone, "is required to follow \"NNN-NNN-NNNN\" or \"NNN-AAAAAAA\" format")
+ errors = []
+
+ errors << [:merchant_name, 'is required'] if self.merchant_name.blank?
+ errors << [:merchant_name, 'is required to be 25 bytes or less'] if self.merchant_name.bytesize > 25
+
+ if(!empty?(self.merchant_phone) && !self.merchant_phone.match(PHONE_FORMAT_1) && !self.merchant_phone.match(PHONE_FORMAT_2))
+ errors << [:merchant_phone, 'is required to follow "NNN-NNN-NNNN" or "NNN-AAAAAAA" format']
end
-
+
[:merchant_email, :merchant_url].each do |attr|
unless self.send(attr).blank?
- errors.add(attr, "is required to be 13 bytes or less") if self.send(attr).bytesize > 13
+ errors << [attr, 'is required to be 13 bytes or less'] if(self.send(attr).bytesize > 13)
end
end
+
+ errors_hash(errors)
end
-
end
end
end
diff --git a/lib/active_merchant/billing/gateways/pac_net_raven.rb b/lib/active_merchant/billing/gateways/pac_net_raven.rb
new file mode 100644
index 00000000000..952684b1943
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/pac_net_raven.rb
@@ -0,0 +1,206 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PacNetRavenGateway < Gateway
+
+ AVS_ADDRESS_CODES = {
+ 'avs_address_unavailable' => 'X',
+ 'avs_address_not_checked' => 'X',
+ 'avs_address_matched' => 'Y',
+ 'avs_address_not_matched' => 'N',
+ 'avs_address_partial_match' => 'N'
+ }
+
+ AVS_POSTAL_CODES = {
+ 'avs_postal_unavailable' => 'X',
+ 'avs_postal_not_checked' => 'X',
+ 'avs_postal_matched' => 'Y',
+ 'avs_postal_not_matched' => 'N',
+ 'avs_postal_partial_match' => 'N'
+ }
+
+ CVV2_CODES = {
+ 'cvv2_matched' => 'Y',
+ 'cvv2_not_matched' => 'N',
+ 'cvv2_unavailable' => 'X',
+ 'cvv2_not_checked' => 'X'
+ }
+
+ self.live_url = 'https://raven.deepcovelabs.net/realtime/'
+ self.test_url = self.live_url
+
+ self.supported_countries = ['US']
+ self.supported_cardtypes = [:visa, :master]
+ self.money_format = :cents
+ self.default_currency = 'USD'
+ self.homepage_url = 'https://www.deepcovelabs.com/raven'
+ self.display_name = 'Raven'
+
+ def initialize(options = {})
+ requires!(options, :user, :secret, :prn)
+ super
+ end
+
+ def authorize(money, creditcard, options = {})
+ post = {}
+ add_creditcard(post, creditcard)
+ add_currency_code(post, money, options)
+ add_address(post, options)
+ post['PRN'] = @options[:prn]
+
+ commit('cc_preauth', money, post)
+ end
+
+ def purchase(money, creditcard, options = {})
+ post = {}
+ add_currency_code(post, money, options)
+ add_creditcard(post, creditcard)
+ add_address(post, options)
+ post['PRN'] = @options[:prn]
+
+ commit('cc_debit', money, post)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ post['TrackingNumber'] = authorization
+ post['PymtType'] = options[:pymt_type]
+
+ commit('void', nil, post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ post['PreauthNumber'] = authorization
+ post['PRN'] = @options[:prn]
+ add_currency_code(post, money, options)
+
+ commit('cc_settle', money, post)
+ end
+
+ def refund(money, template_number, options = {})
+ post = {}
+ post['PRN'] = @options[:prn]
+ post['TemplateNumber'] = template_number
+ add_currency_code(post, money, options)
+
+ commit('cc_refund', money, post)
+ end
+
+ private
+
+ def add_creditcard(post, creditcard)
+ post['CardNumber'] = creditcard.number
+ post['Expiry'] = expdate(creditcard)
+ post['CVV2'] = creditcard.verification_value if creditcard.verification_value
+ end
+
+ def add_currency_code(post, money, options)
+ post['Currency'] = options[:currency] || currency(money)
+ end
+
+ def add_address(post, options)
+ if address = options[:billing_address] || options[:address]
+ post['BillingStreetAddressLineOne'] = address[:address1].to_s
+ post['BillingStreetAddressLineFour'] = address[:address2].to_s
+ post['BillingPostalCode'] = address[:zip].to_s
+ end
+ end
+
+ def parse(body)
+ Hash[body.split('&').map { |x| x.split('=').map { |y| CGI.unescape(y) } }]
+ end
+
+ def commit(action, money, parameters)
+ parameters['Amount'] = amount(money) unless action == 'void'
+
+ data = ssl_post url(action), post_data(action, parameters)
+
+ response = parse(data)
+ response[:action] = action
+
+ message = message_from(response)
+
+ test_mode = test? || message =~ /TESTMODE/
+
+ Response.new(success?(response), message, response,
+ :test => test_mode,
+ :authorization => response['TrackingNumber'],
+ :fraud_review => fraud_review?(response),
+ :avs_result => {
+ :postal_match => AVS_POSTAL_CODES[response['AVSPostalResponseCode']],
+ :street_match => AVS_ADDRESS_CODES[response['AVSAddressResponseCode']]
+ },
+ :cvv_result => CVV2_CODES[response['CVV2ResponseCode']]
+ )
+ end
+
+ def url(action)
+ (test? ? self.test_url : self.live_url) + endpoint(action)
+ end
+
+ def endpoint(action)
+ return 'void' if action == 'void'
+ 'submit'
+ end
+
+ def fraud_review?(response)
+ false
+ end
+
+ def success?(response)
+ if %w(cc_settle cc_debit cc_preauth cc_refund).include?(response[:action])
+ !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Approved'
+ elsif response[:action] = 'void'
+ !response['ApprovalCode'].nil? and response['ErrorCode'].nil? and response['Status'] == 'Voided'
+ end
+ end
+
+ def message_from(response)
+ return response['Message'] if response['Message']
+
+ if response['Status'] == 'Approved'
+ 'This transaction has been approved'
+ elsif response['Status'] == 'Declined'
+ 'This transaction has been declined'
+ elsif response['Status'] == 'Voided'
+ 'This transaction has been voided'
+ else
+ response['Status']
+ end
+ end
+
+ def post_data(action, parameters = {})
+ post = {}
+
+ post['PymtType'] = action
+ post['RAPIVersion'] = '2'
+ post['UserName'] = @options[:user]
+ post['Timestamp'] = timestamp
+ post['RequestID'] = request_id
+ post['Signature'] = signature(action, post, parameters)
+
+ request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ request
+ end
+
+ def timestamp
+ Time.now.strftime('%Y-%m-%dT%H:%M:%S.Z')
+ end
+
+ def request_id
+ SecureRandom.uuid
+ end
+
+ def signature(action, post, parameters = {})
+ string = if %w(cc_settle cc_debit cc_preauth cc_refund).include?(action)
+ post['UserName'] + post['Timestamp'] + post['RequestID'] + post['PymtType'] + parameters['Amount'].to_s + parameters['Currency']
+ elsif action == 'void'
+ post['UserName'] + post['Timestamp'] + post['RequestID'] + parameters['TrackingNumber']
+ else
+ post['UserName']
+ end
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new(@options[:secret]), @options[:secret], string)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/pagarme.rb b/lib/active_merchant/billing/gateways/pagarme.rb
new file mode 100644
index 00000000000..26545c7c2d3
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/pagarme.rb
@@ -0,0 +1,246 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PagarmeGateway < Gateway
+ self.live_url = 'https://api.pagar.me/1/'
+
+ self.supported_countries = ['BR']
+ self.default_currency = 'BRL'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
+
+ self.homepage_url = 'https://pagar.me/'
+ self.display_name = 'Pagar.me'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ 'refused' => STANDARD_ERROR_CODE[:card_declined],
+ 'processing_error' => STANDARD_ERROR_CODE[:processing_error],
+ }
+
+ def initialize(options={})
+ requires!(options, :api_key)
+ @api_key = options[:api_key]
+
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ post = {}
+ add_amount(post, money)
+ add_payment_method(post, payment_method)
+ add_metadata(post, options)
+
+ commit(:post, 'transactions', post)
+ end
+
+ def authorize(money, payment_method, options={})
+ post = {}
+ add_amount(post, money)
+ add_payment_method(post, payment_method)
+ add_metadata(post, options)
+
+ post[:capture] = false
+
+ commit(:post, 'transactions', post)
+ end
+
+ def capture(money, authorization, options={})
+ if authorization.nil?
+ return Response.new(false, 'Não é possível capturar uma transação sem uma prévia autorização.')
+ end
+
+ post = {}
+ commit(:post, "transactions/#{authorization}/capture", post)
+ end
+
+ def refund(money, authorization, options={})
+ if authorization.nil?
+ return Response.new(false, 'Não é possível estornar uma transação sem uma prévia captura.')
+ end
+
+ void(authorization, options)
+ end
+
+ def void(authorization, options={})
+ if authorization.nil?
+ return Response.new(false, 'Não é possível estornar uma transação autorizada sem uma prévia autorização.')
+ end
+
+ post = {}
+ commit(:post, "transactions/#{authorization}/refund", post)
+ end
+
+ def verify(payment_method, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(127, payment_method, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((card_number=)\d+), '\1[FILTERED]').
+ gsub(%r((card_cvv=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_amount(post, money)
+ post[:amount] = amount(money)
+ end
+
+ def add_payment_method(post, payment_method)
+ post[:payment_method] = 'credit_card'
+ add_credit_card(post, payment_method)
+ end
+
+ def add_credit_card(post, credit_card)
+ post[:card_number] = credit_card.number
+ post[:card_holder_name] = credit_card.name
+ post[:card_expiration_date] = "#{credit_card.month}/#{credit_card.year}"
+ post[:card_cvv] = credit_card.verification_value
+ end
+
+ def add_metadata(post, options={})
+ post[:metadata] = {}
+ post[:metadata][:order_id] = options[:order_id]
+ post[:metadata][:ip] = options[:ip]
+ post[:metadata][:customer] = options[:customer]
+ post[:metadata][:invoice] = options[:invoice]
+ post[:metadata][:merchant] = options[:merchant]
+ post[:metadata][:description] = options[:description]
+ post[:metadata][:email] = options[:email]
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def post_data(params)
+ return nil unless params
+
+ params.map do |key, value|
+ next if value != false && value.blank?
+ if value.is_a?(Hash)
+ h = {}
+ value.each do |k, v|
+ h["#{key}[#{k}]"] = v unless v.blank?
+ end
+ post_data(h)
+ elsif value.is_a?(Array)
+ value.map { |v| "#{key}[]=#{CGI.escape(v.to_s)}" }.join('&')
+ else
+ "#{key}=#{CGI.escape(value.to_s)}"
+ end
+ end.compact.join('&')
+ end
+
+ def headers(options = {})
+ {
+ 'Authorization' => 'Basic ' + Base64.encode64(@api_key.to_s + ':x').strip,
+ 'User-Agent' => "Pagar.me/1 ActiveMerchant/#{ActiveMerchant::VERSION}",
+ 'Accept-Encoding' => 'deflate'
+ }
+ end
+
+ def api_request(method, endpoint, parameters = nil, options = {})
+ raw_response = response = nil
+ begin
+ raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options))
+ response = parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response = response_error(raw_response)
+ rescue JSON::ParserError
+ response = json_error(raw_response)
+ end
+ response
+ end
+
+ def commit(method, url, parameters, options = {})
+ response = api_request(method, url, parameters, options)
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def json_error(raw_response)
+ msg = 'Resposta inválida retornada pela API do Pagar.me. Por favor entre em contato com suporte@pagar.me se você continuar recebendo essa mensagem.'
+ msg += " (A resposta retornada pela API foi #{raw_response.inspect})"
+ {
+ 'errors' => [{
+ 'message' => msg
+ }]
+ }
+ end
+
+ def success_from(response)
+ success_purchase = response.key?('status') && response['status'] == 'paid'
+ success_authorize = response.key?('status') && response['status'] == 'authorized'
+ success_refund = response.key?('status') && response['status'] == 'refunded'
+
+ success_purchase || success_authorize || success_refund
+ end
+
+ def failure_from(response)
+ response.key?('status') && response['status'] == 'refused'
+ end
+
+ def message_from(response)
+ if success_from(response)
+ case response['status']
+ when 'paid'
+ 'Transação aprovada'
+ when 'authorized'
+ 'Transação autorizada'
+ when 'refunded'
+ 'Transação estornada'
+ else
+ "Transação com status '#{response["status"]}'"
+ end
+ elsif failure_from(response)
+ 'Transação recusada'
+ elsif response.key?('errors')
+ response['errors'][0]['message']
+ else
+ msg = json_error(response)
+ msg['errors'][0]['message']
+ end
+ end
+
+ def authorization_from(response)
+ if success_from(response)
+ response['id']
+ end
+ end
+
+ def test?
+ @api_key.start_with?('ak_test')
+ end
+
+ def error_code_from(response)
+ if failure_from(response)
+ STANDARD_ERROR_CODE_MAPPING['refused']
+ elsif response.key?('errors')
+ STANDARD_ERROR_CODE_MAPPING['processing_error']
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/pago_facil.rb b/lib/active_merchant/billing/gateways/pago_facil.rb
new file mode 100644
index 00000000000..85793fbb369
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/pago_facil.rb
@@ -0,0 +1,122 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PagoFacilGateway < Gateway
+ self.test_url = 'https://www.pagofacil.net/st/public/Wsrtransaccion/index/format/json?'
+ self.live_url = 'https://www.pagofacil.net/ws/public/Wsrtransaccion/index/format/json?'
+
+ self.supported_countries = ['MX']
+ self.default_currency = 'MXN'
+ self.supported_cardtypes = [:visa, :master, :american_express, :jcb]
+
+ self.homepage_url = 'http://www.pagofacil.net/'
+ self.display_name = 'PagoFacil'
+
+ def initialize(options={})
+ requires!(options, :branch_id, :merchant_id, :service_id)
+ super
+ end
+
+ def purchase(money, credit_card, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, credit_card)
+ add_address(post, options)
+ add_customer_data(post, options)
+ add_merchant_data(post)
+
+ commit(post)
+ end
+
+ private
+
+ def add_customer_data(post, options)
+ post[:email] = options[:email]
+ post[:celular] = options[:cellphone]
+ end
+
+ def add_address(post, options)
+ address = options.fetch(:billing_address, {})
+ post[:calleyNumero] = address[:address1]
+ post[:colonia] = address[:address2]
+ post[:municipio] = address[:city]
+ post[:estado] = address[:state]
+ post[:pais] = address[:country]
+ post[:telefono] = address[:phone]
+ post[:cp] = address[:zip]
+ end
+
+ def add_invoice(post, money, options)
+ post[:monto] = amount(money)
+ post[:idPedido] = options[:order_id]
+ add_currency(post, money, options)
+ end
+
+ def add_currency(post, money, options)
+ currency = options.fetch(:currency, currency(money))
+ unless currency == self.class.default_currency
+ post[:divisa] = currency
+ end
+ end
+
+ def add_payment(post, credit_card)
+ post[:nombre] = credit_card.first_name
+ post[:apellidos] = credit_card.last_name
+ post[:numeroTarjeta] = credit_card.number
+ post[:cvt] = credit_card.verification_value
+ post[:mesExpiracion] = sprintf('%02d', credit_card.month)
+ post[:anyoExpiracion] = credit_card.year.to_s.slice(-2, 2)
+ end
+
+ def add_merchant_data(post)
+ post[:idSucursal] = options.fetch(:branch_id)
+ post[:idUsuario] = options.fetch(:merchant_id)
+ post[:idServicio] = options.fetch(:service_id)
+ end
+
+ def parse(body)
+ JSON.parse(body)['WebServices_Transacciones']['transaccion']
+ rescue JSON::ParserError
+ json_error(body)
+ end
+
+ def commit(parameters)
+ url = (test? ? test_url : live_url)
+ response = parse(ssl_post(url, post_data(parameters)))
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?
+ )
+ end
+
+ def success_from(response)
+ response['autorizado'] == '1' ||
+ response['autorizado'] == true
+ end
+
+ def message_from(response)
+ response['texto']
+ end
+
+ def authorization_from(response)
+ response['autorizacion']
+ end
+
+ def post_data(parameters = {})
+ {
+ method: 'transaccion',
+ data: parameters
+ }.to_query
+ end
+
+ def json_error(response)
+ {
+ 'texto' => 'Invalid response received from the PagoFacil API.',
+ 'raw_response' => response
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/pay_conex.rb b/lib/active_merchant/billing/gateways/pay_conex.rb
new file mode 100644
index 00000000000..d0ae08146e8
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/pay_conex.rb
@@ -0,0 +1,245 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayConexGateway < Gateway
+ include Empty
+
+ self.test_url = 'https://cert.payconex.net/api/qsapi/3.8/'
+ self.live_url = 'https://secure.payconex.net/api/qsapi/3.8/'
+
+ self.supported_countries = %w(US CA)
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
+
+ self.homepage_url = 'http://www.bluefincommerce.com/'
+ self.display_name = 'PayConex'
+
+ def initialize(options={})
+ requires!(options, :account_id, :api_accesskey)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ post = {}
+ add_auth_purchase_params(post, money, payment_method, options)
+ commit('SALE', post)
+ end
+
+ def authorize(money, payment_method, options={})
+ post = {}
+ add_auth_purchase_params(post, money, payment_method, options)
+ commit('AUTHORIZATION', post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ add_reference_params(post, authorization, options)
+ add_amount(post, money, options)
+ commit('CAPTURE', post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ add_reference_params(post, authorization, options)
+ add_amount(post, money, options)
+ commit('REFUND', post)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ add_reference_params(post, authorization, options)
+ commit('REVERSAL', post)
+ end
+
+ def credit(money, payment_method, options={})
+ if payment_method.is_a?(String)
+ raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card or use the #refund method.'
+ end
+
+ post = {}
+ add_auth_purchase_params(post, money, payment_method, options)
+ commit('CREDIT', post)
+ end
+
+ def verify(payment_method, options={})
+ authorize(0, payment_method, options)
+ end
+
+ def store(payment_method, options={})
+ post = {}
+ add_credentials(post)
+ add_payment_method(post, payment_method)
+ add_address(post, options)
+ add_common_options(post, options)
+ commit('STORE', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ force_utf8(transcript).
+ gsub(%r((api_accesskey=)\w+), '\1[FILTERED]').
+ gsub(%r((card_number=)\w+), '\1[FILTERED]').
+ gsub(%r((card_verification=)\w+), '\1[FILTERED]')
+ end
+
+ private
+
+ def force_utf8(string)
+ return nil unless string
+ binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there.
+ binary.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
+ end
+
+ def add_credentials(post)
+ post[:account_id] = @options[:account_id]
+ post[:api_accesskey] = @options[:api_accesskey]
+ end
+
+ def add_auth_purchase_params(post, money, payment_method, options)
+ add_credentials(post)
+ add_payment_method(post, payment_method)
+ add_address(post, options)
+ add_common_options(post, options)
+ add_amount(post, money, options)
+ add_if_present(post, :email, options[:email])
+ end
+
+ def add_reference_params(post, authorization, options)
+ add_credentials(post)
+ add_common_options(post, options)
+ add_token_id(post, authorization)
+ end
+
+ def add_amount(post, money, options)
+ post[:transaction_amount] = amount(money)
+ currency_code = (options[:currency] || currency(money))
+ add_if_present(post, :currency, currency_code)
+ end
+
+ def add_payment_method(post, payment_method)
+ case payment_method
+ when String
+ add_token_payment_method(post, payment_method)
+ when Check
+ add_check(post, payment_method)
+ else
+ if payment_method.respond_to?(:track_data) && payment_method.track_data.present?
+ add_card_present_payment_method(post, payment_method)
+ else
+ add_credit_card(post, payment_method)
+ end
+ end
+ end
+
+ def add_credit_card(post, payment_method)
+ post[:tender_type] = 'CARD'
+ post[:card_number] = payment_method.number
+ post[:card_expiration] = expdate(payment_method)
+ post[:card_verification] = payment_method.verification_value
+ post[:first_name] = payment_method.first_name
+ post[:last_name] = payment_method.last_name
+ end
+
+ def add_token_payment_method(post, payment_method)
+ post[:tender_type] = 'CARD'
+ post[:token_id] = payment_method
+ post[:reissue] = true
+ end
+
+ def add_card_present_payment_method(post, payment_method)
+ post[:tender_type] = 'CARD'
+ post[:card_tracks] = payment_method.track_data
+ end
+
+ def add_check(post, payment_method)
+ post[:tender_type] = 'ACH'
+ post[:first_name] = payment_method.first_name
+ post[:last_name] = payment_method.last_name
+ post[:bank_account_number] = payment_method.account_number
+ post[:bank_routing_number] = payment_method.routing_number
+ post[:check_number] = payment_method.number
+ add_if_present(post, :ach_account_type, payment_method.account_type)
+ end
+
+ def add_address(post, options)
+ address = options[:billing_address]
+ return unless address
+
+ add_if_present(post, :street_address1, address[:address1])
+ add_if_present(post, :street_address2, address[:address2])
+ add_if_present(post, :city, address[:city])
+ add_if_present(post, :state, address[:state])
+ add_if_present(post, :zip, address[:zip])
+ add_if_present(post, :country, address[:country])
+ add_if_present(post, :phone, address[:phone])
+ end
+
+ def add_common_options(post, options)
+ add_if_present(post, :transaction_description, options[:description])
+ add_if_present(post, :custom_id, options[:custom_id])
+ add_if_present(post, :custom_data, options[:custom_data])
+ add_if_present(post, :ip_address, options[:ip])
+ add_if_present(post, :payment_type, options[:payment_type])
+ add_if_present(post, :cashier, options[:cashier])
+
+ post[:disable_cvv] = options[:disable_cvv] unless options[:disable_cvv].nil?
+ post[:response_format] = 'JSON'
+ end
+
+ def add_if_present(post, key, value)
+ post[key] = value unless empty?(value)
+ end
+
+ def add_token_id(post, authorization)
+ post[:token_id] = authorization
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(action, params)
+ raw_response = ssl_post(url, post_data(action, params))
+ response = parse(raw_response)
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: response['transaction_id'],
+ :avs_result => AVSResult.new(code: response['avs_response']),
+ :cvv_result => CVVResult.new(response['cvv2_response']),
+ test: test?
+ )
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def success_from(response)
+ response['transaction_approved'] || !response['error']
+ end
+
+ def message_from(response)
+ success_from(response) ? response['authorization_message'] : response['error_message']
+ end
+
+ def post_data(action, params)
+ params[:transaction_type] = action
+ params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from PayConex. Please contact PayConex if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/pay_gate_xml.rb b/lib/active_merchant/billing/gateways/pay_gate_xml.rb
index 5f84e9f6d3f..35261aaf564 100644
--- a/lib/active_merchant/billing/gateways/pay_gate_xml.rb
+++ b/lib/active_merchant/billing/gateways/pay_gate_xml.rb
@@ -52,7 +52,7 @@ module Billing #:nodoc:
#
# PayGateXML Field ActiveMerchant Use
#
- # pgid use :login value to gateway instantation
+ # pgid use :login value to gateway instantiation
# pwd use :password value to gateway instantiation
#
# cname credit_card.name
@@ -102,45 +102,45 @@ class PayGateXmlGateway < Gateway
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",
+ 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)",
+ 900205 => 'Unexpected authentication result (phase 1)',
+ 900206 => 'Unexpected authentication result (phase 1)',
- 990001 => "Could not insert into Database",
+ 990001 => 'Could not insert into Database',
- 990022 => "Bank not available",
+ 990022 => 'Bank not available',
- 990053 => "Error processing transaction",
+ 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
+ 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",
+ 990024 => 'Duplicate Transaction Detected. Please check before submitting',
- 990028 => "Transaction cancelled" # Customer clicks the 'Cancel' button on the payment page
+ 990028 => 'Transaction cancelled' # Customer clicks the 'Cancel' button on the payment page
}
SUCCESS_CODES = %w( 990004 990005 990017 990012 990018 990031 )
@@ -162,22 +162,32 @@ def initialize(options = {})
def purchase(money, creditcard, options = {})
MultiResponse.run do |r|
- r.process{authorize(money, creditcard, options)}
- r.process{capture(money, r.authorization, options)}
+ r.process { authorize(money, creditcard, options) }
+ r.process { capture(money, r.authorization, options) }
end
end
def authorize(money, creditcard, options = {})
action = 'authtx'
- options.merge!(:money => money, :creditcard => creditcard)
+ options[:money] = money
+ options[:creditcard] = creditcard
commit(action, build_request(action, options))
end
def capture(money, authorization, options = {})
action = 'settletx'
- options.merge!(:money => money, :authorization => authorization)
+ 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
@@ -192,17 +202,18 @@ def build_request(action, options={})
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'
- money = options.delete(:money)
- creditcard = options.delete(:creditcard)
- build_authorization(protocol, money, creditcard, options)
- when 'settletx'
- money = options.delete(:money)
- authorization = options.delete(:authorization)
- build_capture(protocol, money, authorization, options)
+ 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"
+ raise 'no action specified for build_request'
end
end
@@ -218,7 +229,9 @@ def build_authorization(xml, money, creditcard, options={})
:budp => 0,
:amt => amount(money),
:cur => (options[:currency] || currency(money)),
- :cvv => creditcard.verification_value
+ :cvv => creditcard.verification_value,
+ :email => options[:email],
+ :ip => options[:ip]
}
end
@@ -228,6 +241,13 @@ def build_capture(xml, money, authorization, options={})
}
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)
@@ -244,11 +264,11 @@ def parse(action, body)
hash
end
- def commit(action, request)
+ 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 => response[:tid]
+ :authorization => authorization || response[:tid]
)
end
@@ -258,4 +278,3 @@ def message_from(response)
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/pay_hub.rb b/lib/active_merchant/billing/gateways/pay_hub.rb
new file mode 100644
index 00000000000..fdcc6c5d600
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/pay_hub.rb
@@ -0,0 +1,213 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayHubGateway < Gateway
+ self.live_url = 'https://checkout.payhub.com/transaction/api'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'http://www.payhub.com/'
+ self.display_name = 'PayHub'
+
+ CVV_CODE_TRANSLATOR = {
+ 'M' => 'CVV matches',
+ 'N' => 'CVV does not match',
+ 'P' => 'CVV not processed',
+ 'S' => 'CVV should have been present',
+ 'U' => 'CVV request unable to be processed by issuer'
+ }
+
+ AVS_CODE_TRANSLATOR = {
+ '0' => 'Approved, Address verification was not requested.',
+ 'A' => 'Approved, Address matches only.',
+ 'B' => 'Address Match. Street Address math for international transaction Postal Code not verified because of incompatible formats (Acquirer sent both street address and Postal Code)',
+ 'C' => 'Serv Unavailable. Street address and Postal Code not verified for international transaction because of incompatible formats (Acquirer sent both street and Postal Code).',
+ 'D' => 'Exact Match, Street Address and Postal Code match for international transaction.',
+ 'F' => 'Exact Match, Street Address and Postal Code match. Applies to UK only.',
+ 'G' => 'Ver Unavailable, Non-U.S. Issuer does not participate.',
+ 'I' => 'Ver Unavailable, Address information not verified for international transaction',
+ 'M' => 'Exact Match, Street Address and Postal Code match for international transaction',
+ 'N' => 'No - Address and ZIP Code does not match',
+ 'P' => 'Zip Match, Postal Codes match for international transaction Street address not verified because of incompatible formats (Acquirer sent both street address and Postal Code).',
+ 'R' => 'Retry - Issuer system unavailable',
+ 'S' => 'Serv Unavailable, Service not supported',
+ 'U' => 'Ver Unavailable, Address unavailable.',
+ 'W' => 'ZIP match - Nine character numeric ZIP match only.',
+ 'X' => 'Exact match, Address and nine-character ZIP match.',
+ 'Y' => 'Exact Match, Address and five character ZIP match.',
+ 'Z' => 'Zip Match, Five character numeric ZIP match only.',
+ '1' => 'Cardholder name and ZIP match AMEX only.',
+ '2' => 'Cardholder name, address, and ZIP match AMEX only.',
+ '3' => 'Cardholder name and address match AMEX only.',
+ '4' => 'Cardholder name match AMEX only.',
+ '5' => 'Cardholder name incorrect, ZIP match AMEX only.',
+ '6' => 'Cardholder name incorrect, address and ZIP match AMEX only.',
+ '7' => 'Cardholder name incorrect, address match AMEX only.',
+ '8' => 'Cardholder, all do not match AMEX only.'
+ }
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '14' => STANDARD_ERROR_CODE[:invalid_number],
+ '80' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '82' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '54' => STANDARD_ERROR_CODE[:expired_card],
+ '51' => STANDARD_ERROR_CODE[:card_declined],
+ '05' => STANDARD_ERROR_CODE[:card_declined],
+ '61' => STANDARD_ERROR_CODE[:card_declined],
+ '62' => STANDARD_ERROR_CODE[:card_declined],
+ '65' => STANDARD_ERROR_CODE[:card_declined],
+ '93' => STANDARD_ERROR_CODE[:card_declined],
+ '01' => STANDARD_ERROR_CODE[:call_issuer],
+ '02' => STANDARD_ERROR_CODE[:call_issuer],
+ '04' => STANDARD_ERROR_CODE[:pickup_card],
+ '07' => STANDARD_ERROR_CODE[:pickup_card],
+ '41' => STANDARD_ERROR_CODE[:pickup_card],
+ '43' => STANDARD_ERROR_CODE[:pickup_card]
+ }
+
+ def initialize(options={})
+ requires!(options, :orgid, :username, :password, :tid)
+
+ super
+ end
+
+ def authorize(amount, creditcard, options = {})
+ post = setup_post('auth')
+ add_creditcard(post, creditcard)
+ add_amount(post, amount)
+ add_address(post, (options[:address] || options[:billing_address]))
+ add_customer_data(post, options)
+
+ commit(post)
+ end
+
+ def purchase(amount, creditcard, options={})
+ post = setup_post('sale')
+ add_creditcard(post, creditcard)
+ add_amount(post, amount)
+ add_address(post, (options[:address] || options[:billing_address]))
+ add_customer_data(post, options)
+
+ commit(post)
+ end
+
+ def refund(amount, trans_id, options={})
+ # Attempt a void in case the transaction is unsettled
+ post = setup_post('void')
+ add_reference(post, trans_id)
+ response = commit(post)
+ return response if response.success?
+
+ post = setup_post('refund')
+ add_reference(post, trans_id)
+ commit(post)
+ end
+
+ def capture(amount, trans_id, options = {})
+ post = setup_post('capture')
+
+ add_reference(post, trans_id)
+ add_amount(post, amount)
+
+ commit(post)
+ end
+
+ # No void, as PayHub's void does not work on authorizations
+
+ def verify(creditcard, options={})
+ authorize(100, creditcard, options)
+ end
+
+ private
+
+ def setup_post(action)
+ post = {}
+ post[:orgid] = @options[:orgid]
+ post[:tid] = @options[:tid]
+ post[:username] = @options[:username]
+ post[:password] = @options[:password]
+ post[:mode] = (test? ? 'demo' : 'live')
+ post[:trans_type] = action
+ post
+ end
+
+ def add_reference(post, trans_id)
+ post[:trans_id] = trans_id
+ end
+
+ def add_customer_data(post, options = {})
+ post[:first_name] = options[:first_name]
+ post[:last_name] = options[:last_name]
+ post[:phone] = options[:phone]
+ post[:email] = options[:email]
+ end
+
+ def add_address(post, address)
+ return unless address
+ post[:address1] = address[:address1]
+ post[:address2] = address[:address2]
+ post[:zip] = address[:zip]
+ post[:state] = address[:state]
+ post[:city] = address[:city]
+ end
+
+ def add_amount(post, amount)
+ post[:amount] = amount(amount)
+ end
+
+ def add_creditcard(post, creditcard)
+ post[:cc] = creditcard.number
+ post[:month] = creditcard.month.to_s
+ post[:year] = creditcard.year.to_s
+ post[:cvv] = creditcard.verification_value
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(post)
+ success = false
+
+ begin
+ raw_response = ssl_post(live_url, post.to_json, {'Content-Type' => 'application/json'})
+ response = parse(raw_response)
+ success = (response['RESPONSE_CODE'] == '00')
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response = response_error(raw_response)
+ rescue JSON::ParserError
+ response = json_error(raw_response)
+ end
+
+ Response.new(success,
+ response_message(response),
+ response,
+ test: test?,
+ avs_result: {code: response['AVS_RESULT_CODE']},
+ cvv_result: response['VERIFICATION_RESULT_CODE'],
+ error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['RESPONSE_CODE']]),
+ authorization: response['TRANSACTION_ID']
+ )
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def json_error(raw_response)
+ {
+ error_message: 'Invalid response received from the Payhub API. Please contact wecare@payhub.com if you continue to receive this message.' \
+ " (The raw response returned by the API was #{raw_response.inspect})"
+ }
+ end
+
+ def response_message(response)
+ (response['RESPONSE_TEXT'] || response['RESPONSE_CODE'] || response[:error_message])
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/pay_junction.rb b/lib/active_merchant/billing/gateways/pay_junction.rb
index ceb2e184d93..68a24d6f8fc 100644
--- a/lib/active_merchant/billing/gateways/pay_junction.rb
+++ b/lib/active_merchant/billing/gateways/pay_junction.rb
@@ -73,16 +73,16 @@ module Billing #:nodoc:
#
# PayJunction Field ActiveMerchant Use
#
- # dc_logon provide as :login value to gateway instantation
+ # dc_logon provide as :login value to gateway instantiation
# dc_password provide as :password value to gateway instantiation
#
# dc_name will be retrieved from credit_card.name
- # dc_first_name :first_name on CreditCard object instantation
- # dc_last_name :last_name on CreditCard object instantation
- # dc_number :number on CreditCard object instantation
- # dc_expiration_month :month on CreditCard object instantation
- # dc_expiration_year :year on CreditCard object instantation
- # dc_verification_number :verification_value on CC object instantation
+ # dc_first_name :first_name on CreditCard object instantiation
+ # dc_last_name :last_name on CreditCard object instantiation
+ # dc_number :number on CreditCard object instantiation
+ # dc_expiration_month :month on CreditCard object instantiation
+ # dc_expiration_year :year on CreditCard object instantiation
+ # dc_verification_number :verification_value on CC object instantiation
#
# dc_transaction_amount include as argument to method for your transaction type
# dc_transaction_type do nothing, set by your transaction type
@@ -101,19 +101,19 @@ class PayJunctionGateway < Gateway
class_attribute :test_url, :live_url
- self.test_url = "https://www.payjunctionlabs.com/quick_link"
- self.live_url = "https://payjunction.com/quick_link"
+ self.test_url = 'https://www.payjunctionlabs.com/quick_link'
+ self.live_url = 'https://payjunction.com/quick_link'
TEST_LOGIN = 'pj-ql-01'
TEST_PASSWORD = 'pj-ql-01p'
- SUCCESS_CODES = ["00", "85"]
+ SUCCESS_CODES = ['00', '85']
SUCCESS_MESSAGE = 'The transaction was approved.'
FAILURE_MESSAGE = 'The transaction was declined.'
DECLINE_CODES = {
- "AE" => 'Address verification failed because address did not match.',
+ 'AE' => 'Address verification failed because address did not match.',
'ZE' => 'Address verification failed because zip did not match.',
'XE' => 'Address verification failed because zip and address did not match.',
'YE' => 'Address verification failed because zip and address did not match.',
@@ -144,8 +144,8 @@ class PayJunctionGateway < Gateway
'96' => 'Declined because of a system error.',
'N7' => 'Declined because of a CVV2/CVC2 mismatch.',
'M4' => 'Declined.',
- "FE" => "There was a format error with your Trinity Gateway Service (API) request.",
- "LE" => "Could not log you in (problem with dc_logon and/or dc_password).",
+ 'FE' => 'There was a format error with your Trinity Gateway Service (API) request.',
+ 'LE' => 'Could not log you in (problem with dc_logon and/or dc_password).',
'NL' => 'Aborted because of a system error, please try again later. ',
'AB' => 'Aborted because of an upstream system error, please try again later.'
}
@@ -211,7 +211,7 @@ def refund(money, authorization, options = {})
end
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
@@ -239,6 +239,8 @@ def void(authorization, options = {})
# YYYYMMDD format and can be used to specify when the first charge will be made.
# If omitted the first charge will be immediate.
def recurring(money, payment_source, options = {})
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
requires!(options, [:periodicity, :monthly, :weekly, :daily], :payments)
periodic_type = case options[:periodicity]
@@ -295,11 +297,15 @@ def add_payment_source(params, source)
# add fields for credit card
def add_creditcard(params, creditcard)
- params[:name] = creditcard.name
- params[:number] = creditcard.number
- params[:expiration_month] = creditcard.month
- params[:expiration_year] = creditcard.year
- params[:verification_number] = creditcard.verification_value if creditcard.verification_value?
+ if creditcard.respond_to?(:track_data) && creditcard.track_data.present?
+ params[:track] = creditcard.track_data
+ else
+ params[:name] = creditcard.name
+ params[:number] = creditcard.number
+ params[:expiration_month] = creditcard.month
+ params[:expiration_year] = creditcard.year
+ params[:verification_number] = creditcard.verification_value if creditcard.verification_value?
+ end
end
# add field for "instant" transaction, using previous transaction id
@@ -328,7 +334,7 @@ def add_optional_fields(params, options)
def commit(action, parameters)
url = test? ? self.test_url : self.live_url
- response = parse( ssl_post(url, post_data(action, parameters)) )
+ response = parse(ssl_post(url, post_data(action, parameters)))
Response.new(successful?(response), message_from(response), response,
:test => test?,
@@ -360,7 +366,7 @@ def post_data(action, params)
params[:version] = API_VERSION
params[:transaction_type] = action
- params.reject{|k,v| v.blank?}.collect{ |k, v| "dc_#{k.to_s}=#{CGI.escape(v.to_s)}" }.join("&")
+ params.reject { |k, v| v.blank? }.collect { |k, v| "dc_#{k}=#{CGI.escape(v.to_s)}" }.join('&')
end
def parse(body)
@@ -379,18 +385,6 @@ def parse(body)
end
response
end
-
- # Make a ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
- end
- end
-
end
end
end
diff --git a/lib/active_merchant/billing/gateways/pay_junction_v2.rb b/lib/active_merchant/billing/gateways/pay_junction_v2.rb
new file mode 100644
index 00000000000..aed266facd6
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/pay_junction_v2.rb
@@ -0,0 +1,188 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayJunctionV2Gateway < Gateway
+ self.display_name = 'PayJunction'
+ self.homepage_url = 'https://www.payjunction.com/'
+
+ self.test_url = 'https://api.payjunctionlabs.com/transactions'
+ self.live_url = 'https://api.payjunction.com/transactions'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ def initialize(options={})
+ requires!(options, :api_login, :api_password, :api_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+
+ commit('purchase', post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ post[:status] = 'HOLD'
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+
+ commit('authorize', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ post[:status] = 'CAPTURE'
+ post[:transactionId] = authorization
+ add_invoice(post, amount, options)
+
+ commit('capture', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ post[:status] = 'VOID'
+ post[:transactionId] = authorization
+
+ commit('void', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ post[:action] = 'REFUND'
+ post[:transactionId] = authorization
+ add_invoice(post, amount, options)
+
+ commit('refund', post)
+ end
+
+ def credit(amount, payment_method, options={})
+ post = {}
+ post[:action] = 'REFUND'
+ add_invoice(post, amount, options)
+ add_payment_method(post, payment_method)
+
+ commit('credit', 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_method, options = {})
+ verify(payment_method, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((X-Pj-Application-Key: )[\w-]+), '\1[FILTERED]').
+ gsub(%r((cardNumber=)\d+), '\1[FILTERED]').
+ gsub(%r((cardCvv=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_invoice(post, money, options)
+ post[:amountBase] = amount(money) if money
+ post[:invoiceNumber] = options[:order_id] if options[:order_id]
+ end
+
+ def add_payment_method(post, payment_method)
+ if payment_method.is_a? Integer
+ post[:transactionId] = payment_method
+ else
+ post[:cardNumber] = payment_method.number
+ post[:cardExpMonth] = format(payment_method.month, :two_digits)
+ post[:cardExpYear] = format(payment_method.year, :four_digits)
+ post[:cardCvv] = payment_method.verification_value
+ end
+ end
+
+ def commit(action, params)
+ response = begin
+ parse(ssl_invoke(action, params))
+ rescue ResponseError => e
+ parse(e.response.body)
+ end
+
+ success = success_from(response)
+ Response.new(
+ success,
+ message_from(response),
+ response,
+ authorization: success ? authorization_from(response) : nil,
+ error_code: success ? nil : error_from(response),
+ test: test?
+ )
+ end
+
+ def ssl_invoke(action, params)
+ if ['purchase', 'authorize', 'refund', 'credit'].include?(action)
+ ssl_post(url(), post_data(params), headers)
+ else
+ ssl_request(:put, url(params), post_data(params), headers)
+ end
+ end
+
+ def headers
+ {
+ 'Authorization' => 'Basic ' + Base64.encode64("#{@options[:api_login]}:#{@options[:api_password]}").strip,
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8',
+ 'Accept' => 'application/json',
+ 'X-PJ-Application-Key' => @options[:api_key].to_s
+ }
+ end
+
+ def post_data(params)
+ params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def url(params={})
+ test? ? "#{test_url}/#{params[:transactionId]}" : "#{live_url}/#{params[:transactionId]}"
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ rescue JSON::ParserError
+ message = 'Invalid JSON response received from PayJunctionV2Gateway. Please contact PayJunctionV2Gateway if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{body.inspect})"
+ {
+ 'errors' => [{
+ 'message' => message
+ }]
+ }
+ end
+
+ def success_from(response)
+ return response['response']['approved'] if response['response']
+ false
+ end
+
+ def message_from(response)
+ return response['response']['message'] if response['response']
+
+ response['errors']&.inject('') { |message, error| error['message'] + '|' + message }
+ end
+
+ def authorization_from(response)
+ response['transactionId']
+ end
+
+ def error_from(response)
+ response['response']['code'] if response['response']
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/pay_secure.rb b/lib/active_merchant/billing/gateways/pay_secure.rb
index 16df1f9fa84..76c13578379 100644
--- a/lib/active_merchant/billing/gateways/pay_secure.rb
+++ b/lib/active_merchant/billing/gateways/pay_secure.rb
@@ -39,9 +39,10 @@ def purchase(money, credit_card, options = {})
end
private
+
# Used for capturing, which is currently not supported.
def add_reference(post, identification)
- auth, trans_id = identification.split(";")
+ auth, trans_id = identification.split(';')
post[:authnum] = auth
post[:transid] = trans_id
end
@@ -51,7 +52,7 @@ def add_amount(post, money)
end
def add_invoice(post, options)
- post[:merchant_transid] = options[:order_id].to_s.slice(0,21)
+ post[:merchant_transid] = options[:order_id].to_s.slice(0, 21)
post[:memnum] = options[:invoice]
post[:custnum] = options[:customer]
post[:clientdata] = options[:description]
@@ -64,21 +65,13 @@ def add_credit_card(post, credit_card)
post[:cvv2] = credit_card.verification_value
end
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
def commit(action, money, parameters)
- response = parse( ssl_post(self.live_url, post_data(action, parameters)) )
+ response = parse(ssl_post(self.live_url, post_data(action, parameters)))
Response.new(successful?(response), message_from(response), response,
:test => test_response?(response),
:authorization => authorization_from(response)
)
-
end
def successful?(response)
@@ -86,7 +79,7 @@ def successful?(response)
end
def authorization_from(response)
- [ response[:authnum], response[:transid] ].compact.join(";")
+ [ response[:authnum], response[:transid] ].compact.join(';')
end
def test_response?(response)
@@ -100,7 +93,7 @@ def message_from(response)
def parse(body)
response = {}
body.to_s.each_line do |l|
- key, value = l.split(":", 2)
+ key, value = l.split(':', 2)
response[key.to_s.downcase.to_sym] = value.strip
end
response
@@ -111,9 +104,8 @@ def post_data(action, parameters = {})
parameters[:merchant_id] = @options[:login]
parameters[:password] = @options[:password]
- parameters.reject{|k,v| v.blank?}.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join("&")
+ parameters.reject { |k, v| v.blank? }.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join('&')
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/paybox_direct.rb b/lib/active_merchant/billing/gateways/paybox_direct.rb
index a3e3d70ae21..de236330928 100644
--- a/lib/active_merchant/billing/gateways/paybox_direct.rb
+++ b/lib/active_merchant/billing/gateways/paybox_direct.rb
@@ -21,20 +21,21 @@ class PayboxDirectGateway < Gateway
}
CURRENCY_CODES = {
- "AUD"=> '036',
- "CAD"=> '124',
- "CZK"=> '203',
- "DKK"=> '208',
- "HKD"=> '344',
- "ICK"=> '352',
- "JPY"=> '392',
- "NOK"=> '578',
- "SGD"=> '702',
- "SEK"=> '752',
- "CHF"=> '756',
- "GBP"=> '826',
- "USD"=> '840',
- "EUR"=> '978'
+ 'AUD'=> '036',
+ 'CAD'=> '124',
+ 'CZK'=> '203',
+ 'DKK'=> '208',
+ 'HKD'=> '344',
+ 'ICK'=> '352',
+ 'JPY'=> '392',
+ 'NOK'=> '578',
+ 'SGD'=> '702',
+ 'SEK'=> '752',
+ 'CHF'=> '756',
+ 'GBP'=> '826',
+ 'USD'=> '840',
+ 'EUR'=> '978',
+ 'XPF'=> '953'
}
SUCCESS_CODES = ['00000']
@@ -67,6 +68,8 @@ def authorize(money, creditcard, options = {})
post = {}
add_invoice(post, options)
add_creditcard(post, creditcard)
+ add_amount(post, money, options)
+
commit('authorization', money, post)
end
@@ -74,6 +77,8 @@ def purchase(money, creditcard, options = {})
post = {}
add_invoice(post, options)
add_creditcard(post, creditcard)
+ add_amount(post, money, options)
+
commit('purchase', money, post)
end
@@ -81,8 +86,10 @@ def capture(money, authorization, options = {})
requires!(options, :order_id)
post = {}
add_invoice(post, options)
- post[:numappel] = authorization[0,10]
- post[:numtrans] = authorization[10,10]
+ add_amount(post, money, options)
+ post[:numappel] = authorization[0, 10]
+ post[:numtrans] = authorization[10, 10]
+
commit('capture', money, post)
end
@@ -91,13 +98,15 @@ def void(identification, options = {})
post ={}
add_invoice(post, options)
add_reference(post, identification)
+ add_amount(post, options[:amount], options)
post[:porteur] = '000000000000000'
post[:dateval] = '0000'
+
commit('void', options[:amount], post)
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -105,6 +114,7 @@ def refund(money, identification, options = {})
post = {}
add_invoice(post, options)
add_reference(post, identification)
+ add_amount(post, money, options)
commit('refund', money, post)
end
@@ -121,23 +131,26 @@ def add_creditcard(post, creditcard)
end
def add_reference(post, identification)
- post[:numappel] = identification[0,10]
- post[:numtrans] = identification[10,10]
+ post[:numappel] = identification[0, 10]
+ post[:numtrans] = identification[10, 10]
+ end
+
+ def add_amount(post, money, options)
+ post[:montant] = ('0000000000' + (money ? amount(money) : ''))[-10..-1]
+ post[:devise] = CURRENCY_CODES[options[:currency] || currency(money)]
end
def parse(body)
results = {}
body.split(/&/).each do |pair|
- key,val = pair.split(/\=/)
+ key, val = pair.split(/\=/)
results[key.downcase.to_sym] = CGI.unescape(val) if val
end
results
end
def commit(action, money = nil, parameters = nil)
- parameters[:montant] = ('0000000000' + (money ? amount(money) : ''))[-10..-1]
- parameters[:devise] = CURRENCY_CODES[options[:currency] || currency(money)]
- request_data = post_data(action,parameters)
+ request_data = post_data(action, parameters)
response = parse(ssl_post(test? ? self.test_url : self.live_url, request_data))
response = parse(ssl_post(self.live_url_backup, request_data)) if service_unavailable?(response) && !test?
Response.new(success?(response), message_from(response), response.merge(
@@ -145,7 +158,7 @@ def commit(action, money = nil, parameters = nil)
:test => test?,
:authorization => response[:numappel].to_s + response[:numtrans].to_s,
:fraud_review => false,
- :sent_params => parameters.delete_if{|key,value| ['porteur','dateval','cvv'].include?(key.to_s)}
+ :sent_params => parameters.delete_if { |key, value| ['porteur', 'dateval', 'cvv'].include?(key.to_s) }
)
end
@@ -162,20 +175,19 @@ def message_from(response)
end
def post_data(action, parameters = {})
-
parameters.update(
:version => API_VERSION,
:type => TRANSACTIONS[action.to_sym],
:dateq => Time.now.strftime('%d%m%Y%H%M%S'),
:numquestion => unique_id(parameters[:order_id]),
- :site => @options[:login].to_s[0,7],
- :rang => @options[:login].to_s[7..-1],
+ :site => @options[:login].to_s[0, 7],
+ :rang => @options[:rang] || @options[:login].to_s[7..-1],
:cle => @options[:password],
:pays => '',
:archivage => parameters[:order_id]
)
- parameters.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join("&")
+ parameters.collect { |key, value| "#{key.to_s.upcase}=#{CGI.escape(value.to_s)}" }.join('&')
end
def unique_id(seed = 0)
@@ -183,14 +195,6 @@ def unique_id(seed = 0)
"0000000000#{randkey}"[-10..-1]
end
-
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
end
end
end
diff --git a/lib/active_merchant/billing/gateways/payeezy.rb b/lib/active_merchant/billing/gateways/payeezy.rb
new file mode 100644
index 00000000000..b1ac0632f4c
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/payeezy.rb
@@ -0,0 +1,410 @@
+module ActiveMerchant
+ module Billing
+ class PayeezyGateway < Gateway
+ class_attribute :integration_url
+
+ self.test_url = 'https://api-cert.payeezy.com/v1'
+ self.integration_url = 'https://api-cat.payeezy.com/v1'
+ self.live_url = 'https://api.payeezy.com/v1'
+
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_countries = %w(US CA)
+
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
+
+ self.homepage_url = 'https://developer.payeezy.com/'
+ self.display_name = 'Payeezy'
+
+ CREDIT_CARD_BRAND = {
+ 'visa' => 'Visa',
+ 'master' => 'Mastercard',
+ 'american_express' => 'American Express',
+ 'discover' => 'Discover',
+ 'jcb' => 'JCB',
+ 'diners_club' => 'Diners Club'
+ }
+
+ def initialize(options = {})
+ requires!(options, :apikey, :apisecret, :token)
+ super
+ end
+
+ def purchase(amount, payment_method, options = {})
+ params = payment_method.is_a?(String) ? { transaction_type: 'recurring' } : { transaction_type: 'purchase' }
+
+ add_invoice(params, options)
+ add_reversal_id(params, options)
+ add_payment_method(params, payment_method, options)
+ add_address(params, options)
+ add_amount(params, amount, options)
+ add_soft_descriptors(params, options)
+ add_stored_credentials(params, options)
+
+ commit(params, options)
+ end
+
+ def authorize(amount, payment_method, options = {})
+ params = {transaction_type: 'authorize'}
+
+ add_invoice(params, options)
+ add_reversal_id(params, options)
+ add_payment_method(params, payment_method, options)
+ add_address(params, options)
+ add_amount(params, amount, options)
+ add_soft_descriptors(params, options)
+ add_stored_credentials(params, options)
+
+ commit(params, options)
+ end
+
+ def capture(amount, authorization, options = {})
+ params = {transaction_type: 'capture'}
+
+ add_authorization_info(params, authorization)
+ add_amount(params, amount, options)
+ add_soft_descriptors(params, options)
+
+ commit(params, options)
+ end
+
+ def refund(amount, authorization, options = {})
+ params = {transaction_type: 'refund'}
+
+ add_authorization_info(params, authorization)
+ add_amount(params, (amount || amount_from_authorization(authorization)), options)
+
+ commit(params, options)
+ end
+
+ def store(payment_method, options = {})
+ params = {transaction_type: 'store'}
+
+ add_creditcard_for_tokenization(params, payment_method, options)
+
+ commit(params, options)
+ end
+
+ def void(authorization, options = {})
+ params = {transaction_type: 'void'}
+
+ add_authorization_info(params, authorization, options)
+ add_amount(params, amount_from_authorization(authorization), options)
+
+ commit(params, options)
+ end
+
+ def verify(credit_card, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(0, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Token: )(\w|-)+), '\1[FILTERED]').
+ gsub(%r((Apikey: )(\w|-)+), '\1[FILTERED]').
+ gsub(%r((\\?"card_number\\?":\\?")\d+), '\1[FILTERED]').
+ gsub(%r((\\?"cvv\\?":\\?")\d+), '\1[FILTERED]').
+ gsub(%r((\\?"account_number\\?":\\?")\d+), '\1[FILTERED]').
+ gsub(%r((\\?"routing_number\\?":\\?")\d+), '\1[FILTERED]').
+ gsub(%r((\\?card_number=)\d+(&?)), '\1[FILTERED]').
+ gsub(%r((\\?cvv=)\d+(&?)), '\1[FILTERED]').
+ gsub(%r((\\?apikey=)\w+(&?)), '\1[FILTERED]').
+ gsub(%r{(\\?"credit_card\.card_number\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]').
+ gsub(%r{(\\?"credit_card\.cvv\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]').
+ gsub(%r{(\\?"apikey\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_invoice(params, options)
+ params[:merchant_ref] = options[:order_id]
+ end
+
+ def add_reversal_id(params, options)
+ params[:reversal_id] = options[:reversal_id] if options[:reversal_id]
+ end
+
+ def amount_from_authorization(authorization)
+ authorization.split('|').last.to_i
+ end
+
+ def add_authorization_info(params, authorization, options = {})
+ transaction_id, transaction_tag, method, _ = authorization.split('|')
+ params[:method] = method == 'token' ? 'credit_card' : method
+
+ if options[:reversal_id]
+ params[:reversal_id] = options[:reversal_id]
+ else
+ params[:transaction_id] = transaction_id
+ params[:transaction_tag] = transaction_tag
+ end
+ end
+
+ def add_creditcard_for_tokenization(params, payment_method, options)
+ params[:apikey] = @options[:apikey]
+ params[:ta_token] = options[:ta_token]
+ params[:type] = 'FDToken'
+ params[:credit_card] = add_card_data(payment_method)
+ params[:auth] = 'false'
+ end
+
+ def is_store_action?(params)
+ params[:transaction_type] == 'store'
+ end
+
+ def add_payment_method(params, payment_method, options)
+ if payment_method.is_a? Check
+ add_echeck(params, payment_method, options)
+ elsif payment_method.is_a? String
+ add_token(params, payment_method, options)
+ else
+ add_creditcard(params, payment_method)
+ end
+ end
+
+ def add_echeck(params, echeck, options)
+ tele_check = {}
+
+ tele_check[:check_number] = echeck.number || '001'
+ tele_check[:check_type] = 'P'
+ tele_check[:routing_number] = echeck.routing_number
+ tele_check[:account_number] = echeck.account_number
+ tele_check[:accountholder_name] = "#{echeck.first_name} #{echeck.last_name}"
+ tele_check[:customer_id_type] = options[:customer_id_type] if options[:customer_id_type]
+ tele_check[:customer_id_number] = options[:customer_id_number] if options[:customer_id_number]
+ tele_check[:client_email] = options[:client_email] if options[:client_email]
+
+ params[:method] = 'tele_check'
+ params[:tele_check] = tele_check
+ end
+
+ def add_token(params, payment_method, options)
+ token = {}
+ token[:token_type] = 'FDToken'
+
+ type, cardholder_name, exp_date, card_number = payment_method.split('|')
+
+ token[:token_data] = {}
+ token[:token_data][:type] = type
+ token[:token_data][:cardholder_name] = cardholder_name
+ token[:token_data][:value] = card_number
+ token[:token_data][:exp_date] = exp_date
+ token[:token_data][:cvv] = options[:cvv] if options[:cvv]
+
+ params[:method] = 'token'
+ params[:token] = token
+ end
+
+ def add_creditcard(params, creditcard)
+ credit_card = add_card_data(creditcard)
+
+ params[:method] = 'credit_card'
+ params[:credit_card] = credit_card
+ end
+
+ def add_card_data(payment_method)
+ card = {}
+ card[:type] = CREDIT_CARD_BRAND[payment_method.brand]
+ card[:cardholder_name] = payment_method.name
+ card[:card_number] = payment_method.number
+ card[:exp_date] = format_exp_date(payment_method.month, payment_method.year)
+ card[:cvv] = payment_method.verification_value if payment_method.verification_value?
+ card
+ end
+
+ def format_exp_date(month, year)
+ "#{format(month, :two_digits)}#{format(year, :two_digits)}"
+ end
+
+ def add_address(params, options)
+ address = options[:billing_address]
+ return unless address
+
+ billing_address = {}
+ billing_address[:street] = address[:address1] if address[:address1]
+ billing_address[:city] = address[:city] if address[:city]
+ billing_address[:state_province] = address[:state] if address[:state]
+ billing_address[:zip_postal_code] = address[:zip] if address[:zip]
+ billing_address[:country] = address[:country] if address[:country]
+
+ params[:billing_address] = billing_address
+ end
+
+ def add_amount(params, money, options)
+ params[:currency_code] = (options[:currency] || default_currency).upcase
+ params[:amount] = amount(money)
+ end
+
+ def add_soft_descriptors(params, options)
+ params[:soft_descriptors] = options[:soft_descriptors] if options[:soft_descriptors]
+ end
+
+ def add_stored_credentials(params, options)
+ if options[:sequence]
+ params[:stored_credentials] = {}
+ params[:stored_credentials][:cardbrand_original_transaction_id] = options[:cardbrand_original_transaction_id] if options[:cardbrand_original_transaction_id]
+ params[:stored_credentials][:sequence] = options[:sequence]
+ params[:stored_credentials][:initiator] = options[:initiator] if options[:initiator]
+ params[:stored_credentials][:is_scheduled] = options[:is_scheduled]
+ params[:stored_credentials][:auth_type_override] = options[:auth_type_override] if options[:auth_type_override]
+ end
+ end
+
+ def commit(params, options)
+ url = base_url(options) + endpoint(params)
+
+ if transaction_id = params.delete(:transaction_id)
+ url = "#{url}/#{transaction_id}"
+ end
+
+ begin
+ response = api_request(url, params)
+ rescue ResponseError => e
+ response = response_error(e.response.body)
+ rescue JSON::ParserError
+ response = json_error(e.response.body)
+ end
+
+ Response.new(
+ success_from(response),
+ handle_message(response, success_from(response)),
+ response,
+ test: test?,
+ authorization: authorization_from(params, response),
+ avs_result: {code: response['avs']},
+ cvv_result: response['cvv2'],
+ error_code: error_code(response, success_from(response))
+ )
+ end
+
+ def base_url(options)
+ if options[:integration]
+ integration_url
+ elsif test?
+ test_url
+ else
+ live_url
+ end
+ end
+
+ def endpoint(params)
+ is_store_action?(params) ? '/transactions/tokens' : '/transactions'
+ end
+
+ def api_request(url, params)
+ body = params.to_json
+ parse(ssl_post(url, body, headers(body)))
+ end
+
+ def post_data(params)
+ return nil unless params
+ params.reject { |k, v| v.blank? }.collect { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
+ end
+
+ def generate_hmac(nonce, current_timestamp, payload)
+ message = [
+ @options[:apikey],
+ nonce.to_s,
+ current_timestamp.to_s,
+ @options[:token],
+ payload
+ ].join('')
+ hash = Base64.strict_encode64(OpenSSL::HMAC.hexdigest('sha256', @options[:apisecret], message))
+ hash
+ end
+
+ def headers(payload)
+ nonce = (SecureRandom.random_number * 10_000_000_000)
+ current_timestamp = (Time.now.to_f * 1000).to_i
+ {
+ 'Content-Type' => 'application/json',
+ 'apikey' => options[:apikey],
+ 'token' => options[:token],
+ 'nonce' => nonce.to_s,
+ 'timestamp' => current_timestamp.to_s,
+ 'Authorization' => generate_hmac(nonce, current_timestamp, payload)
+ }
+ end
+
+ def error_code(response, success)
+ return if success
+ response['Error'].to_h['messages'].to_a.map { |e| e['code'] }.join(', ')
+ end
+
+ def success_from(response)
+ if response['transaction_status']
+ response['transaction_status'] == 'approved'
+ elsif response['results']
+ response['results']['status'] == 'success'
+ elsif response['status']
+ response['status'] == 'success'
+ else
+ false
+ end
+ end
+
+ def handle_message(response, success)
+ if success && response['status'].present?
+ 'Token successfully created.'
+ elsif success
+ "#{response['gateway_message']} - #{response['bank_message']}"
+ elsif %w(401 403).include?(response['code'])
+ response['message']
+ elsif response.key?('Error')
+ response['Error']['messages'].first['description']
+ elsif response.key?('results')
+ response['results']['Error']['messages'].first['description']
+ elsif response.key?('error')
+ response['error']
+ elsif response.key?('fault')
+ response['fault'].to_h['faultstring']
+ else
+ response['bank_message'] || response['gateway_message'] || 'Failure to successfully create token.'
+ end
+ end
+
+ def authorization_from(params, response)
+ if is_store_action?(params)
+ if success_from(response)
+ [
+ response['token']['type'],
+ response['token']['cardholder_name'],
+ response['token']['exp_date'],
+ response['token']['value']
+ ].join('|')
+ else
+ nil
+ end
+ else
+ [
+ response['transaction_id'],
+ response['transaction_tag'],
+ params[:method],
+ response['amount']&.to_i
+ ].join('|')
+ end
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def json_error(raw_response)
+ {'error' => "Unable to parse response: #{raw_response.inspect}"}
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/payex.rb b/lib/active_merchant/billing/gateways/payex.rb
new file mode 100644
index 00000000000..c43e36bb13f
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/payex.rb
@@ -0,0 +1,410 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayexGateway < Gateway
+ class_attribute :live_external_url, :test_external_url, :live_confined_url, :test_confined_url
+
+ self.live_external_url = 'https://external.payex.com/'
+ self.test_external_url = 'https://test-external.payex.com/'
+
+ self.live_confined_url = 'https://confined.payex.com/'
+ self.test_confined_url = 'https://test-confined.payex.com/'
+
+ self.money_format = :cents
+ self.supported_countries = ['DK', 'FI', 'NO', 'SE']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+ self.homepage_url = 'http://payex.com/'
+ self.display_name = 'Payex'
+ self.default_currency = 'EUR'
+
+ TRANSACTION_STATUS = {
+ sale: '0',
+ initialize: '1',
+ credit: '2',
+ authorize: '3',
+ cancel: '4',
+ failure: '5',
+ capture: '6',
+ }
+
+ SOAP_ACTIONS = {
+ initialize: { name: 'Initialize8', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' },
+ purchasecc: { name: 'PurchaseCC', url: 'pxconfined/pxorder.asmx', xmlns: 'http://confined.payex.com/PxOrder/', confined: true},
+ cancel: { name: 'Cancel2', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' },
+ capture: { name: 'Capture5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' },
+ credit: { name: 'Credit5', url: 'pxorder/pxorder.asmx', xmlns: 'http://external.payex.com/PxOrder/' },
+ create_agreement: { name: 'CreateAgreement3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' },
+ delete_agreement: { name: 'DeleteAgreement', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' },
+ autopay: { name: 'AutoPay3', url: 'pxagreement/pxagreement.asmx', xmlns: 'http://external.payex.com/PxAgreement/' },
+ }
+
+ def initialize(options = {})
+ requires!(options, :account, :encryption_key)
+ super
+ end
+
+ # Public: Send an authorize Payex request
+ #
+ # amount - The monetary amount of the transaction in cents.
+ # payment_method - The Active Merchant payment method or the +store+ authorization for stored transactions.
+ # options - A standard ActiveMerchant options hash:
+ # :currency - Three letter currency code for the transaction (default: "EUR")
+ # :order_id - The unique order ID for this transaction (required).
+ # :product_number - The merchant product number (default: '1').
+ # :description - The merchant description for this product (default: The :order_id).
+ # :ip - The client IP address (default: '127.0.0.1').
+ # :vat - The vat amount (optional).
+ #
+ # Returns an ActiveMerchant::Billing::Response object
+ def authorize(amount, payment_method, options = {})
+ requires!(options, :order_id)
+ amount = amount(amount)
+ if payment_method.respond_to?(:number)
+ # credit card authorization
+ MultiResponse.new.tap do |r|
+ r.process { send_initialize(amount, true, options) }
+ r.process { send_purchasecc(payment_method, r.params['orderref']) }
+ end
+ else
+ # stored authorization
+ send_autopay(amount, payment_method, true, options)
+ end
+ end
+
+ # Public: Send a purchase Payex request
+ #
+ # amount - The monetary amount of the transaction in cents.
+ # payment_method - The Active Merchant payment method or the +store+ authorization for stored transactions.
+ # options - A standard ActiveMerchant options hash:
+ # :currency - Three letter currency code for the transaction (default: "EUR")
+ # :order_id - The unique order ID for this transaction (required).
+ # :product_number - The merchant product number (default: '1').
+ # :description - The merchant description for this product (default: The :order_id).
+ # :ip - The client IP address (default: '127.0.0.1').
+ # :vat - The vat amount (optional).
+ #
+ # Returns an ActiveMerchant::Billing::Response object
+ def purchase(amount, payment_method, options = {})
+ requires!(options, :order_id)
+ amount = amount(amount)
+ if payment_method.respond_to?(:number)
+ # credit card purchase
+ MultiResponse.new.tap do |r|
+ r.process { send_initialize(amount, false, options) }
+ r.process { send_purchasecc(payment_method, r.params['orderref']) }
+ end
+ else
+ # stored purchase
+ send_autopay(amount, payment_method, false, options)
+ end
+ end
+
+ # Public: Capture money from a previously authorized transaction
+ #
+ # money - The amount to capture
+ # authorization - The authorization token from the authorization request
+ #
+ # Returns an ActiveMerchant::Billing::Response object
+ def capture(money, authorization, options = {})
+ amount = amount(money)
+ send_capture(amount, authorization)
+ end
+
+ # Public: Voids an authorize transaction
+ #
+ # authorization - The authorization returned from the successful authorize transaction.
+ # options - A standard ActiveMerchant options hash
+ #
+ # Returns an ActiveMerchant::Billing::Response object
+ def void(authorization, options={})
+ send_cancel(authorization)
+ end
+
+ # Public: Refunds a purchase transaction
+ #
+ # money - The amount to refund
+ # authorization - The authorization token from the purchase request.
+ # options - A standard ActiveMerchant options hash:
+ # :order_id - The unique order ID for this transaction (required).
+ # :vat_amount - The vat amount (optional).
+ #
+ # Returns an ActiveMerchant::Billing::Response object
+ def refund(money, authorization, options = {})
+ requires!(options, :order_id)
+ amount = amount(money)
+ send_credit(authorization, amount, options)
+ end
+
+ # Public: Stores a credit card and creates a Payex agreement with a customer
+ #
+ # creditcard - The credit card to store.
+ # options - A standard ActiveMerchant options hash:
+ # :order_id - The unique order ID for this transaction (required).
+ # :merchant_ref - A reference that links this agreement to something the merchant takes money for (default: '1')
+ # :currency - Three letter currency code for the transaction (default: "EUR")
+ # :product_number - The merchant product number (default: '1').
+ # :description - The merchant description for this product (default: The :order_id).
+ # :ip - The client IP address (default: '127.0.0.1').
+ # :max_amount - The maximum amount to allow to be charged (default: 100000).
+ # :vat - The vat amount (optional).
+ #
+ # Returns an ActiveMerchant::Billing::Response object where the authorization is set to the agreement_ref which is used for stored payments.
+ def store(creditcard, options = {})
+ requires!(options, :order_id)
+ amount = amount(1) # 1 cent for authorization
+ MultiResponse.run(:first) do |r|
+ r.process { send_create_agreement(options) }
+ r.process { send_initialize(amount, true, options.merge({agreement_ref: r.authorization})) }
+ order_ref = r.params['orderref']
+ r.process { send_purchasecc(creditcard, order_ref) }
+ end
+ end
+
+ # Public: Unstores a customer's credit card and deletes their Payex agreement.
+ #
+ # authorization - The authorization token from the store request.
+ #
+ # Returns an ActiveMerchant::Billing::Response object
+ def unstore(authorization, options = {})
+ send_delete_agreement(authorization)
+ end
+
+ private
+
+ def send_initialize(amount, is_auth, options = {})
+ properties = {
+ accountNumber: @options[:account],
+ purchaseOperation: is_auth ? 'AUTHORIZATION' : 'SALE',
+ price: amount,
+ priceArgList: nil,
+ currency: (options[:currency] || default_currency),
+ vat: options[:vat] || 0,
+ orderID: options[:order_id],
+ productNumber: options[:product_number] || '1',
+ description: options[:description] || options[:order_id],
+ clientIPAddress: options[:client_ip_address] || '127.0.0.1',
+ clientIdentifier: nil,
+ additionalValues: nil,
+ externalID: nil,
+ returnUrl: 'http://example.net', # set to dummy value since this is not used but is required
+ view: 'CREDITCARD',
+ agreementRef: options[:agreement_ref], # this is used to attach a stored agreement to a transaction as part of the store card
+ cancelUrl: nil,
+ clientLanguage: nil
+ }
+ hash_fields = [:accountNumber, :purchaseOperation, :price, :priceArgList, :currency, :vat, :orderID,
+ :productNumber, :description, :clientIPAddress, :clientIdentifier, :additionalValues,
+ :externalID, :returnUrl, :view, :agreementRef, :cancelUrl, :clientLanguage]
+ add_request_hash(properties, hash_fields)
+ soap_action = SOAP_ACTIONS[:initialize]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def send_purchasecc(payment_method, order_ref)
+ properties = {
+ accountNumber: @options[:account],
+ orderRef: order_ref,
+ transactionType: 1, # online payment
+ cardNumber: payment_method.number,
+ cardNumberExpireMonth: format(payment_method.month, :two_digits),
+ cardNumberExpireYear: format(payment_method.year, :two_digits),
+ cardHolderName: payment_method.name,
+ cardNumberCVC: payment_method.verification_value
+ }
+ hash_fields = [:accountNumber, :orderRef, :transactionType, :cardNumber, :cardNumberExpireMonth,
+ :cardNumberExpireYear, :cardNumberCVC, :cardHolderName]
+ add_request_hash(properties, hash_fields)
+
+ soap_action = SOAP_ACTIONS[:purchasecc]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def send_autopay(amount, authorization, is_auth, options = {})
+ properties = {
+ accountNumber: @options[:account],
+ agreementRef: authorization,
+ price: amount,
+ productNumber: options[:product_number] || '1',
+ description: options[:description] || options[:order_id],
+ orderId: options[:order_id],
+ purchaseOperation: is_auth ? 'AUTHORIZATION' : 'SALE',
+ currency: (options[:currency] || default_currency),
+ }
+ hash_fields = [:accountNumber, :agreementRef, :price, :productNumber, :description, :orderId, :purchaseOperation, :currency]
+ add_request_hash(properties, hash_fields)
+
+ soap_action = SOAP_ACTIONS[:autopay]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def send_capture(amount, transaction_number, options = {})
+ properties = {
+ accountNumber: @options[:account],
+ transactionNumber: transaction_number,
+ amount: amount,
+ orderId: options[:order_id] || '',
+ vatAmount: options[:vat_amount] || 0,
+ additionalValues: ''
+ }
+ hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues]
+ add_request_hash(properties, hash_fields)
+
+ soap_action = SOAP_ACTIONS[:capture]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def send_credit(transaction_number, amount, options = {})
+ properties = {
+ accountNumber: @options[:account],
+ transactionNumber: transaction_number,
+ amount: amount,
+ orderId: options[:order_id],
+ vatAmount: options[:vat_amount] || 0,
+ additionalValues: ''
+ }
+ hash_fields = [:accountNumber, :transactionNumber, :amount, :orderId, :vatAmount, :additionalValues]
+ add_request_hash(properties, hash_fields)
+
+ soap_action = SOAP_ACTIONS[:credit]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def send_cancel(transaction_number)
+ properties = {
+ accountNumber: @options[:account],
+ transactionNumber: transaction_number,
+ }
+ hash_fields = [:accountNumber, :transactionNumber]
+ add_request_hash(properties, hash_fields)
+
+ soap_action = SOAP_ACTIONS[:cancel]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def send_create_agreement(options)
+ properties = {
+ accountNumber: @options[:account],
+ merchantRef: options[:merchant_ref] || '1',
+ description: options[:description] || options[:order_id],
+ purchaseOperation: 'SALE',
+ maxAmount: options[:max_amount] || 100000, # default to 1,000
+ notifyUrl: '',
+ startDate: options[:startDate] || '',
+ stopDate: options[:stopDate] || ''
+ }
+ hash_fields = [:accountNumber, :merchantRef, :description, :purchaseOperation, :maxAmount, :notifyUrl, :startDate, :stopDate]
+ add_request_hash(properties, hash_fields)
+
+ soap_action = SOAP_ACTIONS[:create_agreement]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def send_delete_agreement(authorization)
+ properties = {
+ accountNumber: @options[:account],
+ agreementRef: authorization,
+ }
+ hash_fields = [:accountNumber, :agreementRef]
+ add_request_hash(properties, hash_fields)
+
+ soap_action = SOAP_ACTIONS[:delete_agreement]
+ request = build_xml_request(soap_action, properties)
+ commit(soap_action, request)
+ end
+
+ def url_for(soap_action)
+ File.join(base_url(soap_action), soap_action[:url])
+ end
+
+ def base_url(soap_action)
+ if soap_action[:confined]
+ test? ? test_confined_url : live_confined_url
+ else
+ test? ? test_external_url : live_external_url
+ end
+ end
+
+ # this will add a hash to the passed in properties as required by Payex requests
+ def add_request_hash(properties, fields)
+ data = fields.map { |e| properties[e] }
+ data << @options[:encryption_key]
+ properties['hash_'] = Digest::MD5.hexdigest(data.join(''))
+ end
+
+ def build_xml_request(soap_action, properties)
+ builder = Nokogiri::XML::Builder.new
+ builder.__send__('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 |root|
+ root.__send__('soap12:Body') do |body|
+ body.__send__(soap_action[:name], xmlns: soap_action[:xmlns]) do |doc|
+ properties.each do |key, val|
+ doc.send(key, val)
+ end
+ end
+ end
+ end
+ builder.to_xml
+ end
+
+ def parse(xml)
+ response = {}
+
+ xmldoc = Nokogiri::XML(xml)
+ body = xmldoc.xpath('//soap:Body/*[1]')[0].inner_text
+
+ doc = Nokogiri::XML(body)
+
+ doc.root&.xpath('*')&.each do |node|
+ if node.elements.size == 0
+ response[node.name.downcase.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name.downcase}_#{childnode.name.downcase}"
+ response[name.to_sym] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ # Commits all requests to the Payex soap endpoint
+ def commit(soap_action, request)
+ url = url_for(soap_action)
+ headers = {
+ 'Content-Type' => 'application/soap+xml; charset=utf-8',
+ 'Content-Length' => request.size.to_s
+ }
+ response = parse(ssl_post(url, request, headers))
+ Response.new(success?(response),
+ message_from(response),
+ response,
+ test: test?,
+ authorization: build_authorization(response)
+ )
+ end
+
+ def build_authorization(response)
+ # agreementref is for the store transaction, everything else gets transactionnumber
+ response[:transactionnumber] || response[:agreementref]
+ end
+
+ def success?(response)
+ response[:status_errorcode] == 'OK' && response[:transactionstatus] != TRANSACTION_STATUS[:failure]
+ end
+
+ def message_from(response)
+ response[:status_description]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/payflow.rb b/lib/active_merchant/billing/gateways/payflow.rb
index 770c426d8fe..709cd19ef45 100644
--- a/lib/active_merchant/billing/gateways/payflow.rb
+++ b/lib/active_merchant/billing/gateways/payflow.rb
@@ -1,6 +1,7 @@
-require File.dirname(__FILE__) + '/payflow/payflow_common_api'
-require File.dirname(__FILE__) + '/payflow/payflow_response'
-require File.dirname(__FILE__) + '/payflow_express'
+require 'nokogiri'
+require 'active_merchant/billing/gateways/payflow/payflow_common_api'
+require 'active_merchant/billing/gateways/payflow/payflow_response'
+require 'active_merchant/billing/gateways/payflow_express'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -19,20 +20,23 @@ def authorize(money, credit_card_or_reference, options = {})
commit(request, options)
end
- def purchase(money, credit_card_or_reference, options = {})
- request = build_sale_or_authorization_request(:purchase, money, credit_card_or_reference, options)
+ def purchase(money, funding_source, options = {})
+ request = build_sale_or_authorization_request(:purchase, money, funding_source, options)
commit(request, options)
end
- def credit(money, identification_or_credit_card, options = {})
- if identification_or_credit_card.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ def credit(money, funding_source, options = {})
+ if funding_source.is_a?(String)
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
# Perform referenced credit
- refund(money, identification_or_credit_card, options)
- else
+ refund(money, funding_source, options)
+ elsif card_brand(funding_source) == 'check'
# Perform non-referenced credit
- request = build_credit_card_request(:credit, money, identification_or_credit_card, options)
+ request = build_check_request(:credit, money, funding_source, options)
+ commit(request, options)
+ else
+ request = build_credit_card_request(:credit, money, funding_source, options)
commit(request, options)
end
end
@@ -41,6 +45,22 @@ def refund(money, reference, options = {})
commit(build_reference_request(:credit, money, reference, options), options)
end
+ def verify(payment, options={})
+ if credit_card_type(payment) == 'Amex'
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, payment, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ else
+ authorize(0, payment, options)
+ end
+ end
+
+ def verify_credentials
+ response = void('0')
+ response.params['result'] != '26'
+ end
+
# Adds or modifies a recurring Payflow profile. See the Payflow Pro Recurring Billing Guide for more details:
# https://www.paypal.com/en_US/pdf/PayflowPro_RecurringBilling_Guide.pdf
#
@@ -54,20 +74,26 @@ def refund(money, reference, options = {})
# * payments - The term, or number of payments that will be made
# * comment - A comment associated with the profile
def recurring(money, credit_card, options = {})
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
options[:name] = credit_card.name if options[:name].blank? && credit_card
request = build_recurring_request(options[:profile_id] ? :modify : :add, money, options) do |xml|
- add_credit_card(xml, credit_card) if credit_card
+ add_credit_card(xml, credit_card, options) if credit_card
end
commit(request, options.merge(:request_type => :recurring))
end
def cancel_recurring(profile_id)
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
request = build_recurring_request(:cancel, 0, :profile_id => profile_id)
commit(request, options.merge(:request_type => :recurring))
end
def recurring_inquiry(profile_id, options = {})
- request = build_recurring_request(:inquiry, nil, options.update( :profile_id => profile_id ))
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
+ request = build_recurring_request(:inquiry, nil, options.update(:profile_id => profile_id))
commit(request, options.merge(:request_type => :recurring))
end
@@ -75,12 +101,27 @@ def express
@express ||= PayflowExpressGateway.new(@options)
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').
+ gsub(%r(()[^<]*()), '\1[FILTERED]\2')
+ end
+
private
- def build_sale_or_authorization_request(action, money, credit_card_or_reference, options)
- if credit_card_or_reference.is_a?(String)
- build_reference_sale_or_authorization_request(action, money, credit_card_or_reference, options)
+
+ def build_sale_or_authorization_request(action, money, funding_source, options)
+ if funding_source.is_a?(String)
+ build_reference_sale_or_authorization_request(action, money, funding_source, options)
+ elsif card_brand(funding_source) == 'check'
+ build_check_request(action, money, funding_source, options)
else
- build_credit_card_request(action, money, credit_card_or_reference, options)
+ build_credit_card_request(action, money, funding_source, options)
end
end
@@ -93,6 +134,7 @@ def build_reference_sale_or_authorization_request(action, money, reference, opti
xml.tag! 'CustIP', options[:ip] unless options[:ip].blank?
xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank?
xml.tag! 'Description', options[:description] unless options[:description].blank?
+ xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank?
xml.tag! 'Comment', options[:comment] unless options[:comment].blank?
xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank?
xml.tag! 'TaxAmt', options[:taxamt] unless options[:taxamt].blank?
@@ -102,7 +144,7 @@ def build_reference_sale_or_authorization_request(action, money, reference, opti
billing_address = options[:billing_address] || options[:address]
add_address(xml, 'BillTo', billing_address, options) if billing_address
- add_address(xml, 'ShipTo', options[:shipping_address],options) if options[:shipping_address]
+ add_address(xml, 'ShipTo', options[:shipping_address], options) if options[:shipping_address]
xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
end
@@ -124,6 +166,7 @@ def build_credit_card_request(action, money, credit_card, options)
xml.tag! 'CustIP', options[:ip] unless options[:ip].blank?
xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank?
xml.tag! 'Description', options[:description] unless options[:description].blank?
+ xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank?
# Comment and Comment2 will show up in manager.paypal.com as Comment1 and Comment2
xml.tag! 'Comment', options[:comment] unless options[:comment].blank?
xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank?
@@ -131,6 +174,7 @@ def build_credit_card_request(action, money, credit_card, options)
xml.tag! 'FreightAmt', options[:freightamt] unless options[:freightamt].blank?
xml.tag! 'DutyAmt', options[:dutyamt] unless options[:dutyamt].blank?
xml.tag! 'DiscountAmt', options[:discountamt] unless options[:discountamt].blank?
+ xml.tag! 'EMail', options[:email] unless options[:email].nil?
billing_address = options[:billing_address] || options[:address]
add_address(xml, 'BillTo', billing_address, options) if billing_address
@@ -140,14 +184,77 @@ def build_credit_card_request(action, money, credit_card, options)
end
xml.tag! 'Tender' do
- add_credit_card(xml, credit_card)
+ add_credit_card(xml, credit_card, options)
end
end
end
- xml.target!
+ add_level_two_three_fields(xml.target!, options)
end
- def add_credit_card(xml, credit_card)
+ def add_level_two_three_fields(xml_string, options)
+ if options[:level_two_fields] || options[:level_three_fields]
+ xml_doc = Nokogiri::XML.parse(xml_string)
+ %i[level_two_fields level_three_fields].each do |fields|
+ xml_string = add_fields(xml_doc, options[fields]) if options[fields]
+ end
+ end
+ xml_string
+ end
+
+ def check_fields(parent, fields, xml_doc)
+ fields.each do |k, v|
+ if v.is_a? String
+ new_node = Nokogiri::XML::Node.new(k, xml_doc)
+ new_node.add_child(v)
+ xml_doc.at_css(parent).add_child(new_node)
+ else
+ check_subparent_before_continuing(parent, k, xml_doc)
+ check_fields(k, v, xml_doc)
+ end
+ end
+ xml_doc
+ end
+
+ def check_subparent_before_continuing(parent, subparent, xml_doc)
+ unless xml_doc.at_css(subparent)
+ subparent_node = Nokogiri::XML::Node.new(subparent, xml_doc)
+ xml_doc.at_css(parent).add_child(subparent_node)
+ end
+ end
+
+ def add_fields(xml_doc, options_fields)
+ fields_to_add = JSON.parse(options_fields)
+ check_fields('Invoice', fields_to_add, xml_doc)
+ xml_doc.root.to_s
+ end
+
+ def build_check_request(action, money, check, options)
+ xml = Builder::XmlMarkup.new
+ xml.tag! TRANSACTIONS[action] do
+ xml.tag! 'PayData' do
+ xml.tag! 'Invoice' do
+ xml.tag! 'CustIP', options[:ip] unless options[:ip].blank?
+ xml.tag! 'InvNum', options[:order_id].to_s.gsub(/[^\w.]/, '') unless options[:order_id].blank?
+ xml.tag! 'Description', options[:description] unless options[:description].blank?
+ xml.tag! 'OrderDesc', options[:order_desc] unless options[:order_desc].blank?
+ xml.tag! 'BillTo' do
+ xml.tag! 'Name', check.name
+ end
+ xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
+ end
+ xml.tag! 'Tender' do
+ xml.tag! 'ACH' do
+ xml.tag! 'AcctType', check.account_type == 'checking' ? 'C' : 'S'
+ xml.tag! 'AcctNum', check.account_number
+ xml.tag! 'ABA', check.routing_number
+ end
+ end
+ end
+ end
+ add_level_two_three_fields(xml.target!, options)
+ end
+
+ def add_credit_card(xml, credit_card, options = {})
xml.tag! 'Card' do
xml.tag! 'CardType', credit_card_type(credit_card)
xml.tag! 'CardNum', credit_card.number
@@ -155,10 +262,19 @@ def add_credit_card(xml, credit_card)
xml.tag! 'NameOnCard', credit_card.first_name
xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value?
- if requires_start_date_or_issue_number?(credit_card)
- xml.tag!('ExtData', 'Name' => 'CardStart', 'Value' => startdate(credit_card)) unless credit_card.start_month.blank? || credit_card.start_year.blank?
- xml.tag!('ExtData', 'Name' => 'CardIssue', 'Value' => format(credit_card.issue_number, :two_digits)) unless credit_card.issue_number.blank?
+ if options[:three_d_secure]
+ three_d_secure = options[:three_d_secure]
+ xml.tag! 'BuyerAuthResult' do
+ xml.tag! 'Status', three_d_secure[:status] unless three_d_secure[:status].blank?
+ xml.tag! 'AuthenticationId', three_d_secure[:authentication_id] unless three_d_secure[:authentication_id].blank?
+ xml.tag! 'PAReq', three_d_secure[:pareq] unless three_d_secure[:pareq].blank?
+ xml.tag! 'ACSUrl', three_d_secure[:acs_url] unless three_d_secure[:acs_url].blank?
+ xml.tag! 'ECI', three_d_secure[:eci] unless three_d_secure[:eci].blank?
+ xml.tag! 'CAVV', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
+ xml.tag! 'XID', three_d_secure[:xid] unless three_d_secure[:xid].blank?
+ end
end
+
xml.tag! 'ExtData', 'Name' => 'LASTNAME', 'Value' => credit_card.last_name
end
end
@@ -170,8 +286,8 @@ def credit_card_type(credit_card)
end
def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year.to_s.sub(/^0+/, ''))
- month = sprintf("%.2i", creditcard.month.to_s.sub(/^0+/, ''))
+ year = sprintf('%.4i', creditcard.year.to_s.sub(/^0+/, ''))
+ month = sprintf('%.2i', creditcard.month.to_s.sub(/^0+/, ''))
"#{year}#{month}"
end
@@ -211,7 +327,7 @@ def build_recurring_request(action, money, options)
end
if action == :add
- xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1 )
+ xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1)
else
xml.tag! 'Start', format_rp_date(options[:starting_at]) unless options[:starting_at].nil?
end
@@ -227,10 +343,10 @@ def build_recurring_request(action, money, options)
end
end
if action != :add
- xml.tag! "ProfileID", options[:profile_id]
+ xml.tag! 'ProfileID', options[:profile_id]
end
if action == :inquiry
- xml.tag! "PaymentHistory", ( options[:history] ? 'Y' : 'N' )
+ xml.tag! 'PaymentHistory', (options[:history] ? 'Y' : 'N')
end
end
end
@@ -240,20 +356,20 @@ def build_recurring_request(action, money, options)
def get_pay_period(options)
requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly])
case options[:periodicity]
- when :weekly then 'Weekly'
- when :biweekly then 'Bi-weekly'
- when :semimonthly then 'Semi-monthly'
- when :quadweekly then 'Every four weeks'
- when :monthly then 'Monthly'
- when :quarterly then 'Quarterly'
- when :semiyearly then 'Semi-yearly'
- when :yearly then 'Yearly'
+ when :weekly then 'Weekly'
+ when :biweekly then 'Bi-weekly'
+ when :semimonthly then 'Semi-monthly'
+ when :quadweekly then 'Every four weeks'
+ when :monthly then 'Monthly'
+ when :quarterly then 'Quarterly'
+ when :semiyearly then 'Semi-yearly'
+ when :yearly then 'Yearly'
end
end
def format_rp_date(time)
case time
- when Time, Date then time.strftime("%m%d%Y")
+ when Time, Date then time.strftime('%m%d%Y')
else
time.to_s
end
@@ -265,4 +381,3 @@ def build_response(success, message, response, options = {})
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb b/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb
index f1aa2baa263..4ad1bd00739 100644
--- a/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb
+++ b/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb
@@ -10,7 +10,7 @@ def self.included(base)
# Set the default partner to PayPal
base.partner = 'PayPal'
- base.supported_countries = ['US', 'CA', 'SG', 'AU']
+ base.supported_countries = ['US', 'CA', 'NZ', 'AU']
base.class_attribute :timeout
base.timeout = 60
@@ -25,6 +25,13 @@ def self.included(base)
# subsequent Responses will have a :duplicate parameter set in the params
# hash.
base.retry_safe = true
+
+ # Send Payflow requests to PayPal directly by activating the NVP protocol.
+ # Valid XMLPay documents may have issues being parsed correctly by
+ # Payflow but will be accepted by PayPal if a PAYPAL-NVP request header
+ # is declared.
+ base.class_attribute :use_paypal_nvp
+ base.use_paypal_nvp = false
end
XMLNS = 'http://www.paypal.com/XMLPay'
@@ -36,16 +43,14 @@ def self.included(base)
:american_express => 'Amex',
:jcb => 'JCB',
:diners_club => 'DinersClub',
- :switch => 'Switch',
- :solo => 'Solo'
}
TRANSACTIONS = {
- :purchase => "Sale",
- :authorization => "Authorization",
- :capture => "Capture",
- :void => "Void",
- :credit => "Credit"
+ :purchase => 'Sale',
+ :authorization => 'Authorization',
+ :capture => 'Capture',
+ :void => 'Void',
+ :credit => 'Credit'
}
CVV_CODE = {
@@ -73,10 +78,11 @@ def void(authorization, options = {})
end
private
+
def build_request(body, options = {})
xml = Builder::XmlMarkup.new
xml.instruct!
- xml.tag! 'XMLPayRequest', 'Timeout' => timeout.to_s, 'version' => "2.1", "xmlns" => XMLNS do
+ xml.tag! 'XMLPayRequest', 'Timeout' => timeout.to_s, 'version' => '2.1', 'xmlns' => XMLNS do
xml.tag! 'RequestData' do
xml.tag! 'Vendor', @options[:login]
xml.tag! 'Partner', @options[:partner]
@@ -85,7 +91,7 @@ def build_request(body, options = {})
else
xml.tag! 'Transactions' do
xml.tag! 'Transaction', 'CustRef' => options[:customer] do
- xml.tag! 'Verbosity', 'MEDIUM'
+ xml.tag! 'Verbosity', @options[:verbosity] || 'MEDIUM'
xml << body
end
end
@@ -112,6 +118,11 @@ def build_reference_request(action, money, authorization, options)
xml.tag!('Description', options[:description]) unless options[:description].blank?
xml.tag!('Comment', options[:comment]) unless options[:comment].blank?
xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank?
+ xml.tag!(
+ 'ExtData',
+ 'Name' => 'CAPTURECOMPLETE',
+ 'Value' => options[:capture_complete]
+ ) unless options[:capture_complete].blank?
end
end
end
@@ -130,8 +141,9 @@ def add_address(xml, tag, address, options)
xml.tag! 'Address' do
xml.tag! 'Street', address[:address1] unless address[:address1].blank?
+ xml.tag! 'Street2', address[:address2] unless address[:address2].blank?
xml.tag! 'City', address[:city] unless address[:city].blank?
- xml.tag! 'State', address[:state].blank? ? "N/A" : address[:state]
+ xml.tag! 'State', address[:state].blank? ? 'N/A' : address[:state]
xml.tag! 'Country', address[:country] unless address[:country].blank?
xml.tag! 'Zip', address[:zip] unless address[:zip].blank?
end
@@ -142,16 +154,16 @@ def parse(data)
response = {}
xml = Nokogiri::XML(data)
xml.remove_namespaces!
- root = xml.xpath("//ResponseData")
+ root = xml.xpath('//ResponseData')
# REXML::XPath in Ruby 1.8.6 is now unable to match nodes based on their attributes
- tx_result = root.xpath(".//TransactionResult").first
+ tx_result = root.xpath('.//TransactionResult').first
- if tx_result && tx_result.attributes['Duplicate'].to_s == "true"
+ if tx_result && tx_result.attributes['Duplicate'].to_s == 'true'
response[:duplicate] = true
end
- root.xpath(".//*").each do |node|
+ root.xpath('.//*').each do |node|
parse_element(response, node)
end
@@ -166,10 +178,10 @@ def parse_element(response, node)
# down as we do everywhere else. RPPaymentResult elements are not contained
# in an RPPaymentResults element so we'll come here multiple times
response[node_name] ||= []
- response[node_name] << ( payment_result_response = {} )
- node.xpath(".//*").each{ |e| parse_element(payment_result_response, e) }
- when node.xpath(".//*").to_a.any?
- node.xpath(".//*").each{|e| parse_element(response, e) }
+ response[node_name] << (payment_result_response = {})
+ node.xpath('.//*').each { |e| parse_element(payment_result_response, e) }
+ when node.xpath('.//*').to_a.any?
+ node.xpath('.//*').each { |e| parse_element(response, e) }
when node_name.to_s =~ /amt$/
# *Amt elements don't put the value in the #text - instead they use a Currency attribute
response[node_name] = node.attributes['Currency'].to_s
@@ -181,29 +193,43 @@ def parse_element(response, node)
end
def build_headers(content_length)
- {
- "Content-Type" => "text/xml",
- "Content-Length" => content_length.to_s,
- "X-VPS-Client-Timeout" => timeout.to_s,
- "X-VPS-VIT-Integration-Product" => "ActiveMerchant",
- "X-VPS-VIT-Runtime-Version" => RUBY_VERSION,
- "X-VPS-Request-ID" => Utils.generate_unique_id
- }
- end
-
- def commit(request_body, options = {})
+ headers = {
+ 'Content-Type' => 'text/xml',
+ 'Content-Length' => content_length.to_s,
+ 'X-VPS-Client-Timeout' => timeout.to_s,
+ 'X-VPS-VIT-Integration-Product' => 'ActiveMerchant',
+ 'X-VPS-VIT-Runtime-Version' => RUBY_VERSION,
+ 'X-VPS-Request-ID' => SecureRandom.hex(16)
+ }
+
+ headers['PAYPAL-NVP'] = 'Y' if self.use_paypal_nvp
+ headers
+ end
+
+ def commit(request_body, options = {})
request = build_request(request_body, options)
headers = build_headers(request.size)
- response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
+ response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
- build_response(response[:result] == "0", response[:message], response,
- :test => test?,
- :authorization => response[:pn_ref] || response[:rp_ref],
- :cvv_result => CVV_CODE[response[:cv_result]],
- :avs_result => { :code => response[:avs_result] }
+ build_response(
+ success_for(response),
+ response[:message], response,
+ test: test?,
+ authorization: response[:pn_ref] || response[:rp_ref],
+ cvv_result: CVV_CODE[response[:cv_result]],
+ avs_result: { code: response[:avs_result] },
+ fraud_review: under_fraud_review?(response)
)
end
+
+ def success_for(response)
+ %w(0 126).include?(response[:result])
+ end
+
+ def under_fraud_review?(response)
+ (response[:result] == '126')
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb b/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb
index 7b4068dea05..3c43642265f 100644
--- a/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb
+++ b/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb
@@ -4,26 +4,30 @@ class PayflowExpressResponse < Response
def email
@params['e_mail']
end
-
+
def full_name
"#{@params['name']} #{@params['lastname']}"
end
-
+
def token
@params['token']
end
-
+
def payer_id
@params['payer_id']
end
-
+
# Really the shipping country, but it is all the information provided
def payer_country
address['country']
end
-
+
+ def phone
+ @params['phone']
+ end
+
def address
- { 'name' => full_name,
+ { 'name' => @params['shiptoname'] || full_name,
'company' => nil,
'address1' => @params['street'],
'address2' => @params['shiptostreet2'] || @params['street2'],
@@ -31,7 +35,7 @@ def address
'state' => @params['state'],
'country' => @params['country'],
'zip' => @params['zip'],
- 'phone' => nil
+ 'phone' => phone,
}
end
end
diff --git a/lib/active_merchant/billing/gateways/payflow/payflow_response.rb b/lib/active_merchant/billing/gateways/payflow/payflow_response.rb
index d2b6e670009..83caaff5800 100644
--- a/lib/active_merchant/billing/gateways/payflow/payflow_response.rb
+++ b/lib/active_merchant/billing/gateways/payflow/payflow_response.rb
@@ -4,10 +4,10 @@ class PayflowResponse < Response
def profile_id
@params['profile_id']
end
-
+
def payment_history
- @payment_history ||= @params['rp_payment_result'].collect{ |result| result.stringify_keys } rescue []
+ @payment_history ||= @params['rp_payment_result'].collect(&:stringify_keys) rescue []
end
end
end
-end
\ No newline at end of file
+end
diff --git a/lib/active_merchant/billing/gateways/payflow_express.rb b/lib/active_merchant/billing/gateways/payflow_express.rb
index f6183218d28..9676d2b1cf2 100644
--- a/lib/active_merchant/billing/gateways/payflow_express.rb
+++ b/lib/active_merchant/billing/gateways/payflow_express.rb
@@ -1,70 +1,67 @@
-require File.dirname(__FILE__) + '/payflow/payflow_common_api'
-require File.dirname(__FILE__) + '/payflow/payflow_express_response'
-require File.dirname(__FILE__) + '/paypal_express_common'
+require 'active_merchant/billing/gateways/payflow/payflow_common_api'
+require 'active_merchant/billing/gateways/payflow/payflow_express_response'
+require 'active_merchant/billing/gateways/paypal_express_common'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- # ==General Parameters
- # The following parameters are supported for #setup_authorization, #setup_purchase, #authorize and #purchase transactions. I've read
- # in the docs that they recommend you pass the exact same parameters to both setup and authorize/purchase.
- #
- # This information was gleaned from a mix of:
- # * PayFlow documentation
- # * for key value pairs: {Express Checkout for Payflow Pro (PDF)}[https://cms.paypal.com/cms_content/US/en_US/files/developer/PFP_ExpressCheckout_PP.pdf]
- # * XMLPay: {Payflow Pro XMLPay Developer's Guide (PDF)}[https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_PayflowPro_XMLPay_Guide.pdf]
- # * previous ActiveMerchant code
- # * trial & error
- #
- # The following parameters are currently supported.
- # [:ip] (opt) Customer IP Address
- # [:order_id] (opt) An order or invoice number. This will be passed through to the Payflow backend at manager.paypal.com, and show up as "Supplier Reference #"
- # [:description] (opt) Order description, shown to buyer (after redirected to PayPal). If Order Line Items are used (see below), then the description is suppressed. This will not be passed through to the Payflow backend.
- # [:billing_address] (opt) See ActiveMerchant::Billing::Gateway for details
- # [:shipping_address] (opt) See ActiveMerchant::Billing::Gateway for details
- # [:currency] (req) Currency of transaction, will be set to USD by default for PayFlow Express if not specified
- # [:email] (opt) Email of buyer; used to pre-fill PayPal login screen
- # [:payer_id] (opt) Unique PayPal buyer account identification number, as returned by details_for request
- # [:token] (req for #authorize & #purchase) Token returned by setup transaction
- # [:no_shipping] (opt) Boolean for whether or not to display shipping address to buyer
- # [:address_override] (opt) Boolean. If true, display shipping address passed by parameters, rather than shipping address on file with PayPal
- # [:allow_note] (opt) Boolean for permitting buyer to add note during checkout. Note contents can be retrieved with details_for transaction
- # [:return_url] (req) URL to which the buyer’s browser is returned after choosing to pay.
- # [:cancel_return_url] (req) URL to which the buyer is returned if the buyer cancels the order.
- # [:notify_url] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
- # [:comment] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment1
- # [:comment2] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment2
- # [:discount] (opt) Total discounts in cents
- #
- # ==Line Items
- # Support for order line items is available, but has to be enabled on the PayFlow backend. This is what I was told by Todd Sieber at Technical Support:
- #
- # You will need to call Payflow Support at 1-888-883-9770, choose option #2. Request that they update your account in "Pandora" under Product Settings >> PayPal Mark and update the Features Bitmap to 1111111111111112. This is 15 ones and a two.
- #
- # See here[https://www.x.com/message/206214#206214] for the forum discussion (requires login to {x.com}[https://x.com]
- #
- # [:items] (opt) Array of Order Line Items hashes. These are shown to the buyer after redirect to PayPal.
- #
- #
- #
- # The following keys are supported for line items:
- # [:name] Name of line item
- # [:description] Description of line item
- # [:amount] Line Item Amount in Cents (as Integer)
- # [:quantity] Line Item Quantity (default to 1 if left blank)
- #
- # ====Customization of Payment Page
- # [:page_style] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
- # [:header_image] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
- # [:background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
- # ====Additional options for old Checkout Experience, being phased out in 2010 and 2011
- # [:header_background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
- # [:header_border_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
-
+ # ==General Parameters
+ # The following parameters are supported for #setup_authorization, #setup_purchase, #authorize and #purchase transactions. I've read
+ # in the docs that they recommend you pass the exact same parameters to both setup and authorize/purchase.
+ #
+ # This information was gleaned from a mix of:
+ # * {PayFlow documentation}[https://developer.paypal.com/docs/classic/payflow/integration-guide/]
+ # * previous ActiveMerchant code
+ # * trial & error
+ #
+ # The following parameters are currently supported.
+ # [:ip] (opt) Customer IP Address
+ # [:order_id] (opt) An order or invoice number. This will be passed through to the Payflow backend at manager.paypal.com, and show up as "Supplier Reference #"
+ # [:description] (opt) Order description, shown to buyer (after redirected to PayPal). If Order Line Items are used (see below), then the description is suppressed. This will not be passed through to the Payflow backend.
+ # [:billing_address] (opt) See ActiveMerchant::Billing::Gateway for details
+ # [:shipping_address] (opt) See ActiveMerchant::Billing::Gateway for details
+ # [:currency] (req) Currency of transaction, will be set to USD by default for PayFlow Express if not specified
+ # [:email] (opt) Email of buyer; used to pre-fill PayPal login screen
+ # [:payer_id] (opt) Unique PayPal buyer account identification number, as returned by details_for request
+ # [:token] (req for #authorize & #purchase) Token returned by setup transaction
+ # [:no_shipping] (opt) Boolean for whether or not to display shipping address to buyer
+ # [:address_override] (opt) Boolean. If true, display shipping address passed by parameters, rather than shipping address on file with PayPal
+ # [:allow_note] (opt) Boolean for permitting buyer to add note during checkout. Note contents can be retrieved with details_for transaction
+ # [:return_url] (req) URL to which the buyer’s browser is returned after choosing to pay.
+ # [:cancel_return_url] (req) URL to which the buyer is returned if the buyer cancels the order.
+ # [:notify_url] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
+ # [:comment] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment1
+ # [:comment2] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment2
+ # [:discount] (opt) Total discounts in cents
+ #
+ # ==Line Items
+ # Support for order line items is available, but has to be enabled on the PayFlow backend. This is what I was told by Todd Sieber at Technical Support:
+ #
+ # You will need to call Payflow Support at 1-888-883-9770, choose option #2. Request that they update your account in "Pandora" under Product Settings >> PayPal Mark and update the Features Bitmap to 1111111111111112. This is 15 ones and a two.
+ #
+ # See here[https://www.x.com/message/206214#206214] for the forum discussion (requires login to {x.com}[https://x.com]
+ #
+ # [:items] (opt) Array of Order Line Items hashes. These are shown to the buyer after redirect to PayPal.
+ #
+ #
+ #
+ # The following keys are supported for line items:
+ # [:name] Name of line item
+ # [:description] Description of line item
+ # [:amount] Line Item Amount in Cents (as Integer)
+ # [:quantity] Line Item Quantity (default to 1 if left blank)
+ #
+ # ====Customization of Payment Page
+ # [:page_style] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
+ # [:header_image] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
+ # [:background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
+ # ====Additional options for old Checkout Experience, being phased out in 2010 and 2011
+ # [:header_background_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
+ # [:header_border_color] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
class PayflowExpressGateway < Gateway
include PayflowCommonAPI
include PaypalExpressCommon
-
+
self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
self.display_name = 'PayPal Express Checkout'
@@ -74,48 +71,49 @@ def authorize(money, options = {})
request = build_sale_or_authorization_request('Authorization', money, options)
commit(request, options)
end
-
- def purchase(money, options = {})
+
+ def purchase(money, options = {})
requires!(options, :token, :payer_id)
request = build_sale_or_authorization_request('Sale', money, options)
commit(request, options)
end
-
+
def refund(money, identification, options = {})
request = build_reference_request(:credit, money, identification, options)
commit(request, options)
- end
+ end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
def setup_authorization(money, options = {})
requires!(options, :return_url, :cancel_return_url)
-
+
request = build_setup_express_sale_or_authorization_request('Authorization', money, options)
commit(request, options)
end
-
+
def setup_purchase(money, options = {})
requires!(options, :return_url, :cancel_return_url)
-
+
request = build_setup_express_sale_or_authorization_request('Sale', money, options)
commit(request, options)
end
-
+
def details_for(token)
request = build_get_express_details_request(token)
commit(request, options)
end
-
+
private
+
def build_get_express_details_request(token)
- xml = Builder::XmlMarkup.new :indent => 2
+ xml = Builder::XmlMarkup.new :indent => 2
xml.tag! 'GetExpressCheckout' do
xml.tag! 'Authorization' do
- xml.tag! 'PayData' do
+ xml.tag! 'PayData' do
xml.tag! 'Tender' do
xml.tag! 'PayPal' do
xml.tag! 'Token', token
@@ -136,8 +134,8 @@ def build_setup_express_sale_or_authorization_request(action, money, options = {
end
xml.target!
end
-
- def build_sale_or_authorization_request(action, money, options)
+
+ def build_sale_or_authorization_request(action, money, options)
xml = Builder::XmlMarkup.new :indent => 2
xml.tag! 'DoExpressCheckout' do
xml.tag! action do
@@ -178,11 +176,10 @@ def add_pay_data(xml, money, options)
end
if items.any?
xml.tag! 'ExtData', 'Name' => 'CURRENCY', 'Value' => options[:currency] || currency(money)
- xml.tag! 'ExtData', 'Name' => "ITEMAMT", 'Value' => amount(options[:subtotal] || money)
+ xml.tag! 'ExtData', 'Name' => 'ITEMAMT', 'Value' => amount(options[:subtotal] || money)
end
xml.tag! 'DiscountAmt', amount(options[:discount]) if options[:discount]
xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
-
end
xml.tag! 'Tender' do
@@ -192,7 +189,7 @@ def add_pay_data(xml, money, options)
end
def add_paypal_details(xml, options)
- xml.tag! 'PayPal' do
+ xml.tag! 'PayPal' do
xml.tag! 'EMail', options[:email] unless options[:email].blank?
xml.tag! 'ReturnURL', options[:return_url] unless options[:return_url].blank?
xml.tag! 'CancelURL', options[:cancel_return_url] unless options[:cancel_return_url].blank?
@@ -201,8 +198,8 @@ def add_paypal_details(xml, options)
xml.tag! 'Token', options[:token] unless options[:token].blank?
xml.tag! 'NoShipping', options[:no_shipping] ? '1' : '0'
xml.tag! 'AddressOverride', options[:address_override] ? '1' : '0'
- xml.tag! 'ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
-
+ xml.tag! 'ButtonSource', application_id.to_s.slice(0, 32) unless application_id.blank?
+
# Customization of the payment page
xml.tag! 'PageStyle', options[:page_style] unless options[:page_style].blank?
xml.tag! 'HeaderImage', options[:header_image] unless options[:header_image].blank?
@@ -214,11 +211,10 @@ def add_paypal_details(xml, options)
xml.tag! 'ExtData', 'Name' => 'ALLOWNOTE', 'Value' => options[:allow_note]
end
end
-
+
def build_response(success, message, response, options = {})
PayflowExpressResponse.new(success, message, response, options)
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/payflow_express_uk.rb b/lib/active_merchant/billing/gateways/payflow_express_uk.rb
index 0469e65e66a..a314bad48c4 100644
--- a/lib/active_merchant/billing/gateways/payflow_express_uk.rb
+++ b/lib/active_merchant/billing/gateways/payflow_express_uk.rb
@@ -1,15 +1,14 @@
-require File.dirname(__FILE__) + '/payflow_express'
+require 'active_merchant/billing/gateways/payflow_express'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PayflowExpressUkGateway < PayflowExpressGateway
self.default_currency = 'GBP'
self.partner = 'PayPalUk'
-
+
self.supported_countries = ['GB']
self.homepage_url = 'https://www.paypal.com/uk/cgi-bin/webscr?cmd=_additional-payment-overview-outside'
self.display_name = 'PayPal Express Checkout (UK)'
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/payflow_uk.rb b/lib/active_merchant/billing/gateways/payflow_uk.rb
index 1bb1bf51730..e963c152ef0 100644
--- a/lib/active_merchant/billing/gateways/payflow_uk.rb
+++ b/lib/active_merchant/billing/gateways/payflow_uk.rb
@@ -1,21 +1,20 @@
-require File.dirname(__FILE__) + '/payflow'
-require File.dirname(__FILE__) + '/payflow_express_uk'
+require 'active_merchant/billing/gateways/payflow'
+require 'active_merchant/billing/gateways/payflow_express_uk'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PayflowUkGateway < PayflowGateway
self.default_currency = 'GBP'
self.partner = 'PayPalUk'
-
+
def express
@express ||= PayflowExpressUkGateway.new(@options)
end
-
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :solo, :switch]
+
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.supported_countries = ['GB']
self.homepage_url = 'https://www.paypal.com/uk/webapps/mpp/pro'
self.display_name = 'PayPal Payments Pro (UK)'
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/payment_express.rb b/lib/active_merchant/billing/gateways/payment_express.rb
index 450b26e2661..ff89eb8cb08 100644
--- a/lib/active_merchant/billing/gateways/payment_express.rb
+++ b/lib/active_merchant/billing/gateways/payment_express.rb
@@ -2,7 +2,6 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
-
# In NZ DPS supports ANZ, Westpac, National Bank, ASB and BNZ.
# In Australia DPS supports ANZ, NAB, Westpac, CBA, St George and Bank of South Australia.
# The Maybank in Malaysia is supported and the Citibank for Singapore.
@@ -16,12 +15,13 @@ class PaymentExpressGateway < Gateway
# However, regular accounts with DPS only support VISA and Mastercard
self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ]
- self.supported_countries = %w[ AU CA DE ES FR GB HK IE MY NL NZ SG US ZA ]
+ self.supported_countries = %w[ AU FJ GB HK IE MY NZ PG SG US ]
self.homepage_url = 'http://www.paymentexpress.com/'
self.display_name = 'PaymentExpress'
- self.live_url = self.test_url = 'https://sec.paymentexpress.com/pxpost.aspx'
+ self.live_url = 'https://sec.paymentexpress.com/pxpost.aspx'
+ self.test_url = 'https://uat.paymentexpress.com/pxpost.aspx'
APPROVED = '1'
@@ -82,7 +82,7 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -122,6 +122,18 @@ def store(credit_card, options = {})
commit(:validate, request)
end
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]\2').
+ gsub(%r(()\d+()), '\1[FILTERED]\2').
+ gsub(%r(()\d+()), '\1[FILTERED]\2')
+ end
+
private
def use_custom_payment_token?
@@ -141,6 +153,7 @@ def build_purchase_or_authorization_request(money, payment_source, options)
add_invoice(result, options)
add_address_verification_data(result, options)
add_optional_elements(result, options)
+ add_ip(result, options)
result
end
@@ -151,83 +164,84 @@ def build_capture_or_credit_request(money, identification, options)
add_invoice(result, options)
add_reference(result, identification)
add_optional_elements(result, options)
+ add_ip(result, options)
result
end
def build_token_request(credit_card, options)
result = new_transaction
add_credit_card(result, credit_card)
- add_amount(result, 100, options) #need to make an auth request for $1
+ add_amount(result, 100, options) # need to make an auth request for $1
add_token_request(result, options)
add_optional_elements(result, options)
+ add_ip(result, options)
result
end
def add_credentials(xml)
- xml.add_element("PostUsername").text = @options[:login]
- xml.add_element("PostPassword").text = @options[:password]
+ xml.add_element('PostUsername').text = @options[:login]
+ xml.add_element('PostPassword').text = @options[:password]
end
def add_reference(xml, identification)
- xml.add_element("DpsTxnRef").text = identification
+ xml.add_element('DpsTxnRef').text = identification
end
def add_credit_card(xml, credit_card)
- xml.add_element("CardHolderName").text = credit_card.name
- xml.add_element("CardNumber").text = credit_card.number
- xml.add_element("DateExpiry").text = format_date(credit_card.month, credit_card.year)
+ xml.add_element('CardHolderName').text = credit_card.name
+ xml.add_element('CardNumber').text = credit_card.number
+ xml.add_element('DateExpiry').text = format_date(credit_card.month, credit_card.year)
if credit_card.verification_value?
- xml.add_element("Cvc2").text = credit_card.verification_value
- xml.add_element("Cvc2Presence").text = "1"
- end
-
- if requires_start_date_or_issue_number?(credit_card)
- xml.add_element("DateStart").text = format_date(credit_card.start_month, credit_card.start_year) unless credit_card.start_month.blank? || credit_card.start_year.blank?
- xml.add_element("IssueNumber").text = credit_card.issue_number unless credit_card.issue_number.blank?
+ xml.add_element('Cvc2').text = credit_card.verification_value
+ xml.add_element('Cvc2Presence').text = '1'
end
end
def add_billing_token(xml, token)
if use_custom_payment_token?
- xml.add_element("BillingId").text = token
+ xml.add_element('BillingId').text = token
else
- xml.add_element("DpsBillingId").text = token
+ xml.add_element('DpsBillingId').text = token
end
end
def add_token_request(xml, options)
- xml.add_element("BillingId").text = options[:billing_id] if options[:billing_id]
- xml.add_element("EnableAddBillCard").text = 1
+ xml.add_element('BillingId').text = options[:billing_id] if options[:billing_id]
+ xml.add_element('EnableAddBillCard').text = 1
end
def add_amount(xml, money, options)
- xml.add_element("Amount").text = amount(money)
- xml.add_element("InputCurrency").text = options[:currency] || currency(money)
+ xml.add_element('Amount').text = amount(money)
+ xml.add_element('InputCurrency').text = options[:currency] || currency(money)
end
def add_transaction_type(xml, action)
- xml.add_element("TxnType").text = TRANSACTIONS[action]
+ xml.add_element('TxnType').text = TRANSACTIONS[action]
end
def add_invoice(xml, options)
- xml.add_element("TxnId").text = options[:order_id].to_s.slice(0, 16) unless options[:order_id].blank?
- xml.add_element("MerchantReference").text = options[:description].to_s.slice(0, 50) unless options[:description].blank?
+ xml.add_element('TxnId').text = options[:order_id].to_s.slice(0, 16) unless options[:order_id].blank?
+ xml.add_element('MerchantReference').text = options[:description].to_s.slice(0, 50) unless options[:description].blank?
end
def add_address_verification_data(xml, options)
address = options[:billing_address] || options[:address]
return if address.nil?
- xml.add_element("EnableAvsData").text = 1
- xml.add_element("AvsAction").text = 1
+ xml.add_element('EnableAvsData').text = 1
+ xml.add_element('AvsAction').text = 1
- xml.add_element("AvsStreetAddress").text = address[:address1]
- xml.add_element("AvsPostCode").text = address[:zip]
+ xml.add_element('AvsStreetAddress').text = address[:address1]
+ xml.add_element('AvsPostCode').text = address[:zip]
+ end
+
+ def add_ip(xml, options)
+ xml.add_element('ClientInfo').text = options[:ip] if options[:ip]
end
# The options hash may contain optional data which will be passed
- # through the the specialized optional fields at PaymentExpress
+ # through the specialized optional fields at PaymentExpress
# as follows:
#
# {
@@ -264,16 +278,16 @@ def add_address_verification_data(xml, options)
# +purchase+, +authorize+, +capture+, +refund+, +store+
def add_optional_elements(xml, options)
if client_type = normalized_client_type(options[:client_type])
- xml.add_element("ClientType").text = client_type
+ xml.add_element('ClientType').text = client_type
end
- xml.add_element("TxnData1").text = options[:txn_data1].to_s.slice(0,255) unless options[:txn_data1].blank?
- xml.add_element("TxnData2").text = options[:txn_data2].to_s.slice(0,255) unless options[:txn_data2].blank?
- xml.add_element("TxnData3").text = options[:txn_data3].to_s.slice(0,255) unless options[:txn_data3].blank?
+ xml.add_element('TxnData1').text = options[:txn_data1].to_s.slice(0, 255) unless options[:txn_data1].blank?
+ xml.add_element('TxnData2').text = options[:txn_data2].to_s.slice(0, 255) unless options[:txn_data2].blank?
+ xml.add_element('TxnData3').text = options[:txn_data3].to_s.slice(0, 255) unless options[:txn_data3].blank?
end
def new_transaction
- REXML::Document.new.add_element("Txn")
+ REXML::Document.new.add_element('Txn')
end
# Take in the request and post it to DPS
@@ -281,8 +295,10 @@ def commit(action, request)
add_credentials(request)
add_transaction_type(request, action)
+ url = test? ? self.test_url : self.live_url
+
# Parse the XML response
- response = parse( ssl_post(self.live_url, request.to_s) )
+ response = parse(ssl_post(url, request.to_s))
# Return a response
PaymentExpressResponse.new(response[:success] == APPROVED, message_from(response), response,
@@ -331,13 +347,13 @@ def format_date(month, year)
def normalized_client_type(client_type_from_options)
case client_type_from_options.to_s.downcase
- when 'web' then "Web"
- when 'ivr' then "IVR"
- when 'moto' then "MOTO"
- when 'unattended' then "Unattended"
- when 'internet' then "Internet"
- when 'recurring' then "Recurring"
- else nil
+ when 'web' then 'Web'
+ when 'ivr' then 'IVR'
+ when 'moto' then 'MOTO'
+ when 'unattended' then 'Unattended'
+ when 'internet' then 'Internet'
+ when 'recurring' then 'Recurring'
+ else nil
end
end
end
@@ -346,7 +362,7 @@ class PaymentExpressResponse < Response
# add a method to response so we can easily get the token
# for Validate transactions
def token
- @params["billing_id"] || @params["dps_billing_id"]
+ @params['billing_id'] || @params['dps_billing_id']
end
end
end
diff --git a/lib/active_merchant/billing/gateways/paymentez.rb b/lib/active_merchant/billing/gateways/paymentez.rb
new file mode 100644
index 00000000000..50463010ea2
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/paymentez.rb
@@ -0,0 +1,300 @@
+require 'base64'
+require 'digest'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PaymentezGateway < Gateway #:nodoc:
+ self.test_url = 'https://ccapi-stg.paymentez.com/v2/'
+ self.live_url = 'https://ccapi.paymentez.com/v2/'
+
+ self.supported_countries = %w[MX EC VE CO BR CL]
+ self.default_currency = 'USD'
+ self.supported_cardtypes = %i[visa master american_express diners_club elo]
+
+ self.homepage_url = 'https://secure.paymentez.com/'
+ self.display_name = 'Paymentez'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ 1 => :processing_error,
+ 6 => :card_declined,
+ 9 => :card_declined,
+ 10 => :processing_error,
+ 11 => :card_declined,
+ 12 => :config_error,
+ 13 => :config_error,
+ 19 => :invalid_cvc,
+ 20 => :config_error,
+ 21 => :card_declined,
+ 22 => :card_declined,
+ 23 => :card_declined,
+ 24 => :card_declined,
+ 25 => :card_declined,
+ 26 => :card_declined,
+ 27 => :card_declined,
+ 28 => :card_declined
+ }.freeze
+
+ CARD_MAPPING = {
+ 'visa' => 'vi',
+ 'master' => 'mc',
+ 'american_express' => 'ax',
+ 'diners_club' => 'di',
+ 'elo' => 'el'
+ }.freeze
+
+ def initialize(options = {})
+ requires!(options, :application_code, :app_key)
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ post = {}
+
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_customer_data(post, options)
+ add_extra_params(post, options)
+ action = payment.is_a?(String) ? 'debit' : 'debit_cc'
+
+ commit_transaction(action, post)
+ end
+
+ def authorize(money, payment, options = {})
+ post = {}
+
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_customer_data(post, options)
+ add_extra_params(post, options)
+
+ commit_transaction('authorize', post)
+ end
+
+ def capture(money, authorization, _options = {})
+ post = {
+ transaction: { id: authorization }
+ }
+ post[:order] = {amount: amount(money).to_f} if money
+
+ commit_transaction('capture', post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {transaction: {id: authorization}}
+ post[:order] = {amount: amount(money).to_f} if money
+
+ commit_transaction('refund', post)
+ end
+
+ def void(authorization, _options = {})
+ post = { transaction: { id: authorization } }
+ commit_transaction('refund', post)
+ end
+
+ def verify(credit_card, options = {})
+ MultiResponse.run do |r|
+ r.process { authorize(100, credit_card, options) }
+ r.process { void(r.authorization, options) }
+ end
+ end
+
+ def store(credit_card, options = {})
+ post = {}
+
+ add_customer_data(post, options)
+ add_payment(post, credit_card)
+
+ response = commit_card('add', post)
+ if !response.success? && !(token = extract_previous_card_token(response)).nil?
+ unstore(token, options)
+ response = commit_card('add', post)
+ end
+ response
+ end
+
+ def unstore(identification, options = {})
+ post = { card: { token: identification }, user: { id: options[:user_id] }}
+ commit_card('delete', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r{(\\?"number\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]').
+ gsub(%r{(\\?"cvc\\?":)(\\?"[^"]+\\?")}, '\1[FILTERED]').
+ gsub(%r{(Auth-Token: )([A-Za-z0-9=]+)}, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_customer_data(post, options)
+ requires!(options, :user_id, :email)
+ post[:user] ||= {}
+ post[:user][:id] = options[:user_id]
+ post[:user][:email] = options[:email]
+ post[:user][:ip_address] = options[:ip] if options[:ip]
+ post[:user][:fiscal_number] = options[:fiscal_number] if options[:fiscal_number]
+ if phone = options[:phone] || options.dig(:billing_address, :phone)
+ post[:user][:phone] = phone
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:session_id] = options[:session_id] if options[:session_id]
+
+ post[:order] ||= {}
+ post[:order][:amount] = amount(money).to_f
+ post[:order][:vat] = options[:vat] if options[:vat]
+ post[:order][:dev_reference] = options[:dev_reference] if options[:dev_reference]
+ post[:order][:description] = options[:description] if options[:description]
+ post[:order][:discount] = options[:discount] if options[:discount]
+ post[:order][:installments] = options[:installments] if options[:installments]
+ post[:order][:installments_type] = options[:installments_type] if options[:installments_type]
+ post[:order][:taxable_amount] = options[:taxable_amount] if options[:taxable_amount]
+ post[:order][:tax_percentage] = options[:tax_percentage] if options[:tax_percentage]
+ end
+
+ def add_payment(post, payment)
+ post[:card] ||= {}
+ if payment.is_a?(String)
+ post[:card][:token] = payment
+ else
+ post[:card][:number] = payment.number
+ post[:card][:holder_name] = payment.name
+ post[:card][:expiry_month] = payment.month
+ post[:card][:expiry_year] = payment.year
+ post[:card][:cvc] = payment.verification_value
+ post[:card][:type] = CARD_MAPPING[payment.brand]
+ end
+ end
+
+ def add_extra_params(post, options)
+ extra_params = {}
+ extra_params.merge!(options[:extra_params]) if options[:extra_params]
+
+ post['extra_params'] = extra_params unless extra_params.empty?
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit_raw(object, action, parameters)
+ url = "#{(test? ? test_url : live_url)}#{object}/#{action}"
+
+ begin
+ raw_response = ssl_post(url, post_data(parameters), headers)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ end
+
+ begin
+ parse(raw_response)
+ rescue JSON::ParserError
+ {'status' => 'Internal server error'}
+ end
+ end
+
+ def commit_transaction(action, parameters)
+ response = commit_raw('transaction', action, parameters)
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def commit_card(action, parameters)
+ response = commit_raw('card', action, parameters)
+ Response.new(
+ card_success_from(response),
+ card_message_from(response),
+ response,
+ authorization: card_authorization_from(response),
+ test: test?,
+ error_code: card_error_code_from(response)
+ )
+ end
+
+ def headers
+ {
+ 'Auth-Token' => authentication_code,
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def success_from(response)
+ !response.include?('error') && (response['status'] || response['transaction']['status']) == 'success'
+ end
+
+ def card_success_from(response)
+ return false if response.include?('error')
+ return true if response['message'] == 'card deleted'
+ response['card']['status'] == 'valid'
+ end
+
+ def message_from(response)
+ if !success_from(response) && response['error']
+ response['error'] && response['error']['type']
+ else
+ response['transaction'] && response['transaction']['message']
+ end
+ end
+
+ def card_message_from(response)
+ if !response.include?('error')
+ response['message'] || response['card']['message']
+ else
+ response['error']['type']
+ end
+ end
+
+ def authorization_from(response)
+ response['transaction'] && response['transaction']['id']
+ end
+
+ def card_authorization_from(response)
+ response['card'] && response['card']['token']
+ end
+
+ def extract_previous_card_token(response)
+ match = /Card already added: (\d+)/.match(response.message)
+ match && match[1]
+ end
+
+ def post_data(parameters = {})
+ JSON.dump(parameters)
+ end
+
+ def error_code_from(response)
+ return if success_from(response)
+ if response['transaction']
+ detail = response['transaction']['status_detail']
+ if STANDARD_ERROR_CODE_MAPPING.include?(detail)
+ return STANDARD_ERROR_CODE[STANDARD_ERROR_CODE_MAPPING[detail]]
+ end
+ elsif response['error']
+ return STANDARD_ERROR_CODE[:config_error]
+ end
+ STANDARD_ERROR_CODE[:processing_error]
+ end
+
+ def card_error_code_from(response)
+ STANDARD_ERROR_CODE[:processing_error] unless card_success_from(response)
+ end
+
+ def authentication_code
+ timestamp = Time.new.to_i
+ unique_token = Digest::SHA256.hexdigest("#{@options[:app_key]}#{timestamp}")
+ authentication_string = "#{@options[:application_code]};#{timestamp};#{unique_token}"
+ Base64.encode64(authentication_string).delete("\n")
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/paymill.rb b/lib/active_merchant/billing/gateways/paymill.rb
index a426e573248..ba4dbeb8f9d 100644
--- a/lib/active_merchant/billing/gateways/paymill.rb
+++ b/lib/active_merchant/billing/gateways/paymill.rb
@@ -2,42 +2,27 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PaymillGateway < Gateway
self.supported_countries = %w(AD AT BE BG CH CY CZ DE DK EE ES FI FO FR GB
- GI GR HU IE IL IS IT LI LT LU LV MT NL NO PL
- PT RO SE SI SK TR VA)
+ GI GR HR HU IE IL IM IS IT LI LT LU LV MC MT
+ NL NO PL PT RO SE SI SK TR VA)
- self.supported_cardtypes = [:visa, :master]
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :union_pay, :jcb]
self.homepage_url = 'https://paymill.com'
self.display_name = 'PAYMILL'
self.money_format = :cents
self.default_currency = 'EUR'
+ self.live_url = 'https://api.paymill.com/v2/'
def initialize(options = {})
requires!(options, :public_key, :private_key)
super
end
- def purchase(money, payment_method, options = {})
- case payment_method
- when String
- purchase_with_token(money, payment_method, options)
- else
- MultiResponse.run do |r|
- r.process { save_card(payment_method) }
- r.process { purchase_with_token(money, r.authorization, options) }
- end
- end
+ def purchase(money, payment_method, options={})
+ action_with_token(:purchase, money, payment_method, options)
end
def authorize(money, payment_method, options = {})
- case payment_method
- when String
- authorize_with_token(money, payment_method, options)
- else
- MultiResponse.run do |r|
- r.process { save_card(payment_method) }
- r.process { authorize_with_token(money, r.authorization, options) }
- end
- end
+ action_with_token(:authorize, money, payment_method, options)
end
def capture(money, authorization, options = {})
@@ -45,7 +30,8 @@ def capture(money, authorization, options = {})
add_amount(post, money, options)
post[:preauthorization] = preauth(authorization)
- post[:description] = options[:description]
+ post[:description] = options[:order_id]
+ post[:source] = 'active_merchant'
commit(:post, 'transactions', post)
end
@@ -53,33 +39,71 @@ def refund(money, authorization, options={})
post = {}
post[:amount] = amount(money)
- post[:description] = options[:description]
+ post[:description] = options[:order_id]
commit(:post, "refunds/#{transaction_id(authorization)}", post)
end
+ def void(authorization, options={})
+ commit(:delete, "preauthorizations/#{preauth(authorization)}")
+ end
+
def store(credit_card, options={})
- save_card(credit_card)
+ # The store request requires a currency and amount of at least $1 USD.
+ # This is used for an authorization that is handled internally by Paymill.
+ options[:currency] = 'USD'
+ options[:money] = 100
+
+ save_card(credit_card, options)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(/(account.number=)(\d*)/, '\1[FILTERED]').
+ gsub(/(account.verification=)(\d*)/, '\1[FILTERED]')
+ end
+
+ def verify_credentials
+ begin
+ ssl_get(live_url + 'transactions/nonexistent', headers)
+ rescue ResponseError => e
+ return false if e.response.code.to_i == 401
+ end
+
+ true
end
private
- def add_credit_card(post, credit_card)
+ def add_credit_card(post, credit_card, options)
+ post['account.holder'] = (credit_card.try(:name) || '')
post['account.number'] = credit_card.number
- post['account.expiry.month'] = sprintf("%.2i", credit_card.month)
- post['account.expiry.year'] = sprintf("%.4i", credit_card.year)
+ post['account.expiry.month'] = sprintf('%.2i', credit_card.month)
+ post['account.expiry.year'] = sprintf('%.4i', credit_card.year)
post['account.verification'] = credit_card.verification_value
+ post['account.email'] = (options[:email] || nil)
+ post['presentation.amount3D'] = (options[:money] || nil)
+ post['presentation.currency3D'] = (options[:currency] || currency(options[:money]))
end
def headers
{ 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:private_key]}:X").chomp) }
end
- def commit(method, url, parameters=nil)
+ def commit(method, action, parameters=nil)
begin
- raw_response = ssl_request(method, "https://api.paymill.com/v2/#{url}", post_data(parameters), headers)
+ raw_response = ssl_request(method, live_url + action, post_data(parameters), headers)
rescue ResponseError => e
- parsed = JSON.parse(e.response.body)
- return Response.new(false, parsed['error'], parsed, {})
+ begin
+ parsed = JSON.parse(e.response.body)
+ rescue JSON::ParserError
+ return Response.new(false, "Unable to parse error response: '#{e.response.body}'")
+ end
+ return Response.new(false, response_message(parsed), parsed, {})
end
response_from(raw_response)
@@ -87,20 +111,36 @@ def commit(method, url, parameters=nil)
def response_from(raw_response)
parsed = JSON.parse(raw_response)
-
options = {
:authorization => authorization_from(parsed),
:test => (parsed['mode'] == 'test'),
}
- Response.new(true, 'Transaction approved', parsed, options)
+ succeeded = (parsed['data'] == []) || (parsed['data']['response_code'].to_i == 20000)
+ Response.new(succeeded, response_message(parsed), parsed, options)
end
def authorization_from(parsed_response)
+ parsed_data = parsed_response['data']
+ return '' unless parsed_data.kind_of?(Hash)
+
[
- parsed_response['data']['id'],
- parsed_response['data']['preauthorization'].try(:[], 'id')
- ].join(";")
+ parsed_data['id'],
+ parsed_data['preauthorization'].try(:[], 'id')
+ ].join(';')
+ end
+
+ def action_with_token(action, money, payment_method, options)
+ options[:money] = money
+ case payment_method
+ when String
+ self.send("#{action}_with_token", money, payment_method, options)
+ else
+ MultiResponse.run do |r|
+ r.process { save_card(payment_method, options) }
+ r.process { self.send("#{action}_with_token", money, r.authorization, options) }
+ end
+ end
end
def purchase_with_token(money, card_token, options)
@@ -108,7 +148,8 @@ def purchase_with_token(money, card_token, options)
add_amount(post, money, options)
post[:token] = card_token
- post[:description] = options[:description]
+ post[:description] = options[:order_id]
+ post[:source] = 'active_merchant'
commit(:post, 'transactions', post)
end
@@ -117,13 +158,15 @@ def authorize_with_token(money, card_token, options)
add_amount(post, money, options)
post[:token] = card_token
+ post[:description] = options[:order_id]
+ post[:source] = 'active_merchant'
commit(:post, 'preauthorizations', post)
end
- def save_card(credit_card)
+ def save_card(credit_card, options)
post = {}
- add_credit_card(post, credit_card)
+ add_credit_card(post, credit_card, options)
post['channel.id'] = @options[:public_key]
post['jsonPFunction'] = 'jsonPFunction'
post['transaction.mode'] = (test? ? 'CONNECTOR_TEST' : 'LIVE')
@@ -131,7 +174,7 @@ def save_card(credit_card)
begin
raw_response = ssl_request(:get, "#{save_card_url}?#{post_data(post)}", nil, {})
rescue ResponseError => e
- return Response.new(false, e.response.body, e.response.body, {})
+ return Response.new(false, e.response.body)
end
response_for_save_from(raw_response)
@@ -140,17 +183,12 @@ def save_card(credit_card)
def response_for_save_from(raw_response)
options = { :test => test? }
- parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, ''))
- if parsed['error']
- succeeded = false
- message = parsed['error']['message']
- else
- succeeded = parsed['transaction']['processing']['result'] == 'ACK'
- message = parsed['transaction']['processing']['return']['message']
- options[:authorization] = parsed['transaction']['identification']['uniqueId'] if succeeded
- end
+ parser = ResponseParser.new(raw_response, options)
+ parser.generate_response
+ end
- Response.new(succeeded, message, parsed, options)
+ def parse_reponse(response)
+ JSON.parse(response.sub(/jsonPFunction\(/, '').sub(/\)\z/, ''))
end
def save_card_url
@@ -158,8 +196,10 @@ def save_card_url
end
def post_data(params)
+ return nil unless params
+
no_blanks = params.reject { |key, value| value.blank? }
- no_blanks.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ no_blanks.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
def add_amount(post, money, options)
@@ -168,12 +208,164 @@ def add_amount(post, money, options)
end
def preauth(authorization)
- authorization.split(";").last
+ authorization.split(';').last
end
def transaction_id(authorization)
authorization.split(';').first
end
+
+ RESPONSE_CODES = {
+ 10001 => 'Undefined response',
+ 10002 => 'Waiting for something',
+ 11000 => 'Retry request at a later time',
+
+ 20000 => 'Operation successful',
+ 20100 => 'Funds held by acquirer',
+ 20101 => 'Funds held by acquirer because merchant is new',
+ 20200 => 'Transaction reversed',
+ 20201 => 'Reversed due to chargeback',
+ 20202 => 'Reversed due to money-back guarantee',
+ 20203 => 'Reversed due to complaint by buyer',
+ 20204 => 'Payment has been refunded',
+ 20300 => 'Reversal has been canceled',
+ 22000 => 'Initiation of transaction successful',
+
+ 30000 => 'Transaction still in progress',
+ 30100 => 'Transaction has been accepted',
+ 31000 => 'Transaction pending',
+ 31100 => 'Pending due to address',
+ 31101 => 'Pending due to uncleared eCheck',
+ 31102 => 'Pending due to risk review',
+ 31103 => 'Pending due regulatory review',
+ 31104 => 'Pending due to unregistered/unconfirmed receiver',
+ 31200 => 'Pending due to unverified account',
+ 31201 => 'Pending due to non-captured funds',
+ 31202 => 'Pending due to international account (accept manually)',
+ 31203 => 'Pending due to currency conflict (accept manually)',
+ 31204 => 'Pending due to fraud filters (accept manually)',
+
+ 40000 => 'Problem with transaction data',
+ 40001 => 'Problem with payment data',
+ 40002 => 'Invalid checksum',
+ 40100 => 'Problem with credit card data',
+ 40101 => 'Problem with CVV',
+ 40102 => 'Card expired or not yet valid',
+ 40103 => 'Card limit exceeded',
+ 40104 => 'Card is not valid',
+ 40105 => 'Expiry date not valid',
+ 40106 => 'Credit card brand required',
+ 40200 => 'Problem with bank account data',
+ 40201 => 'Bank account data combination mismatch',
+ 40202 => 'User authentication failed',
+ 40300 => 'Problem with 3-D Secure data',
+ 40301 => 'Currency/amount mismatch',
+ 40400 => 'Problem with input data',
+ 40401 => 'Amount too low or zero',
+ 40402 => 'Usage field too long',
+ 40403 => 'Currency not allowed',
+ 40410 => 'Problem with shopping cart data',
+ 40420 => 'Problem with address data',
+ 40500 => 'Permission error with acquirer API',
+ 40510 => 'Rate limit reached for acquirer API',
+ 42000 => 'Initiation of transaction failed',
+ 42410 => 'Initiation of transaction expired',
+
+ 50000 => 'Problem with back end',
+ 50001 => 'Country blacklisted',
+ 50002 => 'IP address blacklisted',
+ 50004 => 'Live mode not allowed',
+ 50005 => 'Insufficient permissions (API key)',
+ 50100 => 'Technical error with credit card',
+ 50101 => 'Error limit exceeded',
+ 50102 => 'Card declined',
+ 50103 => 'Manipulation or stolen card',
+ 50104 => 'Card restricted',
+ 50105 => 'Invalid configuration data',
+ 50200 => 'Technical error with bank account',
+ 50201 => 'Account blacklisted',
+ 50300 => 'Technical error with 3-D Secure',
+ 50400 => 'Declined because of risk issues',
+ 50401 => 'Checksum was wrong',
+ 50402 => 'Bank account number was invalid (formal check)',
+ 50403 => 'Technical error with risk check',
+ 50404 => 'Unknown error with risk check',
+ 50405 => 'Unknown bank code',
+ 50406 => 'Open chargeback',
+ 50407 => 'Historical chargeback',
+ 50408 => 'Institution / public bank account (NCA)',
+ 50409 => 'KUNO/Fraud',
+ 50410 => 'Personal Account Protection (PAP)',
+ 50420 => 'Rejected due to acquirer fraud settings',
+ 50430 => 'Rejected due to acquirer risk settings',
+ 50440 => 'Failed due to restrictions with acquirer account',
+ 50450 => 'Failed due to restrictions with user account',
+ 50500 => 'General timeout',
+ 50501 => 'Timeout on side of the acquirer',
+ 50502 => 'Risk management transaction timeout',
+ 50600 => 'Duplicate operation',
+ 50700 => 'Cancelled by user',
+ 50710 => 'Failed due to funding source',
+ 50711 => 'Payment method not usable, use other payment method',
+ 50712 => 'Limit of funding source was exceeded',
+ 50713 => 'Means of payment not reusable (canceled by user)',
+ 50714 => 'Means of payment not reusable (expired)',
+ 50720 => 'Rejected by acquirer',
+ 50730 => 'Transaction denied by merchant',
+ 50800 => 'Preauthorisation failed',
+ 50810 => 'Authorisation has been voided',
+ 50820 => 'Authorisation period expired'
+ }
+
+ def response_message(parsed_response)
+ return parsed_response['error'] if parsed_response['error']
+ return 'Transaction approved.' if parsed_response['data'] == []
+
+ code = parsed_response['data']['response_code'].to_i
+ RESPONSE_CODES[code] || code.to_s
+ end
+
+ class ResponseParser
+ attr_reader :raw_response, :parsed, :succeeded, :message, :options
+
+ def initialize(raw_response='', options={})
+ @raw_response = raw_response
+ @options = options
+ end
+
+ def generate_response
+ parse_response
+ if parsed['error']
+ handle_response_parse_error
+ else
+ handle_response_correct_parsing
+ end
+
+ Response.new(succeeded, message, parsed, options)
+ end
+
+ private
+
+ def parse_response
+ @parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, ''))
+ end
+
+ def handle_response_parse_error
+ @succeeded = false
+ @message = parsed['error']['message']
+ end
+
+ def handle_response_correct_parsing
+ @message = parsed['transaction']['processing']['return']['message']
+ if @succeeded = is_ack?
+ @options[:authorization] = parsed['transaction']['identification']['uniqueId']
+ end
+ end
+
+ def is_ack?
+ parsed['transaction']['processing']['result'] == 'ACK'
+ end
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/paypal.rb b/lib/active_merchant/billing/gateways/paypal.rb
index 4faf808a5af..3563a40410d 100644
--- a/lib/active_merchant/billing/gateways/paypal.rb
+++ b/lib/active_merchant/billing/gateways/paypal.rb
@@ -1,18 +1,18 @@
-require File.dirname(__FILE__) + '/paypal/paypal_common_api'
-require File.dirname(__FILE__) + '/paypal/paypal_recurring_api'
-require File.dirname(__FILE__) + '/paypal_express'
+require 'active_merchant/billing/gateways/paypal/paypal_common_api'
+require 'active_merchant/billing/gateways/paypal/paypal_recurring_api'
+require 'active_merchant/billing/gateways/paypal_express'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PaypalGateway < Gateway
include PaypalCommonAPI
include PaypalRecurringApi
-
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
- self.supported_countries = ['US']
+ self.supported_countries = ['CA', 'NZ', 'GB', 'US']
self.homepage_url = 'https://www.paypal.com/us/webapps/mpp/paypal-payments-pro'
self.display_name = 'PayPal Payments Pro (US)'
-
+
def authorize(money, credit_card_or_referenced_id, options = {})
requires!(options, :ip)
commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Authorization', money, credit_card_or_referenced_id, options)
@@ -22,13 +22,24 @@ def purchase(money, credit_card_or_referenced_id, options = {})
requires!(options, :ip)
commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Sale', money, credit_card_or_referenced_id, options)
end
-
+
+ def verify(credit_card, options = {})
+ if %w(visa master).include?(credit_card.brand)
+ authorize(0, credit_card, options)
+ else
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+ end
+
def express
@express ||= PaypalExpressGateway.new(@options)
end
-
+
private
-
+
def define_transaction_type(transaction_arg)
if transaction_arg.is_a?(String)
return 'DoReferenceTransaction'
@@ -36,14 +47,14 @@ def define_transaction_type(transaction_arg)
return 'DoDirectPayment'
end
end
-
+
def build_sale_or_authorization_request(action, money, credit_card_or_referenced_id, options)
transaction_type = define_transaction_type(credit_card_or_referenced_id)
- reference_id = credit_card_or_referenced_id if transaction_type == "DoReferenceTransaction"
-
+ reference_id = credit_card_or_referenced_id if transaction_type == 'DoReferenceTransaction'
+
billing_address = options[:billing_address] || options[:address]
currency_code = options[:currency] || currency(money)
-
+
xml = Builder::XmlMarkup.new :indent => 2
xml.tag! transaction_type + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
xml.tag! transaction_type + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do
@@ -51,6 +62,7 @@ def build_sale_or_authorization_request(action, money, credit_card_or_referenced
xml.tag! 'n2:' + transaction_type + 'RequestDetails' do
xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction'
xml.tag! 'n2:PaymentAction', action
+ add_descriptors(xml, options)
add_payment_details(xml, money, currency_code, options)
add_credit_card(xml, credit_card_or_referenced_id, billing_address, options) unless transaction_type == 'DoReferenceTransaction'
xml.tag! 'n2:IPAddress', options[:ip]
@@ -58,32 +70,44 @@ def build_sale_or_authorization_request(action, money, credit_card_or_referenced
end
end
- xml.target!
+ xml.target!
end
-
+
def add_credit_card(xml, credit_card, address, options)
xml.tag! 'n2:CreditCard' do
xml.tag! 'n2:CreditCardType', credit_card_type(card_brand(credit_card))
xml.tag! 'n2:CreditCardNumber', credit_card.number
xml.tag! 'n2:ExpMonth', format(credit_card.month, :two_digits)
xml.tag! 'n2:ExpYear', format(credit_card.year, :four_digits)
- xml.tag! 'n2:CVV2', credit_card.verification_value
-
- if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s)
- xml.tag! 'n2:StartMonth', format(credit_card.start_month, :two_digits) unless credit_card.start_month.blank?
- xml.tag! 'n2:StartYear', format(credit_card.start_year, :four_digits) unless credit_card.start_year.blank?
- xml.tag! 'n2:IssueNumber', format(credit_card.issue_number, :two_digits) unless credit_card.issue_number.blank?
- end
-
+ xml.tag! 'n2:CVV2', credit_card.verification_value unless credit_card.verification_value.blank?
+
xml.tag! 'n2:CardOwner' do
xml.tag! 'n2:PayerName' do
xml.tag! 'n2:FirstName', credit_card.first_name
xml.tag! 'n2:LastName', credit_card.last_name
end
-
+
xml.tag! 'n2:Payer', options[:email]
add_address(xml, 'n2:Address', address)
end
+
+ add_three_d_secure(xml, options) if options[:three_d_secure]
+ end
+ end
+
+ def add_descriptors(xml, options)
+ xml.tag! 'n2:SoftDescriptor', options[:soft_descriptor] unless options[:soft_descriptor].blank?
+ xml.tag! 'n2:SoftDescriptorCity', options[:soft_descriptor_city] unless options[:soft_descriptor_city].blank?
+ end
+
+ def add_three_d_secure(xml, options)
+ three_d_secure = options[:three_d_secure]
+ xml.tag! 'ThreeDSecureRequest' do
+ xml.tag! 'MpiVendor3ds', 'Y'
+ xml.tag! 'AuthStatus3ds', three_d_secure[:trans_status] unless three_d_secure[:trans_status].blank?
+ xml.tag! 'Cavv', three_d_secure[:cavv] unless three_d_secure[:cavv].blank?
+ xml.tag! 'Eci3ds', three_d_secure[:eci] unless three_d_secure[:eci].blank?
+ xml.tag! 'Xid', three_d_secure[:xid] unless three_d_secure[:xid].blank?
end
end
@@ -93,13 +117,11 @@ def credit_card_type(type)
when 'master' then 'MasterCard'
when 'discover' then 'Discover'
when 'american_express' then 'Amex'
- when 'switch' then 'Switch'
- when 'solo' then 'Solo'
end
end
-
+
def build_response(success, message, response, options = {})
- Response.new(success, message, response, options)
+ Response.new(success, message, response, options)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb
index 74fb4d4c91b..fe3146eac55 100644
--- a/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb
+++ b/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb
@@ -2,12 +2,14 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# This module is included in both PaypalGateway and PaypalExpressGateway
module PaypalCommonAPI
- API_VERSION = '72'
+ include Empty
+
+ API_VERSION = '124'
URLS = {
:test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',
:signature => 'https://api-3t.sandbox.paypal.com/2.0/' },
- :live => { :certificate => 'https://api-aa.paypal.com/2.0/',
+ :live => { :certificate => 'https://api.paypal.com/2.0/',
:signature => 'https://api-3t.paypal.com/2.0/' }
}
@@ -38,6 +40,20 @@ module PaypalCommonAPI
FRAUD_REVIEW_CODE = "11610"
+ STANDARD_ERROR_CODE_MAPPING = {
+ '15005' => :card_declined,
+ '10754' => :card_declined,
+ '10752' => :card_declined,
+ '10759' => :card_declined,
+ '10761' => :card_declined,
+ '15002' => :card_declined,
+ '11084' => :card_declined,
+ '15004' => :incorrect_cvc,
+ '10762' => :invalid_cvc,
+ }
+
+ STANDARD_ERROR_CODE_MAPPING.default = :processing_error
+
def self.included(base)
base.default_currency = 'USD'
base.cattr_accessor :pem_file
@@ -122,7 +138,7 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated Gateway::CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated Gateway::CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -242,7 +258,7 @@ def authorize_transaction(transaction_id, money, options = {})
commit 'DoAuthorization', build_do_authorize(transaction_id, money, options)
end
- # The ManagePendingTransactionStatus API operation accepts or denys a
+ # The ManagePendingTransactionStatus API operation accepts or denies a
# pending transaction held by Fraud Management Filters.
#
# ==== Parameters:
@@ -253,7 +269,21 @@ def manage_pending_transaction(transaction_id, action)
commit 'ManagePendingTransactionStatus', build_manage_pending_transaction_status(transaction_id, action)
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').
+ gsub(%r(().+()\d+( 2
xml.tag! action + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
@@ -573,7 +603,7 @@ def add_payment_details(xml, money, currency_code, options = {})
xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank?
xml.tag! 'n2:InvoiceID', (options[:order_id] || options[:invoice_id]) unless (options[:order_id] || options[:invoice_id]).blank?
- xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
+ add_button_source(xml)
# The notify URL applies only to DoExpressCheckoutPayment.
# This value is ignored when set in SetExpressCheckout or GetExpressCheckoutDetails
@@ -593,11 +623,18 @@ def add_payment_details(xml, money, currency_code, options = {})
end
end
+ def add_button_source(xml)
+ button_source = (@options[:button_source] || application_id)
+ if !empty?(button_source)
+ xml.tag! 'n2:ButtonSource', button_source.to_s.slice(0, 32)
+ end
+ end
+
def add_express_only_payment_details(xml, options = {})
add_optional_fields(xml,
- %w{n2:NoteText n2:SoftDescriptor
- n2:TransactionId n2:AllowedPaymentMethodType
- n2:PaymentRequestID n2:PaymentAction},
+ %w{n2:NoteText n2:PaymentAction
+ n2:TransactionId n2:AllowedPaymentMethod
+ n2:PaymentRequestID n2:SoftDescriptor },
options)
end
@@ -628,11 +665,21 @@ def commit(action, request)
:test => test?,
:authorization => authorization_from(response),
:fraud_review => fraud_review?(response),
+ :error_code => standardized_error_code(response),
:avs_result => { :code => response[:avs_code] },
:cvv_result => response[:cvv2_code]
)
end
+ def standardized_error_code(response)
+ STANDARD_ERROR_CODE_MAPPING[error_codes(response).first]
+ end
+
+ def error_codes(response)
+ return [] unless response.has_key?(:error_codes)
+ response[:error_codes].split(',')
+ end
+
def fraud_review?(response)
response[:error_codes] == FRAUD_REVIEW_CODE
end
diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb b/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb
index 7cd7aadbb32..eeb38da4117 100644
--- a/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb
+++ b/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb
@@ -56,6 +56,10 @@ def shipping
'name' => shipping['ShippingOptionName']
}
end
+
+ def note
+ @params['note_text']
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb b/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb
index 157037cb176..764e7b0a2eb 100644
--- a/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb
+++ b/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/paypal_common_api'
+require 'active_merchant/billing/gateways/paypal/paypal_common_api'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -12,7 +12,7 @@ module PaypalRecurringApi
# This transaction creates a recurring payment profile
# ==== Parameters
#
- # * money -- The amount to be charged to the customer at each interval as an Integer value in cents.
+ # * amount -- The amount to be charged to the customer at each interval as an Integer value in cents.
# * credit_card -- The CreditCard details for the transaction.
# * options -- A hash of parameters.
#
@@ -23,8 +23,10 @@ module PaypalRecurringApi
# * :cycles -- Limit to certain # of cycles (OPTIONAL)
# * :start_date -- When does the charging starts (REQUIRED)
# * :description -- The description to appear in the profile (REQUIRED)
-
+
def recurring(amount, credit_card, options = {})
+ ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE
+
options[:credit_card] = credit_card
options[:amount] = amount
requires!(options, :description, :start_date, :period, :frequency, :amount)
@@ -34,7 +36,7 @@ def recurring(amount, credit_card, options = {})
# Update a recurring payment's details.
#
# This transaction updates an existing Recurring Billing Profile
- # and the subscription must have already been created previously
+ # and the subscription must have already been created previously
# by calling +recurring()+. The ability to change certain
# details about a recurring payment is dependent on transaction history
# and the type of plan you're subscribed with paypal. Web Payment Pro
@@ -49,6 +51,8 @@ def recurring(amount, credit_card, options = {})
# * :profile_id -- A string containing the :profile_id
# of the recurring payment already in place for a given credit card. (REQUIRED)
def update_recurring(options={})
+ ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE
+
requires!(options, :profile_id)
opts = options.dup
commit 'UpdateRecurringPaymentsProfile', build_change_profile_request(opts.delete(:profile_id), opts)
@@ -65,6 +69,8 @@ def update_recurring(options={})
# recurring payment already in place for a given credit card. (REQUIRED)
# * options -- A hash with extra info ('note' for ex.)
def cancel_recurring(profile_id, options = {})
+ ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE
+
raise_error_if_blank('profile_id', profile_id)
commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Cancel', options)
end
@@ -76,6 +82,8 @@ def cancel_recurring(profile_id, options = {})
# * profile_id -- A string containing the +profile_id+ of the
# recurring payment already in place for a given credit card. (REQUIRED)
def status_recurring(profile_id)
+ ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE
+
raise_error_if_blank('profile_id', profile_id)
commit 'GetRecurringPaymentsProfileDetails', build_get_profile_details_request(profile_id)
end
@@ -87,6 +95,8 @@ def status_recurring(profile_id)
# * profile_id -- A string containing the +profile_id+ of the
# recurring payment already in place for a given credit card. (REQUIRED)
def suspend_recurring(profile_id, options = {})
+ ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE
+
raise_error_if_blank('profile_id', profile_id)
commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Suspend', options)
end
@@ -98,6 +108,8 @@ def suspend_recurring(profile_id, options = {})
# * profile_id -- A string containing the +profile_id+ of the
# recurring payment already in place for a given credit card. (REQUIRED)
def reactivate_recurring(profile_id, options = {})
+ ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE
+
raise_error_if_blank('profile_id', profile_id)
commit 'ManageRecurringPaymentsProfileStatus', build_manage_profile_request(profile_id, 'Reactivate', options)
end
@@ -109,6 +121,8 @@ def reactivate_recurring(profile_id, options = {})
# * profile_id -- A string containing the +profile_id+ of the
# recurring payment already in place for a given credit card. (REQUIRED)
def bill_outstanding_amount(profile_id, options = {})
+ ActiveMerchant.deprecated Gateway::RECURRING_DEPRECATION_MESSAGE
+
raise_error_if_blank('profile_id', profile_id)
commit 'BillOutstandingAmount', build_bill_outstanding_amount(profile_id, options)
end
diff --git a/lib/active_merchant/billing/gateways/paypal_ca.rb b/lib/active_merchant/billing/gateways/paypal_ca.rb
index 69c94ec3c4d..91fb551d342 100644
--- a/lib/active_merchant/billing/gateways/paypal_ca.rb
+++ b/lib/active_merchant/billing/gateways/paypal_ca.rb
@@ -1,4 +1,4 @@
-require File.dirname(__FILE__) + '/paypal'
+require 'active_merchant/billing/gateways/paypal'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
diff --git a/lib/active_merchant/billing/gateways/paypal_digital_goods.rb b/lib/active_merchant/billing/gateways/paypal_digital_goods.rb
index ce5824ceada..653a8c59015 100644
--- a/lib/active_merchant/billing/gateways/paypal_digital_goods.rb
+++ b/lib/active_merchant/billing/gateways/paypal_digital_goods.rb
@@ -1,6 +1,6 @@
-require File.dirname(__FILE__) + '/paypal/paypal_common_api'
-require File.dirname(__FILE__) + '/paypal/paypal_express_response'
-require File.dirname(__FILE__) + '/paypal_express_common'
+require 'active_merchant/billing/gateways/paypal/paypal_common_api'
+require 'active_merchant/billing/gateways/paypal/paypal_express_response'
+require 'active_merchant/billing/gateways/paypal_express_common'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -11,9 +11,10 @@ class PaypalDigitalGoodsGateway < PaypalExpressGateway
self.supported_countries = %w(AU CA CN FI GB ID IN IT MY NO NZ PH PL SE SG TH VN)
self.homepage_url = 'https://www.x.com/community/ppx/xspaces/digital_goods'
self.display_name = 'PayPal Express Checkout for Digital Goods'
-
+
def redirect_url_for(token, options = {})
- "#{redirect_url}?token=#{token}&useraction=commit"
+ options[:review] ||= false
+ super
end
# GATEWAY.setup_purchase(100,
@@ -29,7 +30,7 @@ def redirect_url_for(token, options = {})
# :category => "Digital" } ] )
def build_setup_request(action, money, options)
requires!(options, :items)
- raise ArgumentError, "Must include at least 1 Item" unless options[:items].length > 0
+ raise ArgumentError, 'Must include at least 1 Item' unless options[:items].length > 0
options[:items].each do |item|
requires!(item, :name, :number, :quantity, :amount, :description, :category)
raise ArgumentError, "Each of the items must have the category 'Digital'" unless item[:category] == 'Digital'
diff --git a/lib/active_merchant/billing/gateways/paypal_express.rb b/lib/active_merchant/billing/gateways/paypal_express.rb
index dd87384cc48..60d659437f9 100644
--- a/lib/active_merchant/billing/gateways/paypal_express.rb
+++ b/lib/active_merchant/billing/gateways/paypal_express.rb
@@ -1,7 +1,7 @@
-require File.dirname(__FILE__) + '/paypal/paypal_common_api'
-require File.dirname(__FILE__) + '/paypal/paypal_express_response'
-require File.dirname(__FILE__) + '/paypal/paypal_recurring_api'
-require File.dirname(__FILE__) + '/paypal_express_common'
+require 'active_merchant/billing/gateways/paypal/paypal_common_api'
+require 'active_merchant/billing/gateways/paypal/paypal_express_response'
+require 'active_merchant/billing/gateways/paypal/paypal_recurring_api'
+require 'active_merchant/billing/gateways/paypal_express_common'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
@@ -30,6 +30,7 @@ class PaypalExpressGateway < Gateway
self.supported_countries = ['US']
self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
self.display_name = 'PayPal Express Checkout'
+ self.currencies_without_fractions = %w(HUF JPY TWD)
def setup_authorization(money, options = {})
requires!(options, :return_url, :cancel_return_url)
@@ -67,6 +68,10 @@ def unstore(token, options = {})
commit 'BAUpdate', build_cancel_billing_agreement_request(token)
end
+ def agreement_details(reference_id, options = {})
+ commit 'BAUpdate', build_details_billing_agreement_request(reference_id)
+ end
+
def authorize_reference_transaction(money, options = {})
requires!(options, :reference_id, :payment_type, :invoice_id, :description, :ip)
@@ -127,19 +132,23 @@ def build_setup_request(action, money, options)
if options[:max_amount]
xml.tag! 'n2:MaxAmount', localized_amount(options[:max_amount], currency_code), 'currencyID' => currency_code
end
+ xml.tag! 'n2:ReqBillingAddress', options[:req_billing_address] ? '1' : '0'
xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0'
xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0'
xml.tag! 'n2:LocaleCode', locale_code(options[:locale]) unless options[:locale].blank?
xml.tag! 'n2:BrandName', options[:brand_name] unless options[:brand_name].blank?
# Customization of the payment page
xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank?
+ xml.tag! 'n2:cpp-logo-image', options[:logo_image] unless options[:logo_image].blank?
xml.tag! 'n2:cpp-header-image', options[:header_image] unless options[:header_image].blank?
xml.tag! 'n2:cpp-header-border-color', options[:header_border_color] unless options[:header_border_color].blank?
xml.tag! 'n2:cpp-header-back-color', options[:header_background_color] unless options[:header_background_color].blank?
xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank?
if options[:allow_guest_checkout]
xml.tag! 'n2:SolutionType', 'Sole'
- xml.tag! 'n2:LandingPage', options[:landing_page] || 'Billing'
+ unless options[:paypal_chooses_landing_page]
+ xml.tag! 'n2:LandingPage', options[:landing_page] || 'Billing'
+ end
end
xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank?
@@ -154,6 +163,13 @@ def build_setup_request(action, money, options)
if !options[:allow_note].nil?
xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0'
end
+
+ if options[:funding_sources]
+ xml.tag! 'n2:FundingSourceDetails' do
+ xml.tag! 'n2:UserSelectedFundingSource', options[:funding_sources][:source]
+ end
+ end
+
xml.tag! 'n2:CallbackURL', options[:callback_url] unless options[:callback_url].blank?
add_payment_details(xml, money, currency_code, options)
@@ -173,6 +189,8 @@ def build_setup_request(action, money, options)
if options.has_key?(:allow_buyer_optin)
xml.tag! 'n2:BuyerEmailOptInEnable', (options[:allow_buyer_optin] ? '1' : '0')
end
+
+ xml.tag! 'n2:TotalType', options[:total_type] unless options[:total_type].blank?
end
end
end
@@ -205,6 +223,18 @@ def build_cancel_billing_agreement_request(token)
xml.target!
end
+ def build_details_billing_agreement_request(reference_id)
+ xml = Builder::XmlMarkup.new :indent => 2
+ xml.tag! 'BillAgreementUpdateReq', 'xmlns' => PAYPAL_NAMESPACE do
+ xml.tag! 'BAUpdateRequest', 'xmlns:n2' => EBAY_NAMESPACE do
+ xml.tag! 'n2:Version', API_VERSION
+ xml.tag! 'ReferenceID', reference_id
+ end
+ end
+
+ xml.target!
+ end
+
def build_reference_transaction_request(action, money, options)
currency_code = options[:currency] || currency(money)
diff --git a/lib/active_merchant/billing/gateways/paypal_express_common.rb b/lib/active_merchant/billing/gateways/paypal_express_common.rb
index 47cf830d347..7cb72b82745 100644
--- a/lib/active_merchant/billing/gateways/paypal_express_common.rb
+++ b/lib/active_merchant/billing/gateways/paypal_express_common.rb
@@ -9,13 +9,13 @@ def self.included(base)
base.class_inheritable_accessor :test_redirect_url
base.class_inheritable_accessor :live_redirect_url
end
- base.live_redirect_url = 'https://www.paypal.com/cgibin/webscr'
+ base.live_redirect_url = 'https://www.paypal.com/cgi-bin/webscr'
end
-
+
def redirect_url
test? ? test_redirect_url : live_redirect_url
end
-
+
def redirect_url_for(token, options = {})
options = {:review => true, :mobile => false}.update(options)
diff --git a/lib/active_merchant/billing/gateways/payscout.rb b/lib/active_merchant/billing/gateways/payscout.rb
new file mode 100644
index 00000000000..d2ad18b5a16
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/payscout.rb
@@ -0,0 +1,160 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayscoutGateway < Gateway
+ self.live_url = self.test_url = 'https://secure.payscout.com/api/transact.php'
+
+ self.supported_countries = ['US']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+ self.default_currency = 'USD'
+ self.homepage_url = 'http://www.payscout.com/'
+ self.display_name = 'Payscout'
+
+ def initialize(options = {})
+ requires!(options, :username, :password)
+ super
+ end
+
+ def authorize(money, creditcard, options = {})
+ post = {}
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_currency(post, money, options)
+ add_address(post, options)
+
+ commit('auth', money, post)
+ end
+
+ def purchase(money, creditcard, options = {})
+ post = {}
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_currency(post, money, options)
+ add_address(post, options)
+
+ commit('sale', money, post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ post[:transactionid] = authorization
+
+ commit('capture', money, post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ post[:transactionid] = authorization
+
+ commit('refund', money, post)
+ end
+
+ def void(authorization, options = {})
+ post = {}
+ post[:transactionid] = authorization
+
+ commit('void', nil, post)
+ end
+
+ private
+
+ def add_address(post, options)
+ if address = options[:billing_address] || options[:address]
+ post[:address1] = address[:address1].to_s
+ post[:address2] = address[:address2].to_s
+ post[:city] = address[:city].to_s
+ post[:state] = (address[:state].blank? ? 'n/a' : address[:state])
+ post[:zip] = address[:zip].to_s
+ post[:country] = address[:country].to_s
+ post[:phone] = address[:phone].to_s
+ post[:fax] = address[:fax].to_s
+ post[:email] = address[:email].to_s
+ end
+
+ if address = options[:shipping_address]
+ post[:shipping_firstname] = address[:first_name].to_s
+ post[:shipping_lastname] = address[:last_name].to_s
+ post[:shipping_company] = address[:company].to_s
+ post[:shipping_address1] = address[:address1].to_s
+ post[:shipping_address2] = address[:address2].to_s
+ post[:shipping_city] = address[:city].to_s
+ post[:shipping_country] = address[:country].to_s
+ post[:shipping_state] = (address[:state].blank? ? 'n/a' : address[:state])
+ post[:shipping_zip] = address[:zip].to_s
+ post[:shipping_email] = address[:email].to_s
+ end
+ end
+
+ def add_currency(post, money, options)
+ post[:currency] = options[:currency] || currency(money)
+ end
+
+ def add_invoice(post, options)
+ post[:orderdescription] = options[:description]
+ post[:orderid] = options[:order_id]
+ end
+
+ def add_creditcard(post, creditcard)
+ post[:ccnumber] = creditcard.number
+ post[:cvv] = creditcard.verification_value if creditcard.verification_value?
+ post[:ccexp] = expdate(creditcard)
+ post[:firstname] = creditcard.first_name
+ post[:lastname] = creditcard.last_name
+ end
+
+ def parse(body)
+ Hash[body.split('&').map { |x| x.split('=') }]
+ end
+
+ def commit(action, money, parameters)
+ parameters[:amount] = amount(money) unless action == 'void'
+ url = (test? ? self.test_url : self.live_url)
+ data = ssl_post(url, post_data(action, parameters))
+
+ response = parse(data)
+ response[:action] = action
+
+ message = message_from(response)
+ test_mode = (test? || message =~ /TESTMODE/)
+ Response.new(success?(response), message, response,
+ :test => test_mode,
+ :authorization => response['transactionid'],
+ :fraud_review => fraud_review?(response),
+ :avs_result => { :code => response['avsresponse'] },
+ :cvv_result => response['cvvresponse']
+ )
+ end
+
+ def message_from(response)
+ case response['response']
+ when '1'
+ 'The transaction has been approved'
+ when '2'
+ 'The transaction has been declined'
+ when '3'
+ response['responsetext']
+ else
+ 'There was an error processing the transaction'
+ end
+ end
+
+ def fraud_review?(response)
+ false
+ end
+
+ def success?(response)
+ (response['response'] == '1')
+ end
+
+ def post_data(action, parameters = {})
+ post = {}
+
+ post[:username] = @options[:username]
+ post[:password] = @options[:password]
+ post[:type] = action
+
+ request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ request
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/paystation.rb b/lib/active_merchant/billing/gateways/paystation.rb
index af851810171..0c2afac4358 100644
--- a/lib/active_merchant/billing/gateways/paystation.rb
+++ b/lib/active_merchant/billing/gateways/paystation.rb
@@ -2,7 +2,7 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PaystationGateway < Gateway
- self.live_url = self.test_url = "https://www.paystation.co.nz/direct/paystation.dll"
+ self.live_url = self.test_url = 'https://www.paystation.co.nz/direct/paystation.dll'
# an "error code" of "0" means "No error - transaction successful"
SUCCESSFUL_RESPONSE_CODE = '0'
@@ -32,9 +32,7 @@ def authorize(money, credit_card, options = {})
add_invoice(post, options)
add_amount(post, money, options)
-
add_credit_card(post, credit_card)
-
add_authorize_flag(post, options)
commit(post)
@@ -45,7 +43,6 @@ def capture(money, authorization_token, options = {})
add_invoice(post, options)
add_amount(post, money, options)
-
add_authorization_token(post, authorization_token, options[:credit_card_verification])
commit(post)
@@ -78,122 +75,133 @@ def store(credit_card, options = {})
commit(post)
end
- private
-
- def new_request
- {
- :pi => @options[:paystation_id], # paystation account id
- :gi => @options[:gateway_id], # paystation gateway id
- "2p" => "t", # two-party transaction type
- :nr => "t", # -- redirect??
- :df => "yymm" # date format: optional sometimes, required others
- }
- end
-
- def add_customer_data(post, options)
- post[:mc] = options[:customer]
- end
+ def refund(money, authorization, options={})
+ post = new_request
+ add_amount(post, money, options)
+ add_invoice(post, options)
+ add_refund_specific_fields(post, authorization)
- def add_invoice(post, options)
- requires!(options, :order_id)
+ commit(post)
+ end
- post[:ms] = options[:order_id] # "Merchant Session", must be unique per request
- post[:mo] = options[:invoice] # "Order Details", displayed in Paystation Admin
- post[:mr] = options[:description] # "Merchant Reference Code", seen from Paystation Admin
- end
+ def verify(credit_card, options={})
+ authorize(0, credit_card, options)
+ end
- def add_credit_card(post, credit_card)
+ def supports_scrubbing?
+ true
+ end
- post[:cn] = credit_card.number
- post[:ct] = credit_card.brand
- post[:ex] = format_date(credit_card.month, credit_card.year)
- post[:cc] = credit_card.verification_value if credit_card.verification_value?
+ def scrub(transcript)
+ transcript.
+ gsub(%r((pstn_cn=)\d*), '\1[FILTERED]').
+ gsub(%r((pstn_cc=)\d*), '\1[FILTERED]')
+ end
- end
+ private
- # bill a token (stored via "store") rather than a Credit Card
- def add_token(post, token)
- post[:fp] = "t" # turn on "future payments" - what paystation calls Token Billing
- post[:ft] = token
- end
+ def new_request
+ {
+ :pi => @options[:paystation_id], # paystation account id
+ :gi => @options[:gateway_id], # paystation gateway id
+ '2p' => 't', # two-party transaction type
+ :nr => 't', # -- redirect??
+ :df => 'yymm' # date format: optional sometimes, required others
+ }
+ end
- def store_credit_card(post, options)
+ def add_customer_data(post, options)
+ post[:mc] = options[:customer]
+ end
- post[:fp] = "t" # turn on "future payments" - what paystation calls Token Billing
- post[:fs] = "t" # tells paystation to store right now, not bill
- post[:ft] = options[:token] if options[:token] # specify a token to use that, or let Paystation generate one
+ def add_invoice(post, options)
+ post[:ms] = generate_unique_id
+ post[:mo] = options[:description]
+ post[:mr] = options[:order_id]
+ end
- end
+ def add_credit_card(post, credit_card)
+ post[:cn] = credit_card.number
+ post[:ct] = credit_card.brand
+ post[:ex] = format_date(credit_card.month, credit_card.year)
+ post[:cc] = credit_card.verification_value if credit_card.verification_value?
+ end
- def add_authorize_flag(post, options)
- post[:pa] = "t" # tells Paystation that this is a pre-auth authorisation payment (account must be in pre-auth mode)
- end
+ def add_token(post, token)
+ post[:fp] = 't' # turn on "future payments" - what paystation calls Token Billing
+ post[:ft] = token
+ end
- def add_authorization_token(post, auth_token, verification_value = nil)
- post[:cp] = "t" # Capture Payment flag – tells Paystation this transaction should be treated as a capture payment
- post[:cx] = auth_token
- post[:cc] = verification_value
- end
+ def store_credit_card(post, options)
+ post[:fp] = 't' # turn on "future payments" - what paystation calls Token Billing
+ post[:fs] = 't' # tells paystation to store right now, not bill
+ post[:ft] = options[:token] if options[:token] # specify a token to use that, or let Paystation generate one
+ end
- def add_amount(post, money, options)
+ def add_authorize_flag(post, options)
+ post[:pa] = 't' # tells Paystation that this is a pre-auth authorisation payment (account must be in pre-auth mode)
+ end
- post[:am] = amount(money)
- post[:cu] = options[:currency] || currency(money)
+ def add_refund_specific_fields(post, authorization)
+ post[:rc] = 't'
+ post[:rt] = authorization
+ end
- end
+ def add_authorization_token(post, auth_token, verification_value = nil)
+ post[:cp] = 't' # Capture Payment flag – tells Paystation this transaction should be treated as a capture payment
+ post[:cx] = auth_token
+ post[:cc] = verification_value
+ end
- def parse(xml_response)
- response = {}
+ def add_amount(post, money, options)
+ post[:am] = amount(money)
+ post[:cu] = options[:currency] || currency(money)
+ end
- xml = REXML::Document.new(xml_response)
+ def parse(xml_response)
+ response = {}
- # for normal payments, the root node is
- # for "future payments", it's
- xml.elements.each("#{xml.root.name}/*") do |element|
- response[element.name.underscore.to_sym] = element.text
- end
+ xml = REXML::Document.new(xml_response)
- response
+ xml.elements.each("#{xml.root.name}/*") do |element|
+ response[element.name.underscore.to_sym] = element.text
end
- def commit(post)
-
- post[:tm] = "T" if test? # test mode
+ response
+ end
- pstn_prefix_params = post.collect { |key, value| "pstn_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ def commit(post)
+ post[:tm] = 'T' if test?
+ pstn_prefix_params = post.collect { |key, value| "pstn_#{key}=#{CGI.escape(value.to_s)}" }.join('&')
- # need include paystation param as "initiator flag for payment engine"
- data = ssl_post(self.live_url, "#{pstn_prefix_params}&paystation=_empty")
- response = parse(data)
- message = message_from(response)
+ data = ssl_post(self.live_url, "#{pstn_prefix_params}&paystation=_empty")
+ response = parse(data)
+ message = message_from(response)
- PaystationResponse.new(success?(response), message, response,
- :test => (response[:tm] && response[:tm].downcase == "t"),
- :authorization => response[:paystation_transaction_id]
- )
- end
+ PaystationResponse.new(success?(response), message, response,
+ :test => (response[:tm]&.casecmp('t')&.zero?),
+ :authorization => response[:paystation_transaction_id]
+ )
+ end
- def success?(response)
- (response[:ec] == SUCCESSFUL_RESPONSE_CODE) || (response[:ec] == SUCCESSFUL_FUTURE_PAYMENT)
- end
+ def success?(response)
+ (response[:ec] == SUCCESSFUL_RESPONSE_CODE) || (response[:ec] == SUCCESSFUL_FUTURE_PAYMENT)
+ end
- def message_from(response)
- response[:em]
- end
+ def message_from(response)
+ response[:em]
+ end
- def format_date(month, year)
- "#{format(year, :two_digits)}#{format(month, :two_digits)}"
- end
+ def format_date(month, year)
+ "#{format(year, :two_digits)}#{format(month, :two_digits)}"
+ end
end
class PaystationResponse < Response
- # add a method to response so we can easily get the token
- # for Validate transactions
def token
- @params["future_payment_token"]
+ @params['future_payment_token']
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/payu_in.rb b/lib/active_merchant/billing/gateways/payu_in.rb
new file mode 100644
index 00000000000..eabc32c1cd6
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/payu_in.rb
@@ -0,0 +1,248 @@
+# encoding: utf-8
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayuInGateway < Gateway
+ self.test_url = 'https://test.payu.in/_payment'
+ self.live_url = 'https://secure.payu.in/_payment'
+
+ TEST_INFO_URL = 'https://test.payu.in/merchant/postservice.php?form=2'
+ LIVE_INFO_URL = 'https://info.payu.in/merchant/postservice.php?form=2'
+
+ self.supported_countries = ['IN']
+ self.default_currency = 'INR'
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :maestro]
+
+ self.homepage_url = 'https://www.payu.in/'
+ self.display_name = 'PayU India'
+
+ def initialize(options={})
+ requires!(options, :key, :salt)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ requires!(options, :order_id)
+
+ post = {}
+ add_invoice(post, money, options)
+ add_payment(post, payment)
+ add_addresses(post, options)
+ add_customer_data(post, options)
+ add_auth(post)
+
+ MultiResponse.run do |r|
+ r.process { commit(url('purchase'), post) }
+ if(r.params['enrolled'].to_s == '0')
+ r.process { commit(r.params['post_uri'], r.params['form_post_vars']) }
+ else
+ r.process { handle_3dsecure(r) }
+ end
+ end
+ end
+
+ def refund(money, authorization, options={})
+ raise ArgumentError, 'Amount is required' unless money
+
+ post = {}
+
+ post[:command] = 'cancel_refund_transaction'
+ post[:var1] = authorization
+ post[:var2] = generate_unique_id
+ post[:var3] = amount(money)
+
+ add_auth(post, :command, :var1)
+
+ commit(url('refund'), post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(/(ccnum=)[^&\n"]*(&|\n|"|$)/, '\1[FILTERED]\2').
+ gsub(/(ccvv=)[^&\n"]*(&|\n|"|$)/, '\1[FILTERED]\2').
+ gsub(/(card_hash=)[^&\n"]*(&|\n|"|$)/, '\1[FILTERED]\2').
+ gsub(/(ccnum":")[^"]*(")/, '\1[FILTERED]\2').
+ gsub(/(ccvv":")[^"]*(")/, '\1[FILTERED]\2')
+ end
+
+ private
+
+ PAYMENT_DIGEST_KEYS = %w(
+ txnid amount productinfo firstname email
+ udf1 udf2 udf3 udf4 udf5
+ bogus bogus bogus bogus bogus
+ )
+ def add_auth(post, *digest_keys)
+ post[:key] = @options[:key]
+ post[:txn_s2s_flow] = 1
+
+ digest_keys = PAYMENT_DIGEST_KEYS if digest_keys.empty?
+ digest = Digest::SHA2.new(512)
+ digest << @options[:key] << '|'
+ digest_keys.each do |key|
+ digest << (post[key.to_sym] || '') << '|'
+ end
+ digest << @options[:salt]
+ post[:hash] = digest.hexdigest
+ end
+
+ def add_customer_data(post, options)
+ post[:email] = clean(options[:email] || 'unknown@example.com', nil, 50)
+ post[:phone] = clean((options[:billing_address] && options[:billing_address][:phone]) || '11111111111', :numeric, 50)
+ end
+
+ def add_addresses(post, options)
+ if options[:billing_address]
+ post[:address1] = clean(options[:billing_address][:address1], :text, 100)
+ post[:address2] = clean(options[:billing_address][:address2], :text, 100)
+ post[:city] = clean(options[:billing_address][:city], :text, 50)
+ post[:state] = clean(options[:billing_address][:state], :text, 50)
+ post[:country] = clean(options[:billing_address][:country], :text, 50)
+ post[:zipcode] = clean(options[:billing_address][:zip], :numeric, 20)
+ end
+
+ if options[:shipping_address]
+ if options[:shipping_address][:name]
+ first, *rest = options[:shipping_address][:name].split(/\s+/)
+ post[:shipping_firstname] = clean(first, :name, 60)
+ post[:shipping_lastname] = clean(rest.join(' '), :name, 20)
+ end
+ post[:shipping_address1] = clean(options[:shipping_address][:address1], :text, 100)
+ post[:shipping_address2] = clean(options[:shipping_address][:address2], :text, 100)
+ post[:shipping_city] = clean(options[:shipping_address][:city], :text, 50)
+ post[:shipping_state] = clean(options[:shipping_address][:state], :text, 50)
+ post[:shipping_country] = clean(options[:shipping_address][:country], :text, 50)
+ post[:shipping_zipcode] = clean(options[:shipping_address][:zip], :numeric, 20)
+ post[:shipping_phone] = clean(options[:shipping_address][:phone], :numeric, 50)
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+
+ post[:txnid] = clean(options[:order_id], :alphanumeric, 30)
+ post[:productinfo] = clean(options[:description] || 'Purchase', nil, 100)
+
+ post[:surl] = 'http://example.com'
+ post[:furl] = 'http://example.com'
+ end
+
+ BRAND_MAP = {
+ visa: 'VISA',
+ master: 'MAST',
+ american_express: 'AMEX',
+ diners_club: 'DINR',
+ maestro: 'MAES'
+ }
+
+ def add_payment(post, payment)
+ post[:pg] = 'CC'
+ post[:firstname] = clean(payment.first_name, :name, 60)
+ post[:lastname] = clean(payment.last_name, :name, 20)
+
+ post[:bankcode] = BRAND_MAP[payment.brand.to_sym]
+ post[:ccnum] = payment.number
+ post[:ccvv] = payment.verification_value
+ post[:ccname] = payment.name
+ post[:ccexpmon] = format(payment.month, :two_digits)
+ post[:ccexpyr] = format(payment.year, :four_digits)
+ end
+
+ def clean(value, format, maxlength)
+ value ||= ''
+ value = case format
+ when :alphanumeric
+ value.gsub(/[^A-Za-z0-9]/, '')
+ when :name
+ value.gsub(/[^A-Za-z ]/, '')
+ when :numeric
+ value.gsub(/[^0-9]/, '')
+ when :text
+ value.gsub(/[^A-Za-z0-9@\-_\/\. ]/, '')
+ when nil
+ value
+ else
+ raise "Unknown format #{format} for #{value}"
+ end
+ value[0...maxlength]
+ end
+
+ def parse(body)
+ top = JSON.parse(body)
+
+ if result = top.delete('result')
+ result.split('&').inject({}) do |hash, string|
+ key, value = string.split('=')
+ hash[CGI.unescape(key).downcase] = CGI.unescape(value || '')
+ hash
+ end.each do |key, value|
+ if top[key]
+ top["result_#{key}"] = value
+ else
+ top[key] = value
+ end
+ end
+ end
+
+ if response = top.delete('response')
+ top.merge!(response)
+ end
+
+ top
+ rescue JSON::ParserError
+ {
+ 'error' => "Invalid response received from the PayU API. (The raw response was `#{body}`)."
+ }
+ end
+
+ def commit(url, parameters)
+ response = parse(ssl_post(url, post_data(parameters), 'Accept-Encoding' => 'identity'))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?
+ )
+ end
+
+ def url(action)
+ case action
+ when 'purchase'
+ (test? ? test_url : live_url)
+ else
+ (test? ? TEST_INFO_URL : LIVE_INFO_URL)
+ end
+ end
+
+ def success_from(response)
+ if response['result_status']
+ (response['status'] == 'success' && response['result_status'] == 'success')
+ else
+ (response['status'] == 'success' || response['status'].to_s == '1')
+ end
+ end
+
+ def message_from(response)
+ (response['error_message'] || response['error'] || response['msg'])
+ end
+
+ def authorization_from(response)
+ response['mihpayid']
+ end
+
+ def post_data(parameters = {})
+ PostData.new.merge!(parameters).to_post_data
+ end
+
+ def handle_3dsecure(response)
+ Response.new(false, '3D-secure enrolled cards are not supported.')
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/payu_latam.rb b/lib/active_merchant/billing/gateways/payu_latam.rb
new file mode 100644
index 00000000000..aef7ea42e89
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/payu_latam.rb
@@ -0,0 +1,449 @@
+require 'digest/md5'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class PayuLatamGateway < Gateway
+ self.display_name = 'PayU Latam'
+ self.homepage_url = 'http://www.payulatam.com'
+
+ self.test_url = 'https://sandbox.api.payulatam.com/payments-api/4.0/service.cgi'
+ self.live_url = 'https://api.payulatam.com/payments-api/4.0/service.cgi'
+
+ self.supported_countries = ['AR', 'BR', 'CL', 'CO', 'MX', 'PA', 'PE']
+ self.default_currency = 'USD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :naranja, :cabal]
+
+ BRAND_MAP = {
+ 'visa' => 'VISA',
+ 'master' => 'MASTERCARD',
+ 'american_express' => 'AMEX',
+ 'diners_club' => 'DINERS',
+ 'naranja' => 'NARANJA',
+ 'cabal' => 'CABAL'
+ }
+
+ MINIMUMS = {
+ 'ARS' => 1700,
+ 'BRL' => 600,
+ 'MXN' => 3900,
+ 'PEN' => 500
+ }
+
+ def initialize(options={})
+ requires!(options, :merchant_id, :account_id, :api_login, :api_key, :payment_country)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ auth_or_sale(post, 'AUTHORIZATION_AND_CAPTURE', amount, payment_method, options)
+ commit('purchase', post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ auth_or_sale(post, 'AUTHORIZATION', amount, payment_method, options)
+ commit('auth', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+
+ add_credentials(post, 'SUBMIT_TRANSACTION', options)
+ add_transaction_elements(post, 'CAPTURE', options)
+ add_reference(post, authorization)
+
+ if !amount.nil? && amount.to_f != 0.0
+ post[:transaction][:additionalValues] ||= {}
+ post[:transaction][:additionalValues][:TX_VALUE] = invoice_for(amount, options)[:TX_VALUE]
+ end
+
+ commit('capture', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+
+ add_credentials(post, 'SUBMIT_TRANSACTION', options)
+ add_transaction_elements(post, 'VOID', options)
+ add_reference(post, authorization)
+
+ commit('void', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+
+ add_credentials(post, 'SUBMIT_TRANSACTION', options)
+ add_transaction_elements(post, 'REFUND', options)
+ add_reference(post, authorization)
+
+ commit('refund', post)
+ end
+
+ def verify(credit_card, options={})
+ minimum = MINIMUMS[options[:currency].upcase] if options[:currency]
+ amount = options[:verify_amount] || minimum || 100
+
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(amount, credit_card, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def store(payment_method, options = {})
+ post = {}
+
+ add_credentials(post, 'CREATE_TOKEN')
+ add_payment_method_to_be_tokenized(post, payment_method)
+
+ commit('store', post)
+ end
+
+ def verify_credentials
+ post = {}
+ add_credentials(post, 'GET_PAYMENT_METHODS')
+ response = commit('verify_credentials', post)
+ response.success?
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((\"creditCard\\\":{\\\"number\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"securityCode\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"apiKey\\\":\\\")\w+), '\1[FILTERED]')
+ end
+
+ private
+
+ def auth_or_sale(post, transaction_type, amount, payment_method, options)
+ add_credentials(post, 'SUBMIT_TRANSACTION', options)
+ add_transaction_elements(post, transaction_type, options)
+ add_order(post, options)
+ add_buyer(post, payment_method, options)
+ add_invoice(post, amount, options)
+ add_signature(post)
+ add_payment_method(post, payment_method, options)
+ add_payer(post, payment_method, options)
+ add_extra_parameters(post, options)
+ end
+
+ def add_credentials(post, command, options={})
+ post[:test] = test? unless command == 'CREATE_TOKEN'
+ post[:language] = options[:language] || 'en'
+ post[:command] = command
+ merchant = {}
+ merchant[:apiLogin] = @options[:api_login]
+ merchant[:apiKey] = @options[:api_key]
+ post[:merchant] = merchant
+ end
+
+ def add_transaction_elements(post, type, options)
+ transaction = {}
+ transaction[:paymentCountry] = @options[:payment_country]
+ transaction[:type] = type
+ transaction[:ipAddress] = options[:ip] || ''
+ transaction[:userAgent] = options[:user_agent] if options[:user_agent]
+ transaction[:cookie] = options[:cookie] if options[:cookie]
+ transaction[:deviceSessionId] = options[:device_session_id] if options[:device_session_id]
+ post[:transaction] = transaction
+ end
+
+ def add_order(post, options)
+ order = {}
+ order[:accountId] = @options[:account_id]
+ order[:partnerId] = options[:partner_id] if options[:partner_id]
+ order[:referenceCode] = options[:order_id] || generate_unique_id
+ order[:description] = options[:description] || 'Compra en ' + @options[:merchant_id]
+ order[:language] = options[:language] || 'en'
+ order[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
+ post[:transaction][:order] = order
+ end
+
+ def add_payer(post, payment_method, options)
+ address = options[:billing_address]
+ payer = {}
+ payer[:fullName] = payment_method.name.strip
+ payer[:contactPhone] = address[:phone] if address && address[:phone]
+ payer[:dniNumber] = options[:dni_number] if options[:dni_number]
+ payer[:dniType] = options[:dni_type] if options[:dni_type]
+ payer[:emailAddress] = options[:email] if options[:email]
+ payer[:birthdate] = options[:birth_date] if options[:birth_date] && @options[:payment_country] == 'MX'
+ payer[:billingAddress] = billing_address_fields(options)
+ post[:transaction][:payer] = payer
+ end
+
+ def billing_address_fields(options)
+ return unless address = options[:billing_address]
+ billing_address = {}
+ billing_address[:street1] = address[:address1]
+ billing_address[:street2] = address[:address2]
+ billing_address[:city] = address[:city]
+ billing_address[:state] = address[:state]
+ billing_address[:country] = address[:country]
+ billing_address[:postalCode] = address[:zip] if @options[:payment_country] == 'MX'
+ billing_address[:phone] = address[:phone]
+ billing_address
+ end
+
+ def add_buyer(post, payment_method, options)
+ buyer = {}
+ if buyer_hash = options[:buyer]
+ buyer[:fullName] = buyer_hash[:name]
+ buyer[:dniNumber] = buyer_hash[:dni_number]
+ buyer[:dniType] = buyer_hash[:dni_type]
+ buyer[:merchantBuyerId] = buyer_hash[:merchant_buyer_id]
+ buyer[:cnpj] = buyer_hash[:cnpj] if @options[:payment_country] == 'BR'
+ buyer[:emailAddress] = buyer_hash[:email]
+ buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || ''
+ buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
+ else
+ buyer[:fullName] = payment_method.name.strip
+ buyer[:dniNumber] = options[:dni_number]
+ buyer[:dniType] = options[:dni_type]
+ buyer[:merchantBuyerId] = options[:merchant_buyer_id]
+ buyer[:cnpj] = options[:cnpj] if @options[:payment_country] == 'BR'
+ buyer[:emailAddress] = options[:email]
+ buyer[:contactPhone] = (options[:billing_address][:phone] if options[:billing_address]) || (options[:shipping_address][:phone] if options[:shipping_address]) || ''
+ buyer[:shippingAddress] = shipping_address_fields(options) if options[:shipping_address]
+ end
+ post[:transaction][:order][:buyer] = buyer
+ end
+
+ def shipping_address_fields(options)
+ return unless address = options[:shipping_address]
+ shipping_address = {}
+ shipping_address[:street1] = address[:address1]
+ shipping_address[:street2] = address[:address2]
+ shipping_address[:city] = address[:city]
+ shipping_address[:state] = address[:state]
+ shipping_address[:country] = address[:country]
+ shipping_address[:postalCode] = address[:zip]
+ shipping_address[:phone] = address[:phone]
+ shipping_address
+ end
+
+ def add_invoice(post, money, options)
+ post[:transaction][:order][:additionalValues] = invoice_for(money, options)
+ end
+
+ def invoice_for(money, options)
+ tx_value = {}
+ tx_value[:value] = amount(money)
+ tx_value[:currency] = options[:currency] || currency(money)
+
+ tx_tax = {}
+ tx_tax[:value] = options[:tax] || '0'
+ tx_tax[:currency] = options[:currency] || currency(money)
+
+ tx_tax_return_base = {}
+ tx_tax_return_base[:value] = options[:tax_return_base] || '0'
+ tx_tax_return_base[:currency] = options[:currency] || currency(money)
+
+ additional_values = {}
+ additional_values[:TX_VALUE] = tx_value
+ additional_values[:TX_TAX] = tx_tax if @options[:payment_country] == 'CO'
+ additional_values[:TX_TAX_RETURN_BASE] = tx_tax_return_base if @options[:payment_country] == 'CO'
+
+ additional_values
+ end
+
+ def add_signature(post)
+ post[:transaction][:order][:signature] = signature_from(post)
+ end
+
+ def signature_from(post)
+ signature_string = [
+ @options[:api_key],
+ @options[:merchant_id],
+ post[:transaction][:order][:referenceCode],
+ post[:transaction][:order][:additionalValues][:TX_VALUE][:value],
+ post[:transaction][:order][:additionalValues][:TX_VALUE][:currency]
+ ].compact.join('~')
+
+ Digest::MD5.hexdigest(signature_string)
+ end
+
+ def add_payment_method(post, payment_method, options)
+ if payment_method.is_a?(String)
+ brand, token = split_authorization(payment_method)
+ credit_card = {}
+ credit_card[:securityCode] = options[:cvv] if options[:cvv]
+ credit_card[:processWithoutCvv2] = true if options[:cvv].blank?
+ post[:transaction][:creditCard] = credit_card
+ post[:transaction][:creditCardTokenId] = token
+ post[:transaction][:paymentMethod] = brand.upcase
+ else
+ credit_card = {}
+ credit_card[:number] = payment_method.number
+ credit_card[:securityCode] = payment_method.verification_value || options[:cvv]
+ credit_card[:expirationDate] = format(payment_method.year, :four_digits).to_s + '/' + format(payment_method.month, :two_digits).to_s
+ credit_card[:name] = payment_method.name.strip
+ credit_card[:processWithoutCvv2] = true if add_process_without_cvv2(payment_method, options)
+ post[:transaction][:creditCard] = credit_card
+ post[:transaction][:paymentMethod] = BRAND_MAP[payment_method.brand.to_s]
+ end
+ end
+
+ def add_process_without_cvv2(payment_method, options)
+ return true if payment_method.verification_value.blank? && options[:cvv].blank?
+ false
+ end
+
+ def add_extra_parameters(post, options)
+ extra_parameters = {}
+ extra_parameters[:INSTALLMENTS_NUMBER] = options[:installments_number] || 1
+ post[:transaction][:extraParameters] = extra_parameters
+ end
+
+ def add_reference(post, authorization)
+ order_id, transaction_id = split_authorization(authorization)
+ order = {}
+ order[:id] = order_id
+ post[:transaction][:order] = order
+ post[:transaction][:parentTransactionId] = transaction_id
+ post[:transaction][:reason] = 'n/a'
+ end
+
+ def add_payment_method_to_be_tokenized(post, payment_method)
+ credit_card_token = {}
+ credit_card_token[:payerId] = generate_unique_id
+ credit_card_token[:name] = payment_method.name.strip
+ credit_card_token[:identificationNumber] = generate_unique_id
+ credit_card_token[:paymentMethod] = BRAND_MAP[payment_method.brand.to_s]
+ credit_card_token[:number] = payment_method.number
+ credit_card_token[:expirationDate] = format(payment_method.year, :four_digits).to_s + '/' + format(payment_method.month, :two_digits).to_s
+ credit_card_token[:securityCode] = payment_method.verification_value
+ post[:creditCardToken] = credit_card_token
+ end
+
+ def commit(action, params)
+ raw_response = ssl_post(url, post_data(params), headers)
+ response = parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response_error(raw_response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ else
+ success = success_from(action, response)
+ Response.new(
+ success,
+ message_from(action, success, response),
+ response,
+ authorization: success ? authorization_from(action, response) : nil,
+ error_code: success ? nil : error_from(action, response),
+ test: test?
+ )
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json'
+ }
+ end
+
+ def post_data(params)
+ params.merge(test: test?)
+ params.to_json
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(action, response)
+ case action
+ when 'store'
+ response['code'] == 'SUCCESS' && response['creditCardToken'] && response['creditCardToken']['creditCardTokenId'].present?
+ when 'verify_credentials'
+ response['code'] == 'SUCCESS'
+ when 'refund', 'void'
+ response['code'] == 'SUCCESS' && response['transactionResponse'] && (response['transactionResponse']['state'] == 'PENDING' || response['transactionResponse']['state'] == 'APPROVED')
+ else
+ response['code'] == 'SUCCESS' && response['transactionResponse'] && (response['transactionResponse']['state'] == 'APPROVED')
+ end
+ end
+
+ def message_from(action, success, response)
+ case action
+ when 'store'
+ return response['code'] if success
+ error_description = response['creditCardToken']['errorDescription'] if response['creditCardToken']
+ response['error'] || error_description || 'FAILED'
+ when 'verify_credentials'
+ return 'VERIFIED' if success
+ 'FAILED'
+ else
+ if response['transactionResponse']
+ response_message = response['transactionResponse']['responseMessage']
+ response_code = response['transactionResponse']['responseCode'] || response['transactionResponse']['pendingReason']
+ end
+ return response_code if success
+ response['error'] || response_message || response_code || 'FAILED'
+ end
+ end
+
+ def authorization_from(action, response)
+ case action
+ when 'store'
+ [
+ response['creditCardToken']['paymentMethod'],
+ response['creditCardToken']['creditCardTokenId']
+ ].compact.join('|')
+ when 'verify_credentials'
+ nil
+ else
+ [
+ response['transactionResponse']['orderId'],
+ response['transactionResponse']['transactionId']
+ ].compact.join('|')
+ end
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def error_from(action, response)
+ case action
+ when 'store'
+ response['creditCardToken']['errorDescription'] if response['creditCardToken']
+ when 'verify_credentials'
+ response['error'] || 'FAILED'
+ else
+ response['transactionResponse']['errorCode'] || response['transactionResponse']['responseCode'] if response['transactionResponse']
+ end
+ end
+
+ def response_error(raw_response)
+ response = parse(raw_response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ else
+ return Response.new(
+ false,
+ message_from('', false, response),
+ response,
+ :test => test?
+ )
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from PayuLatamGateway. Please contact PayuLatamGateway if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/payway.rb b/lib/active_merchant/billing/gateways/payway.rb
index 10777f7b00a..b80e5f937a1 100644
--- a/lib/active_merchant/billing/gateways/payway.rb
+++ b/lib/active_merchant/billing/gateways/payway.rb
@@ -150,7 +150,7 @@ def add_payment_method(post, payment_method)
post['card.cardHolderName'] = "#{payment_method.first_name} #{payment_method.last_name}"
post['card.PAN'] = payment_method.number
post['card.CVN'] = payment_method.verification_value
- post['card.expiryYear'] = payment_method.year.to_s[-2,2]
+ post['card.expiryYear'] = payment_method.year.to_s[-2, 2]
post['card.expiryMonth'] = sprintf('%02d', payment_method.month)
else
post['customer.customerReferenceNumber'] = payment_method
@@ -177,30 +177,30 @@ def add_auth(post)
# Creates the request and returns the summarized result
def commit(action, post)
add_auth(post)
- post.merge!('order.type' => TRANSACTIONS[action])
+ post['order.type'] = TRANSACTIONS[action]
- request = post.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&")
+ request = post.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
response = ssl_post(self.live_url, request)
params = {}
CGI.parse(response).each_pair do |key, value|
- actual_key = key.split(".").last
+ actual_key = key.split('.').last
params[actual_key.underscore.to_sym] = value[0]
end
message = "#{SUMMARY_CODES[params[:summary_code]]} - #{RESPONSE_CODES[params[:response_code]]}"
- success = (params[:summary_code] ? (params[:summary_code] == "0") : (params[:response_code] == "00"))
+ success = (params[:summary_code] ? (params[:summary_code] == '0') : (params[:response_code] == '00'))
Response.new(success, message, params,
- :test => (@options[:merchant].to_s == "TEST"),
+ :test => (@options[:merchant].to_s == 'TEST'),
:authorization => post[:order_number]
)
rescue ActiveMerchant::ResponseError => e
raise unless e.response.code == '403'
- return Response.new(false, "Invalid credentials", {}, :test => test?)
+ return Response.new(false, 'Invalid credentials', {}, :test => test?)
rescue ActiveMerchant::ClientCertificateError
- return Response.new(false, "Invalid certificate", {}, :test => test?)
+ return Response.new(false, 'Invalid certificate', {}, :test => test?)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/pin.rb b/lib/active_merchant/billing/gateways/pin.rb
index 8e1c2ffd0be..89e92895980 100644
--- a/lib/active_merchant/billing/gateways/pin.rb
+++ b/lib/active_merchant/billing/gateways/pin.rb
@@ -1,15 +1,15 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class PinGateway < Gateway
- self.test_url = 'https://test-api.pin.net.au/1'
- self.live_url = 'https://api.pin.net.au/1'
+ self.test_url = 'https://test-api.pinpayments.com/1'
+ self.live_url = 'https://api.pinpayments.com/1'
self.default_currency = 'AUD'
self.money_format = :cents
self.supported_countries = ['AU']
- self.supported_cardtypes = [:visa, :master]
- self.homepage_url = 'http://www.pin.net.au/'
- self.display_name = 'Pin'
+ self.supported_cardtypes = [:visa, :master, :american_express]
+ self.homepage_url = 'http://www.pinpayments.com/'
+ self.display_name = 'Pin Payments'
def initialize(options = {})
requires!(options, :api_key)
@@ -28,8 +28,10 @@ def purchase(money, creditcard, options = {})
add_invoice(post, options)
add_creditcard(post, creditcard)
add_address(post, creditcard, options)
+ add_capture(post, options)
+ add_metadata(post, options)
- commit('charges', post, options)
+ commit(:post, 'charges', post, options)
end
# Create a customer and associated credit card. The token that is returned
@@ -40,17 +42,52 @@ def store(creditcard, options = {})
add_creditcard(post, creditcard)
add_customer_data(post, options)
add_address(post, creditcard, options)
- commit('customers', post, options)
+ commit(:post, 'customers', post, options)
end
- # Refund a transaction, note that the money attribute is ignored at the
- # moment as the API does not support partial refunds. The parameter is
- # kept for compatibility reasons
+ # Refund a transaction
def refund(money, token, options = {})
- commit("charges/#{CGI.escape(token)}/refunds", { :amount => amount(money) }, options)
+ commit(:post, "charges/#{CGI.escape(token)}/refunds", { :amount => amount(money) }, options)
+ end
+
+ # Authorize an amount on a credit card. Once authorized, you can later
+ # capture this charge using the charge token that is returned.
+ def authorize(money, creditcard, options = {})
+ options[:capture] = false
+
+ purchase(money, creditcard, options)
+ end
+
+ # Captures a previously authorized charge. Capturing only part of the original
+ # authorization is currently not supported.
+ def capture(money, token, options = {})
+ commit(:put, "charges/#{CGI.escape(token)}/capture", { :amount => amount(money) }, options)
+ end
+
+ # Updates the credit card for the customer.
+ def update(token, creditcard, options = {})
+ post = {}
+ token = get_customer_token(token)
+
+ add_creditcard(post, creditcard)
+ add_customer_data(post, options)
+ add_address(post, creditcard, options)
+ commit(:put, "customers/#{CGI.escape(token)}", post, options)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(/(number\\?":\\?")(\d*)/, '\1[FILTERED]').
+ gsub(/(cvc\\?":\\?")(\d*)/, '\1[FILTERED]')
end
private
+
def add_amount(post, money, options)
post[:amount] = amount(money)
post[:currency] = (options[:currency] || currency(money))
@@ -58,8 +95,8 @@ def add_amount(post, money, options)
end
def add_customer_data(post, options)
- post[:email] = options[:email]
- post[:ip_address] = options[:ip]
+ post[:email] = options[:email] if options[:email]
+ post[:ip_address] = options[:ip] if options[:ip]
end
def add_address(post, creditcard, options)
@@ -78,7 +115,14 @@ def add_address(post, creditcard, options)
end
def add_invoice(post, options)
- post[:description] = options[:description] || "Active Merchant Purchase"
+ post[:description] = options[:description] || 'Active Merchant Purchase'
+ post[:reference] = options[:reference] if options[:reference]
+ end
+
+ def add_capture(post, options)
+ capture = options[:capture]
+
+ post[:capture] = capture != false
end
def add_creditcard(post, creditcard)
@@ -90,21 +134,33 @@ def add_creditcard(post, creditcard)
:expiry_month => creditcard.month,
:expiry_year => creditcard.year,
:cvc => creditcard.verification_value,
- :name => "#{creditcard.first_name} #{creditcard.last_name}"
+ :name => creditcard.name
)
elsif creditcard.kind_of?(String)
if creditcard =~ /^card_/
- post[:card_token] = creditcard
+ post[:card_token] = get_card_token(creditcard)
else
post[:customer_token] = creditcard
end
end
end
+ def get_customer_token(token)
+ token.split(/;(?=cus)/).last
+ end
+
+ def get_card_token(token)
+ token.split(/;(?=cus)/).first
+ end
+
+ def add_metadata(post, options)
+ post[:metadata] = options[:metadata] if options[:metadata]
+ end
+
def headers(params = {})
result = {
- "Content-Type" => "application/json",
- "Authorization" => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}"
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}"
}
result['X-Partner-Key'] = params[:partner_key] if params[:partner_key]
@@ -112,24 +168,27 @@ def headers(params = {})
result
end
- def commit(action, params, options)
+ def commit(method, action, params, options)
url = "#{test? ? test_url : live_url}/#{action}"
begin
- body = parse(ssl_post(url, post_data(params), headers(options)))
+ raw_response = ssl_request(method, url, post_data(params), headers(options))
+ body = parse(raw_response)
rescue ResponseError => e
body = parse(e.response.body)
end
- if body["response"]
+ if body['response']
success_response(body)
- elsif body["error"]
+ elsif body['error']
error_response(body)
end
+ rescue JSON::ParserError
+ return unparsable_response(raw_response)
end
def success_response(body)
- response = body["response"]
+ response = body['response']
Response.new(
true,
response['status_message'],
@@ -149,8 +208,18 @@ def error_response(body)
)
end
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from Pin Payments. Please contact support@pinpayments.com if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+
def token(response)
- response['token']
+ if response['token'].start_with?('cus')
+ "#{response.dig('card', 'token')};#{response['token']}"
+ else
+ response['token']
+ end
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/plugnpay.rb b/lib/active_merchant/billing/gateways/plugnpay.rb
index 8f743cebd7c..36becf69ff8 100644
--- a/lib/active_merchant/billing/gateways/plugnpay.rb
+++ b/lib/active_merchant/billing/gateways/plugnpay.rb
@@ -3,80 +3,80 @@ module Billing
class PlugnpayGateway < Gateway
class PlugnpayPostData < PostData
# Fields that will be sent even if they are blank
- self.required_fields = [ :publisher_name, :publisher_password,
- :card_amount, :card_name, :card_number, :card_exp, :orderID ]
+ self.required_fields = [:publisher_name, :publisher_password,
+ :card_amount, :card_name, :card_number, :card_exp, :orderID]
end
self.live_url = self.test_url = 'https://pay1.plugnpay.com/payment/pnpremote.cgi'
CARD_CODE_MESSAGES = {
- "M" => "Card verification number matched",
- "N" => "Card verification number didn't match",
- "P" => "Card verification number was not processed",
- "S" => "Card verification number should be on card but was not indicated",
- "U" => "Issuer was not certified for card verification"
+ 'M' => 'Card verification number matched',
+ 'N' => "Card verification number didn't match",
+ 'P' => 'Card verification number was not processed',
+ 'S' => 'Card verification number should be on card but was not indicated',
+ 'U' => 'Issuer was not certified for card verification'
}
CARD_CODE_ERRORS = %w( N S )
AVS_MESSAGES = {
- "A" => "Street address matches billing information, zip/postal code does not",
- "B" => "Address information not provided for address verification check",
- "E" => "Address verification service error",
- "G" => "Non-U.S. card-issuing bank",
- "N" => "Neither street address nor zip/postal match billing information",
- "P" => "Address verification not applicable for this transaction",
- "R" => "Payment gateway was unavailable or timed out",
- "S" => "Address verification service not supported by issuer",
- "U" => "Address information is unavailable",
- "W" => "9-digit zip/postal code matches billing information, street address does not",
- "X" => "Street address and 9-digit zip/postal code matches billing information",
- "Y" => "Street address and 5-digit zip/postal code matches billing information",
- "Z" => "5-digit zip/postal code matches billing information, street address does not",
+ 'A' => 'Street address matches billing information, zip/postal code does not',
+ 'B' => 'Address information not provided for address verification check',
+ 'E' => 'Address verification service error',
+ 'G' => 'Non-U.S. card-issuing bank',
+ 'N' => 'Neither street address nor zip/postal match billing information',
+ 'P' => 'Address verification not applicable for this transaction',
+ 'R' => 'Payment gateway was unavailable or timed out',
+ 'S' => 'Address verification service not supported by issuer',
+ 'U' => 'Address information is unavailable',
+ 'W' => '9-digit zip/postal code matches billing information, street address does not',
+ 'X' => 'Street address and 9-digit zip/postal code matches billing information',
+ 'Y' => 'Street address and 5-digit zip/postal code matches billing information',
+ 'Z' => '5-digit zip/postal code matches billing information, street address does not',
}
AVS_ERRORS = %w( A E N R W Z )
PAYMENT_GATEWAY_RESPONSES = {
- "P01" => "AVS Mismatch Failure",
- "P02" => "CVV2 Mismatch Failure",
- "P21" => "Transaction may not be marked",
- "P30" => "Test Tran. Bad Card",
- "P35" => "Test Tran. Problem",
- "P40" => "Username already exists",
- "P41" => "Username is blank",
- "P50" => "Fraud Screen Failure",
- "P51" => "Missing PIN Code",
- "P52" => "Invalid Bank Acct. No.",
- "P53" => "Invalid Bank Routing No.",
- "P54" => "Invalid/Missing Check No.",
- "P55" => "Invalid Credit Card No.",
- "P56" => "Invalid CVV2/CVC2 No.",
- "P57" => "Expired. CC Exp. Date",
- "P58" => "Missing Data",
- "P59" => "Missing Email Address",
- "P60" => "Zip Code does not match Billing State.",
- "P61" => "Invalid Billing Zip Code",
- "P62" => "Zip Code does not match Shipping State.",
- "P63" => "Invalid Shipping Zip Code",
- "P64" => "Invalid Credit Card CVV2/CVC2 Format.",
- "P65" => "Maximum number of attempts has been exceeded.",
- "P66" => "Credit Card number has been flagged and can not be used to access this service.",
- "P67" => "IP Address is on Blocked List.",
- "P68" => "Billing country does not match ipaddress country.",
- "P69" => "US based ipaddresses are currently blocked.",
- "P70" => "Credit Cards issued from this bank are currently not being accepted.",
- "P71" => "Credit Cards issued from this bank are currently not being accepted.",
- "P72" => "Daily volume exceeded.",
- "P73" => "Too many transactions within allotted time.",
- "P91" => "Missing/incorrect password",
- "P92" => "Account not configured for mobil administration",
- "P93" => "IP Not registered to username.",
- "P94" => "Mode not permitted for this account.",
- "P95" => "Currently Blank",
- "P96" => "Currently Blank",
- "P97" => "Processor not responding",
- "P98" => "Missing merchant/publisher name",
- "P99" => "Currently Blank"
+ 'P01' => 'AVS Mismatch Failure',
+ 'P02' => 'CVV2 Mismatch Failure',
+ 'P21' => 'Transaction may not be marked',
+ 'P30' => 'Test Tran. Bad Card',
+ 'P35' => 'Test Tran. Problem',
+ 'P40' => 'Username already exists',
+ 'P41' => 'Username is blank',
+ 'P50' => 'Fraud Screen Failure',
+ 'P51' => 'Missing PIN Code',
+ 'P52' => 'Invalid Bank Acct. No.',
+ 'P53' => 'Invalid Bank Routing No.',
+ 'P54' => 'Invalid/Missing Check No.',
+ 'P55' => 'Invalid Credit Card No.',
+ 'P56' => 'Invalid CVV2/CVC2 No.',
+ 'P57' => 'Expired. CC Exp. Date',
+ 'P58' => 'Missing Data',
+ 'P59' => 'Missing Email Address',
+ 'P60' => 'Zip Code does not match Billing State.',
+ 'P61' => 'Invalid Billing Zip Code',
+ 'P62' => 'Zip Code does not match Shipping State.',
+ 'P63' => 'Invalid Shipping Zip Code',
+ 'P64' => 'Invalid Credit Card CVV2/CVC2 Format.',
+ 'P65' => 'Maximum number of attempts has been exceeded.',
+ 'P66' => 'Credit Card number has been flagged and can not be used to access this service.',
+ 'P67' => 'IP Address is on Blocked List.',
+ 'P68' => 'Billing country does not match ipaddress country.',
+ 'P69' => 'US based ipaddresses are currently blocked.',
+ 'P70' => 'Credit Cards issued from this bank are currently not being accepted.',
+ 'P71' => 'Credit Cards issued from this bank are currently not being accepted.',
+ 'P72' => 'Daily volume exceeded.',
+ 'P73' => 'Too many transactions within allotted time.',
+ 'P91' => 'Missing/incorrect password',
+ 'P92' => 'Account not configured for mobil administration',
+ 'P93' => 'IP Not registered to username.',
+ 'P94' => 'Mode not permitted for this account.',
+ 'P95' => 'Currently Blank',
+ 'P96' => 'Currently Blank',
+ 'P97' => 'Processor not responding',
+ 'P98' => 'Missing merchant/publisher name',
+ 'P99' => 'Currently Blank'
}
TRANSACTIONS = {
@@ -153,7 +153,7 @@ def credit(money, identification_or_creditcard, options = {})
add_amount(post, money, options)
if identification_or_creditcard.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification_or_creditcard, options)
else
add_creditcard(post, identification_or_creditcard)
@@ -172,8 +172,9 @@ def refund(money, reference, options = {})
end
private
+
def commit(action, post)
- response = parse( ssl_post(self.live_url, post_data(action, post)) )
+ response = parse(ssl_post(self.live_url, post_data(action, post)))
success = SUCCESS_CODES.include?(response[:finalstatus])
message = success ? 'Success' : message_from(response)
@@ -188,7 +189,7 @@ def commit(action, post)
def parse(body)
body = CGI.unescape(body)
results = {}
- body.split('&').collect { |e| e.split('=') }.each do |key,value|
+ body.split('&').collect { |e| e.split('=') }.each do |key, value|
results[key.downcase.to_sym] = normalize(value.to_s.strip)
end
@@ -268,24 +269,13 @@ def add_amount(post, money, options)
post[:currency] = options[:currency] || currency(money)
end
- # Make a ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
- end
- end
-
def message_from(results)
PAYMENT_GATEWAY_RESPONSES[results[:resp_code]]
end
def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
+ year = sprintf('%.4i', creditcard.year)
+ month = sprintf('%.2i', creditcard.month)
"#{month}/#{year[-2..-1]}"
end
diff --git a/lib/active_merchant/billing/gateways/pro_pay.rb b/lib/active_merchant/billing/gateways/pro_pay.rb
new file mode 100644
index 00000000000..dd286ad6fcb
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/pro_pay.rb
@@ -0,0 +1,326 @@
+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].to_s.delete('-')
+ 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
diff --git a/lib/active_merchant/billing/gateways/psigate.rb b/lib/active_merchant/billing/gateways/psigate.rb
index c30a1f20f77..b987dd1e74b 100644
--- a/lib/active_merchant/billing/gateways/psigate.rb
+++ b/lib/active_merchant/billing/gateways/psigate.rb
@@ -35,8 +35,8 @@ module Billing #:nodoc:
# :email => 'jack@yahoo.com'
# )
class PsigateGateway < Gateway
- self.test_url = 'https://dev.psigate.com:7989/Messenger/XMLMessenger'
- self.live_url = 'https://secure.psigate.com:7934/Messenger/XMLMessenger'
+ self.test_url = 'https://realtimestaging.psigate.com/xml'
+ self.live_url = 'https://realtime.psigate.com/xml'
self.supported_cardtypes = [:visa, :master, :american_express]
self.supported_countries = ['CA']
@@ -53,39 +53,50 @@ def initialize(options = {})
def authorize(money, creditcard, options = {})
requires!(options, :order_id)
- options[:CardAction] = "1"
+ options[:CardAction] = '1'
commit(money, creditcard, options)
end
def purchase(money, creditcard, options = {})
requires!(options, :order_id)
- options[:CardAction] = "0"
+ options[:CardAction] = '0'
commit(money, creditcard, options)
end
def capture(money, authorization, options = {})
- options[:CardAction] = "2"
+ options[:CardAction] = '2'
options[:order_id], options[:trans_ref_number] = split_authorization(authorization)
commit(money, nil, options)
end
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
def refund(money, authorization, options = {})
- options[:CardAction] = "3"
+ options[:CardAction] = '3'
options[:order_id], options[:trans_ref_number] = split_authorization(authorization)
commit(money, nil, options)
end
def void(authorization, options = {})
- options[:CardAction] = "9"
+ options[:CardAction] = '9'
options[:order_id], options[:trans_ref_number] = split_authorization(authorization)
commit(nil, nil, options)
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2')
+ end
+
private
def commit(money, creditcard, options = {})
@@ -93,7 +104,7 @@ def commit(money, creditcard, options = {})
Response.new(successful?(response), message_from(response), response,
:test => test?,
- :authorization => build_authorization(response) ,
+ :authorization => build_authorization(response),
:avs_result => { :code => response[:avsresult] },
:cvv_result => response[:cardidresult]
)
@@ -104,11 +115,11 @@ def url
end
def successful?(response)
- response[:approved] == "APPROVED"
+ response[:approved] == 'APPROVED'
end
def parse(xml)
- response = {:message => "Global Error Receipt", :complete => false}
+ response = {:message => 'Global Error Receipt', :complete => false}
xml = REXML::Document.new(xml)
xml.elements.each('//Result/*') do |node|
@@ -121,7 +132,7 @@ def parse(xml)
def post_data(money, creditcard, options)
xml = REXML::Document.new
xml << REXML::XMLDecl.new
- root = xml.add_element("Order")
+ root = xml.add_element('Order')
parameters(money, creditcard, options).each do |key, value|
root.add_element(key.to_s).text = value if value
@@ -144,7 +155,7 @@ def parameters(money, creditcard, options = {})
:TransRefNumber => options[:trans_ref_number],
# Credit Card parameters
- :PaymentType => "CC",
+ :PaymentType => 'CC',
:CardAction => options[:CardAction],
# Financial parameters
@@ -156,9 +167,9 @@ def parameters(money, creditcard, options = {})
}
if creditcard
- exp_month = sprintf("%.2i", creditcard.month) unless creditcard.month.blank?
- exp_year = creditcard.year.to_s[2,2] unless creditcard.year.blank?
- card_id_code = (creditcard.verification_value.blank? ? nil : "1")
+ exp_month = sprintf('%.2i', creditcard.month) unless creditcard.month.blank?
+ exp_year = creditcard.year.to_s[2, 2] unless creditcard.year.blank?
+ card_id_code = (creditcard.verification_value.blank? ? nil : '1')
params.update(
:CardNumber => creditcard.number,
@@ -195,22 +206,11 @@ def parameters(money, creditcard, options = {})
end
def message_from(response)
- if response[:approved] == "APPROVED"
+ if response[:approved] == 'APPROVED'
return SUCCESS_MESSAGE
else
return FAILURE_MESSAGE if response[:errmsg].blank?
- return response[:errmsg].gsub(/[^\w]/, ' ').split.join(" ").capitalize
- end
- end
-
- # Make a ruby type out of the response string
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
+ return response[:errmsg].gsub(/[^\w]/, ' ').split.join(' ').capitalize
end
end
@@ -220,7 +220,7 @@ def split_authorization(authorization)
end
def build_authorization(response)
- [response[:orderid], response[:transrefnumber]].join(";")
+ [response[:orderid], response[:transrefnumber]].join(';')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/psl_card.rb b/lib/active_merchant/billing/gateways/psl_card.rb
index 758aac6fefb..77da4a123db 100644
--- a/lib/active_merchant/billing/gateways/psl_card.rb
+++ b/lib/active_merchant/billing/gateways/psl_card.rb
@@ -17,11 +17,11 @@ class PslCardGateway < Gateway
self.default_currency = 'GBP'
self.supported_countries = ['GB']
- # Visa Credit, Visa Debit, Mastercard, Maestro, Solo, Electron,
+ # Visa Credit, Visa Debit, Mastercard, Maestro, Electron,
# American Express, Diners Club, JCB, International Maestro,
# Style, Clydesdale Financial Services, Other
- self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :switch, :solo, :maestro ]
+ self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :maestro ]
self.homepage_url = 'http://www.paymentsolutionsltd.com/'
self.display_name = 'PSL Payment Solutions'
@@ -46,38 +46,38 @@ class PslCardGateway < Gateway
'USD' => 840
}
- #The terminal used - only for swipe transactions, so hard coded to 32 for online
+ # The terminal used - only for swipe transactions, so hard coded to 32 for online
EMV_TERMINAL_TYPE = 32
- #Different Dispatch types
+ # Different Dispatch types
DISPATCH_LATER = 'LATER'
DISPATCH_NOW = 'NOW'
# Return codes
APPROVED = '00'
- #Nominal amount to authorize for a 'dispatch later' type
- #The nominal amount is held straight away, when the goods are ready
- #to be dispatched, PSL is informed and the full amount is the
- #taken.
+ # Nominal amount to authorize for a 'dispatch later' type
+ # The nominal amount is held straight away, when the goods are ready
+ # to be dispatched, PSL is informed and the full amount is the
+ # taken.
NOMINAL_AMOUNT = 101
AVS_CODE = {
- "ALL MATCH" => 'Y',
- "SECURITY CODE MATCH ONLY" => 'N',
- "ADDRESS MATCH ONLY" => 'Y',
- "NO DATA MATCHES" => 'N',
- "DATA NOT CHECKED" => 'R',
- "SECURITY CHECKS NOT SUPPORTED" => 'X'
+ 'ALL MATCH' => 'Y',
+ 'SECURITY CODE MATCH ONLY' => 'N',
+ 'ADDRESS MATCH ONLY' => 'Y',
+ 'NO DATA MATCHES' => 'N',
+ 'DATA NOT CHECKED' => 'R',
+ 'SECURITY CHECKS NOT SUPPORTED' => 'X'
}
CVV_CODE = {
- "ALL MATCH" => 'M',
- "SECURITY CODE MATCH ONLY" => 'M',
- "ADDRESS MATCH ONLY" => 'N',
- "NO DATA MATCHES" => 'N',
- "DATA NOT CHECKED" => 'P',
- "SECURITY CHECKS NOT SUPPORTED" => 'X'
+ 'ALL MATCH' => 'M',
+ 'SECURITY CODE MATCH ONLY' => 'M',
+ 'ADDRESS MATCH ONLY' => 'N',
+ 'NO DATA MATCHES' => 'N',
+ 'DATA NOT CHECKED' => 'P',
+ 'SECURITY CHECKS NOT SUPPORTED' => 'X'
}
# Create a new PslCardGateway
@@ -101,7 +101,7 @@ def initialize(options = {})
# -options:
#
# Returns:
- # -ActiveRecord::Billing::Response object
+ # -ActiveMerchant::Billing::Response object
#
def purchase(money, credit_card, options = {})
post = {}
@@ -129,7 +129,7 @@ def purchase(money, credit_card, options = {})
# -options:
#
# Returns:
- # -ActiveRecord::Billing::Response object
+ # -ActiveMerchant::Billing::Response object
#
def authorize(money, credit_card, options = {})
post = {}
@@ -153,7 +153,7 @@ def authorize(money, credit_card, options = {})
# -options:
#
# Returns:
- # -ActiveRecord::Billing::Response object
+ # -ActiveMerchant::Billing::Response object
#
def capture(money, authorization, options = {})
post = {}
@@ -174,12 +174,6 @@ def add_credit_card(post, credit_card)
post[:ExpMonth] = credit_card.month
post[:ExpYear] = credit_card.year
- if requires_start_date_or_issue_number?(credit_card)
- post[:IssueNumber] = credit_card.issue_number unless credit_card.issue_number.blank?
- post[:StartMonth] = credit_card.start_month unless credit_card.start_month.blank?
- post[:StartYear] = credit_card.start_year unless credit_card.start_year.blank?
- end
-
# CV2 check
post[:AVSCV2Check] = credit_card.verification_value? ? 'YES' : 'NO'
post[:CV2] = credit_card.verification_value if credit_card.verification_value?
@@ -189,7 +183,7 @@ def add_address(post, options)
address = options[:billing_address] || options[:address]
return if address.nil?
- post[:QAAddress] = [:address1, :address2, :city, :state].collect{|a| address[a]}.reject{|a| a.blank?}.join(' ')
+ post[:QAAddress] = [:address1, :address2, :city, :state].collect { |a| address[a] }.reject(&:blank?).join(' ')
post[:QAPostcode] = address[:zip]
end
@@ -246,10 +240,9 @@ def currency_code(currency)
# -a hash with all of the values returned in the PSL response
#
def parse(body)
-
fields = {}
for line in body.split('&')
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
+ key, value = *line.scan(%r{^(\w+)\=(.*)$}).flatten
fields[key] = CGI.unescape(value)
end
fields.symbolize_keys
@@ -264,7 +257,7 @@ def parse(body)
# - ActiveMerchant::Billing::Response object
#
def commit(request)
- response = parse( ssl_post(self.live_url, post_data(request)) )
+ response = parse(ssl_post(self.live_url, post_data(request)))
Response.new(response[:ResponseCode] == APPROVED, response[:Message], response,
:test => test?,
@@ -296,7 +289,7 @@ def post_data(post)
post.collect { |key, value|
"#{key}=#{CGI.escape(value.to_s.tr('&=', ' '))}"
- }.join("&")
+ }.join('&')
end
end
end
diff --git a/lib/active_merchant/billing/gateways/qbms.rb b/lib/active_merchant/billing/gateways/qbms.rb
index a1ca4bccca4..5d37f900bf4 100644
--- a/lib/active_merchant/billing/gateways/qbms.rb
+++ b/lib/active_merchant/billing/gateways/qbms.rb
@@ -5,8 +5,8 @@ class QbmsGateway < Gateway
class_attribute :test_url, :live_url
- self.test_url = "https://webmerchantaccount.ptc.quickbooks.com/j/AppGateway"
- self.live_url = "https://webmerchantaccount.quickbooks.com/j/AppGateway"
+ self.test_url = 'https://webmerchantaccount.ptc.quickbooks.com/j/AppGateway'
+ self.live_url = 'https://webmerchantaccount.quickbooks.com/j/AppGateway'
self.homepage_url = 'http://payments.intuit.com/'
self.display_name = 'QuickBooks Merchant Services'
@@ -100,8 +100,8 @@ def void(authorization, options = {})
#
#
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, identification, options = {})
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
+ refund(money, identification, {})
end
def refund(money, identification, options = {})
@@ -113,6 +113,17 @@ def query
commit(:query, nil, {})
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]*())i, '\1[FILTERED]\2')
+ end
+
private
def hosted?
@@ -127,7 +138,7 @@ def commit(action, money, parameters)
req = build_request(type, money, parameters)
- data = ssl_post(url, req, "Content-Type" => "application/x-qbmsxml")
+ data = ssl_post(url, req, 'Content-Type' => 'application/x-qbmsxml')
response = parse(type, data)
message = (response[:status_message] || '').strip
@@ -152,20 +163,20 @@ def parse(type, body)
xml = REXML::Document.new(body)
signon = REXML::XPath.first(xml, "//SignonMsgsRs/#{hosted? ? 'SignonAppCertRs' : 'SignonDesktopRs'}")
- status_code = signon.attributes["statusCode"].to_i
+ status_code = signon.attributes['statusCode'].to_i
if status_code != 0
return {
:status_code => status_code,
- :status_message => signon.attributes["statusMessage"],
+ :status_message => signon.attributes['statusMessage'],
}
end
response = REXML::XPath.first(xml, "//QBMSXMLMsgsRs/#{type}Rs")
results = {
- :status_code => response.attributes["statusCode"].to_i,
- :status_message => response.attributes["statusMessage"],
+ :status_code => response.attributes['statusCode'].to_i,
+ :status_message => response.attributes['statusMessage'],
}
response.elements.each do |e|
@@ -189,16 +200,16 @@ def build_request(type, money, parameters = {})
xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
xml.instruct!(:qbmsxml, :version => API_VERSION)
- xml.tag!("QBMSXML") do
- xml.tag!("SignonMsgsRq") do
- xml.tag!(hosted? ? "SignonAppCertRq" : "SignonDesktopRq") do
- xml.tag!("ClientDateTime", Time.now.xmlschema)
- xml.tag!("ApplicationLogin", @options[:login])
- xml.tag!("ConnectionTicket", @options[:ticket])
+ xml.tag!('QBMSXML') do
+ xml.tag!('SignonMsgsRq') do
+ xml.tag!(hosted? ? 'SignonAppCertRq' : 'SignonDesktopRq') do
+ xml.tag!('ClientDateTime', Time.now.xmlschema)
+ xml.tag!('ApplicationLogin', @options[:login])
+ xml.tag!('ConnectionTicket', @options[:ticket])
end
end
- xml.tag!("QBMSXMLMsgsRq") do
+ xml.tag!('QBMSXMLMsgsRq') do
xml.tag!("#{type}Rq") do
method("build_#{type}").call(xml, money, parameters)
end
@@ -212,47 +223,47 @@ def build_CustomerCreditCardAuth(xml, money, parameters)
cc = parameters[:credit_card]
name = "#{cc.first_name} #{cc.last_name}"[0...30]
- xml.tag!("TransRequestID", parameters[:trans_request_id])
- xml.tag!("CreditCardNumber", cc.number)
- xml.tag!("ExpirationMonth", cc.month)
- xml.tag!("ExpirationYear", cc.year)
- xml.tag!("IsECommerce", "true")
- xml.tag!("Amount", amount(money))
- xml.tag!("NameOnCard", name)
+ xml.tag!('TransRequestID', parameters[:trans_request_id])
+ xml.tag!('CreditCardNumber', cc.number)
+ xml.tag!('ExpirationMonth', cc.month)
+ xml.tag!('ExpirationYear', cc.year)
+ xml.tag!('IsECommerce', 'true')
+ xml.tag!('Amount', amount(money))
+ xml.tag!('NameOnCard', name)
add_address(xml, parameters)
- xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value?
+ xml.tag!('CardSecurityCode', cc.verification_value) if cc.verification_value?
end
def build_CustomerCreditCardCapture(xml, money, parameters)
- xml.tag!("TransRequestID", parameters[:trans_request_id])
- xml.tag!("CreditCardTransID", parameters[:transaction_id])
- xml.tag!("Amount", amount(money))
+ xml.tag!('TransRequestID', parameters[:trans_request_id])
+ xml.tag!('CreditCardTransID', parameters[:transaction_id])
+ xml.tag!('Amount', amount(money))
end
def build_CustomerCreditCardCharge(xml, money, parameters)
cc = parameters[:credit_card]
name = "#{cc.first_name} #{cc.last_name}"[0...30]
- xml.tag!("TransRequestID", parameters[:trans_request_id])
- xml.tag!("CreditCardNumber", cc.number)
- xml.tag!("ExpirationMonth", cc.month)
- xml.tag!("ExpirationYear", cc.year)
- xml.tag!("IsECommerce", "true")
- xml.tag!("Amount", amount(money))
- xml.tag!("NameOnCard", name)
+ xml.tag!('TransRequestID', parameters[:trans_request_id])
+ xml.tag!('CreditCardNumber', cc.number)
+ xml.tag!('ExpirationMonth', cc.month)
+ xml.tag!('ExpirationYear', cc.year)
+ xml.tag!('IsECommerce', 'true')
+ xml.tag!('Amount', amount(money))
+ xml.tag!('NameOnCard', name)
add_address(xml, parameters)
- xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value?
+ xml.tag!('CardSecurityCode', cc.verification_value) if cc.verification_value?
end
def build_CustomerCreditCardTxnVoidOrRefund(xml, money, parameters)
- xml.tag!("TransRequestID", parameters[:trans_request_id])
- xml.tag!("CreditCardTransID", parameters[:transaction_id])
- xml.tag!("Amount", amount(money))
+ xml.tag!('TransRequestID', parameters[:trans_request_id])
+ xml.tag!('CreditCardTransID', parameters[:transaction_id])
+ xml.tag!('Amount', amount(money))
end
def build_CustomerCreditCardTxnVoid(xml, money, parameters)
- xml.tag!("TransRequestID", parameters[:trans_request_id])
- xml.tag!("CreditCardTransID", parameters[:transaction_id])
+ xml.tag!('TransRequestID', parameters[:trans_request_id])
+ xml.tag!('CreditCardTransID', parameters[:transaction_id])
end
# Called reflectively by build_request
@@ -261,30 +272,30 @@ def build_MerchantAccountQuery(xml, money, parameters)
def add_address(xml, parameters)
if address = parameters[:billing_address] || parameters[:address]
- xml.tag!("CreditCardAddress", (address[:address1] || "")[0...30])
- xml.tag!("CreditCardPostalCode", (address[:zip] || "")[0...9])
+ xml.tag!('CreditCardAddress', (address[:address1] || '')[0...30])
+ xml.tag!('CreditCardPostalCode', (address[:zip] || '')[0...9])
end
end
def cvv_result(response)
case response[:card_security_code_match]
- when "Pass" then 'M'
- when "Fail" then 'N'
- when "NotAvailable" then 'P'
+ when 'Pass' then 'M'
+ when 'Fail' then 'N'
+ when 'NotAvailable' then 'P'
end
end
def avs_result(response)
case "#{response[:avs_street]}|#{response[:avs_zip]}"
- when "Pass|Pass" then "D"
- when "Pass|Fail" then "A"
- when "Pass|NotAvailable" then "B"
- when "Fail|Pass" then "Z"
- when "Fail|Fail" then "C"
- when "Fail|NotAvailable" then "N"
- when "NotAvailable|Pass" then "P"
- when "NotAvailable|Fail" then "N"
- when "NotAvailable|NotAvailable" then "U"
+ when 'Pass|Pass' then 'D'
+ when 'Pass|Fail' then 'A'
+ when 'Pass|NotAvailable' then 'B'
+ when 'Fail|Pass' then 'Z'
+ when 'Fail|Fail' then 'C'
+ when 'Fail|NotAvailable' then 'N'
+ when 'NotAvailable|Pass' then 'P'
+ when 'NotAvailable|Fail' then 'N'
+ when 'NotAvailable|NotAvailable' then 'U'
end
end
end
diff --git a/lib/active_merchant/billing/gateways/quantum.rb b/lib/active_merchant/billing/gateways/quantum.rb
index 7d8b51be13d..4fd43ad07ca 100644
--- a/lib/active_merchant/billing/gateways/quantum.rb
+++ b/lib/active_merchant/billing/gateways/quantum.rb
@@ -44,7 +44,7 @@ def initialize(options = {})
#
def authorize(money, creditcard, options = {})
setup_address_hash(options)
- commit(build_auth_request(money, creditcard, options), options )
+ commit(build_auth_request(money, creditcard, options), options)
end
# Capture an authorization that has previously been requested
@@ -69,7 +69,7 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -81,7 +81,7 @@ def setup_address_hash(options)
def build_auth_request(money, creditcard, options)
xml = Builder::XmlMarkup.new
- add_common_credit_card_info(xml,'AUTH_ONLY')
+ add_common_credit_card_info(xml, 'AUTH_ONLY')
add_purchase_data(xml, money)
add_creditcard(xml, creditcard)
add_address(xml, creditcard, options[:billing_address], options)
@@ -94,7 +94,7 @@ def build_auth_request(money, creditcard, options)
def build_capture_request(money, authorization, options)
xml = Builder::XmlMarkup.new
- add_common_credit_card_info(xml,'PREVIOUS_SALE')
+ add_common_credit_card_info(xml, 'PREVIOUS_SALE')
transaction_id, _ = authorization_parts_from(authorization)
add_transaction_id(xml, transaction_id)
xml.target!
@@ -115,7 +115,7 @@ def build_purchase_request(money, creditcard, options)
def build_void_request(authorization, options)
xml = Builder::XmlMarkup.new
- add_common_credit_card_info(xml,'VOID')
+ add_common_credit_card_info(xml, 'VOID')
transaction_id, _ = authorization_parts_from(authorization)
add_transaction_id(xml, transaction_id)
xml.target!
@@ -123,7 +123,7 @@ def build_void_request(authorization, options)
def build_credit_request(money, authorization, options)
xml = Builder::XmlMarkup.new
- add_common_credit_card_info(xml,'RETURN')
+ add_common_credit_card_info(xml, 'RETURN')
add_purchase_data(xml, money)
transaction_id, cc = authorization_parts_from(authorization)
add_transaction_id(xml, transaction_id)
@@ -182,7 +182,7 @@ def add_creditcard(xml, creditcard)
xml.tag! 'CreditCardNumber', creditcard.number
xml.tag! 'ExpireMonth', format(creditcard.month, :two_digits)
xml.tag! 'ExpireYear', format(creditcard.year, :four_digits)
- xml.tag!('CVV2', creditcard.verification_value) unless (@options[:ignore_cvv] || creditcard.verification_value.blank? )
+ xml.tag!('CVV2', creditcard.verification_value) unless @options[:ignore_cvv] || creditcard.verification_value.blank?
end
# Where we actually build the full SOAP request using builder
@@ -206,7 +206,7 @@ def commit(request, options)
headers = { 'Content-Type' => 'text/xml' }
response = parse(ssl_post(self.live_url, build_request(request, options), headers))
- success = response[:request_status] == "Success"
+ success = response[:request_status] == 'Success'
message = response[:request_message]
if success # => checking for connectivity success first
@@ -216,10 +216,10 @@ def commit(request, options)
end
Response.new(success, message, response,
- :test => test?,
- :authorization => authorization,
- :avs_result => { :code => response[:AVSResponseCode] },
- :cvv_result => response[:CVV2ResponseCode]
+ :test => test?,
+ :authorization => authorization,
+ :avs_result => { :code => response[:AVSResponseCode] },
+ :cvv_result => response[:CVV2ResponseCode]
)
end
@@ -231,19 +231,19 @@ def parse(xml)
begin
xml = REXML::Document.new(xml)
- root = REXML::XPath.first(xml, "//QGWRequest/ResponseSummary")
+ root = REXML::XPath.first(xml, '//QGWRequest/ResponseSummary')
parse_element(reply, root)
reply[:request_status] = reply[:Status]
reply[:request_message] = "#{reply[:Status]}: #{reply[:StatusDescription]}"
- if root = REXML::XPath.first(xml, "//QGWRequest/Result")
+ if root = REXML::XPath.first(xml, '//QGWRequest/Result')
root.elements.to_a.each do |node|
parse_element(reply, node)
end
end
- rescue Exception => e
+ rescue Exception
reply[:request_status] = 'Failure'
- reply[:request_message] = "Failure: There was a problem parsing the response XML"
+ reply[:request_message] = 'Failure: There was a problem parsing the response XML'
end
return reply
@@ -251,10 +251,10 @@ def parse(xml)
def parse_element(reply, node)
if node.has_elements?
- node.elements.each{|e| parse_element(reply, e) }
+ node.elements.each { |e| parse_element(reply, e) }
else
if node.parent.name =~ /item/
- parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '')
+ parent = node.parent.name + (node.parent.attributes['id'] ? '_' + node.parent.attributes['id'] : '')
reply[(parent + '_' + node.name).to_sym] = node.text
else
reply[node.name.to_sym] = node.text
diff --git a/lib/active_merchant/billing/gateways/quickbooks.rb b/lib/active_merchant/billing/gateways/quickbooks.rb
new file mode 100644
index 00000000000..6759a57aee6
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/quickbooks.rb
@@ -0,0 +1,290 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class QuickbooksGateway < Gateway
+ self.test_url = 'https://sandbox.api.intuit.com'
+ self.live_url = 'https://api.intuit.com'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners]
+
+ self.homepage_url = 'http://payments.intuit.com'
+ self.display_name = 'QuickBooks Payments'
+ ENDPOINT = '/quickbooks/v4/payments/charges'
+ OAUTH_ENDPOINTS = {
+ site: 'https://oauth.intuit.com',
+ request_token_path: '/oauth/v1/get_request_token',
+ authorize_url: 'https://appcenter.intuit.com/Connect/Begin',
+ access_token_path: '/oauth/v1/get_access_token'
+ }
+
+ # https://developer.intuit.com/docs/0150_payments/0300_developer_guides/error_handling
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ # Fraud Warnings
+ 'PMT-1000' => STANDARD_ERROR_CODE[:processing_error], # payment was accepted, but refund was unsuccessful
+ 'PMT-1001' => STANDARD_ERROR_CODE[:invalid_cvc], # payment processed, but cvc was invalid
+ 'PMT-1002' => STANDARD_ERROR_CODE[:incorrect_address], # payment processed, incorrect address info
+ 'PMT-1003' => STANDARD_ERROR_CODE[:processing_error], # payment processed, address info couldn't be validated
+
+ # Fraud Errors
+ 'PMT-2000' => STANDARD_ERROR_CODE[:incorrect_cvc], # Incorrect CVC
+ 'PMT-2001' => STANDARD_ERROR_CODE[:invalid_cvc], # CVC check unavaliable
+ 'PMT-2002' => STANDARD_ERROR_CODE[:incorrect_address], # Incorrect address
+ 'PMT-2003' => STANDARD_ERROR_CODE[:incorrect_address], # Address info unavailable
+
+ 'PMT-3000' => STANDARD_ERROR_CODE[:processing_error], # Merchant account could not be validated
+
+ # Invalid Request
+ 'PMT-4000' => STANDARD_ERROR_CODE[:processing_error], # Object is invalid
+ 'PMT-4001' => STANDARD_ERROR_CODE[:processing_error], # Object not found
+ 'PMT-4002' => STANDARD_ERROR_CODE[:processing_error], # Object is required
+
+ # Transaction Declined
+ 'PMT-5000' => STANDARD_ERROR_CODE[:card_declined], # Request was declined
+ 'PMT-5001' => STANDARD_ERROR_CODE[:card_declined], # Merchant does not support given payment method
+
+ # System Error
+ 'PMT-6000' => STANDARD_ERROR_CODE[:processing_error], # A temporary Issue prevented this request from being processed.
+ }
+
+ FRAUD_WARNING_CODES = ['PMT-1000', 'PMT-1001', 'PMT-1002', 'PMT-1003']
+
+ def initialize(options = {})
+ requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm)
+ @options = options
+ super
+ end
+
+ def purchase(money, payment, options = {})
+ post = {}
+ add_amount(post, money, options)
+ add_charge_data(post, payment, options)
+ post[:capture] = 'true'
+
+ commit(ENDPOINT, post)
+ end
+
+ def authorize(money, payment, options = {})
+ post = {}
+ add_amount(post, money, options)
+ add_charge_data(post, payment, options)
+ post[:capture] = 'false'
+
+ commit(ENDPOINT, post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ capture_uri = "#{ENDPOINT}/#{CGI.escape(authorization)}/capture"
+ post[:amount] = localized_amount(money, currency(money))
+ add_context(post, options)
+
+ commit(capture_uri, post)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ post[:amount] = localized_amount(money, currency(money))
+ add_context(post, options)
+
+ commit(refund_uri(authorization), post)
+ end
+
+ def verify(credit_card, options = {})
+ authorize(1.00, credit_card, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((realm=\")\w+), '\1[FILTERED]').
+ gsub(%r((oauth_consumer_key=\")\w+), '\1[FILTERED]').
+ gsub(%r((oauth_nonce=\")\w+), '\1[FILTERED]').
+ gsub(%r((oauth_signature=\")[a-zA-Z%0-9]+), '\1[FILTERED]').
+ gsub(%r((oauth_token=\")\w+), '\1[FILTERED]').
+ gsub(%r((number\D+)\d{16}), '\1[FILTERED]').
+ gsub(%r((cvc\D+)\d{3}), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_charge_data(post, payment, options = {})
+ add_payment(post, payment, options)
+ add_address(post, options)
+ end
+
+ def add_address(post, options)
+ return unless post[:card]&.kind_of?(Hash)
+
+ card_address = {}
+ if address = options[:billing_address] || options[:address]
+ card_address[:streetAddress] = address[:address1]
+ card_address[:city] = address[:city]
+ card_address[:region] = address[:state] || address[:region]
+ card_address[:country] = address[:country]
+ card_address[:postalCode] = address[:zip] if address[:zip]
+ end
+ post[:card][:address] = card_address
+ end
+
+ def add_amount(post, money, options = {})
+ currency = options[:currency] || currency(money)
+ post[:amount] = localized_amount(money, currency)
+ post[:currency] = currency.upcase
+ end
+
+ def add_payment(post, payment, options = {})
+ add_creditcard(post, payment, options)
+ add_context(post, options)
+ end
+
+ def add_creditcard(post, creditcard, options = {})
+ card = {}
+ card[:number] = creditcard.number
+ card[:expMonth] = '%02d' % creditcard.month
+ card[:expYear] = creditcard.year
+ card[:cvc] = creditcard.verification_value if creditcard.verification_value?
+ card[:name] = creditcard.name if creditcard.name
+ card[:commercialCardCode] = options[:card_code] if options[:card_code]
+
+ post[:card] = card
+ end
+
+ def add_context(post, options = {})
+ post[:context] = {
+ mobile: options.fetch(:mobile, false),
+ isEcommerce: options.fetch(:ecommerce, true)
+ }
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(uri, body = {}, method = :post)
+ endpoint = gateway_url + uri
+ # The QuickBooks API returns HTTP 4xx on failed transactions, which causes a
+ # ResponseError raise, so we have to inspect the response and discern between
+ # a legitimate HTTP error and an actual gateway transactional error.
+ response = begin
+ case method
+ when :post
+ ssl_post(endpoint, post_data(body), headers(:post, endpoint))
+ when :get
+ ssl_request(:get, endpoint, nil, headers(:get, endpoint))
+ else
+ raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get"
+ end
+ rescue ResponseError => e
+ extract_response_body_or_raise(e)
+ end
+
+ response_object(response)
+ end
+
+ def response_object(raw_response)
+ parsed_response = parse(raw_response)
+
+ Response.new(
+ success?(parsed_response),
+ message_from(parsed_response),
+ parsed_response,
+ authorization: authorization_from(parsed_response),
+ test: test?,
+ cvv_result: cvv_code_from(parsed_response),
+ error_code: errors_from(parsed_response),
+ fraud_review: fraud_review_status_from(parsed_response)
+ )
+ end
+
+ def gateway_url
+ test? ? test_url : live_url
+ end
+
+ def post_data(data = {})
+ data.to_json
+ end
+
+ def headers(method, uri)
+ raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" unless [:post, :get].include?(method)
+ request_uri = URI.parse(uri)
+
+ # Following the guidelines from http://nouncer.com/oauth/authentication.html
+ oauth_parameters = {
+ oauth_nonce: generate_unique_id,
+ oauth_timestamp: Time.now.to_i.to_s,
+ oauth_signature_method: 'HMAC-SHA1',
+ oauth_version: '1.0',
+ oauth_consumer_key: @options[:consumer_key],
+ oauth_token: @options[:access_token]
+ }
+
+ # prepare components for signature
+ oauth_signature_base_string = [method.to_s.upcase, request_uri.to_s, oauth_parameters.to_param].map { |v| CGI.escape(v) }.join('&')
+ oauth_signing_key = [@options[:consumer_secret], @options[:token_secret]].map { |v| CGI.escape(v) }.join('&')
+ hmac_signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), oauth_signing_key, oauth_signature_base_string)
+
+ # append signature to required OAuth parameters
+ oauth_parameters[:oauth_signature] = CGI.escape(Base64.encode64(hmac_signature).chomp.gsub(/\n/, ''))
+
+ # prepare Authorization header string
+ oauth_parameters = Hash[oauth_parameters.sort_by { |k, _| k }]
+ oauth_headers = ["OAuth realm=\"#{@options[:realm]}\""]
+ oauth_headers += oauth_parameters.map { |k, v| "#{k}=\"#{v}\"" }
+
+ {
+ 'Content-type' => 'application/json',
+ 'Request-Id' => generate_unique_id,
+ 'Authorization' => oauth_headers.join(', ')
+ }
+ end
+
+ def cvv_code_from(response)
+ if response['errors'].present?
+ FRAUD_WARNING_CODES.include?(response['errors'].first['code']) ? 'I' : ''
+ else
+ success?(response) ? 'M' : ''
+ end
+ end
+
+ def success?(response)
+ return FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) if response['errors']
+
+ !['DECLINED', 'CANCELLED'].include?(response['status'])
+ end
+
+ def message_from(response)
+ response['errors'].present? ? response['errors'].map { |error_hash| error_hash['message'] }.join(' ') : response['status']
+ end
+
+ def errors_from(response)
+ response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response['errors'].first['code']] : ''
+ end
+
+ def authorization_from(response)
+ response['id']
+ end
+
+ def fraud_review_status_from(response)
+ response['errors'] && FRAUD_WARNING_CODES.include?(response['errors'].first['code'])
+ end
+
+ def extract_response_body_or_raise(response_error)
+ begin
+ parse(response_error.response.body)
+ rescue JSON::ParserError
+ raise response_error
+ end
+ response_error.response.body
+ end
+
+ def refund_uri(authorization)
+ "#{ENDPOINT}/#{CGI.escape(authorization)}/refunds"
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/quickpay.rb b/lib/active_merchant/billing/gateways/quickpay.rb
index 141d1ff03a0..5c1f6fb33bb 100644
--- a/lib/active_merchant/billing/gateways/quickpay.rb
+++ b/lib/active_merchant/billing/gateways/quickpay.rb
@@ -1,335 +1,25 @@
require 'rexml/document'
require 'digest/md5'
+require 'active_merchant/billing/gateways/quickpay/quickpay_v10'
+require 'active_merchant/billing/gateways/quickpay/quickpay_v4to7'
+
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class QuickpayGateway < Gateway
- self.live_url = self.test_url = 'https://secure.quickpay.dk/api'
-
- self.default_currency = 'DKK'
- self.money_format = :cents
- self.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master, :american_express, :diners_club, :jcb, :maestro]
- self.supported_countries = ['DK', 'SE']
- self.homepage_url = 'http://quickpay.dk/'
- self.display_name = 'Quickpay'
-
- MD5_CHECK_FIELDS = {
- 3 => {
- :authorize => %w(protocol msgtype merchant ordernumber amount
- currency autocapture cardnumber expirationdate
- cvd cardtypelock testmode),
-
- :capture => %w(protocol msgtype merchant amount transaction),
-
- :cancel => %w(protocol msgtype merchant transaction),
-
- :refund => %w(protocol msgtype merchant amount transaction),
-
- :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
- expirationdate cvd cardtypelock description testmode),
-
- :recurring => %w(protocol msgtype merchant ordernumber amount
- currency autocapture transaction),
-
- :status => %w(protocol msgtype merchant transaction),
-
- :chstatus => %w(protocol msgtype merchant)
- },
-
- 4 => {
- :authorize => %w(protocol msgtype merchant ordernumber amount
- currency autocapture cardnumber expirationdate cvd
- cardtypelock testmode fraud_remote_addr
- fraud_http_accept fraud_http_accept_language
- fraud_http_accept_encoding fraud_http_accept_charset
- fraud_http_referer fraud_http_user_agent apikey),
-
- :capture => %w(protocol msgtype merchant amount transaction apikey),
-
- :cancel => %w(protocol msgtype merchant transaction apikey),
-
- :refund => %w(protocol msgtype merchant amount transaction apikey),
-
- :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
- expirationdate cvd cardtypelock description testmode
- fraud_remote_addr fraud_http_accept fraud_http_accept_language
- fraud_http_accept_encoding fraud_http_accept_charset
- fraud_http_referer fraud_http_user_agent apikey),
-
- :recurring => %w(protocol msgtype merchant ordernumber amount currency
- autocapture transaction apikey),
-
- :status => %w(protocol msgtype merchant transaction apikey),
-
- :chstatus => %w(protocol msgtype merchant apikey)
- },
-
- 5 => {
- :authorize => %w(protocol msgtype merchant ordernumber amount
- currency autocapture cardnumber expirationdate cvd
- cardtypelock testmode fraud_remote_addr
- fraud_http_accept fraud_http_accept_language
- fraud_http_accept_encoding fraud_http_accept_charset
- fraud_http_referer fraud_http_user_agent apikey),
-
- :capture => %w(protocol msgtype merchant amount transaction apikey),
-
- :cancel => %w(protocol msgtype merchant transaction apikey),
-
- :refund => %w(protocol msgtype merchant amount transaction apikey),
-
- :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
- expirationdate cvd cardtypelock description testmode
- fraud_remote_addr fraud_http_accept fraud_http_accept_language
- fraud_http_accept_encoding fraud_http_accept_charset
- fraud_http_referer fraud_http_user_agent apikey),
-
- :recurring => %w(protocol msgtype merchant ordernumber amount currency
- autocapture transaction apikey),
-
- :status => %w(protocol msgtype merchant transaction apikey),
-
- :chstatus => %w(protocol msgtype merchant apikey)
- },
-
- 6 => {
- :authorize => %w(protocol msgtype merchant ordernumber amount
- currency autocapture cardnumber expirationdate cvd
- cardtypelock testmode fraud_remote_addr
- fraud_http_accept fraud_http_accept_language
- fraud_http_accept_encoding fraud_http_accept_charset
- fraud_http_referer fraud_http_user_agent apikey),
-
- :capture => %w(protocol msgtype merchant amount transaction
- apikey),
-
- :cancel => %w(protocol msgtype merchant transaction apikey),
-
- :refund => %w(protocol msgtype merchant amount transaction apikey),
-
- :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
- expirationdate cvd cardtypelock description testmode
- fraud_remote_addr fraud_http_accept fraud_http_accept_language
- fraud_http_accept_encoding fraud_http_accept_charset
- fraud_http_referer fraud_http_user_agent apikey),
-
- :recurring => %w(protocol msgtype merchant ordernumber amount currency
- autocapture transaction apikey),
-
- :status => %w(protocol msgtype merchant transaction apikey),
-
- :chstatus => %w(protocol msgtype merchant apikey)
- }
- }
-
- APPROVED = '000'
-
- # The login is the QuickpayId
- # The password is the md5checkword from the Quickpay manager
- # To use the API-key from the Quickpay manager, specify :api-key
- # Using the API-key, requires that you use version 4+. Specify :version => 4/5/6 in options.
- def initialize(options = {})
- requires!(options, :login, :password)
- @protocol = options.delete(:version) || 3 # default to protocol version 3
- super
- end
-
- def authorize(money, credit_card_or_reference, options = {})
- post = {}
-
- action = recurring_or_authorize(credit_card_or_reference)
-
- add_amount(post, money, options)
- add_invoice(post, options)
- add_creditcard_or_reference(post, credit_card_or_reference, options)
- add_autocapture(post, false)
- add_fraud_parameters(post, options) if action.eql?(:authorize)
- add_testmode(post)
-
- commit(action, post)
- end
-
- def purchase(money, credit_card_or_reference, options = {})
- post = {}
-
- action = recurring_or_authorize(credit_card_or_reference)
-
- add_amount(post, money, options)
- add_creditcard_or_reference(post, credit_card_or_reference, options)
- add_invoice(post, options)
- add_fraud_parameters(post, options) if action.eql?(:authorize)
- add_autocapture(post, true)
-
- commit(action, post)
- end
-
- def capture(money, authorization, options = {})
- post = {}
-
- add_reference(post, authorization)
- add_amount_without_currency(post, money)
- commit(:capture, post)
- end
-
- def void(identification, options = {})
- post = {}
-
- add_reference(post, identification)
-
- commit(:cancel, post)
- end
-
- def refund(money, identification, options = {})
- post = {}
+ self.abstract_class = true
- add_amount_without_currency(post, money)
- add_reference(post, identification)
+ def self.new(options = {})
+ options.fetch(:login) { raise ArgumentError.new('Missing required parameter: login') }
- commit(:refund, post)
- end
-
- def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, identification, options)
- end
-
- def store(creditcard, options = {})
- post = {}
-
- add_creditcard(post, creditcard, options)
- add_invoice(post, options)
- add_description(post, options)
- add_fraud_parameters(post, options)
- add_testmode(post)
-
- commit(:subscribe, post)
- end
-
- private
-
- def add_amount(post, money, options = {})
- post[:amount] = amount(money)
- post[:currency] = options[:currency] || currency(money)
- end
-
- def add_amount_without_currency(post, money, options = {})
- post[:amount] = amount(money)
- end
-
- def add_invoice(post, options)
- post[:ordernumber] = format_order_number(options[:order_id])
- end
-
- def add_creditcard(post, credit_card, options)
- post[:cardnumber] = credit_card.number
- post[:cvd] = credit_card.verification_value
- post[:expirationdate] = expdate(credit_card)
- post[:cardtypelock] = options[:cardtypelock] unless options[:cardtypelock].blank?
- end
-
- def add_reference(post, identification)
- post[:transaction] = identification
- end
-
- def add_creditcard_or_reference(post, credit_card_or_reference, options)
- if credit_card_or_reference.is_a?(String)
- add_reference(post, credit_card_or_reference)
+ version = options[:login].to_i < 10000000 ? 10 : 7
+ if version <= 7
+ QuickpayV4to7Gateway.new(options)
else
- add_creditcard(post, credit_card_or_reference, options)
+ QuickpayV10Gateway.new(options)
end
end
- def add_autocapture(post, autocapture)
- post[:autocapture] = autocapture ? 1 : 0
- end
-
- def recurring_or_authorize(credit_card_or_reference)
- credit_card_or_reference.is_a?(String) ? :recurring : :authorize
- end
-
- def add_description(post, options)
- post[:description] = options[:description]
- end
-
- def add_testmode(post)
- return if post[:transaction].present?
- post[:testmode] = test? ? '1' : '0'
- end
-
- def add_fraud_parameters(post, options)
- if @protocol >= 4
- post[:fraud_remote_addr] = options[:fraud_remote_addr] if options[:fraud_remote_addr]
- post[:fraud_http_accept] = options[:fraud_http_accept] if options[:fraud_http_accept]
- post[:fraud_http_accept_language] = options[:fraud_http_accept_language] if options[:fraud_http_accept_language]
- post[:fraud_http_accept_encoding] = options[:fraud_http_accept_encoding] if options[:fraud_http_accept_encoding]
- post[:fraud_http_accept_charset] = options[:fraud_http_accept_charset] if options[:fraud_http_accept_charset]
- post[:fraud_http_referer] = options[:fraud_http_referer] if options[:fraud_http_referer]
- post[:fraud_http_user_agent] = options[:fraud_http_user_agent] if options[:fraud_http_user_agent]
- end
- end
-
- def commit(action, params)
- response = parse(ssl_post(self.live_url, post_data(action, params)))
-
- Response.new(successful?(response), message_from(response), response,
- :test => test?,
- :authorization => response[:transaction]
- )
- end
-
- def successful?(response)
- response[:qpstat] == APPROVED
- end
-
- def parse(data)
- response = {}
-
- doc = REXML::Document.new(data)
-
- doc.root.elements.each do |element|
- response[element.name.to_sym] = element.text
- end
-
- response
- end
-
- def message_from(response)
- response[:qpstatmsg].to_s
- end
-
- def post_data(action, params = {})
- params[:protocol] = @protocol
- params[:msgtype] = action.to_s
- params[:merchant] = @options[:login]
- params[:apikey] = @options[:apikey] if @options[:apikey]
- params[:md5check] = generate_check_hash(action, params)
-
- params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
- end
-
- def generate_check_hash(action, params)
- string = MD5_CHECK_FIELDS[@protocol][action].collect do |key|
- params[key.to_sym]
- end.join('')
-
- # Add the md5checkword
- string << @options[:password].to_s
-
- Digest::MD5.hexdigest(string)
- end
-
- def expdate(credit_card)
- year = format(credit_card.year, :two_digits)
- month = format(credit_card.month, :two_digits)
-
- "#{year}#{month}"
- end
-
- # Limited to 20 digits max
- def format_order_number(number)
- number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20]
- end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb
new file mode 100644
index 00000000000..930958e0cbb
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb
@@ -0,0 +1,184 @@
+module QuickpayCommon
+ MD5_CHECK_FIELDS = {
+ 3 => {
+ :authorize => %w(protocol msgtype merchant ordernumber amount
+ currency autocapture cardnumber expirationdate
+ cvd cardtypelock testmode),
+
+ :capture => %w(protocol msgtype merchant amount finalize transaction),
+
+ :cancel => %w(protocol msgtype merchant transaction),
+
+ :refund => %w(protocol msgtype merchant amount transaction),
+
+ :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
+ expirationdate cvd cardtypelock description testmode),
+
+ :recurring => %w(protocol msgtype merchant ordernumber amount
+ currency autocapture transaction),
+
+ :status => %w(protocol msgtype merchant transaction),
+
+ :chstatus => %w(protocol msgtype merchant)
+ },
+
+ 4 => {
+ :authorize => %w(protocol msgtype merchant ordernumber amount
+ currency autocapture cardnumber expirationdate cvd
+ cardtypelock testmode fraud_remote_addr
+ fraud_http_accept fraud_http_accept_language
+ fraud_http_accept_encoding fraud_http_accept_charset
+ fraud_http_referer fraud_http_user_agent apikey),
+
+ :capture => %w(protocol msgtype merchant amount finalize transaction apikey),
+
+ :cancel => %w(protocol msgtype merchant transaction apikey),
+
+ :refund => %w(protocol msgtype merchant amount transaction apikey),
+
+ :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
+ expirationdate cvd cardtypelock description testmode
+ fraud_remote_addr fraud_http_accept fraud_http_accept_language
+ fraud_http_accept_encoding fraud_http_accept_charset
+ fraud_http_referer fraud_http_user_agent apikey),
+
+ :recurring => %w(protocol msgtype merchant ordernumber amount currency
+ autocapture transaction apikey),
+
+ :status => %w(protocol msgtype merchant transaction apikey),
+
+ :chstatus => %w(protocol msgtype merchant apikey)
+ },
+
+ 5 => {
+ :authorize => %w(protocol msgtype merchant ordernumber amount
+ currency autocapture cardnumber expirationdate cvd
+ cardtypelock testmode fraud_remote_addr
+ fraud_http_accept fraud_http_accept_language
+ fraud_http_accept_encoding fraud_http_accept_charset
+ fraud_http_referer fraud_http_user_agent apikey),
+
+ :capture => %w(protocol msgtype merchant amount finalize transaction apikey),
+
+ :cancel => %w(protocol msgtype merchant transaction apikey),
+
+ :refund => %w(protocol msgtype merchant amount transaction apikey),
+
+ :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
+ expirationdate cvd cardtypelock description testmode
+ fraud_remote_addr fraud_http_accept fraud_http_accept_language
+ fraud_http_accept_encoding fraud_http_accept_charset
+ fraud_http_referer fraud_http_user_agent apikey),
+
+ :recurring => %w(protocol msgtype merchant ordernumber amount currency
+ autocapture transaction apikey),
+
+ :status => %w(protocol msgtype merchant transaction apikey),
+
+ :chstatus => %w(protocol msgtype merchant apikey)
+ },
+
+ 6 => {
+ :authorize => %w(protocol msgtype merchant ordernumber amount
+ currency autocapture cardnumber expirationdate cvd
+ cardtypelock testmode fraud_remote_addr
+ fraud_http_accept fraud_http_accept_language
+ fraud_http_accept_encoding fraud_http_accept_charset
+ fraud_http_referer fraud_http_user_agent apikey),
+
+ :capture => %w(protocol msgtype merchant amount finalize transaction
+ apikey),
+
+ :cancel => %w(protocol msgtype merchant transaction apikey),
+
+ :refund => %w(protocol msgtype merchant amount transaction apikey),
+
+ :subscribe => %w(protocol msgtype merchant ordernumber cardnumber
+ expirationdate cvd cardtypelock description testmode
+ fraud_remote_addr fraud_http_accept fraud_http_accept_language
+ fraud_http_accept_encoding fraud_http_accept_charset
+ fraud_http_referer fraud_http_user_agent apikey),
+
+ :recurring => %w(protocol msgtype merchant ordernumber amount currency
+ autocapture transaction apikey),
+
+ :status => %w(protocol msgtype merchant transaction apikey),
+
+ :chstatus => %w(protocol msgtype merchant apikey)
+ },
+
+ 7 => {
+ :authorize => %w(protocol msgtype merchant ordernumber amount
+ currency autocapture cardnumber expirationdate cvd
+ acquirers cardtypelock testmode fraud_remote_addr
+ fraud_http_accept fraud_http_accept_language
+ fraud_http_accept_encoding fraud_http_accept_charset
+ fraud_http_referer fraud_http_user_agent apikey),
+
+ :capture => %w(protocol msgtype merchant amount finalize transaction
+ apikey),
+
+ :cancel => %w(protocol msgtype merchant transaction apikey),
+
+ :refund => %w(protocol msgtype merchant amount transaction apikey),
+
+ :subscribe => %w(protocol msgtype merchant ordernumber amount currency
+ cardnumber expirationdate cvd acquirers cardtypelock
+ description testmode fraud_remote_addr fraud_http_accept
+ fraud_http_accept_language fraud_http_accept_encoding
+ fraud_http_accept_charset fraud_http_referer
+ fraud_http_user_agent apikey),
+
+ :recurring => %w(protocol msgtype merchant ordernumber amount currency
+ autocapture transaction apikey),
+
+ :status => %w(protocol msgtype merchant transaction apikey),
+
+ :chstatus => %w(protocol msgtype merchant apikey)
+ },
+
+ 10 => {
+ :authorize => %w(mobile_number acquirer autofee customer_id extras
+ zero_auth customer_ip),
+ :capture => %w( extras ),
+ :cancel => %w( extras ),
+ :refund => %w( extras ),
+ :subscribe => %w( variables branding_id),
+ :authorize_subscription => %w( mobile_number acquirer customer_ip),
+ :recurring => %w(auto_capture autofee zero_auth)
+ }
+ }
+
+ RESPONSE_CODES = {
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 400 => 'Bad Request',
+ 401 => 'UnAuthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 409 => 'Conflict',
+ 500 => 'Internal Server Error'
+ }
+
+ def self.included(base)
+ base.default_currency = 'DKK'
+ base.money_format = :cents
+
+ base.supported_cardtypes = [:dankort, :forbrugsforeningen, :visa, :master,
+ :american_express, :diners_club, :jcb, :maestro]
+ base.supported_countries = ['DE', 'DK', 'ES', 'FI', 'FR', 'FO', 'GB', 'IS', 'NO', 'SE']
+ base.homepage_url = 'http://quickpay.net/'
+ base.display_name = 'QuickPay'
+ end
+
+ def expdate(credit_card)
+ year = format(credit_card.year, :two_digits)
+ month = format(credit_card.month, :two_digits)
+
+ "#{year}#{month}"
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
new file mode 100644
index 00000000000..565890a150e
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb
@@ -0,0 +1,301 @@
+require 'json'
+require 'active_merchant/billing/gateways/quickpay/quickpay_common'
+
+module ActiveMerchant
+ module Billing
+ class QuickpayV10Gateway < Gateway
+ include QuickpayCommon
+ API_VERSION = 10
+
+ self.live_url = self.test_url = 'https://api.quickpay.net'
+
+ def initialize(options = {})
+ requires!(options, :api_key)
+ super
+ end
+
+ def purchase(money, credit_card_or_reference, options = {})
+ MultiResponse.run do |r|
+ if credit_card_or_reference.is_a?(String)
+ r.process { create_token(credit_card_or_reference, options) }
+ credit_card_or_reference = r.authorization
+ end
+ r.process { create_payment(money, options) }
+ r.process {
+ post = authorization_params(money, credit_card_or_reference, options)
+ add_autocapture(post, false)
+ commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/authorize"), post)
+ }
+ r.process {
+ post = capture_params(money, credit_card_or_reference, options)
+ commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/capture"), post)
+ }
+ end
+ end
+
+ def authorize(money, credit_card_or_reference, options = {})
+ MultiResponse.run do |r|
+ if credit_card_or_reference.is_a?(String)
+ r.process { create_token(credit_card_or_reference, options) }
+ credit_card_or_reference = r.authorization
+ end
+ r.process { create_payment(money, options) }
+ r.process {
+ post = authorization_params(money, credit_card_or_reference, options)
+ commit(synchronized_path("/payments/#{r.responses.last.params["id"]}/authorize"), post)
+ }
+ end
+ end
+
+ def void(identification, _options = {})
+ commit(synchronized_path "/payments/#{identification}/cancel")
+ end
+
+ def credit(money, identification, options = {})
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
+ refund(money, identification, options)
+ end
+
+ def capture(money, identification, options = {})
+ post = capture_params(money, identification, options)
+ commit(synchronized_path("/payments/#{identification}/capture"), post)
+ end
+
+ def refund(money, identification, options = {})
+ post = {}
+ add_amount(post, money, options)
+ add_additional_params(:refund, post, options)
+ commit(synchronized_path("/payments/#{identification}/refund"), 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(credit_card, options = {})
+ MultiResponse.run do |r|
+ r.process { create_store(options) }
+ r.process { authorize_store(r.authorization, credit_card, options) }
+ end
+ end
+
+ def unstore(identification)
+ commit(synchronized_path "/cards/#{identification}/cancel")
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(("card\\?":{\\?"number\\?":\\?")\d+), '\1[FILTERED]').
+ gsub(%r(("cvd\\?":\\?")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def authorization_params(money, credit_card_or_reference, options = {})
+ post = {}
+
+ add_amount(post, money, options)
+ add_credit_card_or_reference(post, credit_card_or_reference, options)
+ add_additional_params(:authorize, post, options)
+
+ post
+ end
+
+ def capture_params(money, credit_card, options = {})
+ post = {}
+
+ add_amount(post, money, options)
+ add_additional_params(:capture, post, options)
+
+ post
+ end
+
+ def create_store(options = {})
+ post = {}
+ commit('/cards', post)
+ end
+
+ def authorize_store(identification, credit_card, options = {})
+ post = {}
+
+ add_credit_card_or_reference(post, credit_card, options)
+ commit(synchronized_path("/cards/#{identification}/authorize"), post)
+ end
+
+ def create_token(identification, options)
+ post = {}
+ commit(synchronized_path("/cards/#{identification}/tokens"), post)
+ end
+
+ def create_payment(money, options = {})
+ post = {}
+ add_currency(post, money, options)
+ add_invoice(post, options)
+ commit('/payments', post)
+ end
+
+ def commit(action, params = {})
+ success = false
+ begin
+ response = parse(ssl_post(self.live_url + action, params.to_json, headers))
+ success = successful?(response)
+ rescue ResponseError => e
+ response = response_error(e.response.body)
+ rescue JSON::ParserError
+ response = json_error(response)
+ end
+
+ Response.new(success, message_from(success, response), response,
+ :test => test?,
+ :authorization => authorization_from(response)
+ )
+ end
+
+ def authorization_from(response)
+ if response['token']
+ response['token'].to_s
+ else
+ response['id'].to_s
+ end
+ end
+
+ def add_currency(post, money, options)
+ post[:currency] = options[:currency] || currency(money)
+ end
+
+ def add_amount(post, money, options)
+ post[:amount] = options[:amount] || amount(money)
+ end
+
+ def add_autocapture(post, value)
+ post[:auto_capture] = value
+ end
+
+ def add_order_id(post, options)
+ requires!(options, :order_id)
+ post[:order_id] = format_order_id(options[:order_id])
+ end
+
+ def add_invoice(post, options)
+ add_order_id(post, options)
+
+ if options[:billing_address]
+ post[:invoice_address] = map_address(options[:billing_address])
+ end
+
+ if options[:shipping_address]
+ post[:shipping_address] = map_address(options[:shipping_address])
+ end
+
+ [:metadata, :branding_id, :variables].each do |field|
+ post[field] = options[field] if options[field]
+ end
+ end
+
+ def add_additional_params(action, post, options = {})
+ MD5_CHECK_FIELDS[API_VERSION][action].each do |key|
+ key = key.to_sym
+ post[key] = options[key] if options[key]
+ end
+ end
+
+ def add_credit_card_or_reference(post, credit_card_or_reference, options = {})
+ post[:card] ||= {}
+ if credit_card_or_reference.is_a?(String)
+ post[:card][:token] = credit_card_or_reference
+ else
+ post[:card][:number] = credit_card_or_reference.number
+ post[:card][:cvd] = credit_card_or_reference.verification_value
+ post[:card][:expiration] = expdate(credit_card_or_reference)
+ post[:card][:issued_to] = credit_card_or_reference.name
+ end
+
+ if options[:three_d_secure]
+ post[:card][:cavv]= options.dig(:three_d_secure, :cavv)
+ post[:card][:eci] = options.dig(:three_d_secure, :eci)
+ post[:card][:xav] = options.dig(:three_d_secure, :xid)
+ end
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def successful?(response)
+ has_error = response['errors']
+ invalid_code = invalid_operation_code?(response)
+
+ !(has_error || invalid_code)
+ end
+
+ def message_from(success, response)
+ success ? 'OK' : (response['message'] || invalid_operation_message(response) || 'Unknown error - please contact QuickPay')
+ end
+
+ def invalid_operation_code?(response)
+ if response['operations']
+ operation = response['operations'].last
+ operation && operation['qp_status_code'] != '20000'
+ end
+ end
+
+ def invalid_operation_message(response)
+ response['operations'] && response['operations'].last['qp_status_msg']
+ end
+
+ def map_address(address)
+ return {} if address.nil?
+ requires!(address, :name, :address1, :city, :zip, :country)
+ country = Country.find(address[:country])
+ mapped = {
+ :name => address[:name],
+ :street => address[:address1],
+ :city => address[:city],
+ :region => address[:address2],
+ :zip_code => address[:zip],
+ :country_code => country.code(:alpha3).value
+ }
+ mapped
+ end
+
+ def format_order_id(order_id)
+ truncate(order_id.to_s.gsub(/#/, ''), 20)
+ end
+
+ def headers
+ auth = Base64.strict_encode64(":#{@options[:api_key]}")
+ {
+ 'Authorization' => 'Basic ' + auth,
+ 'User-Agent' => "Quickpay-v#{API_VERSION} ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ 'Accept' => 'application/json',
+ 'Accept-Version' => "v#{API_VERSION}",
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def json_error(raw_response)
+ msg = 'Invalid response received from the Quickpay API.'
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
+ { 'message' => msg }
+ end
+
+ def synchronized_path(path)
+ "#{path}?synchronized"
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
new file mode 100644
index 00000000000..810d5cdefaa
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb
@@ -0,0 +1,226 @@
+require 'rexml/document'
+require 'digest/md5'
+require 'active_merchant/billing/gateways/quickpay/quickpay_common'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class QuickpayV4to7Gateway < Gateway
+ include QuickpayCommon
+ self.live_url = self.test_url = 'https://secure.quickpay.dk/api'
+ APPROVED = '000'
+
+ # The login is the QuickpayId
+ # The password is the md5checkword from the Quickpay manager
+ # To use the API-key from the Quickpay manager, specify :api-key
+ # Using the API-key, requires that you use version 4+. Specify :version => 4/5/6/7 in options.
+ def initialize(options = {})
+ requires!(options, :login, :password)
+ @protocol = options.delete(:version) || 7 # default to protocol version 7
+ super
+ end
+
+ def authorize(money, credit_card_or_reference, options = {})
+ post = {}
+
+ action = recurring_or_authorize(credit_card_or_reference)
+
+ add_amount(post, money, options)
+ add_invoice(post, options)
+ add_creditcard_or_reference(post, credit_card_or_reference, options)
+ add_autocapture(post, false)
+ add_fraud_parameters(post, options) if action.eql?(:authorize)
+ add_testmode(post)
+
+ commit(action, post)
+ end
+
+ def purchase(money, credit_card_or_reference, options = {})
+ post = {}
+
+ action = recurring_or_authorize(credit_card_or_reference)
+
+ add_amount(post, money, options)
+ add_creditcard_or_reference(post, credit_card_or_reference, options)
+ add_invoice(post, options)
+ add_fraud_parameters(post, options) if action.eql?(:authorize)
+ add_autocapture(post, true)
+
+ commit(action, post)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+
+ add_finalize(post, options)
+ add_reference(post, authorization)
+ add_amount_without_currency(post, money)
+ commit(:capture, post)
+ end
+
+ def void(identification, options = {})
+ post = {}
+
+ add_reference(post, identification)
+
+ commit(:cancel, post)
+ end
+
+ def refund(money, identification, options = {})
+ post = {}
+
+ add_amount_without_currency(post, money)
+ add_reference(post, identification)
+
+ commit(:refund, post)
+ end
+
+ def credit(money, identification, options = {})
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
+ refund(money, identification, options)
+ end
+
+ def store(creditcard, options = {})
+ post = {}
+
+ add_creditcard(post, creditcard, options)
+ add_amount(post, 0, options) if @protocol >= 7
+ add_invoice(post, options)
+ add_description(post, options)
+ add_fraud_parameters(post, options)
+ add_testmode(post)
+
+ commit(:subscribe, post)
+ end
+
+ private
+
+ def add_amount(post, money, options = {})
+ post[:amount] = amount(money)
+ post[:currency] = options[:currency] || currency(money)
+ end
+
+ def add_amount_without_currency(post, money, options = {})
+ post[:amount] = amount(money)
+ end
+
+ def add_invoice(post, options)
+ post[:ordernumber] = format_order_number(options[:order_id])
+ end
+
+ def add_creditcard(post, credit_card, options)
+ post[:cardnumber] = credit_card.number
+ post[:cvd] = credit_card.verification_value
+ post[:expirationdate] = expdate(credit_card)
+ post[:cardtypelock] = options[:cardtypelock] unless options[:cardtypelock].blank?
+ post[:acquirers] = options[:acquirers] unless options[:acquirers].blank?
+ end
+
+ def add_reference(post, identification)
+ post[:transaction] = identification
+ end
+
+ def add_creditcard_or_reference(post, credit_card_or_reference, options)
+ if credit_card_or_reference.is_a?(String)
+ add_reference(post, credit_card_or_reference)
+ else
+ add_creditcard(post, credit_card_or_reference, options)
+ end
+ end
+
+ def add_autocapture(post, autocapture)
+ post[:autocapture] = autocapture ? 1 : 0
+ end
+
+ def recurring_or_authorize(credit_card_or_reference)
+ credit_card_or_reference.is_a?(String) ? :recurring : :authorize
+ end
+
+ def add_description(post, options)
+ post[:description] = options[:description] || 'Description'
+ end
+
+ def add_testmode(post)
+ return if post[:transaction].present?
+ post[:testmode] = test? ? '1' : '0'
+ end
+
+ def add_fraud_parameters(post, options)
+ if @protocol >= 4
+ post[:fraud_remote_addr] = options[:ip] if options[:ip]
+ post[:fraud_http_accept] = options[:fraud_http_accept] if options[:fraud_http_accept]
+ post[:fraud_http_accept_language] = options[:fraud_http_accept_language] if options[:fraud_http_accept_language]
+ post[:fraud_http_accept_encoding] = options[:fraud_http_accept_encoding] if options[:fraud_http_accept_encoding]
+ post[:fraud_http_accept_charset] = options[:fraud_http_accept_charset] if options[:fraud_http_accept_charset]
+ post[:fraud_http_referer] = options[:fraud_http_referer] if options[:fraud_http_referer]
+ post[:fraud_http_user_agent] = options[:fraud_http_user_agent] if options[:fraud_http_user_agent]
+ end
+ end
+
+ def add_finalize(post, options)
+ post[:finalize] = options[:finalize] ? '1' : '0'
+ end
+
+ def commit(action, params)
+ response = parse(ssl_post(self.live_url, post_data(action, params)))
+
+ Response.new(successful?(response), message_from(response), response,
+ :test => test?,
+ :authorization => response[:transaction]
+ )
+ end
+
+ def successful?(response)
+ response[:qpstat] == APPROVED
+ end
+
+ def parse(data)
+ response = {}
+
+ doc = REXML::Document.new(data)
+
+ doc.root.elements.each do |element|
+ response[element.name.to_sym] = element.text
+ end
+
+ response
+ end
+
+ def message_from(response)
+ response[:qpstatmsg].to_s
+ end
+
+ def post_data(action, params = {})
+ params[:protocol] = @protocol
+ params[:msgtype] = action.to_s
+ params[:merchant] = @options[:login]
+ params[:apikey] = @options[:apikey] if @options[:apikey]
+ params[:md5check] = generate_check_hash(action, params)
+
+ params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ def generate_check_hash(action, params)
+ string = MD5_CHECK_FIELDS[@protocol][action].collect do |key|
+ params[key.to_sym]
+ end.join('')
+
+ # Add the md5checkword
+ string << @options[:password].to_s
+
+ Digest::MD5.hexdigest(string)
+ end
+
+ def expdate(credit_card)
+ year = format(credit_card.year, :two_digits)
+ month = format(credit_card.month, :two_digits)
+
+ "#{year}#{month}"
+ end
+
+ # Limited to 20 digits max
+ def format_order_number(number)
+ number.to_s.gsub(/[^\w]/, '').rjust(4, '0')[0...20]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/qvalent.rb b/lib/active_merchant/billing/gateways/qvalent.rb
new file mode 100644
index 00000000000..420b8cccc96
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/qvalent.rb
@@ -0,0 +1,289 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class QvalentGateway < Gateway
+ self.display_name = 'Qvalent'
+ self.homepage_url = 'https://www.qvalent.com/'
+
+ self.test_url = 'https://ccapi.client.support.qvalent.com/post/CreditCardAPIReceiver'
+ self.live_url = 'https://ccapi.client.qvalent.com/post/CreditCardAPIReceiver'
+
+ self.supported_countries = ['AU']
+ self.default_currency = 'AUD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners]
+
+ CVV_CODE_MAPPING = {
+ 'S' => 'D'
+ }
+
+ def initialize(options={})
+ requires!(options, :username, :password, :merchant, :pem, :pem_password)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_order_number(post, options)
+ add_payment_method(post, payment_method)
+ add_verification_value(post, payment_method)
+ add_stored_credential_data(post, payment_method, options)
+ add_customer_data(post, options)
+ add_soft_descriptors(post, options)
+
+ commit('capture', post)
+ end
+
+ def authorize(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_order_number(post, options)
+ add_payment_method(post, payment_method)
+ add_verification_value(post, payment_method)
+ add_stored_credential_data(post, payment_method, options)
+ add_customer_data(post, options)
+ add_soft_descriptors(post, options)
+
+ commit('preauth', post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization, options)
+ add_customer_data(post, options)
+ add_soft_descriptors(post, options)
+
+ commit('captureWithoutAuth', post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization, options)
+ add_customer_data(post, options)
+ add_soft_descriptors(post, options)
+ post['order.ECI'] = options[:eci] || 'SSL'
+
+ commit('refund', post)
+ end
+
+ # Credit requires the merchant account to be enabled for "Adhoc Refunds"
+ def credit(amount, payment_method, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_order_number(post, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+ add_soft_descriptors(post, options)
+
+ commit('refund', post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, authorization, options)
+ add_customer_data(post, options)
+ add_soft_descriptors(post, options)
+
+ commit('reversal', post)
+ end
+
+ def store(payment_method, options = {})
+ post = {}
+ add_payment_method(post, payment_method)
+ add_card_reference(post)
+
+ commit('registerAccount', post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?customer.password=)[^&]*), '\1[FILTERED]').
+ gsub(%r((&?card.PAN=)[^&]*), '\1[FILTERED]').
+ gsub(%r((&?card.CVN=)[^&]*), '\1[FILTERED]')
+ end
+
+ private
+
+ CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") }
+ CURRENCY_CODES['AUD'] = 'AUD'
+ CURRENCY_CODES['INR'] = 'INR'
+
+ def add_soft_descriptors(post, options)
+ post['customer.merchantName'] = options[:customer_merchant_name] if options[:customer_merchant_name]
+ post['customer.merchantStreetAddress'] = options[:customer_merchant_street_address] if options[:customer_merchant_street_address]
+ post['customer.merchantLocation'] = options[:customer_merchant_location] if options[:customer_merchant_location]
+ post['customer.merchantState'] = options[:customer_merchant_state] if options[:customer_merchant_state]
+ post['customer.merchantCountry'] = options[:customer_merchant_country] if options[:customer_merchant_country]
+ post['customer.merchantPostCode'] = options[:customer_merchant_post_code] if options[:customer_merchant_post_code]
+ post['customer.subMerchantId'] = options[:customer_sub_merchant_id] if options[:customer_sub_merchant_id]
+ end
+
+ def add_invoice(post, money, options)
+ post['order.amount'] = amount(money)
+ post['card.currency'] = CURRENCY_CODES[options[:currency] || currency(money)]
+ end
+
+ def add_payment_method(post, payment_method)
+ post['card.cardHolderName'] = payment_method.name
+ post['card.PAN'] = payment_method.number
+ post['card.expiryYear'] = format(payment_method.year, :two_digits)
+ post['card.expiryMonth'] = format(payment_method.month, :two_digits)
+ end
+
+ def add_stored_credential_data(post, payment_method, options)
+ post['order.ECI'] = options[:eci] || eci(options)
+ if (stored_credential = options[:stored_credential]) && %w(visa master).include?(payment_method.brand)
+ post['card.posEntryMode'] = stored_credential[:initial_transaction] ? 'MANUAL' : 'STORED_CREDENTIAL'
+ stored_credential_usage(post, payment_method, options) unless stored_credential[:initiator] && stored_credential[:initiator] == 'cardholder'
+ post['order.authTraceId'] = stored_credential[:network_transaction_id] if stored_credential[:network_transaction_id]
+ end
+ end
+
+ def stored_credential_usage(post, payment_method, options)
+ return unless payment_method.brand == 'visa'
+ stored_credential = options[:stored_credential]
+ if stored_credential[:initial_transaction]
+ post['card.storedCredentialUsage'] = 'INITIAL_STORAGE'
+ elsif stored_credential[:reason_type] == ('recurring' || 'installment')
+ post['card.storedCredentialUsage'] = 'RECURRING'
+ elsif stored_credential[:reason_type] == 'unscheduled'
+ post['card.storedCredentialUsage'] = 'UNSCHEDULED'
+ end
+ end
+
+ def eci(options)
+ if options.dig(:stored_credential, :initial_transaction)
+ 'SSL'
+ elsif options.dig(:stored_credential, :initiator) && options[:stored_credential][:initiator] == 'cardholder'
+ 'MTO'
+ elsif options.dig(:stored_credential, :reason_type)
+ case options[:stored_credential][:reason_type]
+ when 'recurring'
+ 'REC'
+ when 'installment'
+ 'INS'
+ when 'unscheduled'
+ 'MTO'
+ end
+ else
+ 'SSL'
+ end
+ end
+
+ def add_verification_value(post, payment_method)
+ post['card.CVN'] = payment_method.verification_value
+ end
+
+ def add_card_reference(post)
+ post['customer.customerReferenceNumber'] = options[:order_id]
+ end
+
+ def add_reference(post, authorization, options)
+ post['customer.originalOrderNumber'] = authorization
+ add_order_number(post, options)
+ end
+
+ def add_order_number(post, options)
+ post['customer.orderNumber'] = options[:order_id] || SecureRandom.uuid
+ end
+
+ def add_customer_data(post, options)
+ post['order.ipAddress'] = options[:ip] || '127.0.0.1'
+ post['order.xid'] = options[:xid] if options[:xid]
+ post['order.cavv'] = options[:cavv] if options[:cavv]
+ end
+
+ def commit(action, post)
+ post['customer.username'] = @options[:username]
+ post['customer.password'] = @options[:password]
+ post['customer.merchant'] = @options[:merchant]
+ post['order.type'] = action
+
+ data = build_request(post)
+ raw = parse(ssl_post(url(action), data, headers))
+
+ succeeded = success_from(raw['response.responseCode'])
+ Response.new(
+ succeeded,
+ message_from(succeeded, raw),
+ raw,
+ authorization: raw['response.orderNumber'] || raw['response.customerReferenceNumber'],
+ cvv_result: cvv_result(succeeded, raw),
+ error_code: error_code_from(succeeded, raw),
+ test: test?
+ )
+ end
+
+ def cvv_result(succeeded, raw)
+ return unless succeeded
+ code = CVV_CODE_MAPPING[raw['response.cvnResponse']] || raw['response.cvnResponse']
+ CVVResult.new(code)
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'application/x-www-form-urlencoded'
+ }
+ end
+
+ def build_request(post)
+ post.to_query + '&message.end'
+ end
+
+ def url(action)
+ (test? ? test_url : live_url)
+ end
+
+ def parse(body)
+ result = {}
+ body.to_s.each_line do |pair|
+ result[$1] = $2 if pair.strip =~ /\A([^=]+)=(.+)\Z/im
+ end
+ result
+ end
+
+ def parse_element(response, node)
+ if node.has_elements?
+ node.elements.each { |element| parse_element(response, element) }
+ else
+ response[node.name.underscore.to_sym] = node.text
+ end
+ end
+
+ SUCCESS_CODES = %w(00 08 10 11 16 QS QZ)
+
+ def success_from(response)
+ SUCCESS_CODES.include?(response)
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response['response.text'] || 'Unable to read error message'
+ end
+ end
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '14' => STANDARD_ERROR_CODE[:invalid_number],
+ 'QQ' => STANDARD_ERROR_CODE[:invalid_cvc],
+ '33' => STANDARD_ERROR_CODE[:expired_card],
+ 'NT' => STANDARD_ERROR_CODE[:incorrect_address],
+ '12' => STANDARD_ERROR_CODE[:card_declined],
+ '06' => STANDARD_ERROR_CODE[:processing_error],
+ '01' => STANDARD_ERROR_CODE[:call_issuer],
+ '04' => STANDARD_ERROR_CODE[:pickup_card],
+ }
+
+ def error_code_from(succeeded, response)
+ succeeded ? nil : STANDARD_ERROR_CODE_MAPPING[response['response.responseCode']]
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/realex.rb b/lib/active_merchant/billing/gateways/realex.rb
index 1cbbe5b0481..ca402a5ca97 100644
--- a/lib/active_merchant/billing/gateways/realex.rb
+++ b/lib/active_merchant/billing/gateways/realex.rb
@@ -12,7 +12,7 @@ module Billing
# login - The unique id of the merchant
# password - The secret is used to digitally sign the request
# account - This is an optional third part of the authentication process
- # and is used if the merchant wishes do distuinguish cc traffic from the different sources
+ # and is used if the merchant wishes do distinguish cc traffic from the different sources
# by using a different account. This must be created in advance
#
# the Realex team decided to make the orderid unique per request,
@@ -26,25 +26,24 @@ class RealexGateway < Gateway
'visa' => 'VISA',
'american_express' => 'AMEX',
'diners_club' => 'DINERS',
- 'switch' => 'SWITCH',
- 'solo' => 'SWITCH',
- 'laser' => 'LASER'
+ 'maestro' => 'MC'
}
self.money_format = :cents
self.default_currency = 'EUR'
- self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :switch, :solo, :laser ]
- self.supported_countries = %w(IE GB FR BE NL LU IT)
+ self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club ]
+ self.supported_countries = %w(IE GB FR BE NL LU IT US CA ES)
self.homepage_url = 'http://www.realexpayments.com/'
self.display_name = 'Realex'
- SUCCESS, DECLINED = "Successful", "Declined"
- BANK_ERROR = REALEX_ERROR = "Gateway is in maintenance. Please try again later."
- ERROR = CLIENT_DEACTIVATED = "Gateway Error"
+ SUCCESS, DECLINED = 'Successful', 'Declined'
+ BANK_ERROR = REALEX_ERROR = 'Gateway is in maintenance. Please try again later.'
+ ERROR = CLIENT_DEACTIVATED = 'Gateway Error'
def initialize(options = {})
requires!(options, :login, :password)
- options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options.has_key?(:rebate_secret)
+ options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options[:rebate_secret].present?
+ options[:credit_hash] = Digest::SHA1.hexdigest(options[:refund_secret]) if options[:refund_secret].present?
super
end
@@ -63,7 +62,7 @@ def authorize(money, creditcard, options = {})
end
def capture(money, authorization, options = {})
- request = build_capture_request(authorization, options)
+ request = build_capture_request(money, authorization, options)
commit(request)
end
@@ -72,9 +71,9 @@ def refund(money, authorization, options = {})
commit(request)
end
- def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, authorization, options)
+ def credit(money, creditcard, options = {})
+ request = build_credit_request(money, creditcard, options)
+ commit(request)
end
def void(authorization, options = {})
@@ -82,18 +81,36 @@ def void(authorization, options = {})
commit(request)
end
+ def verify(credit_card, options = {})
+ requires!(options, :order_id)
+
+ request = build_verify_request(credit_card, options)
+ commit(request)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2')
+ end
+
private
+
def commit(request)
response = parse(ssl_post(self.live_url, request))
- Response.new(response[:result] == "00", message_from(response), response,
- :test => response[:message] =~ /\[ test system \]/,
+ Response.new(
+ (response[:result] == '00'),
+ message_from(response),
+ response,
+ :test => (response[:message] =~ %r{\[ test system \]}),
:authorization => authorization_from(response),
- :cvv_result => response[:cvnresult],
- :avs_result => {
- :street_match => response[:avspostcoderesponse],
- :postal_match => response[:avspostcoderesponse]
- }
+ avs_result: AVSResult.new(code: response[:avspostcoderesponse]),
+ cvv_result: CVVResult.new(response[:cvnresult])
)
end
@@ -102,7 +119,7 @@ def parse(xml)
doc = Nokogiri::XML(xml)
doc.xpath('//response/*').each do |node|
- if (node.elements.size == 0)
+ if node.elements.size == 0
response[node.name.downcase.to_sym] = normalize(node.text)
else
node.elements.each do |childnode|
@@ -129,20 +146,26 @@ def build_purchase_or_authorization_request(action, money, credit_card, options)
add_card(xml, credit_card)
xml.tag! 'autosettle', 'flag' => auto_settle_flag(action)
add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number)
+ if credit_card.is_a?(NetworkTokenizationCreditCard)
+ add_network_tokenization_card(xml, credit_card)
+ else
+ add_three_d_secure(xml, options)
+ end
add_comments(xml, options)
add_address_and_customer_info(xml, options)
end
xml.target!
end
- def build_capture_request(authorization, options)
+ def build_capture_request(money, authorization, options)
timestamp = new_timestamp
xml = Builder::XmlMarkup.new :indent => 2
xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'settle' do
add_merchant_details(xml, options)
+ add_amount(xml, money, options)
add_transaction_identifiers(xml, authorization, options)
add_comments(xml, options)
- add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), nil, nil, nil)
+ add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), nil)
end
xml.target!
end
@@ -162,6 +185,22 @@ def build_refund_request(money, authorization, options)
xml.target!
end
+ def build_credit_request(money, credit_card, options)
+ timestamp = new_timestamp
+ xml = Builder::XmlMarkup.new :indent => 2
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'credit' do
+ add_merchant_details(xml, options)
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
+ add_amount(xml, money, options)
+ add_card(xml, credit_card)
+ xml.tag! 'refundhash', @options[:credit_hash] if @options[:credit_hash]
+ xml.tag! 'autosettle', 'flag' => 1
+ add_comments(xml, options)
+ add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), amount(money), (options[:currency] || currency(money)), credit_card.number)
+ end
+ xml.target!
+ end
+
def build_void_request(authorization, options)
timestamp = new_timestamp
xml = Builder::XmlMarkup.new :indent => 2
@@ -174,6 +213,20 @@ def build_void_request(authorization, options)
xml.target!
end
+ # Verify initiates an OTB (Open To Buy) request
+ def build_verify_request(credit_card, options)
+ timestamp = new_timestamp
+ xml = Builder::XmlMarkup.new :indent => 2
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'otb' do
+ add_merchant_details(xml, options)
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
+ add_card(xml, credit_card)
+ add_comments(xml, options)
+ add_signed_digest(xml, timestamp, @options[:login], sanitize_order_id(options[:order_id]), credit_card.number)
+ end
+ xml.target!
+ end
+
def add_address_and_customer_info(xml, options)
billing_address = options[:billing_address] || options[:address]
shipping_address = options[:shipping_address]
@@ -187,14 +240,14 @@ def add_address_and_customer_info(xml, options)
if billing_address
xml.tag! 'address', 'type' => 'billing' do
- xml.tag! 'code', format_shipping_zip_code(billing_address[:zip])
+ xml.tag! 'code', format_address_code(billing_address)
xml.tag! 'country', billing_address[:country]
end
end
if shipping_address
xml.tag! 'address', 'type' => 'shipping' do
- xml.tag! 'code', format_shipping_zip_code(shipping_address[:zip])
+ xml.tag! 'code', format_address_code(shipping_address)
xml.tag! 'country', shipping_address[:country]
end
end
@@ -232,7 +285,7 @@ def add_card(xml, credit_card)
xml.tag! 'expdate', expiry_date(credit_card)
xml.tag! 'chname', credit_card.name
xml.tag! 'type', CARD_MAPPING[card_brand(credit_card).to_s]
- xml.tag! 'issueno', credit_card.issue_number
+ xml.tag! 'issueno', ''
xml.tag! 'cvn' do
xml.tag! 'number', credit_card.verification_value
xml.tag! 'presind', (options['presind'] || (credit_card.verification_value? ? 1 : nil))
@@ -240,8 +293,37 @@ def add_card(xml, credit_card)
end
end
- def format_shipping_zip_code(zip)
- zip.to_s.gsub(/\W/, '')
+ def add_network_tokenization_card(xml, payment)
+ xml.tag! 'mpi' do
+ xml.tag! 'cavv', payment.payment_cryptogram
+ xml.tag! 'eci', payment.eci
+ end
+ xml.tag! 'supplementarydata' do
+ xml.tag! 'item', 'type' => 'mobile' do
+ xml.tag! 'field01', payment.source.to_s.gsub('_', '-')
+ end
+ end
+ end
+
+ def add_three_d_secure(xml, options)
+ return unless three_d_secure = options[:three_d_secure]
+ version = three_d_secure.fetch(:version, '')
+ xml.tag! 'mpi' do
+ if version =~ /^2/
+ xml.tag! 'authentication_value', three_d_secure[:cavv]
+ xml.tag! 'ds_trans_id', three_d_secure[:ds_transaction_id]
+ else
+ xml.tag! 'cavv', three_d_secure[:cavv]
+ xml.tag! 'xid', three_d_secure[:xid]
+ end
+ xml.tag! 'eci', three_d_secure[:eci]
+ xml.tag! 'message_version', version
+ end
+ end
+
+ def format_address_code(address)
+ code = [address[:zip].to_s, address[:address1].to_s + address[:address2].to_s]
+ code.collect { |e| e.gsub(/\D/, '') }.reject(&:empty?).join('|')
end
def new_timestamp
@@ -249,8 +331,8 @@ def new_timestamp
end
def add_signed_digest(xml, *values)
- string = Digest::SHA1.hexdigest(values.join("."))
- xml.tag! 'sha1hash', Digest::SHA1.hexdigest([string, @options[:password]].join("."))
+ string = Digest::SHA1.hexdigest(values.join('.'))
+ xml.tag! 'sha1hash', Digest::SHA1.hexdigest([string, @options[:password]].join('.'))
end
def auto_settle_flag(action)
@@ -261,37 +343,26 @@ def expiry_date(credit_card)
"#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
end
- def normalize(field)
- case field
- when "true" then true
- when "false" then false
- when "" then nil
- when "null" then nil
- else field
- end
- end
-
def message_from(response)
- message = nil
case response[:result]
- when "00"
- message = SUCCESS
- when "101"
- message = response[:message]
- when "102", "103"
- message = DECLINED
+ when '00'
+ SUCCESS
+ when '101'
+ response[:message]
+ when '102', '103'
+ DECLINED
when /^2[0-9][0-9]/
- message = BANK_ERROR
+ BANK_ERROR
when /^3[0-9][0-9]/
- message = REALEX_ERROR
+ REALEX_ERROR
when /^5[0-9][0-9]/
- message = response[:message]
- when "600", "601", "603"
- message = ERROR
- when "666"
- message = CLIENT_DEACTIVATED
+ response[:message]
+ when '600', '601', '603'
+ ERROR
+ when '666'
+ CLIENT_DEACTIVATED
else
- message = DECLINED
+ DECLINED
end
end
diff --git a/lib/active_merchant/billing/gateways/redsys.rb b/lib/active_merchant/billing/gateways/redsys.rb
index 583e025393c..792b935d557 100644
--- a/lib/active_merchant/billing/gateways/redsys.rb
+++ b/lib/active_merchant/billing/gateways/redsys.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+
require 'nokogiri'
module ActiveMerchant #:nodoc:
@@ -9,30 +10,14 @@ module Billing #:nodoc:
# used by many banks in Spain and is particularly well supported by
# Catalunya Caixa's ecommerce department.
#
- # Standard ActiveMerchant methods are supported, with one notable exception:
- # :order_id must be provided and must conform to a very specific format.
- #
- # == Example use:
- #
- # gateway = ActiveMerchant::Billing::RedsysGateway.new(
- # :login => "091358382",
- # :secret_key => "qwertyasdf0123456789"
- # )
- #
- # # Run a purchase for 10 euros
- # response = gateway.purchase(1000, creditcard, :order_id => "123456")
- # puts reponse.success? # => true
- #
- # # Partially refund the purchase
- # response = gateway.refund(500, response.authorization)
+ # Redsys requires an order_id be provided with each transaction and it must
+ # follow a specific format. The rules are as follows:
#
- # Redsys requires an order_id be provided with each transaction of a
- # specific format. The rules are as follows:
- #
- # * Minimum length: 4
- # * Maximum length: 12
# * First 4 digits must be numerical
# * Remaining 8 digits may be alphanumeric
+ # * Max length: 12
+ #
+ # If an invalid order_id is provided, we do our best to clean it up.
#
# Much of the code for this library is based on the active_merchant_sermepa
# integration gateway which uses essentially the same API but with the
@@ -40,43 +25,66 @@ module Billing #:nodoc:
#
# Written by Samuel Lown for Cabify. For implementation questions, or
# test access details please get in touch: sam@cabify.com.
+ #
+ # *** SHA256 Authentication Update ***
+ #
+ # Redsys is dropping support for the SHA1 authentication method. This
+ # adapter has been updated to work with the new SHA256 authentication
+ # method, however in your initialization options hash you will need to
+ # specify the key/value :signature_algorithm => "sha256" to use the
+ # SHA256 method. Otherwise it will default to using the SHA1.
+ #
+ #
class RedsysGateway < Gateway
- self.live_url = "https://sis.sermepa.es/sis/operaciones"
- self.test_url = "https://sis-t.sermepa.es:25443/sis/operaciones"
+ self.live_url = 'https://sis.sermepa.es/sis/operaciones'
+ self.test_url = 'https://sis-t.redsys.es:25443/sis/operaciones'
- # Sensible region specific defaults.
self.supported_countries = ['ES']
self.default_currency = 'EUR'
self.money_format = :cents
- # Not all card types may be actived by the bank!
+ # Not all card types may be activated by the bank!
self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :diners_club]
-
- # Homepage URL of the gateway for reference
- self.homepage_url = "http://www.redsys.es/"
-
- # What to call this gateway
- self.display_name = "Redsys"
+ self.homepage_url = 'http://www.redsys.es/'
+ self.display_name = 'Redsys'
CURRENCY_CODES = {
- "ARS" => '032',
- "AUD" => '036',
- "BRL" => '986',
- "BOB" => '068',
- "CAD" => '124',
- "CHF" => '756',
- "CLP" => '152',
- "COP" => '170',
- "EUR" => '978',
- "GBP" => '826',
- "GTQ" => '320',
- "JPY" => '392',
- "MXN" => '484',
- "NZD" => '554',
- "PEN" => '604',
- "RUB" => '643',
- "USD" => '840',
- "UYU" => '858'
+ 'AED' => '784',
+ 'ARS' => '32',
+ 'AUD' => '36',
+ 'BRL' => '986',
+ 'BOB' => '68',
+ 'CAD' => '124',
+ 'CHF' => '756',
+ 'CLP' => '152',
+ 'CNY' => '156',
+ 'COP' => '170',
+ 'CRC' => '188',
+ 'CZK' => '203',
+ 'DKK' => '208',
+ 'DOP' => '214',
+ 'EUR' => '978',
+ 'GBP' => '826',
+ 'GTQ' => '320',
+ 'HUF' => '348',
+ 'IDR' => '360',
+ 'INR' => '356',
+ 'JPY' => '392',
+ 'KRW' => '410',
+ 'MYR' => '458',
+ 'MXN' => '484',
+ 'NOK' => '578',
+ 'NZD' => '554',
+ 'PEN' => '604',
+ 'PLN' => '985',
+ 'RUB' => '643',
+ 'SAR' => '682',
+ 'SEK' => '752',
+ 'SGD' => '702',
+ 'THB' => '764',
+ 'TWD' => '901',
+ 'USD' => '840',
+ 'UYU' => '858'
}
# The set of supported transactions for this gateway.
@@ -94,77 +102,72 @@ class RedsysGateway < Gateway
# a card has been rejected. Syntax or general request errors
# are not covered here.
RESPONSE_TEXTS = {
- # Accepted Codes
- 0 => "Transaction Approved",
- 400 => "Cancellation Accepted",
- 481 => "Cancellation Accepted",
- 500 => "Reconciliation Accepted",
- 900 => "Refund / Confirmation approved",
-
- # Declined error codes
- 101 => "Card expired",
- 102 => "Card blocked temporarily or under susciption of fraud",
- 104 => "Transaction not permitted",
- 107 => "Contact the card issuer",
- 109 => "Invalid identification by merchant or POS terminal",
- 110 => "Invalid amount",
- 114 => "Card cannot be used to the requested transaction",
- 116 => "Insufficient credit",
- 118 => "Non-registered card",
- 125 => "Card not effective",
- 129 => "CVV2/CVC2 Error",
- 167 => "Contact the card issuer: suspected fraud",
- 180 => "Card out of service",
- 181 => "Card with credit or debit restrictions",
- 182 => "Card with credit or debit restrictions",
- 184 => "Authentication error",
- 190 => "Refusal with no specific reason",
- 191 => "Expiry date incorrect",
-
- # Declined, and suspected of fraud
- 201 => "Card expired",
- 202 => "Card blocked temporarily or under suscipition of fraud",
- 204 => "Transaction not permitted",
- 207 => "Contact the card issuer",
- 208 => "Lost or stolen card",
- 209 => "Lost or stolen card",
- 280 => "CVV2/CVC2 Error",
- 290 => "Declined with no specific reason",
-
- # More general codes for specific types of transaction
- 480 => "Original transaction not located, or time-out exceeded",
- 501 => "Original transaction not located, or time-out exceeded",
- 502 => "Original transaction not located, or time-out exceeded",
- 503 => "Original transaction not located, or time-out exceeded",
-
- # Declined transactions by the bank
- 904 => "Merchant not registered at FUC",
- 909 => "System error",
- 912 => "Issuer not available",
- 913 => "Duplicate transmission",
- 916 => "Amount too low",
- 928 => "Time-out exceeded",
- 940 => "Transaction cancelled previously",
- 941 => "Authorization operation already cancelled",
- 942 => "Original authorization declined",
- 943 => "Different details from origin transaction",
- 944 => "Session error",
- 945 => "Duplicate transmission",
- 946 => "Cancellation of transaction while in progress",
- 947 => "Duplicate tranmission while in progress",
- 949 => "POS Inoperative",
- 950 => "Refund not possible",
- 9064 => "Card number incorrect",
- 9078 => "No payment method available",
- 9093 => "Non-existent card",
- 9218 => "Recursive transaction in bad gateway",
- 9253 => "Check-digit incorrect",
- 9256 => "Preauth not allowed for merchant",
- 9257 => "Preauth not allowed for card",
- 9261 => "Operating limit exceeded",
- 9912 => "Issuer not available",
- 9913 => "Confirmation error",
- 9914 => "KO Confirmation"
+ 0 => 'Transaction Approved',
+ 400 => 'Cancellation Accepted',
+ 481 => 'Cancellation Accepted',
+ 500 => 'Reconciliation Accepted',
+ 900 => 'Refund / Confirmation approved',
+
+ 101 => 'Card expired',
+ 102 => 'Card blocked temporarily or under susciption of fraud',
+ 104 => 'Transaction not permitted',
+ 107 => 'Contact the card issuer',
+ 109 => 'Invalid identification by merchant or POS terminal',
+ 110 => 'Invalid amount',
+ 114 => 'Card cannot be used to the requested transaction',
+ 116 => 'Insufficient credit',
+ 118 => 'Non-registered card',
+ 125 => 'Card not effective',
+ 129 => 'CVV2/CVC2 Error',
+ 167 => 'Contact the card issuer: suspected fraud',
+ 180 => 'Card out of service',
+ 181 => 'Card with credit or debit restrictions',
+ 182 => 'Card with credit or debit restrictions',
+ 184 => 'Authentication error',
+ 190 => 'Refusal with no specific reason',
+ 191 => 'Expiry date incorrect',
+
+ 201 => 'Card expired',
+ 202 => 'Card blocked temporarily or under suspicion of fraud',
+ 204 => 'Transaction not permitted',
+ 207 => 'Contact the card issuer',
+ 208 => 'Lost or stolen card',
+ 209 => 'Lost or stolen card',
+ 280 => 'CVV2/CVC2 Error',
+ 290 => 'Declined with no specific reason',
+
+ 480 => 'Original transaction not located, or time-out exceeded',
+ 501 => 'Original transaction not located, or time-out exceeded',
+ 502 => 'Original transaction not located, or time-out exceeded',
+ 503 => 'Original transaction not located, or time-out exceeded',
+
+ 904 => 'Merchant not registered at FUC',
+ 909 => 'System error',
+ 912 => 'Issuer not available',
+ 913 => 'Duplicate transmission',
+ 916 => 'Amount too low',
+ 928 => 'Time-out exceeded',
+ 940 => 'Transaction cancelled previously',
+ 941 => 'Authorization operation already cancelled',
+ 942 => 'Original authorization declined',
+ 943 => 'Different details from origin transaction',
+ 944 => 'Session error',
+ 945 => 'Duplicate transmission',
+ 946 => 'Cancellation of transaction while in progress',
+ 947 => 'Duplicate tranmission while in progress',
+ 949 => 'POS Inoperative',
+ 950 => 'Refund not possible',
+ 9064 => 'Card number incorrect',
+ 9078 => 'No payment method available',
+ 9093 => 'Non-existent card',
+ 9218 => 'Recursive transaction in bad gateway',
+ 9253 => 'Check-digit incorrect',
+ 9256 => 'Preauth not allowed for merchant',
+ 9257 => 'Preauth not allowed for card',
+ 9261 => 'Operating limit exceeded',
+ 9912 => 'Issuer not available',
+ 9913 => 'Confirmation error',
+ 9914 => 'KO Confirmation'
}
# Creates a new instance
@@ -178,32 +181,38 @@ class RedsysGateway < Gateway
# * :secret_key -- The Redsys Secret Key. (REQUIRED)
# * :terminal -- The Redsys Terminal. Defaults to 1. (OPTIONAL)
# * :test -- +true+ or +false+. Defaults to +false+. (OPTIONAL)
+ # * :signature_algorithm -- +"sha256"+ Defaults to +"sha1"+. (OPTIONAL)
def initialize(options = {})
requires!(options, :login, :secret_key)
options[:terminal] ||= 1
+ options[:signature_algorithm] ||= 'sha1'
super
end
- def purchase(money, creditcard, options = {})
+ def purchase(money, payment, options = {})
requires!(options, :order_id)
data = {}
add_action(data, :purchase)
add_amount(data, money, options)
add_order(data, options[:order_id])
- add_creditcard(data, creditcard)
+ add_payment(data, payment)
+ data[:description] = options[:description]
+ data[:store_in_vault] = options[:store]
commit data
end
- def authorize(money, creditcard, options = {})
+ def authorize(money, payment, options = {})
requires!(options, :order_id)
data = {}
add_action(data, :authorize)
add_amount(data, money, options)
add_order(data, options[:order_id])
- add_creditcard(data, creditcard)
+ add_payment(data, payment)
+ data[:description] = options[:description]
+ data[:store_in_vault] = options[:store]
commit data
end
@@ -214,6 +223,7 @@ def capture(money, authorization, options = {})
add_amount(data, money, options)
order_id, _, _ = split_authorization(authorization)
add_order(data, order_id)
+ data[:description] = options[:description]
commit data
end
@@ -224,6 +234,7 @@ def void(authorization, options = {})
order_id, amount, currency = split_authorization(authorization)
add_amount(data, amount, :currency => currency)
add_order(data, order_id)
+ data[:description] = options[:description]
commit data
end
@@ -234,10 +245,37 @@ def refund(money, authorization, options = {})
add_amount(data, money, options)
order_id, _, _ = split_authorization(authorization)
add_order(data, order_id)
+ data[:description] = options[:description]
commit data
end
+ def verify(creditcard, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((%3CDS_MERCHANT_PAN%3E)\d+(%3C%2FDS_MERCHANT_PAN%3E))i, '\1[FILTERED]\2').
+ gsub(%r((%3CDS_MERCHANT_CVV2%3E)\d+(%3C%2FDS_MERCHANT_CVV2%3E))i, '\1[FILTERED]\2').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2').
+ gsub(%r((DS_MERCHANT_CVV2)%2F%3E%0A%3C%2F)i, '\1[BLANK]').
+ gsub(%r((DS_MERCHANT_CVV2)%2F%3E%3C)i, '\1[BLANK]').
+ gsub(%r((DS_MERCHANT_CVV2%3E)(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2').
+ gsub(%r(()())i, '\1[BLANK]\2').
+ gsub(%r((DS_MERCHANT_CVV2%3E)\++(%3C%2FDS_MERCHANT_CVV2))i, '\1[BLANK]\2').
+ gsub(%r(()\s+())i, '\1[BLANK]\2')
+ end
+
private
def add_action(data, action)
@@ -250,39 +288,52 @@ def add_amount(data, money, options)
end
def add_order(data, order_id)
- raise ArgumentError.new("Invalid order_id format") unless(/^\d{4}[\da-zA-Z]{0,8}$/ =~ order_id)
- data[:order_id] = order_id
+ data[:order_id] = clean_order_id(order_id)
end
def url
test? ? test_url : live_url
end
- def add_creditcard(data, card)
- name = [card.first_name, card.last_name].join(' ').slice(0, 60)
- year = sprintf("%.4i", card.year)
- month = sprintf("%.2i", card.month)
- data[:card] = {
- :name => name,
- :pan => card.number,
- :date => "#{year[2..3]}#{month}",
- :cvv => card.verification_value
- }
+ def add_payment(data, card)
+ if card.is_a?(String)
+ data[:credit_card_token] = card
+ else
+ name = [card.first_name, card.last_name].join(' ').slice(0, 60)
+ year = sprintf('%.4i', card.year)
+ month = sprintf('%.2i', card.month)
+ data[:card] = {
+ :name => name,
+ :pan => card.number,
+ :date => "#{year[2..3]}#{month}",
+ :cvv => card.verification_value
+ }
+ end
end
def commit(data)
- headers = {
+ parse(ssl_post(url, "entrada=#{CGI.escape(xml_request_from(data))}", headers))
+ end
+
+ def headers
+ {
'Content-Type' => 'application/x-www-form-urlencoded'
}
- xml = build_xml_request(data)
- parse(ssl_post(url, "entrada=#{CGI.escape(xml)}", headers))
+ end
+
+ def xml_request_from(data)
+ if sha256_authentication?
+ build_sha256_xml_request(data)
+ else
+ build_sha1_xml_request(data)
+ end
end
def build_signature(data)
str = data[:amount] +
- data[:order_id].to_s +
- @options[:login].to_s +
- data[:currency]
+ data[:order_id].to_s +
+ @options[:login].to_s +
+ data[:currency]
if card = data[:card]
str << card[:pan]
@@ -290,23 +341,51 @@ def build_signature(data)
end
str << data[:action]
+ if data[:store_in_vault]
+ str << 'REQUIRED'
+ elsif data[:credit_card_token]
+ str << data[:credit_card_token]
+ end
str << @options[:secret_key]
Digest::SHA1.hexdigest(str)
end
- def build_xml_request(data)
+ def build_sha256_xml_request(data)
+ xml = Builder::XmlMarkup.new
+ xml.instruct!
+ xml.REQUEST do
+ build_merchant_data(xml, data)
+ xml.DS_SIGNATUREVERSION 'HMAC_SHA256_V1'
+ xml.DS_SIGNATURE sign_request(merchant_data_xml(data), data[:order_id])
+ end
+ xml.target!
+ end
+
+ def build_sha1_xml_request(data)
xml = Builder::XmlMarkup.new :indent => 2
+ build_merchant_data(xml, data)
+ xml.target!
+ end
+
+ def merchant_data_xml(data)
+ xml = Builder::XmlMarkup.new
+ build_merchant_data(xml, data)
+ xml.target!
+ end
+
+ def build_merchant_data(xml, data)
xml.DATOSENTRADA do
# Basic elements
xml.DS_Version 0.1
- xml.DS_MERCHANT_CURRENCY data[:currency]
- xml.DS_MERCHANT_AMOUNT data[:amount]
- xml.DS_MERCHANT_ORDER data[:order_id]
- xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
- xml.DS_MERCHANT_TERMINAL @options[:terminal]
- xml.DS_MERCHANT_MERCHANTCODE @options[:login]
- xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data)
+ xml.DS_MERCHANT_CURRENCY data[:currency]
+ xml.DS_MERCHANT_AMOUNT data[:amount]
+ xml.DS_MERCHANT_ORDER data[:order_id]
+ xml.DS_MERCHANT_TRANSACTIONTYPE data[:action]
+ xml.DS_MERCHANT_PRODUCTDESCRIPTION data[:description]
+ xml.DS_MERCHANT_TERMINAL @options[:terminal]
+ xml.DS_MERCHANT_MERCHANTCODE @options[:login]
+ xml.DS_MERCHANT_MERCHANTSIGNATURE build_signature(data) unless sha256_authentication?
# Only when card is present
if data[:card]
@@ -314,20 +393,23 @@ def build_xml_request(data)
xml.DS_MERCHANT_PAN data[:card][:pan]
xml.DS_MERCHANT_EXPIRYDATE data[:card][:date]
xml.DS_MERCHANT_CVV2 data[:card][:cvv]
+ xml.DS_MERCHANT_IDENTIFIER 'REQUIRED' if data[:store_in_vault]
+ elsif data[:credit_card_token]
+ xml.DS_MERCHANT_IDENTIFIER data[:credit_card_token]
+ xml.DS_MERCHANT_DIRECTPAYMENT 'true'
end
end
- xml.target!
end
def parse(data)
params = {}
success = false
- message = ""
+ message = ''
options = @options.merge(:test => test?)
xml = Nokogiri::XML(data)
- code = xml.xpath("//RETORNOXML/CODIGO").text
- if code == "0"
- op = xml.xpath("//RETORNOXML/OPERACION")
+ code = xml.xpath('//RETORNOXML/CODIGO').text
+ if code == '0'
+ op = xml.xpath('//RETORNOXML/OPERACION')
op.children.each do |element|
params[element.name.downcase.to_sym] = element.text
end
@@ -337,7 +419,7 @@ def parse(data)
options[:authorization] = build_authorization(params)
success = is_success_response?(params[:ds_response])
else
- message = "Response failed validation check"
+ message = 'Response failed validation check'
end
else
# Some kind of programmer error with the request!
@@ -348,31 +430,37 @@ def parse(data)
end
def validate_signature(data)
- str = data[:ds_amount] +
- data[:ds_order].to_s +
- data[:ds_merchantcode] +
- data[:ds_currency] +
- data[:ds_response] +
- data[:ds_cardnumber].to_s +
- data[:ds_transactiontype].to_s +
- data[:ds_securepayment].to_s +
- @options[:secret_key]
-
- sig = Digest::SHA1.hexdigest(str)
- data[:ds_signature].to_s.downcase == sig
+ if sha256_authentication?
+ sig = Base64.strict_encode64(mac256(get_key(data[:ds_order].to_s), xml_signed_fields(data)))
+ sig.casecmp(data[:ds_signature].to_s).zero?
+ else
+ str = data[:ds_amount] +
+ data[:ds_order].to_s +
+ data[:ds_merchantcode] +
+ data[:ds_currency] +
+ data[:ds_response] +
+ data[:ds_cardnumber].to_s +
+ data[:ds_transactiontype].to_s +
+ data[:ds_securepayment].to_s +
+ @options[:secret_key]
+
+ sig = Digest::SHA1.hexdigest(str)
+ data[:ds_signature].to_s.downcase == sig
+ end
end
def build_authorization(params)
- [params[:ds_order], params[:ds_amount], params[:ds_currency]].join("|")
+ [params[:ds_order], params[:ds_amount], params[:ds_currency]].join('|')
end
def split_authorization(authorization)
- order_id, amount, currency = authorization.split("|")
+ order_id, amount, currency = authorization.split('|')
[order_id, amount.to_i, currency]
end
def currency_code(currency)
return currency if currency =~ /^\d+$/
+ raise ArgumentError, "Unknown currency #{currency}" unless CURRENCY_CODES[currency]
CURRENCY_CODES[currency]
end
@@ -383,12 +471,64 @@ def transaction_code(type)
def response_text(code)
code = code.to_i
code = 0 if code < 100
- RESPONSE_TEXTS[code] || "Unkown code, please check in manual"
+ RESPONSE_TEXTS[code] || 'Unkown code, please check in manual'
end
def is_success_response?(code)
(code.to_i < 100) || [400, 481, 500, 900].include?(code.to_i)
end
+
+ def clean_order_id(order_id)
+ cleansed = order_id.gsub(/[^\da-zA-Z]/, '')
+ if cleansed =~ /^\d{4}/
+ cleansed[0..11]
+ else
+ '%04d%s' % [rand(0..9999), cleansed[0...8]]
+ end
+ end
+
+ def sha256_authentication?
+ @options[:signature_algorithm] == 'sha256'
+ end
+
+ def sign_request(xml_request_string, order_id)
+ key = encrypt(@options[:secret_key], order_id)
+ Base64.strict_encode64(mac256(key, xml_request_string))
+ end
+
+ def encrypt(key, order_id)
+ block_length = 8
+ cipher = OpenSSL::Cipher.new('DES3')
+ cipher.encrypt
+
+ cipher.key = Base64.strict_decode64(key)
+ # The OpenSSL default of an all-zeroes ("\\0") IV is used.
+ cipher.padding = 0
+
+ order_id += "\0" until order_id.bytesize % block_length == 0 # Pad with zeros
+
+ output = cipher.update(order_id) + cipher.final
+ output
+ end
+
+ def mac256(key, data)
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, data)
+ end
+
+ def xml_signed_fields(data)
+ xml_signed_fields = data[:ds_amount] + data[:ds_order] + data[:ds_merchantcode] +
+ data[:ds_currency] + data[:ds_response]
+
+ if data[:ds_cardnumber]
+ xml_signed_fields += data[:ds_cardnumber]
+ end
+
+ xml_signed_fields + data[:ds_transactiontype] + data[:ds_securepayment]
+ end
+
+ def get_key(order_id)
+ encrypt(@options[:secret_key], order_id)
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/s5.rb b/lib/active_merchant/billing/gateways/s5.rb
new file mode 100644
index 00000000000..9f36a91e54e
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/s5.rb
@@ -0,0 +1,246 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class S5Gateway < Gateway
+ self.test_url = 'https://test.ctpe.io/payment/ctpe'
+ self.live_url = 'https://ctpe.io/payment/ctpe'
+
+ self.supported_countries = ['DK']
+ self.default_currency = 'EUR'
+ self.supported_cardtypes = [:visa, :master, :maestro]
+
+ self.homepage_url = 'http://www.s5.dk/'
+ self.display_name = 'S5'
+
+ SUPPORTED_TRANSACTIONS = {
+ 'sale' => 'CC.DB',
+ 'authonly' => 'CC.PA',
+ 'capture' => 'CC.CP',
+ 'refund' => 'CC.RF',
+ 'void' => 'CC.RV',
+ 'store' => 'CC.RG'
+ }
+
+ def initialize(options={})
+ requires!(options, :sender, :channel, :login, :password)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ request = build_xml_request do |xml|
+ add_identification(xml, options)
+ add_payment(xml, money, 'sale', options)
+ add_account(xml, payment)
+ add_customer(xml, payment, options)
+ add_recurrence_mode(xml, options)
+ end
+
+ commit(request)
+ end
+
+ def refund(money, authorization, options={})
+ request = build_xml_request do |xml|
+ add_identification(xml, options, authorization)
+ add_payment(xml, money, 'refund', options)
+ end
+
+ commit(request)
+ end
+
+ def authorize(money, payment, options={})
+ request = build_xml_request do |xml|
+ add_identification(xml, options)
+ add_payment(xml, money, 'authonly', options)
+ add_account(xml, payment)
+ add_customer(xml, payment, options)
+ add_recurrence_mode(xml, options)
+ end
+
+ commit(request)
+ end
+
+ def capture(money, authorization, options={})
+ request = build_xml_request do |xml|
+ add_identification(xml, options, authorization)
+ add_payment(xml, money, 'capture', options)
+ end
+
+ commit(request)
+ end
+
+ def void(authorization, options={})
+ request = build_xml_request do |xml|
+ add_identification(xml, options, authorization)
+ add_payment(xml, nil, 'void', options)
+ end
+
+ commit(request)
+ end
+
+ def store(payment, options = {})
+ request = build_xml_request do |xml|
+ xml.Payment(code: SUPPORTED_TRANSACTIONS['store'])
+ add_account(xml, payment)
+ add_customer(xml, payment, options)
+ add_recurrence_mode(xml, options)
+ 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((pwd=).+?(/>))i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2').
+ gsub(%r(().+?())i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_identification(xml, options, authorization = nil)
+ xml.Identification do
+ xml.TransactionID options[:order_id] if options[:order_id]
+ xml.ReferenceID authorization if authorization
+ end
+ end
+
+ def add_payment(xml, money, action, options)
+ xml.Payment(code: SUPPORTED_TRANSACTIONS[action]) do
+ xml.Memo "return_code=#{options[:memo]}" if options[:memo]
+ xml.Presentation do
+ xml.Amount amount(money)
+ xml.Currency options[:currency] || currency(money)
+ xml.Usage options[:description]
+ end
+ end
+ end
+
+ def add_account(xml, payment_method)
+ if !payment_method.respond_to?(:number)
+ xml.Account(registration: payment_method)
+ else
+ xml.Account do
+ xml.Number payment_method.number
+ xml.Holder "#{payment_method.first_name} #{payment_method.last_name}"
+ xml.Brand payment_method.brand
+ xml.Expiry(year: payment_method.year, month: payment_method.month)
+ xml.Verification payment_method.verification_value
+ end
+ end
+ end
+
+ def add_customer(xml, creditcard, options)
+ return unless creditcard.respond_to?(:number)
+ address = options[:billing_address]
+ xml.Customer do
+ xml.Contact do
+ xml.Email options[:email]
+ xml.Ip options[:ip]
+ xml.Phone address[:phone] if address
+ end
+ add_address(xml, address)
+ xml.Name do
+ xml.Given creditcard.first_name
+ xml.Family creditcard.last_name
+ xml.Company options[:company]
+ end
+ end
+ end
+
+ def add_address(xml, address)
+ return unless address
+
+ xml.Address do
+ xml.Street "#{address[:address1]} #{address[:address2]}"
+ xml.Zip address[:zip]
+ xml.City address[:city]
+ xml.State address[:state]
+ xml.Country address[:country]
+ end
+ end
+
+ def add_recurrence_mode(xml, options)
+ if options[:recurring] == true
+ xml.Recurrence(mode: 'REPEATED')
+ else
+ xml.Recurrence(mode: 'INITIAL')
+ end
+ end
+
+ def parse(body)
+ results = {}
+ xml = Nokogiri::XML(body)
+ resp = xml.xpath('//Response/Transaction/Identification')
+ resp.children.each do |element|
+ results[element.name.downcase.to_sym] = element.text
+ end
+ resp = xml.xpath('//Response/Transaction/Processing')
+ resp.children.each do |element|
+ results[element.name.downcase.to_sym] = element.text
+ end
+ results
+ end
+
+ def commit(xml)
+ url = (test? ? test_url : live_url)
+ headers = {
+ 'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
+ }
+
+ response = parse(ssl_post(url, post_data(xml), headers))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response),
+ test: test?
+ )
+ end
+
+ def success_from(response)
+ response[:result] == 'ACK'
+ end
+
+ def message_from(response)
+ response[:return]
+ end
+
+ def authorization_from(response)
+ response[:uniqueid]
+ end
+
+ def post_data(xml)
+ "load=#{xml}"
+ end
+
+ def build_xml_request
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
+ xml.Request(version: '1.0') do
+ xml.Header do
+ xml.Security(sender: @options[:sender])
+ end
+ xml.Transaction(mode: @options[:mode] || 'LIVE', channel: @options[:channel]) do
+ xml.User(login: @options[:login], pwd: @options[:password])
+ yield(xml)
+ end
+ end
+ end
+
+ builder.to_xml
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/safe_charge.rb b/lib/active_merchant/billing/gateways/safe_charge.rb
new file mode 100644
index 00000000000..8d3cccd3cc6
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/safe_charge.rb
@@ -0,0 +1,262 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SafeChargeGateway < Gateway
+ self.test_url = 'https://process.sandbox.safecharge.com/service.asmx/Process'
+ self.live_url = 'https://process.safecharge.com/service.asmx/Process'
+
+ self.supported_countries = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'GR', 'ES', 'FI', 'FR', 'HR', 'HU', 'IE', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'GB', 'US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master]
+
+ self.homepage_url = 'https://www.safecharge.com'
+ self.display_name = 'SafeCharge'
+
+ VERSION = '4.1.0'
+
+ def initialize(options={})
+ requires!(options, :client_login_id, :client_password)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = {}
+ post[:sg_APIType] = 1 if options[:three_d_secure]
+ trans_type = options[:three_d_secure] ? 'Sale3D' : 'Sale'
+ add_transaction_data(trans_type, post, money, options)
+ add_payment(post, payment, options)
+ add_customer_details(post, payment, options)
+
+ commit(post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ add_transaction_data('Auth', post, money, options)
+ add_payment(post, payment, options)
+ add_customer_details(post, payment, options)
+
+ commit(post)
+ end
+
+ def capture(money, authorization, options={})
+ post = {}
+ auth, transaction_id, token, exp_month, exp_year, _, original_currency = authorization.split('|')
+ add_transaction_data('Settle', post, money, options.merge!({currency: original_currency}))
+ post[:sg_AuthCode] = auth
+ post[:sg_TransactionID] = transaction_id
+ post[:sg_CCToken] = token
+ post[:sg_ExpMonth] = exp_month
+ post[:sg_ExpYear] = exp_year
+
+ commit(post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+ auth, transaction_id, token, exp_month, exp_year, _, original_currency = authorization.split('|')
+ add_transaction_data('Credit', post, money, options.merge!({currency: original_currency}))
+ post[:sg_CreditType] = 2
+ post[:sg_AuthCode] = auth
+ post[:sg_TransactionID] = transaction_id
+ post[:sg_CCToken] = token
+ post[:sg_ExpMonth] = exp_month
+ post[:sg_ExpYear] = exp_year
+
+ commit(post)
+ end
+
+ def credit(money, payment, options={})
+ post = {}
+ add_payment(post, payment, options)
+ add_transaction_data('Credit', post, money, options)
+ post[:sg_CreditType] = 1
+
+ commit(post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ auth, transaction_id, token, exp_month, exp_year, original_amount, original_currency = authorization.split('|')
+ add_transaction_data('Void', post, (original_amount.to_f * 100), options.merge!({currency: original_currency}))
+ post[:sg_CreditType] = 2
+ post[:sg_AuthCode] = auth
+ post[:sg_TransactionID] = transaction_id
+ post[:sg_CCToken] = token
+ post[:sg_ExpMonth] = exp_month
+ post[:sg_ExpYear] = exp_year
+
+ commit(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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((sg_ClientPassword=)[^&]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((sg_CardNumber=)[^&]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((sg_CVV2=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def add_transaction_data(trans_type, post, money, options)
+ post[:sg_TransType] = trans_type
+ post[:sg_Currency] = (options[:currency] || currency(money))
+ post[:sg_Amount] = amount(money)
+ post[:sg_ClientLoginID] = @options[:client_login_id]
+ post[:sg_ClientPassword] = @options[:client_password]
+ post[:sg_ResponseFormat] = '4'
+ post[:sg_Version] = VERSION
+ post[:sg_ClientUniqueID] = options[:order_id] if options[:order_id]
+ post[:sg_UserID] = options[:user_id] if options[:user_id]
+ post[:sg_AuthType] = options[:auth_type] if options[:auth_type]
+ post[:sg_ExpectedFulfillmentCount] = options[:expected_fulfillment_count] if options[:expected_fulfillment_count]
+ post[:sg_WebsiteID] = options[:website_id] if options[:website_id]
+ post[:sg_IPAddress] = options[:ip] if options[:ip]
+ post[:sg_VendorID] = options[:vendor_id] if options[:vendor_id]
+ post[:sg_Descriptor] = options[:merchant_descriptor] if options[:merchant_descriptor]
+ post[:sg_MerchantPhoneNumber] = options[:merchant_phone_number] if options[:merchant_phone_number]
+ post[:sg_MerchantName] = options[:merchant_name] if options[:merchant_name]
+ end
+
+ def add_payment(post, payment, options={})
+ post[:sg_NameOnCard] = payment.name
+ post[:sg_CardNumber] = payment.number
+ post[:sg_ExpMonth] = format(payment.month, :two_digits)
+ post[:sg_ExpYear] = format(payment.year, :two_digits)
+ post[:sg_CVV2] = payment.verification_value
+ post[:sg_StoredCredentialMode] = (options[:stored_credential_mode] == true ? 1 : 0)
+ end
+
+ def add_customer_details(post, payment, options)
+ if address = options[:billing_address] || options[:address]
+ post[:sg_FirstName] = payment.first_name
+ post[:sg_LastName] = payment.last_name
+ post[:sg_Address] = address[:address1] if address[:address1]
+ post[:sg_City] = address[:city] if address[:city]
+ post[:sg_State] = address[:state] if address[:state]
+ post[:sg_Zip] = address[:zip] if address[:zip]
+ post[:sg_Country] = address[:country] if address[:country]
+ post[:sg_Phone] = address[:phone] if address[:phone]
+ end
+
+ post[:sg_Email] = options[:email]
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.root.xpath('*').each do |node|
+ if node.elements.size == 0
+ response[node.name.underscore.downcase.to_sym] = node.text
+ else
+ node.traverse do |childnode|
+ childnode_to_response(response, childnode)
+ end
+ end
+ end
+ response
+ end
+
+ def childnode_to_response(response, childnode)
+ if childnode.elements.size == 0
+ element_name_to_symbol(response, childnode)
+ else
+ childnode.traverse do |node|
+ element_name_to_symbol(response, node)
+ end
+ end
+ end
+
+ def element_name_to_symbol(response, childnode)
+ name = childnode.name.downcase
+ response[name.to_sym] = childnode.text
+ end
+
+ def commit(parameters)
+ url = (test? ? test_url : live_url)
+ response = parse(ssl_post(url, post_data(parameters)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, parameters),
+ avs_result: AVSResult.new(code: response[:avs_code]),
+ cvv_result: CVVResult.new(response[:cvv2_reply]),
+ test: test?,
+ error_code: error_code_from(response)
+ )
+ end
+
+ def success_from(response)
+ response[:status] == 'APPROVED'
+ end
+
+ def message_from(response)
+ return 'Success' if success_from(response)
+ response[:reason_codes] || response[:reason]
+ end
+
+ def authorization_from(response, parameters)
+ [
+ response[:auth_code],
+ response[:transaction_id],
+ response[:token],
+ parameters[:sg_ExpMonth],
+ parameters[:sg_ExpYear],
+ parameters[:sg_Amount],
+ parameters[:sg_Currency]
+ ].join('|')
+ end
+
+ def split_authorization(authorization)
+ auth_code, transaction_id, token, month, year, original_amount = authorization.split('|')
+
+ {
+ auth_code: auth_code,
+ transaction_id: transaction_id,
+ token: token,
+ exp_month: month,
+ exp_year: year,
+ original_amount: amount(original_amount.to_f * 100)
+ }
+ end
+
+ def post_data(params)
+ return nil unless params
+
+ params.map do |key, value|
+ next if value != false && value.blank?
+ "#{key}=#{CGI.escape(value.to_s)}"
+ end.compact.join('&')
+ end
+
+ def error_code_from(response)
+ unless success_from(response)
+ response[:ex_err_code] || response[:err_code]
+ 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
+end
diff --git a/lib/active_merchant/billing/gateways/sage.rb b/lib/active_merchant/billing/gateways/sage.rb
index 5fbae0df9e3..3f3c9a96bcf 100644
--- a/lib/active_merchant/billing/gateways/sage.rb
+++ b/lib/active_merchant/billing/gateways/sage.rb
@@ -1,152 +1,448 @@
-require File.dirname(__FILE__) + '/sage/sage_bankcard'
-require File.dirname(__FILE__) + '/sage/sage_virtual_check'
-
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class SageGateway < Gateway
- self.supported_countries = SageBankcardGateway.supported_countries
- self.supported_cardtypes = SageBankcardGateway.supported_cardtypes
-
- self.abstract_class = true
-
- # Creates a new SageGateway
- #
- # The gateway requires that a valid login and password be passed
- # in the +options+ hash.
- #
- # ==== Options
- #
- # * :login - The Sage Payment Solutions Merchant ID Number.
- # * :password - The Sage Payment Solutions Merchant Key Number.
+ include Empty
+
+ self.display_name = 'http://www.sagepayments.com'
+ self.homepage_url = 'Sage Payment Solutions'
+ self.live_url = 'https://www.sagepayments.net/cgi-bin'
+
+ self.supported_countries = ['US', 'CA']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
+
+ TRANSACTIONS = {
+ :purchase => '01',
+ :authorization => '02',
+ :capture => '11',
+ :void => '04',
+ :credit => '06',
+ :refund => '10'
+ }
+
+ SOURCE_CARD = 'bankcard'
+ SOURCE_ECHECK = 'virtual_check'
+
def initialize(options = {})
requires!(options, :login, :password)
super
end
- # Performs an authorization transaction
- #
- # ==== Parameters
- # * money - The amount to be authorized as an integer value in cents.
- # * credit_card - The CreditCard object to be used as the funding source for the transaction.
- # * options - A hash of optional parameters.
- # * :order_id - A unique reference for this order. (maximum of 20 characters).
- # * :email - The customer's email address
- # * :customer - The Customer Number for Purchase Card Level II Transactions
- # * :billing_address - The customer's billing address as a hash of address information.
- # * :address1 - The billing address street
- # * :city - The billing address city
- # * :state - The billing address state
- # * :country - The 2 digit ISO billing address country code
- # * :zip - The billing address zip code
- # * :phone - The billing address phone number
- # * :fax - The billing address fax number
- # * :shipping_address - The customer's shipping address as a hash of address information.
- # * :name - The name at the shipping address
- # * :address1 - The shipping address street
- # * :city - The shipping address city
- # * :state - The shipping address state code
- # * :country - The 2 digit ISO shipping address country code
- # * :zip - The shipping address zip code
- # * :tax - The tax amount for the transaction as an Integer value in cents. Maps to Sage T_tax.
- # * :shipping - The shipping amount for the transaction as an Integer value in cents. Maps to Sage T_shipping.
def authorize(money, credit_card, options = {})
- bankcard.authorize(money, credit_card, options)
- end
-
- # Performs a purchase, which is essentially an authorization and capture in a single operation.
- #
- # ==== Parameters
- #
- # * money - The amount to be authorized as an integer value in cents.
- # * source - The CreditCard or Check object to be used as the funding source for the transaction.
- # * options - A hash of optional parameters.
- # * :order_id - A unique reference for this order. (maximum of 20 characters).
- # * :email - The customer's email address
- # * :customer - The Customer Number for Purchase Card Level II Transactions
- # * :billing_address - The customer's billing address as a hash of address information.
- # * :address1 - The billing address street
- # * :city - The billing address city
- # * :state - The billing address state
- # * :country - The 2 digit ISO billing address country code
- # * :zip - The billing address zip code
- # * :phone - The billing address phone number
- # * :fax - The billing address fax number
- # * :shipping_address - The customer's shipping address as a hash of address information.
- # * :name - The name at the shipping address
- # * :address1 - The shipping address street
- # * :city - The shipping address city
- # * :state - The shipping address state code
- # * :country - The 2 digit ISO shipping address country code
- # * :zip - The shipping address zip code
- # * :tax - The tax amount for the transaction as an integer value in cents. Maps to Sage T_tax.
- # * :shipping - The shipping amount for the transaction as an integer value in cents. Maps to Sage T_shipping.
- #
- # ==== Additional options in the +options+ hash for when using a Check as the funding source
- # * :originator_id - 10 digit originator. If not provided, Sage will use the default Originator ID for the specific customer type.
- # * :addenda - Transaction addenda.
- # * :ssn - The customer's Social Security Number.
- # * :drivers_license_state - The customer's drivers license state code.
- # * :drivers_license_number - The customer's drivers license number.
- # * :date_of_birth - The customer's date of birth as a Time or Date object or a string in the format mm/dd/yyyy.
- def purchase(money, source, options = {})
- if card_brand(source) == "check"
- virtual_check.purchase(money, source, options)
+ post = {}
+ add_credit_card(post, credit_card)
+ add_transaction_data(post, money, options)
+ commit(:authorization, post, SOURCE_CARD)
+ end
+
+ def purchase(money, payment_method, options = {})
+ post = {}
+ if card_brand(payment_method) == 'check'
+ source = SOURCE_ECHECK
+ add_check(post, payment_method)
+ add_check_customer_data(post, options)
else
- bankcard.purchase(money, source, options)
+ source = SOURCE_CARD
+ add_credit_card(post, payment_method)
end
+ add_transaction_data(post, money, options)
+ commit(:purchase, post, source)
end
- # Captures authorized funds.
- #
- # ==== Parameters
- #
- # * money - The amount to be authorized as an integer value in cents. Sage doesn't support changing the capture amount, so the full amount of the initial transaction will be captured.
- # * reference - The authorization reference string returned by the original transaction's Response#authorization.
+ # The +money+ amount is not used. The entire amount of the
+ # initial authorization will be captured.
def capture(money, reference, options = {})
- bankcard.capture(money, reference, options)
+ post = {}
+ add_reference(post, reference)
+ commit(:capture, post, SOURCE_CARD)
end
- # Voids a prior transaction. Works for both CreditCard and Check transactions.
- #
- # ==== Parameters
- #
- # * reference - The authorization reference string returned by the original transaction's Response#authorization.
def void(reference, options = {})
- if reference.split(";").last == "virtual_check"
- virtual_check.void(reference, options)
+ post = {}
+ add_reference(post, reference)
+ source = reference.split(';').last
+ commit(:void, post, source)
+ end
+
+ def credit(money, payment_method, options = {})
+ post = {}
+ if card_brand(payment_method) == 'check'
+ source = SOURCE_ECHECK
+ add_check(post, payment_method)
+ add_check_customer_data(post, options)
else
- bankcard.void(reference, options)
+ source = SOURCE_CARD
+ add_credit_card(post, payment_method)
+ end
+ add_transaction_data(post, money, options)
+ commit(:credit, post, source)
+ end
+
+ def refund(money, reference, options={})
+ post = {}
+ add_reference(post, reference)
+ add_transaction_data(post, money, options)
+ commit(:refund, post, SOURCE_CARD)
+ end
+
+ def store(credit_card, options = {})
+ vault.store(credit_card, options)
+ end
+
+ def unstore(identification, options = {})
+ vault.unstore(identification, options)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ force_utf8(transcript).
+ gsub(%r((M_id=)[^&]*), '\1[FILTERED]').
+ gsub(%r((M_key=)[^&]*), '\1[FILTERED]').
+ gsub(%r((C_cardnumber=)[^&]*), '\1[FILTERED]').
+ gsub(%r((C_cvv=)[^&]*), '\1[FILTERED]').
+ gsub(%r((C_rte=)[^&]*), '\1[FILTERED]').
+ gsub(%r((C_acct=)[^&]*), '\1[FILTERED]').
+ gsub(%r((C_ssn=)[^&]*), '\1[FILTERED]').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2')
+ end
+
+ private
+
+ # use the same method as in pay_conex
+ def force_utf8(string)
+ return nil unless string
+ binary = string.encode('BINARY', invalid: :replace, undef: :replace, replace: '?') # Needed for Ruby 2.0 since #encode is a no-op if the string is already UTF-8. It's not needed for Ruby 2.1 and up since it's not a no-op there.
+ binary.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
+ end
+
+ def add_credit_card(post, credit_card)
+ post[:C_name] = credit_card.name
+ post[:C_cardnumber] = credit_card.number
+ post[:C_exp] = expdate(credit_card)
+ post[:C_cvv] = credit_card.verification_value if credit_card.verification_value?
+ end
+
+ def add_check(post, check)
+ post[:C_first_name] = check.first_name
+ post[:C_last_name] = check.last_name
+ post[:C_rte] = check.routing_number
+ post[:C_acct] = check.account_number
+ post[:C_check_number] = check.number
+ post[:C_acct_type] = account_type(check)
+ end
+
+ def add_check_customer_data(post, options)
+ # Required Customer Type – (NACHA Transaction Class)
+ # CCD for Commercial, Merchant Initiated
+ # PPD for Personal, Merchant Initiated
+ # WEB for Internet, Consumer Initiated
+ # RCK for Returned Checks
+ # ARC for Account Receivable Entry
+ # TEL for TelephoneInitiated
+ post[:C_customer_type] = 'WEB'
+
+ # Optional 10 Digit Originator ID – Assigned By for each transaction class or business purpose. If not provided, the default Originator ID for the specific Customer Type will be applied.
+ post[:C_originator_id] = options[:originator_id]
+
+ # Optional Transaction Addenda
+ post[:T_addenda] = options[:addenda]
+
+ # Required Check Writer Social Security Number ( Numbers Only, No Dashes )
+ post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '')
+
+ post[:C_dl_state_code] = options[:drivers_license_state]
+ post[:C_dl_number] = options[:drivers_license_number]
+ post[:C_dob] = format_birth_date(options[:date_of_birth])
+ end
+
+ def format_birth_date(date)
+ date.respond_to?(:strftime) ? date.strftime('%m/%d/%Y') : date
+ end
+
+ # DDA for Checking
+ # SAV for Savings
+ def account_type(check)
+ case check.account_type
+ when 'checking' then 'DDA'
+ when 'savings' then 'SAV'
+ else raise ArgumentError, "Unknown account type #{check.account_type}"
+ end
+ end
+
+ def parse(data, source)
+ source == SOURCE_ECHECK ? parse_check(data) : parse_credit_card(data)
+ end
+
+ def parse_check(data)
+ response = {}
+ response[:success] = data[1, 1]
+ response[:code] = data[2, 6].strip
+ response[:message] = data[8, 32].strip
+ response[:risk] = data[40, 2]
+ response[:reference] = data[42, 10]
+
+ extra_data = data[53...-1].split("\034")
+ response[:order_number] = extra_data[0]
+ response[:authentication_indicator] = extra_data[1]
+ response[:authentication_disclosure] = extra_data[2]
+ response
+ end
+
+ def parse_credit_card(data)
+ response = {}
+ response[:success] = data[1, 1]
+ response[:code] = data[2, 6]
+ response[:message] = data[8, 32].strip
+ response[:front_end] = data[40, 2]
+ response[:cvv_result] = data[42, 1]
+ response[:avs_result] = data[43, 1].strip
+ response[:risk] = data[44, 2]
+ response[:reference] = data[46, 10]
+
+ response[:order_number], response[:recurring] = data[57...-1].split("\034")
+ response
+ end
+
+ def add_invoice(post, options)
+ post[:T_ordernum] = (options[:order_id] || generate_unique_id).slice(0, 20)
+ post[:T_tax] = amount(options[:tax]) unless empty?(options[:tax])
+ post[:T_shipping] = amount(options[:shipping]) unless empty?(options[:shipping])
+ end
+
+ def add_reference(post, reference)
+ ref, _ = reference.to_s.split(';')
+ post[:T_reference] = ref
+ end
+
+ def add_amount(post, money)
+ post[:T_amt] = amount(money)
+ end
+
+ def add_customer_data(post, options)
+ post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil
+ end
+
+ def add_addresses(post, options)
+ billing_address = options[:billing_address] || options[:address] || {}
+
+ post[:C_address] = billing_address[:address1]
+ post[:C_city] = billing_address[:city]
+ post[:C_state] = empty?(billing_address[:state]) ? 'Outside of US' : billing_address[:state]
+ post[:C_zip] = billing_address[:zip]
+ post[:C_country] = billing_address[:country]
+ post[:C_telephone] = billing_address[:phone]
+ post[:C_fax] = billing_address[:fax]
+ post[:C_email] = options[:email]
+
+ if shipping_address = options[:shipping_address]
+ post[:C_ship_name] = shipping_address[:name]
+ post[:C_ship_address] = shipping_address[:address1]
+ post[:C_ship_city] = shipping_address[:city]
+ post[:C_ship_state] = shipping_address[:state]
+ post[:C_ship_zip] = shipping_address[:zip]
+ post[:C_ship_country] = shipping_address[:country]
end
end
- def credit(money, source, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, source, options)
+ def add_transaction_data(post, money, options)
+ add_amount(post, money)
+ add_invoice(post, options)
+ add_addresses(post, options)
+ add_customer_data(post, options)
+ end
+
+ def commit(action, params, source)
+ url = url(params, source)
+ response = parse(ssl_post(url, post_data(action, params)), source)
+
+ Response.new(success?(response), response[:message], response,
+ :test => test?,
+ :authorization => authorization_from(response, source),
+ :avs_result => { :code => response[:avs_result] },
+ :cvv_result => response[:cvv_result]
+ )
end
- # Performs a refund transaction.
- #
- # ==== Parameters
- #
- # * money - The amount to be authorized as an integer value in cents.
- # * source - The CreditCard or Check object to be used as the target for the refund.
- def refund(money, source, options = {})
- if card_brand(source) == "check"
- virtual_check.refund(money, source, options)
+ def url(params, source)
+ if source == SOURCE_ECHECK
+ "#{live_url}/eftVirtualCheck.dll?transaction"
else
- bankcard.refund(money, source, options)
+ "#{live_url}/eftBankcard.dll?transaction"
end
end
- private
- def bankcard
- @bankcard ||= SageBankcardGateway.new(@options)
+ def authorization_from(response, source)
+ "#{response[:reference]};#{source}"
+ end
+
+ def success?(response)
+ response[:success] == 'A'
+ end
+
+ def post_data(action, params = {})
+ params[:M_id] = @options[:login]
+ params[:M_key] = @options[:password]
+ params[:T_code] = TRANSACTIONS[action]
+
+ params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
- def virtual_check
- @virtual_check ||= SageVirtualCheckGateway.new(@options)
+ def vault
+ @vault ||= SageVault.new(@options, self)
+ end
+
+ class SageVault
+
+ def initialize(options, gateway)
+ @live_url = 'https://www.sagepayments.net/web_services/wsVault/wsVault.asmx'
+ @options = options
+ @gateway = gateway
+ end
+
+ def store(credit_card, options = {})
+ request = build_store_request(credit_card, options)
+ commit(:store, request)
+ end
+
+ def unstore(identification, options = {})
+ request = build_unstore_request(identification, options)
+ commit(:unstore, request)
+ end
+
+ private
+
+ # A valid request example, since the Sage docs have none:
+ #
+ #
+ #
+ #
+ #
+ # 279277516172
+ # O3I8G2H8V6A3
+ # 4111111111111111
+ # 0915
+ #
+ #
+ #
+ def build_store_request(credit_card, options)
+ xml = Builder::XmlMarkup.new
+ add_credit_card(xml, credit_card, options)
+ xml.target!
+ end
+
+ def build_unstore_request(identification, options)
+ xml = Builder::XmlMarkup.new
+ add_identification(xml, identification, options)
+ xml.target!
+ end
+
+ def add_customer_data(xml)
+ xml.tag! 'ns1:M_ID', @options[:login]
+ xml.tag! 'ns1:M_KEY', @options[:password]
+ end
+
+ def add_credit_card(xml, credit_card, options)
+ xml.tag! 'ns1:CARDNUMBER', credit_card.number
+ xml.tag! 'ns1:EXPIRATION_DATE', exp_date(credit_card)
+ end
+
+ def add_identification(xml, identification, options)
+ xml.tag! 'ns1:GUID', identification
+ end
+
+ def exp_date(credit_card)
+ year = sprintf('%.4i', credit_card.year)
+ month = sprintf('%.2i', credit_card.month)
+
+ "#{month}#{year[-2..-1]}"
+ end
+
+ def commit(action, request)
+ response = parse(
+ @gateway.ssl_post(
+ @live_url,
+ build_soap_request(action, request),
+ build_headers(action)
+ )
+ )
+
+ case action
+ when :store
+ success = response[:success] == 'true'
+ message = response[:message].downcase.capitalize if response[:message]
+ when :unstore
+ success = response[:delete_data_result] == 'true'
+ message = success ? 'Succeeded' : 'Failed'
+ end
+
+ Response.new(success, message, response,
+ authorization: response[:guid]
+ )
+ end
+
+ ENVELOPE_NAMESPACES = {
+ 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
+ 'xmlns:ns1' => 'https://www.sagepayments.net/web_services/wsVault/wsVault'
+ }
+
+ ACTION_ELEMENTS = {
+ store: 'INSERT_CREDIT_CARD_DATA',
+ unstore: 'DELETE_DATA'
+ }
+
+ def build_soap_request(action, body)
+ xml = Builder::XmlMarkup.new
+
+ xml.instruct!
+ xml.tag! 'SOAP-ENV:Envelope', ENVELOPE_NAMESPACES do
+ xml.tag! 'SOAP-ENV:Body' do
+ xml.tag! "ns1:#{ACTION_ELEMENTS[action]}" do
+ add_customer_data(xml)
+ xml << body
+ end
+ end
+ end
+ xml.target!
+ end
+
+ SOAP_ACTIONS = {
+ store: 'https://www.sagepayments.net/web_services/wsVault/wsVault/INSERT_CREDIT_CARD_DATA',
+ unstore: 'https://www.sagepayments.net/web_services/wsVault/wsVault/DELETE_DATA'
+ }
+
+ def build_headers(action)
+ {
+ 'SOAPAction' => SOAP_ACTIONS[action],
+ 'Content-Type' => 'text/xml; charset=utf-8'
+ }
+ end
+
+ def parse(body)
+ response = {}
+ hashify_xml!(body, response)
+ response
+ end
+
+ def hashify_xml!(xml, response)
+ xml = REXML::Document.new(xml)
+
+ # Store
+ xml.elements.each('//Table1/*') do |node|
+ response[node.name.underscore.to_sym] = node.text
+ end
+
+ # Unstore
+ xml.elements.each('//DELETE_DATAResponse/*') do |node|
+ response[node.name.underscore.to_sym] = node.text
+ end
+ end
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb b/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb
deleted file mode 100644
index 893db3ee4bb..00000000000
--- a/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require File.dirname(__FILE__) + '/sage_core'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class SageBankcardGateway < Gateway #:nodoc:
- include SageCore
- self.live_url = 'https://www.sagepayments.net/cgi-bin/eftBankcard.dll?transaction'
- self.source = 'bankcard'
-
- # Credit cards supported by Sage
- # * VISA
- # * MasterCard
- # * AMEX
- # * Diners
- # * Carte Blanche
- # * Discover
- # * JCB
- # * Sears
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
-
- def authorize(money, credit_card, options = {})
- post = {}
- add_credit_card(post, credit_card)
- add_transaction_data(post, money, options)
- commit(:authorization, post)
- end
-
- def purchase(money, credit_card, options = {})
- post = {}
- add_credit_card(post, credit_card)
- add_transaction_data(post, money, options)
- commit(:purchase, post)
- end
-
- # The +money+ amount is not used. The entire amount of the
- # initial authorization will be captured.
- def capture(money, reference, options = {})
- post = {}
- add_reference(post, reference)
- commit(:capture, post)
- end
-
- def void(reference, options = {})
- post = {}
- add_reference(post, reference)
- commit(:void, post)
- end
-
- def credit(money, credit_card, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, credit_card, options)
- end
-
- def refund(money, credit_card, options = {})
- post = {}
- add_credit_card(post, credit_card)
- add_transaction_data(post, money, options)
- commit(:credit, post)
- end
-
- private
- def exp_date(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
- def add_credit_card(post, credit_card)
- post[:C_name] = credit_card.name
- post[:C_cardnumber] = credit_card.number
- post[:C_exp] = exp_date(credit_card)
- post[:C_cvv] = credit_card.verification_value if credit_card.verification_value?
- end
-
- def parse(data)
- response = {}
- response[:success] = data[1,1]
- response[:code] = data[2,6]
- response[:message] = data[8,32].strip
- response[:front_end] = data[40, 2]
- response[:cvv_result] = data[42, 1]
- response[:avs_result] = data[43, 1].strip
- response[:risk] = data[44, 2]
- response[:reference] = data[46, 10]
-
- response[:order_number], response[:recurring] = data[57...-1].split("\034")
- response
- end
- end
- end
-end
-
diff --git a/lib/active_merchant/billing/gateways/sage/sage_core.rb b/lib/active_merchant/billing/gateways/sage/sage_core.rb
deleted file mode 100644
index 41580d8cc5e..00000000000
--- a/lib/active_merchant/billing/gateways/sage/sage_core.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module SageCore #:nodoc:
- def self.included(base)
- base.cattr_accessor :source
- base.supported_countries = ['US', 'CA']
- base.homepage_url = 'http://www.sagepayments.com'
- base.display_name = 'Sage Payment Solutions'
- end
-
- # Transactions types:
- # 01 - Sale
- # 02 - AuthOnly
- # 03 - Force/PriorAuthSale
- # 04 - Void
- # 06 - Credit
- # 11 - PriorAuthSale by Reference*
- TRANSACTIONS = {
- :purchase => '01',
- :authorization => '02',
- :capture => '11',
- :void => '04',
- :credit => '06'
- }
-
- def initialize(options = {})
- requires!(options, :login, :password)
- super
- end
-
- private
- def add_invoice(post, options)
- post[:T_ordernum] = options[:order_id].slice(0, 20)
- post[:T_tax] = amount(options[:tax]) unless options[:tax].blank?
- post[:T_shipping] = amount(options[:shipping]) unless options[:shipping].blank?
- end
-
- def add_reference(post, reference)
- ref, source = reference.to_s.split(";")
- post[:T_reference] = ref
- end
-
- def add_amount(post, money)
- post[:T_amt] = amount(money)
- end
-
- def add_customer_data(post, options)
- post[:T_customer_number] = options[:customer] if Float(options[:customer]) rescue nil
- end
-
- def add_addresses(post, options)
- billing_address = options[:billing_address] || options[:address] || {}
-
- post[:C_address] = billing_address[:address1]
- post[:C_city] = billing_address[:city]
-
- if ['US', 'CA'].include?(billing_address[:country])
- post[:C_state] = billing_address[:state]
- else
- post[:C_state] = "Outside of United States"
- end
-
- post[:C_zip] = billing_address[:zip]
- post[:C_country] = billing_address[:country]
- post[:C_telephone] = billing_address[:phone]
- post[:C_fax] = billing_address[:fax]
- post[:C_email] = options[:email]
-
- if shipping_address = options[:shipping_address]
- post[:C_ship_name] = shipping_address[:name]
- post[:C_ship_address] = shipping_address[:address1]
- post[:C_ship_city] = shipping_address[:city]
- post[:C_ship_state] = shipping_address[:state]
- post[:C_ship_zip] = shipping_address[:zip]
- post[:C_ship_country] = shipping_address[:country]
- end
- end
-
- def add_transaction_data(post, money, options)
- add_amount(post, money)
- add_invoice(post, options)
- add_addresses(post, options)
- add_customer_data(post, options)
- end
-
- def commit(action, params)
- response = parse(ssl_post(self.live_url, post_data(action, params)))
-
- Response.new(success?(response), response[:message], response,
- :test => test?,
- :authorization => authorization_from(response),
- :avs_result => { :code => response[:avs_result] },
- :cvv_result => response[:cvv_result]
- )
- end
-
- def authorization_from(response)
- "#{response[:reference]};#{source}"
- end
-
- def success?(response)
- response[:success] == 'A'
- end
-
- def post_data(action, params = {})
- params[:M_id] = @options[:login]
- params[:M_key] = @options[:password]
- params[:T_code] = TRANSACTIONS[action]
-
- params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb b/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb
deleted file mode 100644
index e197fe5d1fa..00000000000
--- a/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb
+++ /dev/null
@@ -1,102 +0,0 @@
-require File.dirname(__FILE__) + '/sage_core'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class SageVirtualCheckGateway < Gateway #:nodoc:
- include SageCore
- self.live_url = 'https://www.sagepayments.net/cgi-bin/eftVirtualCheck.dll?transaction'
- self.source = 'virtual_check'
-
- def purchase(money, credit_card, options = {})
- post = {}
- add_check(post, credit_card)
- add_check_customer_data(post, options)
- add_transaction_data(post, money, options)
- commit(:purchase, post)
- end
-
- def void(reference, options = {})
- post = {}
- add_reference(post, reference)
- commit(:void, post)
- end
-
- def credit(money, credit_card, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
- refund(money, source, options)
- end
-
- def refund(money, credit_card, options = {})
- post = {}
- add_check(post, credit_card)
- add_check_customer_data(post, options)
- add_transaction_data(post, money, options)
- commit(:credit, post)
- end
-
- private
- def add_check(post, check)
- post[:C_first_name] = check.first_name
- post[:C_last_name] = check.last_name
- post[:C_rte] = check.routing_number
- post[:C_acct] = check.account_number
- post[:C_check_number] = check.number
- post[:C_acct_type] = account_type(check)
- end
-
- def add_check_customer_data(post, options)
- # Required Customer Type – (NACHA Transaction Class)
- # CCD for Commercial, Merchant Initiated
- # PPD for Personal, Merchant Initiated
- # WEB for Internet, Consumer Initiated
- # RCK for Returned Checks
- # ARC for Account Receivable Entry
- # TEL for TelephoneInitiated
- post[:C_customer_type] = "WEB"
-
- # Optional 10 Digit Originator ID – Assigned By for each transaction class or business purpose. If not provided, the default Originator ID for the specific Customer Type will be applied.
- post[:C_originator_id] = options[:originator_id]
-
- # Optional Transaction Addenda
- post[:T_addenda] = options[:addenda]
-
- # Required Check Writer Social Security Number ( Numbers Only, No Dashes )
- post[:C_ssn] = options[:ssn].to_s.gsub(/[^\d]/, '')
-
- post[:C_dl_state_code] = options[:drivers_license_state]
- post[:C_dl_number] = options[:drivers_license_number]
- post[:C_dob] = format_birth_date(options[:date_of_birth])
- end
-
- def format_birth_date(date)
- date.respond_to?(:strftime) ? date.strftime("%m/%d/%Y") : date
- end
-
- # DDA for Checking
- # SAV for Savings
- def account_type(check)
- case check.account_type
- when 'checking' then 'DDA'
- when 'savings' then 'SAV'
- else raise ArgumentError, "Unknown account type #{check.account_type}"
- end
- end
-
- def parse(data)
- response = {}
- response[:success] = data[1,1]
- response[:code] = data[2,6].strip
- response[:message] = data[8,32].strip
- response[:risk] = data[40, 2]
- response[:reference] = data[42, 10]
-
- extra_data = data[53...-1].split("\034")
- response[:order_number] = extra_data[0]
- response[:authentication_indicator] = extra_data[1]
- response[:authentication_disclosure] = extra_data[2]
- response
- end
- end
- end
-end
-
diff --git a/lib/active_merchant/billing/gateways/sage_pay.rb b/lib/active_merchant/billing/gateways/sage_pay.rb
index d56d80318e5..a3cd5f7b657 100644
--- a/lib/active_merchant/billing/gateways/sage_pay.rb
+++ b/lib/active_merchant/billing/gateways/sage_pay.rb
@@ -18,32 +18,58 @@ class SagePayGateway < Gateway
:authorization => 'DEFERRED',
:capture => 'RELEASE',
:void => 'VOID',
- :abort => 'ABORT'
+ :abort => 'ABORT',
+ :store => 'TOKEN',
+ :unstore => 'REMOVETOKEN',
+ :repeat => 'REPEAT'
}
CREDIT_CARDS = {
- :visa => "VISA",
- :master => "MC",
- :delta => "DELTA",
- :solo => "SOLO",
- :switch => "MAESTRO",
- :maestro => "MAESTRO",
- :american_express => "AMEX",
- :electron => "UKE",
- :diners_club => "DC",
- :jcb => "JCB"
+ :visa => 'VISA',
+ :master => 'MC',
+ :delta => 'DELTA',
+ :maestro => 'MAESTRO',
+ :american_express => 'AMEX',
+ :electron => 'UKE',
+ :diners_club => 'DC',
+ :jcb => 'JCB'
}
- ELECTRON = /^(424519|42496[23]|450875|48440[6-8]|4844[1-5][1-5]|4917[3-5][0-9]|491880)\d{10}(\d{3})?$/
+ AVS_CODE = {
+ 'NOTPROVIDED' => nil,
+ 'NOTCHECKED' => 'X',
+ 'MATCHED' => 'Y',
+ 'NOTMATCHED' => 'N'
+ }
+
+ CVV_CODE = {
+ 'NOTPROVIDED' => 'S',
+ 'NOTCHECKED' => 'X',
+ 'MATCHED' => 'M',
+ 'NOTMATCHED' => 'N'
+ }
- AVS_CVV_CODE = {
- "NOTPROVIDED" => nil,
- "NOTCHECKED" => 'X',
- "MATCHED" => 'Y',
- "NOTMATCHED" => 'N'
+ OPTIONAL_REQUEST_FIELDS = {
+ paypal_callback_url: :PayPalCallbackURL,
+ basket: :Basket,
+ gift_aid_payment: :GiftAidPayment,
+ apply_avscv2: :ApplyAVSCV2,
+ apply_3d_secure: :Apply3DSecure,
+ account_type: :AccountType,
+ billing_agreement: :BillingAgreement,
+ basket_xml: :BasketXML,
+ customer_xml: :CustomerXML,
+ surcharge_xml: :SurchargeXML,
+ vendor_data: :VendorData,
+ language: :Language,
+ website: :Website,
+ recipient_account_number: :FIRecipientAcctNumber,
+ recipient_surname: :FIRecipientSurname,
+ recipient_postcode: :FIRecipientPostcode,
+ recipient_dob: :FIRecipientDoB
}
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :switch, :solo, :maestro, :diners_club]
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :diners_club]
self.supported_countries = ['GB', 'IE']
self.default_currency = 'GBP'
@@ -55,29 +81,29 @@ def initialize(options = {})
super
end
- def purchase(money, credit_card, options = {})
+ def purchase(money, payment_method, options = {})
requires!(options, :order_id)
post = {}
add_amount(post, money, options)
add_invoice(post, options)
- add_credit_card(post, credit_card)
+ add_payment_method(post, payment_method, options)
add_address(post, options)
add_customer_data(post, options)
add_optional_data(post, options)
- commit(:purchase, post)
+ commit((past_purchase_reference?(payment_method) ? :repeat : :purchase), post)
end
- def authorize(money, credit_card, options = {})
+ def authorize(money, payment_method, options = {})
requires!(options, :order_id)
post = {}
add_amount(post, money, options)
add_invoice(post, options)
- add_credit_card(post, credit_card)
+ add_payment_method(post, payment_method, options)
add_address(post, options)
add_customer_data(post, options)
add_optional_data(post, options)
@@ -110,7 +136,7 @@ def refund(money, identification, options = {})
post = {}
- add_credit_reference(post, identification)
+ add_related_reference(post, identification)
add_amount(post, money, options)
add_invoice(post, options)
@@ -118,11 +144,55 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
+ def store(credit_card, options = {})
+ post = {}
+ add_credit_card(post, credit_card)
+ add_currency(post, 0, options)
+
+ commit(:store, post)
+ end
+
+ def unstore(token, options = {})
+ post = {}
+ add_token(post, token)
+ commit(:unstore, 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 supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?CardNumber=)\d+(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?CV2=)\d+(&?)), '\1[FILTERED]\2')
+ end
+
private
+
+ def truncate(value, max_size)
+ return nil unless value
+ return value.to_s if CGI.escape(value.to_s).length <= max_size
+
+ if value.size > max_size
+ truncate(super(value, max_size), max_size)
+ else
+ truncate(value.to_s.chop, max_size)
+ end
+ end
+
def add_reference(post, identification)
order_id, transaction_id, authorization, security_key = identification.split(';')
@@ -132,7 +202,7 @@ def add_reference(post, identification)
add_pair(post, :SecurityKey, security_key)
end
- def add_credit_reference(post, identification)
+ def add_related_reference(post, identification)
order_id, transaction_id, authorization, security_key = identification.split(';')
add_pair(post, :RelatedVendorTxCode, order_id)
@@ -147,79 +217,115 @@ def add_amount(post, money, options)
add_pair(post, :Currency, currency, :required => true)
end
+ def add_currency(post, money, options)
+ currency = options[:currency] || currency(money)
+ add_pair(post, :Currency, currency, :required => true)
+ end
+
# doesn't actually use the currency -- dodgy!
def add_release_amount(post, money, options)
add_pair(post, :ReleaseAmount, amount(money), :required => true)
end
def add_customer_data(post, options)
- add_pair(post, :CustomerEMail, options[:email][0,255]) unless options[:email].blank?
- add_pair(post, :BillingPhone, options[:phone].gsub(/[^0-9+]/, '')[0,20]) unless options[:phone].blank?
+ add_pair(post, :CustomerEMail, truncate(options[:email], 255)) unless options[:email].blank?
add_pair(post, :ClientIPAddress, options[:ip])
end
def add_optional_data(post, options)
- add_pair(post, :GiftAidPayment, options[:gift_aid_payment]) unless options[:gift_aid_payment].blank?
- add_pair(post, :Apply3DSecure, options[:apply_3d_secure]) unless options[:apply_3d_secure].blank?
+ add_pair(post, :CreateToken, 1) unless options[:store].blank?
+
+ OPTIONAL_REQUEST_FIELDS.each do |gateway_option, sagepay_field|
+ add_pair(post, sagepay_field, options[gateway_option])
+ end
end
def add_address(post, options)
if billing_address = options[:billing_address] || options[:address]
- first_name, last_name = parse_first_and_last_name(billing_address[:name])
- add_pair(post, :BillingSurname, last_name)
- add_pair(post, :BillingFirstnames, first_name)
- add_pair(post, :BillingAddress1, billing_address[:address1])
- add_pair(post, :BillingAddress2, billing_address[:address2])
- add_pair(post, :BillingCity, billing_address[:city])
- add_pair(post, :BillingState, billing_address[:state]) if billing_address[:country] == 'US'
- add_pair(post, :BillingCountry, billing_address[:country])
- add_pair(post, :BillingPostCode, billing_address[:zip])
+ first_name, last_name = split_names(billing_address[:name])
+ add_pair(post, :BillingSurname, truncate(last_name, 20))
+ add_pair(post, :BillingFirstnames, truncate(first_name, 20))
+ add_pair(post, :BillingAddress1, truncate(billing_address[:address1], 100))
+ add_pair(post, :BillingAddress2, truncate(billing_address[:address2], 100))
+ add_pair(post, :BillingCity, truncate(billing_address[:city], 40))
+ add_pair(post, :BillingState, truncate(billing_address[:state], 2)) if is_usa(billing_address[:country])
+ add_pair(post, :BillingCountry, truncate(billing_address[:country], 2))
+ add_pair(post, :BillingPhone, sanitize_phone(billing_address[:phone]))
+ add_pair(post, :BillingPostCode, truncate(billing_address[:zip], 10))
end
if shipping_address = options[:shipping_address] || billing_address
- first_name, last_name = parse_first_and_last_name(shipping_address[:name])
- add_pair(post, :DeliverySurname, last_name)
- add_pair(post, :DeliveryFirstnames, first_name)
- add_pair(post, :DeliveryAddress1, shipping_address[:address1])
- add_pair(post, :DeliveryAddress2, shipping_address[:address2])
- add_pair(post, :DeliveryCity, shipping_address[:city])
- add_pair(post, :DeliveryState, shipping_address[:state]) if shipping_address[:country] == 'US'
- add_pair(post, :DeliveryCountry, shipping_address[:country])
- add_pair(post, :DeliveryPostCode, shipping_address[:zip])
+ first_name, last_name = split_names(shipping_address[:name])
+ add_pair(post, :DeliverySurname, truncate(last_name, 20))
+ add_pair(post, :DeliveryFirstnames, truncate(first_name, 20))
+ add_pair(post, :DeliveryAddress1, truncate(shipping_address[:address1], 100))
+ add_pair(post, :DeliveryAddress2, truncate(shipping_address[:address2], 100))
+ add_pair(post, :DeliveryCity, truncate(shipping_address[:city], 40))
+ add_pair(post, :DeliveryState, truncate(shipping_address[:state], 2)) if is_usa(shipping_address[:country])
+ add_pair(post, :DeliveryCountry, truncate(shipping_address[:country], 2))
+ add_pair(post, :DeliveryPhone, sanitize_phone(shipping_address[:phone]))
+ add_pair(post, :DeliveryPostCode, truncate(shipping_address[:zip], 10))
end
end
def add_invoice(post, options)
add_pair(post, :VendorTxCode, sanitize_order_id(options[:order_id]), :required => true)
- add_pair(post, :Description, options[:description] || options[:order_id])
+ add_pair(post, :Description, truncate(options[:description] || options[:order_id], 100))
+ end
+
+ def add_payment_method(post, payment_method, options)
+ if payment_method.is_a?(String)
+ if past_purchase_reference?(payment_method)
+ add_related_reference(post, payment_method)
+ else
+ add_token_details(post, payment_method, options)
+ end
+ else
+ add_credit_card(post, payment_method)
+ end
end
def add_credit_card(post, credit_card)
- add_pair(post, :CardHolder, credit_card.name, :required => true)
+ add_pair(post, :CardHolder, truncate(credit_card.name, 50), :required => true)
add_pair(post, :CardNumber, credit_card.number, :required => true)
add_pair(post, :ExpiryDate, format_date(credit_card.month, credit_card.year), :required => true)
-
- if requires_start_date_or_issue_number?(credit_card)
- add_pair(post, :StartDate, format_date(credit_card.start_month, credit_card.start_year))
- add_pair(post, :IssueNumber, credit_card.issue_number)
- end
add_pair(post, :CardType, map_card_type(credit_card))
add_pair(post, :CV2, credit_card.verification_value)
end
+ def add_token_details(post, token, options)
+ add_token(post, token)
+ add_pair(post, :StoreToken, options[:customer])
+ add_pair(post, :CV2, options[:verification_value])
+ end
+
+ def add_token(post, token)
+ add_pair(post, :Token, token)
+ end
+
def sanitize_order_id(order_id)
- order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '')
+ cleansed = order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '')
+ truncate(cleansed, 40)
+ end
+
+ def sanitize_phone(phone)
+ return nil unless phone
+ cleansed = phone.to_s.gsub(/[^0-9+]/, '')
+ truncate(cleansed, 20)
+ end
+
+ def is_usa(country)
+ truncate(country, 2) == 'US'
end
def map_card_type(credit_card)
- raise ArgumentError, "The credit card type must be provided" if card_brand(credit_card).blank?
+ raise ArgumentError, 'The credit card type must be provided' if card_brand(credit_card).blank?
card_type = card_brand(credit_card).to_sym
- # Check if it is an electron card
- if card_type == :visa && credit_card.number =~ ELECTRON
+ if card_type == :visa && credit_card.electron?
CREDIT_CARDS[:electron]
else
CREDIT_CARDS[card_type]
@@ -230,32 +336,37 @@ def map_card_type(credit_card)
def format_date(month, year)
return nil if year.blank? || month.blank?
- year = sprintf("%.4i", year)
- month = sprintf("%.2i", month)
+ year = sprintf('%.4i', year)
+ month = sprintf('%.2i', month)
"#{month}#{year[-2..-1]}"
end
def commit(action, parameters)
- response = parse( ssl_post(url_for(action), post_data(action, parameters)) )
+ response = parse(ssl_post(url_for(action), post_data(action, parameters)))
- Response.new(response["Status"] == APPROVED, message_from(response), response,
+ Response.new(response['Status'] == APPROVED, message_from(response), response,
:test => test?,
:authorization => authorization_from(response, parameters, action),
:avs_result => {
- :street_match => AVS_CVV_CODE[ response["AddressResult"] ],
- :postal_match => AVS_CVV_CODE[ response["PostCodeResult"] ],
+ :street_match => AVS_CODE[response['AddressResult']],
+ :postal_match => AVS_CODE[response['PostCodeResult']],
},
- :cvv_result => AVS_CVV_CODE[ response["CV2Result"] ]
+ :cvv_result => CVV_CODE[response['CV2Result']]
)
end
def authorization_from(response, params, action)
- [ params[:VendorTxCode],
- response["VPSTxId"],
- response["TxAuthNo"],
- response["SecurityKey"],
- action ].join(";")
+ case action
+ when :store
+ response['Token']
+ else
+ [ params[:VendorTxCode],
+ response['VPSTxId'] || params[:VPSTxId],
+ response['TxAuthNo'],
+ response['SecurityKey'] || params[:SecurityKey],
+ action ].join(';')
+ end
end
def abort_or_void_from(identification)
@@ -268,12 +379,16 @@ def url_for(action)
end
def build_url(action)
- endpoint = [ :purchase, :authorization ].include?(action) ? "vspdirect-register" : TRANSACTIONS[action].downcase
+ endpoint = case action
+ when :purchase, :authorization then 'vspdirect-register'
+ when :store then 'directtoken'
+ else TRANSACTIONS[action].downcase
+ end
"#{test? ? self.test_url : self.live_url}/#{endpoint}.vsp"
end
def build_simulator_url(action)
- endpoint = [ :purchase, :authorization ].include?(action) ? "VSPDirectGateway.asp" : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx"
+ endpoint = [ :purchase, :authorization ].include?(action) ? 'VSPDirectGateway.asp' : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx"
"#{self.simulator_url}/#{endpoint}"
end
@@ -285,10 +400,14 @@ def post_data(action, parameters = {})
parameters.update(
:Vendor => @options[:login],
:TxType => TRANSACTIONS[action],
- :VPSProtocol => "2.23"
+ :VPSProtocol => @options.fetch(:protocol_version, '3.00')
)
- parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ if(application_id && (application_id != Gateway.application_id))
+ parameters.update(:ReferrerID => application_id)
+ end
+
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
# SagePay returns data in the following format
@@ -306,19 +425,10 @@ def add_pair(post, key, value, options = {})
post[key] = value if !value.blank? || options[:required]
end
- def parse_first_and_last_name(value)
- name = value.to_s.split(' ')
-
- last_name = name.pop || ''
- first_name = name.join(' ')
- [ first_name[0,20], last_name[0,20] ]
- end
-
- def localized_amount(money, currency)
- amount = amount(money)
- CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) ? amount.split('.').first : amount
+ def past_purchase_reference?(payment_method)
+ return false unless payment_method.is_a?(String)
+ payment_method.split(';').last == 'purchase'
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/sallie_mae.rb b/lib/active_merchant/billing/gateways/sallie_mae.rb
index 1098790d292..7e15f08e232 100644
--- a/lib/active_merchant/billing/gateways/sallie_mae.rb
+++ b/lib/active_merchant/billing/gateways/sallie_mae.rb
@@ -21,7 +21,7 @@ def initialize(options = {})
end
def test?
- @options[:login] == "TEST0"
+ @options[:login] == 'TEST0'
end
def authorize(money, creditcard, options = {})
@@ -94,11 +94,11 @@ def add_creditcard(post, creditcard)
def parse(body)
h = {}
- body.gsub!("", "")
+ body.gsub!('', '')
body.
split("\r\n").
map do |i|
- a = i.split("=")
+ a = i.split('=')
h[a.first] = a.last unless a.first.nil?
end
h
@@ -111,33 +111,32 @@ def commit(action, money, parameters)
case action
when :sale
- parameters[:action] = "ns_quicksale_cc"
+ parameters[:action] = 'ns_quicksale_cc'
when :authonly
- parameters[:action] = "ns_quicksale_cc"
+ parameters[:action] = 'ns_quicksale_cc'
parameters[:authonly] = 1
when :capture
- parameters[:action] = "ns_quicksale_cc"
+ parameters[:action] = 'ns_quicksale_cc'
end
- response = parse(ssl_post(self.live_url, parameters.to_post_data) || "")
+ response = parse(ssl_post(self.live_url, parameters.to_post_data) || '')
Response.new(successful?(response), message_from(response), response,
:test => test?,
- :authorization => response["refcode"]
+ :authorization => response['refcode']
)
end
def successful?(response)
- response["Status"] == "Accepted"
+ response['Status'] == 'Accepted'
end
def message_from(response)
if successful?(response)
- "Accepted"
+ 'Accepted'
else
- response["Reason"].split(":")[2].capitalize unless response["Reason"].nil?
+ response['Reason'].split(':')[2].capitalize unless response['Reason'].nil?
end
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/samurai.rb b/lib/active_merchant/billing/gateways/samurai.rb
deleted file mode 100644
index c263c79f0d3..00000000000
--- a/lib/active_merchant/billing/gateways/samurai.rb
+++ /dev/null
@@ -1,118 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- class SamuraiGateway < Gateway
-
- self.homepage_url = 'https://samurai.feefighters.com'
- self.display_name = 'Samurai'
- self.supported_countries = ['US']
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
- self.default_currency = 'USD'
- self.money_format = :dollars
-
- def initialize(options = {})
- begin
- require 'samurai'
- rescue LoadError
- raise "Could not load the samurai gem (>= 0.2.25). Use `gem install samurai` to install it."
- end
-
- requires!(options, :login, :password, :processor_token)
- Samurai.options = {
- :merchant_key => options[:login],
- :merchant_password => options[:password],
- :processor_token => options[:processor_token]
- }
-
- super
- end
-
- def authorize(money, credit_card_or_vault_id, options = {})
- token = payment_method_token(credit_card_or_vault_id, options)
- return token if token.is_a?(Response)
-
- authorize = Samurai::Processor.authorize(token, amount(money), processor_options(options))
- handle_result(authorize)
- end
-
- def purchase(money, credit_card_or_vault_id, options = {})
- token = payment_method_token(credit_card_or_vault_id, options)
- return token if token.is_a?(Response)
-
- purchase = Samurai::Processor.purchase(token, amount(money), processor_options(options))
- handle_result(purchase)
- end
-
- def capture(money, authorization_id, options = {})
- transaction = Samurai::Transaction.find(authorization_id)
- handle_result(transaction.capture(amount(money)))
- end
-
- def refund(money, transaction_id, options = {})
- transaction = Samurai::Transaction.find(transaction_id)
- handle_result(transaction.credit(amount(money)))
- end
-
- def void(transaction_id, options = {})
- transaction = Samurai::Transaction.find(transaction_id)
- handle_result(transaction.void)
- end
-
- def store(creditcard, options = {})
- address = options[:billing_address] || options[:address] || {}
-
- result = Samurai::PaymentMethod.create({
- :card_number => creditcard.number,
- :expiry_month => creditcard.month.to_s.rjust(2, "0"),
- :expiry_year => creditcard.year.to_s,
- :cvv => creditcard.verification_value,
- :first_name => creditcard.first_name,
- :last_name => creditcard.last_name,
- :address_1 => address[:address1],
- :address_2 => address[:address2],
- :city => address[:city],
- :zip => address[:zip],
- :sandbox => test?
- })
- result.retain if options[:retain] && result.is_sensitive_data_valid && result.payment_method_token
-
- Response.new(result.is_sensitive_data_valid,
- message_from_result(result),
- { :payment_method_token => result.is_sensitive_data_valid && result.payment_method_token })
- end
-
- private
-
- def payment_method_token(credit_card_or_vault_id, options)
- return credit_card_or_vault_id if credit_card_or_vault_id.is_a?(String)
- store_result = store(credit_card_or_vault_id, options)
- store_result.success? ? store_result.params["payment_method_token"] : store_result
- end
-
- def handle_result(result)
- response_params, response_options = {}, {}
- if result.success?
- response_options[:test] = test?
- response_options[:authorization] = result.reference_id
- response_params[:reference_id] = result.reference_id
- response_params[:transaction_token] = result.transaction_token
- response_params[:payment_method_token] = result.payment_method.payment_method_token
- end
-
- response_options[:avs_result] = { :code => result.processor_response && result.processor_response.avs_result_code }
- response_options[:cvv_result] = result.processor_response && result.processor_response.cvv_result_code
-
- message = message_from_result(result)
- Response.new(result.success?, message, response_params, response_options)
- end
-
- def message_from_result(result)
- return "OK" if result.success?
- result.errors.map {|_, messages| messages }.join(" ")
- end
-
- def processor_options(options)
- options.slice(:billing_reference, :customer_reference, :custom, :descriptor)
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/gateways/secure_net.rb b/lib/active_merchant/billing/gateways/secure_net.rb
index fd2aad4e282..f3abdf29935 100644
--- a/lib/active_merchant/billing/gateways/secure_net.rb
+++ b/lib/active_merchant/billing/gateways/secure_net.rb
@@ -2,21 +2,21 @@ module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class SecureNetGateway < Gateway
- API_VERSION = "4.0"
+ API_VERSION = '4.0'
TRANSACTIONS = {
- :auth_only => "0000",
- :auth_capture => "0100",
- :prior_auth_capture => "0200",
- :void => "0400",
- :credit => "0500"
+ :auth_only => '0000',
+ :auth_capture => '0100',
+ :prior_auth_capture => '0200',
+ :void => '0400',
+ :credit => '0500'
}
XML_ATTRIBUTES = {
- 'xmlns' => "http://gateway.securenet.com/API/Contracts",
- 'xmlns:i' => "http://www.w3.org/2001/XMLSchema-instance"
+ 'xmlns' => 'http://gateway.securenet.com/API/Contracts',
+ 'xmlns:i' => 'http://www.w3.org/2001/XMLSchema-instance'
}
- NIL_ATTRIBUTE = { 'i:nil' => "true" }
+ NIL_ATTRIBUTE = { 'i:nil' => 'true' }
self.supported_countries = ['US']
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
@@ -57,16 +57,27 @@ def refund(money, authorization, options = {})
end
def credit(money, authorization, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, authorization, options)
end
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()\d+())i, '\1[FILTERED]\2').
+ gsub(%r(()\d+())i, '\1[FILTERED]\2').
+ gsub(%r(().+())i, '\1[FILTERED]\2')
+ end
private
+
def commit(request)
xml = build_request(request)
url = test? ? self.test_url : self.live_url
- data = ssl_post(url, xml, "Content-Type" => "text/xml")
+ data = ssl_post(url, xml, 'Content-Type' => 'text/xml')
response = parse(data)
Response.new(success?(response), message_from(response), response,
@@ -81,7 +92,7 @@ def build_request(request)
xml = Builder::XmlMarkup.new
xml.instruct!
- xml.tag!("TRANSACTION", XML_ATTRIBUTES) do
+ xml.tag!('TRANSACTION', XML_ATTRIBUTES) do
xml << request
end
@@ -93,9 +104,7 @@ def build_sale_or_authorization(creditcard, options, action, money)
xml.tag! 'AMOUNT', amount(money)
add_credit_card(xml, creditcard)
- add_required_params(xml, action, options)
- add_address(xml, creditcard, options)
- add_invoice(xml, options)
+ add_params_in_required_order(xml, action, creditcard, options)
add_more_required_params(xml, options)
xml.target!
@@ -107,11 +116,11 @@ def build_capture_refund_void(authorization, options, action, money = nil)
transaction_id, amount_in_ref, last_four = split_authorization(authorization)
xml.tag! 'AMOUNT', amount(money) || amount_in_ref
- xml.tag!("CARD") do
+ xml.tag!('CARD') do
xml.tag! 'CARDNUMBER', last_four
end
- add_required_params(xml, action, options)
+ add_params_in_required_order(xml, action, nil, options)
xml.tag! 'REF_TRANSID', transaction_id
add_more_required_params(xml, options)
@@ -119,20 +128,13 @@ def build_capture_refund_void(authorization, options, action, money = nil)
end
def add_credit_card(xml, creditcard)
- xml.tag!("CARD") do
+ xml.tag!('CARD') do
xml.tag! 'CARDCODE', creditcard.verification_value if creditcard.verification_value?
xml.tag! 'CARDNUMBER', creditcard.number
xml.tag! 'EXPDATE', expdate(creditcard)
end
end
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
def add_customer_data(xml, options)
if options.has_key? :customer
xml.tag! 'CUSTOMERID', options[:customer]
@@ -144,9 +146,10 @@ def add_customer_data(xml, options)
end
def add_address(xml, creditcard, options)
+ return unless creditcard
if address = options[:billing_address] || options[:address]
- xml.tag!("CUSTOMER_BILL") do
+ xml.tag!('CUSTOMER_BILL') do
xml.tag! 'ADDRESS', address[:address1].to_s
xml.tag! 'CITY', address[:city].to_s
xml.tag! 'COMPANY', address[:company].to_s
@@ -164,13 +167,21 @@ def add_address(xml, creditcard, options)
end
if address = options[:shipping_address]
- xml.tag!("CUSTOMER_SHIP") do
+ xml.tag!('CUSTOMER_SHIP') do
xml.tag! 'ADDRESS', address[:address1].to_s
xml.tag! 'CITY', address[:city].to_s
xml.tag! 'COMPANY', address[:company].to_s
xml.tag! 'COUNTRY', address[:country].to_s
- xml.tag! 'FIRSTNAME', address[:first_name].to_s
- xml.tag! 'LASTNAME', address[:last_name].to_s
+
+ if address[:name]
+ first_name, last_name = split_names(address[:name])
+ xml.tag! 'FIRSTNAME', first_name
+ xml.tag! 'LASTNAME', last_name
+ else
+ xml.tag! 'FIRSTNAME', address[:first_name].to_s
+ xml.tag! 'LASTNAME', address[:last_name].to_s
+ end
+
xml.tag! 'STATE', address[:state].blank? ? 'n/a' : address[:state]
xml.tag! 'ZIP', address[:zip].to_s
end
@@ -178,39 +189,39 @@ def add_address(xml, creditcard, options)
xml.tag!('CUSTOMER_SHIP', NIL_ATTRIBUTE) do
end
end
-
- end
-
- def add_invoice(xml, options)
- xml.tag! 'NOTE', options[:description] if options[:description]
- xml.tag! 'INVOICEDESC', options[:invoice_description] if options[:invoice_description]
- xml.tag! 'INVOICENUM', options[:invoice_number] if options[:invoice_number]
end
def add_merchant_key(xml, options)
- xml.tag!("MERCHANT_KEY") do
+ xml.tag!('MERCHANT_KEY') do
xml.tag! 'GROUPID', 0
xml.tag! 'SECUREKEY', @options[:password]
xml.tag! 'SECURENETID', @options[:login]
end
end
- def add_required_params(xml, action, options)
+ # SecureNet requires some of the xml params to be in a certain order. http://cl.ly/image/3K260E0p0a0n/content.png
+ def add_params_in_required_order(xml, action, creditcard, options)
xml.tag! 'CODE', TRANSACTIONS[action]
add_customer_data(xml, options)
+ add_address(xml, creditcard, options)
xml.tag! 'DCI', 0 # No duplicate checking will be done, except for ORDERID
xml.tag! 'INSTALLMENT_SEQUENCENUM', 1
+ xml.tag! 'INVOICEDESC', options[:invoice_description] if options[:invoice_description]
+ xml.tag! 'INVOICENUM', options[:invoice_number] if options[:invoice_number]
add_merchant_key(xml, options)
xml.tag! 'METHOD', 'CC'
- xml.tag! 'ORDERID', options[:order_id]
+ xml.tag! 'NOTE', options[:description] if options[:description]
+ xml.tag! 'ORDERID', truncate(options[:order_id], 25)
xml.tag! 'OVERRIDE_FROM', 0 # Docs say not required, but doesn't work without it
end
def add_more_required_params(xml, options)
+ test_mode = options[:test_mode].nil? ? test? : options[:test_mode]
xml.tag! 'RETAIL_LANENUM', '0'
- xml.tag! 'TEST', 'TRUE' if test?
+ xml.tag! 'TEST', test_mode ? 'TRUE' : 'FALSE'
xml.tag! 'TOTAL_INSTALLMENTCOUNT', 0
xml.tag! 'TRANSACTION_SERVICE', 0
+ xml.tag! 'DEVELOPERID', options[:developer_id] if options[:developer_id]
end
def success?(response)
@@ -218,18 +229,13 @@ def success?(response)
end
def message_from(response)
- if response[:response_code].to_i == DECLINED
- return CVVResult.messages[ response[:card_code_response_code] ] if CARD_CODE_ERRORS.include?(response[:card_code_response_code])
- return AVSResult.messages[ response[:avs_result_code] ] if AVS_ERRORS.include?(response[:avs_result_code])
- end
-
return response[:response_reason_text].nil? ? '' : response[:response_reason_text][0..-1]
end
def parse(xml)
response = {}
xml = REXML::Document.new(xml)
- root = REXML::XPath.first(xml, "//GATEWAYRESPONSE")
+ root = REXML::XPath.first(xml, '//GATEWAYRESPONSE')
if root
root.elements.to_a.each do |node|
recurring_parse_element(response, node)
@@ -241,22 +247,21 @@ def parse(xml)
def recurring_parse_element(response, node)
if node.has_elements?
- node.elements.each{|e| recurring_parse_element(response, e) }
+ node.elements.each { |e| recurring_parse_element(response, e) }
else
response[node.name.underscore.to_sym] = node.text
end
end
def split_authorization(authorization)
- transaction_id, amount, last_four = authorization.split("|")
+ transaction_id, amount, last_four = authorization.split('|')
[transaction_id, amount, last_four]
end
def build_authorization(response)
- [response[:transactionid], response[:transactionamount], response[:last4_digits]].join("|")
+ [response[:transactionid], response[:transactionamount], response[:last4_digits]].join('|')
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/secure_pay.rb b/lib/active_merchant/billing/gateways/secure_pay.rb
index 96aca9e82e1..4cd0c8a63d3 100644
--- a/lib/active_merchant/billing/gateways/secure_pay.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay.rb
@@ -1,28 +1,200 @@
-require File.dirname(__FILE__) + '/authorize_net'
+require 'active_merchant/billing/gateways/authorize_net'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- class SecurePayGateway < AuthorizeNetGateway
+ class SecurePayGateway < Gateway
+ API_VERSION = '3.1'
+
self.live_url = self.test_url = 'https://www.securepay.com/AuthSpayAdapter/process.aspx'
+ class_attribute :duplicate_window
+
+ APPROVED, DECLINED, ERROR, FRAUD_REVIEW = 1, 2, 3, 4
+
+ RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT, AUTHORIZATION_CODE = 0, 2, 3, 4
+ AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE, CARDHOLDER_AUTH_CODE = 5, 6, 38, 39
+
+ self.default_currency = 'USD'
+
+ self.supported_countries = %w(US CA GB AU)
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
self.homepage_url = 'http://www.securepay.com/'
self.display_name = 'SecurePay'
- # Limit support to purchase() for the time being
- # JRuby chokes here
- # undef_method :authorize, :capture, :void, :credit
+ CARD_CODE_ERRORS = %w( N S )
+ AVS_ERRORS = %w( A E N R W Z )
+ AVS_REASON_CODES = %w(27 45)
+ TRANSACTION_ALREADY_ACTIONED = %w(310 311)
+
+ def initialize(options = {})
+ requires!(options, :login, :password)
+ super
+ end
- undef_method :authorize
- undef_method :capture
- undef_method :void
- undef_method :credit
+ def purchase(money, paysource, options = {})
+ post = {}
+ add_currency_code(post, money, options)
+ add_invoice(post, options)
+ add_payment_source(post, paysource, options)
+ add_address(post, options)
+ add_customer_data(post, options)
+ add_duplicate_window(post)
+
+ commit('AUTH_CAPTURE', money, post)
+ end
private
+ def commit(action, money, parameters)
+ parameters[:amount] = amount(money) unless action == 'VOID'
+
+ url = (test? ? self.test_url : self.live_url)
+ data = ssl_post(url, post_data(action, parameters))
+
+ response = parse(data)
+ response[:action] = action
+
+ message = message_from(response)
+
+ Response.new(success?(response), message, response,
+ :test => test?,
+ :authorization => response[:transaction_id],
+ :fraud_review => fraud_review?(response),
+ :avs_result => { :code => response[:avs_result_code] },
+ :cvv_result => response[:card_code]
+ )
+ end
+
+ def success?(response)
+ response[:response_code] == APPROVED && TRANSACTION_ALREADY_ACTIONED.exclude?(response[:response_reason_code])
+ end
+
+ def fraud_review?(response)
+ response[:response_code] == FRAUD_REVIEW
+ end
+
+ def parse(body)
+ fields = split(body)
+
+ results = {
+ :response_code => fields[RESPONSE_CODE].to_i,
+ :response_reason_code => fields[RESPONSE_REASON_CODE],
+ :response_reason_text => fields[RESPONSE_REASON_TEXT],
+ :avs_result_code => fields[AVS_RESULT_CODE],
+ :transaction_id => fields[TRANSACTION_ID],
+ :card_code => fields[CARD_CODE_RESPONSE_CODE],
+ :authorization_code => fields[AUTHORIZATION_CODE],
+ :cardholder_authentication_code => fields[CARDHOLDER_AUTH_CODE]
+ }
+ results
+ end
+
+ def post_data(action, parameters = {})
+ post = {}
+
+ post[:version] = API_VERSION
+ post[:login] = @options[:login]
+ post[:tran_key] = @options[:password]
+ post[:relay_response] = 'FALSE'
+ post[:type] = action
+ post[:delim_data] = 'TRUE'
+ post[:delim_char] = ','
+ post[:encap_char] = '$'
+ post[:solution_ID] = application_id if application_id
+
+ request = post.merge(parameters).collect { |key, value| "x_#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ request
+ end
+
+ def add_currency_code(post, money, options)
+ post[:currency_code] = options[:currency] || currency(money)
+ end
+
+ def add_invoice(post, options)
+ post[:invoice_num] = options[:order_id]
+ post[:description] = options[:description]
+ end
+
+ def add_creditcard(post, creditcard, options={})
+ post[:card_num] = creditcard.number
+ post[:card_code] = creditcard.verification_value if creditcard.verification_value?
+ post[:exp_date] = expdate(creditcard)
+ post[:first_name] = creditcard.first_name
+ post[:last_name] = creditcard.last_name
+ end
+
+ def add_payment_source(params, source, options={})
+ add_creditcard(params, source, options)
+ end
+
+ def add_customer_data(post, options)
+ if options.has_key? :email
+ post[:email] = options[:email]
+ post[:email_customer] = false
+ end
+
+ if options.has_key? :customer
+ post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil
+ end
+
+ if options.has_key? :ip
+ post[:customer_ip] = options[:ip]
+ end
+
+ if options.has_key? :cardholder_authentication_value
+ post[:cardholder_authentication_value] = options[:cardholder_authentication_value]
+ end
+
+ if options.has_key? :authentication_indicator
+ post[:authentication_indicator] = options[:authentication_indicator]
+ end
+ end
+
+ # x_duplicate_window won't be sent by default, because sending it changes the response.
+ # "If this field is present in the request with or without a value, an enhanced duplicate transaction response will be sent."
+ # (as of 2008-12-30) http://www.authorize.net/support/AIM_guide_SCC.pdf
+ def add_duplicate_window(post)
+ post[:duplicate_window] = duplicate_window if duplicate_window
+ end
+
+ def add_address(post, options)
+ if address = options[:billing_address] || options[:address]
+ post[:address] = address[:address1].to_s
+ post[:company] = address[:company].to_s
+ post[:phone] = address[:phone].to_s
+ post[:zip] = address[:zip].to_s
+ post[:city] = address[:city].to_s
+ post[:country] = address[:country].to_s
+ post[:state] = address[:state].blank? ? 'n/a' : address[:state]
+ end
+
+ if address = options[:shipping_address]
+ post[:ship_to_first_name] = address[:first_name].to_s
+ post[:ship_to_last_name] = address[:last_name].to_s
+ post[:ship_to_address] = address[:address1].to_s
+ post[:ship_to_company] = address[:company].to_s
+ post[:ship_to_phone] = address[:phone].to_s
+ post[:ship_to_zip] = address[:zip].to_s
+ post[:ship_to_city] = address[:city].to_s
+ post[:ship_to_country] = address[:country].to_s
+ post[:ship_to_state] = address[:state].blank? ? 'n/a' : address[:state]
+ end
+ end
+
+ def message_from(results)
+ if results[:response_code] == DECLINED
+ return CVVResult.messages[results[:card_code]] if CARD_CODE_ERRORS.include?(results[:card_code])
+ if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code])
+ return AVSResult.messages[results[:avs_result_code]]
+ end
+ end
+
+ (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '')
+ end
+
def split(response)
response.split(',')
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/secure_pay_au.rb b/lib/active_merchant/billing/gateways/secure_pay_au.rb
index dd552114edb..37a2845bbd5 100644
--- a/lib/active_merchant/billing/gateways/secure_pay_au.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay_au.rb
@@ -8,8 +8,8 @@ class SecurePayAuGateway < Gateway
class_attribute :test_periodic_url, :live_periodic_url
- self.test_url = 'https://www.securepay.com.au/test/payment'
- self.live_url = 'https://www.securepay.com.au/xmlapi/payment'
+ self.test_url = 'https://api.securepay.com.au/test/payment'
+ self.live_url = 'https://api.securepay.com.au/xmlapi/payment'
self.test_periodic_url = 'https://test.securepay.com.au/xmlapi/periodic'
self.live_periodic_url = 'https://api.securepay.com.au/xmlapi/periodic'
@@ -43,9 +43,9 @@ class SecurePayAuGateway < Gateway
}
PERIODIC_ACTIONS = {
- :add_triggered => "add",
- :remove_triggered => "delete",
- :trigger => "trigger"
+ :add_triggered => 'add',
+ :remove_triggered => 'delete',
+ :trigger => 'trigger'
}
PERIODIC_TYPES = {
@@ -85,7 +85,7 @@ def refund(money, reference, options = {})
end
def credit(money, reference, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, reference)
end
@@ -103,13 +103,27 @@ def unstore(identification, options = {})
commit_periodic(build_periodic_item(:remove_triggered, options[:amount], nil, options))
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').
+ gsub(%r(().+()), '\1[FILTERED]\2')
+ end
+
private
def build_purchase_request(money, credit_card, options)
xml = Builder::XmlMarkup.new
- xml.tag! 'amount', amount(money)
- xml.tag! 'currency', options[:currency] || currency(money)
+ currency = options[:currency] || currency(money)
+
+ xml.tag! 'amount', localized_amount(money, currency)
+ xml.tag! 'currency', currency
xml.tag! 'purchaseOrderNo', options[:order_id].to_s.gsub(/[ ']/, '')
xml.tag! 'CreditCardInfo' do
@@ -140,7 +154,7 @@ def build_request(action, body)
xml.instruct!
xml.tag! 'SecurePayMessage' do
xml.tag! 'MessageInfo' do
- xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30)
+ xml.tag! 'messageID', SecureRandom.hex(15)
xml.tag! 'messageTimestamp', generate_timestamp
xml.tag! 'timeoutValue', request_timeout
xml.tag! 'apiVersion', API_VERSION
@@ -153,8 +167,8 @@ def build_request(action, body)
xml.tag! 'RequestType', 'Payment'
xml.tag! 'Payment' do
- xml.tag! 'TxnList', "count" => 1 do
- xml.tag! 'Txn', "ID" => 1 do
+ xml.tag! 'TxnList', 'count' => 1 do
+ xml.tag! 'Txn', 'ID' => 1 do
xml.tag! 'txnType', TRANSACTIONS[action]
xml.tag! 'txnSource', 23
xml << body
@@ -199,7 +213,7 @@ def build_periodic_request(body)
xml.instruct!
xml.tag! 'SecurePayMessage' do
xml.tag! 'MessageInfo' do
- xml.tag! 'messageID', ActiveMerchant::Utils.generate_unique_id.slice(0, 30)
+ xml.tag! 'messageID', SecureRandom.hex(15)
xml.tag! 'messageTimestamp', generate_timestamp
xml.tag! 'timeoutValue', request_timeout
xml.tag! 'apiVersion', PERIODIC_API_VERSION
@@ -212,8 +226,8 @@ def build_periodic_request(body)
xml.tag! 'RequestType', 'Periodic'
xml.tag! 'Periodic' do
- xml.tag! 'PeriodicList', "count" => 1 do
- xml.tag! 'PeriodicItem', "ID" => 1 do
+ xml.tag! 'PeriodicList', 'count' => 1 do
+ xml.tag! 'PeriodicItem', 'ID' => 1 do
xml << body
end
end
@@ -224,7 +238,6 @@ def build_periodic_request(body)
def commit_periodic(request)
my_request = build_periodic_request(request)
- #puts my_request
response = parse(ssl_post(test? ? self.test_periodic_url : self.live_periodic_url, my_request))
Response.new(success?(response), message_from(response), response,
@@ -263,7 +276,7 @@ def parse(body)
def parse_element(response, node)
if node.has_elements?
- node.elements.each{|element| parse_element(response, element) }
+ node.elements.each { |element| parse_element(response, element) }
else
response[node.name.underscore.to_sym] = node.text
end
diff --git a/lib/active_merchant/billing/gateways/secure_pay_tech.rb b/lib/active_merchant/billing/gateways/secure_pay_tech.rb
index 7ab6f62e1a5..b6980d4cf99 100644
--- a/lib/active_merchant/billing/gateways/secure_pay_tech.rb
+++ b/lib/active_merchant/billing/gateways/secure_pay_tech.rb
@@ -8,15 +8,15 @@ class SecurePayTechPostData < PostData
self.live_url = self.test_url = 'https://tx.securepaytech.com/web/HttpPostPurchase'
PAYMENT_GATEWAY_RESPONSES = {
- 1 => "Transaction OK",
- 2 => "Insufficient funds",
- 3 => "Card expired",
- 4 => "Card declined",
- 5 => "Server error",
- 6 => "Communications error",
- 7 => "Unsupported transaction type",
- 8 => "Bad or malformed request",
- 9 => "Invalid card number"
+ 1 => 'Transaction OK',
+ 2 => 'Insufficient funds',
+ 3 => 'Card expired',
+ 4 => 'Card declined',
+ 5 => 'Server error',
+ 6 => 'Communications error',
+ 7 => 'Unsupported transaction type',
+ 8 => 'Bad or malformed request',
+ 9 => 'Invalid card number'
}
self.default_currency = 'NZD'
@@ -82,7 +82,7 @@ def parse(body)
end
def commit(action, post)
- response = parse( ssl_post(self.live_url, post_data(action, post) ) )
+ response = parse(ssl_post(self.live_url, post_data(action, post)))
Response.new(response[:result_code] == 1, message_from(response), response,
:test => test?,
@@ -99,14 +99,6 @@ def post_data(action, post)
post[:MerchantKey] = @options[:password]
post.to_s
end
-
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
-
- "#{month}#{year[-2..-1]}"
- end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/securion_pay.rb b/lib/active_merchant/billing/gateways/securion_pay.rb
new file mode 100644
index 00000000000..c59370bad4d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/securion_pay.rb
@@ -0,0 +1,264 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SecurionPayGateway < Gateway
+ self.test_url = 'https://api.securionpay.com/'
+ self.live_url = 'https://api.securionpay.com/'
+
+ self.supported_countries = %w(AL AD AT BY BE BG HR CY CZ RE DK EE IS FI FR DE GI GR HU IS IE IT IL LV LI LT LU
+ MK MT MD MC NL NO PL PT RO RU MA RS SK SI ES SE CH UA GB KI CI ME)
+
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
+
+ self.homepage_url = 'https://securionpay.com/'
+ self.display_name = 'SecurionPay'
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ 'incorrect_number' => STANDARD_ERROR_CODE[:incorrect_number],
+ 'invalid_number' => STANDARD_ERROR_CODE[:invalid_number],
+ 'invalid_expiry_month' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ 'invalid_expiry_year' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ 'invalid_cvc' => STANDARD_ERROR_CODE[:invalid_cvc],
+ 'expired_card' => STANDARD_ERROR_CODE[:expired_card],
+ 'insufficient_funds' => STANDARD_ERROR_CODE[:card_declined],
+ 'incorrect_cvc' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ 'incorrect_zip' => STANDARD_ERROR_CODE[:incorrect_zip],
+ 'card_declined' => STANDARD_ERROR_CODE[:card_declined],
+ 'processing_error' => STANDARD_ERROR_CODE[:processing_error],
+ 'lost_or_stolen' => STANDARD_ERROR_CODE[:card_declined],
+ 'suspected_fraud' => STANDARD_ERROR_CODE[:card_declined],
+ 'expired_token' => STANDARD_ERROR_CODE[:card_declined]
+ }
+
+ def initialize(options={})
+ requires!(options, :secret_key)
+ super
+ end
+
+ def purchase(money, payment, options={})
+ post = create_post_for_auth_or_purchase(money, payment, options)
+ commit('charges', post, options)
+ end
+
+ def authorize(money, payment, options={})
+ post = create_post_for_auth_or_purchase(money, payment, options)
+ post[:captured] = 'false'
+ commit('charges', post, options)
+ end
+
+ def capture(money, authorization, options = {})
+ post = {}
+ add_amount(post, money, options)
+ commit("charges/#{CGI.escape(authorization)}/capture", post, options)
+ end
+
+ def refund(money, authorization, options = {})
+ post = {}
+ add_amount(post, money, options)
+ commit("charges/#{CGI.escape(authorization)}/refund", post, options)
+ end
+
+ def void(authorization, options = {})
+ commit("charges/#{CGI.escape(authorization)}/refund", {}, options)
+ 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(credit_card, options = {})
+ if options[:customer_id].blank?
+ MultiResponse.run() do |r|
+ # create charge object
+ r.process { authorize(100, credit_card, options) }
+ # create customer and save card
+ r.process { create_customer_add_card(r.authorization, options) }
+ # void the charge
+ r.process(:ignore_result) { void(r.params['metadata']['chargeId'], options) }
+ end
+ else
+ verify(credit_card, options)
+ end
+ end
+
+ def customer(options = {})
+ if options[:customer_id].blank?
+ return nil
+ else
+ commit("customers/#{CGI.escape(options[:customer_id])}", nil, options, :get)
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((card\[number\]=)\d+), '\1[FILTERED]').
+ gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ def create_customer_add_card(authorization, options)
+ post = {}
+ post[:email] = options[:email]
+ post[:description] = options[:description]
+ post[:card] = authorization
+ post[:metadata] = {}
+ post[:metadata][:chargeId] = authorization
+ commit('customers', post, options)
+ end
+
+ def add_customer(post, payment, options)
+ post[:customerId] = options[:customer_id] if options[:customer_id]
+ end
+
+ def add_customer_data(post, options)
+ post[:description] = options[:description]
+ post[:ip] = options[:ip]
+ post[:user_agent] = options[:user_agent]
+ post[:referrer] = options[:referrer]
+ end
+
+ def create_post_for_auth_or_purchase(money, payment, options)
+ post = {}
+ add_amount(post, money, options, true)
+ add_creditcard(post, payment, options)
+ add_customer(post, payment, options)
+ add_customer_data(post, options)
+ if options[:email]
+ post[:metadata] = {}
+ post[:metadata][:email] = options[:email]
+ end
+ post
+ end
+
+ def add_amount(post, money, options, include_currency = false)
+ currency = (options[:currency] || default_currency)
+ post[:amount] = localized_amount(money, currency)
+ post[:currency] = currency.downcase if include_currency
+ end
+
+ def add_creditcard(post, creditcard, options)
+ card = {}
+ if creditcard.respond_to?(:number)
+ card[:number] = creditcard.number
+ card[:expMonth] = creditcard.month
+ card[:expYear] = creditcard.year
+ card[:cvc] = creditcard.verification_value if creditcard.verification_value?
+ card[:cardholderName] = creditcard.name if creditcard.name
+
+ post[:card] = card
+ add_address(post, options)
+ elsif creditcard.kind_of?(String)
+ post[:card] = creditcard
+ else
+ raise ArgumentError.new("Unhandled payment method #{creditcard.class}.")
+ end
+ end
+
+ def add_address(post, options)
+ return unless post[:card]&.kind_of?(Hash)
+ if address = options[:billing_address]
+ post[:card][:addressLine1] = address[:address1] if address[:address1]
+ post[:card][:addressLine2] = address[:address2] if address[:address2]
+ post[:card][:addressCountry] = address[:country] if address[:country]
+ post[:card][:addressZip] = address[:zip] if address[:zip]
+ post[:card][:addressState] = address[:state] if address[:state]
+ post[:card][:addressCity] = address[:city] if address[:city]
+ end
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def commit(url, parameters = nil, options = {}, method = nil)
+ response = api_request(url, parameters, options, method)
+ success = !response.key?('error')
+
+ Response.new(success,
+ (success ? 'Transaction approved' : response['error']['message']),
+ response,
+ test: test?,
+ authorization: (success ? response['id'] : response['error']['charge']),
+ error_code: (success ? nil : STANDARD_ERROR_CODE_MAPPING[response['error']['code']])
+ )
+ end
+
+ def headers(options = {})
+ secret_key = options[:secret_key] || @options[:secret_key]
+
+ headers = {
+ 'Authorization' => 'Basic ' + Base64.encode64(secret_key.to_s + ':').strip,
+ 'User-Agent' => "SecurionPay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
+ }
+ headers
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def post_data(params)
+ return nil unless params
+
+ params.map do |key, value|
+ next if value.blank?
+ if value.is_a?(Hash)
+ h = {}
+ value.each do |k, v|
+ h["#{key}[#{k}]"] = v unless v.blank?
+ end
+ post_data(h)
+ elsif value.is_a?(Array)
+ value.map { |v| "#{key}[]=#{CGI.escape(v.to_s)}" }.join('&')
+ else
+ "#{key}=#{CGI.escape(value.to_s)}"
+ end
+ end.compact.join('&')
+ end
+
+ def api_request(endpoint, parameters = nil, options = {}, method = nil)
+ raw_response = response = nil
+ begin
+ if method.blank?
+ raw_response = ssl_post(self.live_url + endpoint, post_data(parameters), headers(options))
+ else
+ raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options))
+ end
+ response = parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response = response_error(raw_response)
+ rescue JSON::ParserError
+ response = json_error(raw_response)
+ end
+ response
+ end
+
+ def json_error(raw_response)
+ msg = 'Invalid response received from the SecurionPay API.'
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
+ {
+ 'error' => {
+ 'message' => msg
+ }
+ }
+ end
+
+ def test?
+ (@options[:secret_key]&.include?('_test_'))
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/skip_jack.rb b/lib/active_merchant/billing/gateways/skip_jack.rb
index 5450d10fd66..158742d58f7 100644
--- a/lib/active_merchant/billing/gateways/skip_jack.rb
+++ b/lib/active_merchant/billing/gateways/skip_jack.rb
@@ -1,4 +1,3 @@
-#!ruby19
# encoding: utf-8
module ActiveMerchant #:nodoc:
@@ -6,11 +5,11 @@ module Billing #:nodoc:
class SkipJackGateway < Gateway
API_VERSION = '?.?'
- self.live_url = "https://www.skipjackic.com"
- self.test_url = "https://developer.skipjackic.com"
+ self.live_url = 'https://www.skipjackic.com'
+ self.test_url = 'https://developer.skipjackic.com'
- BASIC_PATH = "/scripts/evolvcc.dll"
- ADVANCED_PATH = "/evolvcc/evolvcc.aspx"
+ BASIC_PATH = '/scripts/evolvcc.dll'
+ ADVANCED_PATH = '/evolvcc/evolvcc.aspx'
ACTIONS = {
:authorization => 'AuthorizeAPI',
@@ -25,35 +24,34 @@ class SkipJackGateway < Gateway
CARD_CODE_ERRORS = %w( N S "" )
CARD_CODE_MESSAGES = {
- "M" => "Card verification number matched",
- "N" => "Card verification number didn't match",
- "P" => "Card verification number was not processed",
- "S" => "Card verification number should be on card but was not indicated",
- "U" => "Issuer was not certified for card verification",
- "" => "Transaction failed because incorrect card verification number was entered or no number was entered"
+ 'M' => 'Card verification number matched',
+ 'N' => "Card verification number didn't match",
+ 'P' => 'Card verification number was not processed',
+ 'S' => 'Card verification number should be on card but was not indicated',
+ 'U' => 'Issuer was not certified for card verification',
+ '' => 'Transaction failed because incorrect card verification number was entered or no number was entered'
}
AVS_ERRORS = %w( A B C E I N O P R W Z )
AVS_MESSAGES = {
- "A" => "Street address matches billing information, zip/postal code does not",
- "B" => "Street address match for international transaction. Postal code not verified due to incompatible formats",
- "C" => "Street address and postal code not verified for internation transaction due to incompatible formats",
- "D" => "Street address and postal code match for international transaction",
- "E" => "Address verification service error",
- "I" => "Address information not verified by international issuer",
- "M" => "Street address and postal code match for international transaction",
- "N" => "Neither street address nor zip/postal match billing information",
- "O" => "Non-US issuer does not participate",
- "P" => "Postal codes match for international transaction but street address not verified due to incompatible formats",
- "P" => "Address verification not applicable for this transaction",
- "R" => "Payment gateway was unavailable or timed out",
- "S" => "Address verification service not supported by issuer",
- "U" => "Address information is unavailable",
- "W" => "9-digit zip/postal code matches billing information, street address does not",
- "X" => "Street address and 9-digit zip/postal code matches billing information",
- "Y" => "Street address and 5-digit zip/postal code matches billing information",
- "Z" => "5-digit zip/postal code matches billing information, street address does not",
+ 'A' => 'Street address matches billing information, zip/postal code does not',
+ 'B' => 'Street address match for international transaction. Postal code not verified due to incompatible formats',
+ 'C' => 'Street address and postal code not verified for internation transaction due to incompatible formats',
+ 'D' => 'Street address and postal code match for international transaction',
+ 'E' => 'Address verification service error',
+ 'I' => 'Address information not verified by international issuer',
+ 'M' => 'Street address and postal code match for international transaction',
+ 'N' => 'Neither street address nor zip/postal match billing information',
+ 'O' => 'Non-US issuer does not participate',
+ 'P' => 'Postal codes match for international transaction but street address not verified due to incompatible formats',
+ 'R' => 'Payment gateway was unavailable or timed out',
+ 'S' => 'Address verification service not supported by issuer',
+ 'U' => 'Address information is unavailable',
+ 'W' => '9-digit zip/postal code matches billing information, street address does not',
+ 'X' => 'Street address and 9-digit zip/postal code matches billing information',
+ 'Y' => 'Street address and 5-digit zip/postal code matches billing information',
+ 'Z' => '5-digit zip/postal code matches billing information, street address does not',
}
CHANGE_STATUS_ERROR_MESSAGES = {
@@ -195,7 +193,6 @@ def authorize(money, creditcard, options = {})
end
def purchase(money, creditcard, options = {})
- post = {}
authorization = authorize(money, creditcard, options)
if authorization.success?
capture(money, authorization.authorization)
@@ -240,7 +237,7 @@ def refund(money, identification, options = {})
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -263,7 +260,7 @@ def add_status_action(post, action)
end
def commit(action, money, parameters)
- response = parse( ssl_post( url_for(action), post_data(action, money, parameters) ), action )
+ response = parse(ssl_post(url_for(action), post_data(action, money, parameters)), action)
# Pass along the original transaction id in the case an update transaction
Response.new(response[:success], message_from(response, action), response,
@@ -277,7 +274,7 @@ def commit(action, money, parameters)
def url_for(action)
result = test? ? self.test_url : self.live_url
result += advanced? && action == :authorization ? ADVANCED_PATH : BASIC_PATH
- result += "?#{ACTIONS[action]}"
+ result + "?#{ACTIONS[action]}"
end
def add_credentials(params, action)
@@ -320,7 +317,7 @@ def split_line(line)
def authorize_response_map(body)
lines = split_lines(body)
keys, values = split_line(lines[0]), split_line(lines[1])
- Hash[*(keys.zip(values).flatten)].symbolize_keys
+ Hash[*keys.zip(values).flatten].symbolize_keys
end
def parse_authorization_response(body)
@@ -335,7 +332,7 @@ def parse_status_response(body, response_keys)
keys = [ :szSerialNumber, :szErrorCode, :szNumberRecords]
values = split_line(lines[0])[0..2]
- result = Hash[*(keys.zip(values).flatten)]
+ result = Hash[*keys.zip(values).flatten]
result[:szErrorMessage] = ''
result[:success] = (result[:szErrorCode] == '0')
@@ -356,8 +353,8 @@ def parse_status_response(body, response_keys)
def post_data(action, money, params = {})
add_credentials(params, action)
add_amount(params, action, money)
- sorted_params = params.to_a.sort{|a,b| a.to_s <=> b.to_s}.reverse
- sorted_params.collect { |key, value| "#{key.to_s}=#{CGI.escape(value.to_s)}" }.join("&")
+ sorted_params = params.to_a.sort_by(&:to_s).reverse
+ sorted_params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
def add_transaction_id(post, transaction_id)
@@ -371,7 +368,7 @@ def add_invoice(post, options)
post[:OrderDescription] = options[:description]
if order_items = options[:items]
- post[:OrderString] = order_items.collect { |item| "#{item[:sku]}~#{item[:description].tr('~','-')}~#{item[:declared_value]}~#{item[:quantity]}~#{item[:taxable]}~~~~~~~~#{item[:tax_rate]}~||"}.join
+ post[:OrderString] = order_items.collect { |item| "#{item[:sku]}~#{item[:description].tr('~', '-')}~#{item[:declared_value]}~#{item[:quantity]}~#{item[:taxable]}~~~~~~~~#{item[:tax_rate]}~||" }.join
else
post[:OrderString] = '1~None~0.00~0~N~||'
end
diff --git a/lib/active_merchant/billing/gateways/smart_ps.rb b/lib/active_merchant/billing/gateways/smart_ps.rb
index bc1690e9e05..0e1b7c0a699 100644
--- a/lib/active_merchant/billing/gateways/smart_ps.rb
+++ b/lib/active_merchant/billing/gateways/smart_ps.rb
@@ -23,9 +23,9 @@ def initialize(options = {})
def authorize(money, creditcard, options = {})
post = {}
add_invoice(post, options)
- add_payment_source(post, creditcard,options)
+ add_payment_source(post, creditcard, options)
add_address(post, options[:billing_address] || options[:address])
- add_address(post, options[:shipping_address], "shipping")
+ add_address(post, options[:shipping_address], 'shipping')
add_customer_data(post, options)
add_currency(post, money, options)
add_taxes(post, options)
@@ -38,11 +38,12 @@ def purchase(money, payment_source, options = {})
add_invoice(post, options)
add_payment_source(post, payment_source, options)
add_address(post, options[:billing_address] || options[:address])
- add_address(post, options[:shipping_address], "shipping")
+ add_address(post, options[:shipping_address], 'shipping')
add_customer_data(post, options)
add_currency(post, money, options)
add_taxes(post, options)
add_processor(post, options)
+ add_eci(post, options)
commit('sale', money, post)
end
@@ -64,7 +65,7 @@ def credit(money, payment_source, options = {})
add_payment_source(post, payment_source, options)
add_address(post, options[:billing_address] || options[:address])
add_customer_data(post, options)
- add_sku(post,options)
+ add_sku(post, options)
add_currency(post, money, options)
add_processor(post, options)
commit('credit', money, post)
@@ -76,13 +77,19 @@ def refund(money, auth, options = {})
commit('refund', money, 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
# Update the values (such as CC expiration) stored at
# the gateway. The CC number must be supplied in the
# CreditCard object.
def update(vault_id, creditcard, options = {})
post = {}
- post[:customer_vault] = "update_customer"
+ post[:customer_vault] = 'update_customer'
add_customer_vault_id(post, vault_id)
add_creditcard(post, creditcard, options)
add_address(post, options[:billing_address] || options[:address])
@@ -99,10 +106,9 @@ def amend(auth, options = {})
commit('update', nil, post)
end
-
def delete(vault_id)
post = {}
- post[:customer_vault] = "delete_customer"
+ post[:customer_vault] = 'delete_customer'
add_customer_vault_id(post, vault_id)
commit(nil, nil, post)
end
@@ -121,6 +127,7 @@ def store(payment_source, options = {})
alias_method :unstore, :delete
private
+
def add_customer_data(post, options)
if options.has_key? :email
post[:email] = options[:email]
@@ -131,17 +138,17 @@ def add_customer_data(post, options)
end
end
- def add_address(post, address,prefix="")
- prefix +="_" unless prefix.blank?
+ def add_address(post, address, prefix='')
+ prefix +='_' unless prefix.blank?
unless address.blank? or address.values.blank?
- post[prefix+"address1"] = address[:address1].to_s
- post[prefix+"address2"] = address[:address2].to_s unless address[:address2].blank?
- post[prefix+"company"] = address[:company].to_s
- post[prefix+"phone"] = address[:phone].to_s
- post[prefix+"zip"] = address[:zip].to_s
- post[prefix+"city"] = address[:city].to_s
- post[prefix+"country"] = address[:country].to_s
- post[prefix+"state"] = address[:state].blank? ? 'n/a' : address[:state]
+ post[prefix+'address1'] = address[:address1].to_s
+ post[prefix+'address2'] = address[:address2].to_s unless address[:address2].blank?
+ post[prefix+'company'] = address[:company].to_s
+ post[prefix+'phone'] = address[:phone].to_s
+ post[prefix+'zip'] = address[:zip].to_s
+ post[prefix+'city'] = address[:city].to_s
+ post[prefix+'country'] = address[:country].to_s
+ post[prefix+'state'] = address[:state].blank? ? 'n/a' : address[:state]
end
end
@@ -175,7 +182,7 @@ def add_customer_vault_id(params, vault_id)
def add_creditcard(post, creditcard, options)
if options[:store]
- post[:customer_vault] = "add_customer"
+ post[:customer_vault] = 'add_customer'
post[:customer_vault_id] = options[:store] unless options[:store] == true
end
post[:ccnumber] = creditcard.number
@@ -187,7 +194,7 @@ def add_creditcard(post, creditcard, options)
def add_check(post, check, options)
if options[:store]
- post[:customer_vault] = "add_customer"
+ post[:customer_vault] = 'add_customer'
post[:customer_vault_id] = options[:store] unless options[:store] == true
end
@@ -199,18 +206,22 @@ def add_check(post, check, options)
post[:account_type] = check.account_type # The customer's type of ACH account
end
- def add_sku(post,options)
- post["product_sku_#"] = options[:sku] || options["product_sku_#"]
+ def add_sku(post, options)
+ post['product_sku_#'] = options[:sku] || options['product_sku_#']
end
def add_transaction(post, auth)
post[:transactionid] = auth
end
+ def add_eci(post, options)
+ post[:billing_method] = options[:eci] if options[:eci]
+ end
+
def parse(body)
results = {}
body.split(/&/).each do |pair|
- key,val = pair.split(/=/)
+ key, val = pair.split(/=/)
results[key] = val
end
@@ -218,33 +229,31 @@ def parse(body)
end
def commit(action, money, parameters)
- parameters[:amount] = amount(money) if money
- response = parse( ssl_post(self.live_url, post_data(action,parameters)) )
- Response.new(response["response"] == "1", message_from(response), response,
- :authorization => (response["transactionid"] || response["customer_vault_id"]),
+ parameters[:amount] = localized_amount(money, parameters[:currency] || default_currency) if money
+ response = parse(ssl_post(self.live_url, post_data(action, parameters)))
+ Response.new(response['response'] == '1', message_from(response), response,
+ :authorization => (response['transactionid'] || response['customer_vault_id']),
:test => test?,
- :cvv_result => response["cvvresponse"],
- :avs_result => { :code => response["avsresponse"] }
+ :cvv_result => response['cvvresponse'],
+ :avs_result => { :code => response['avsresponse'] }
)
-
end
def expdate(creditcard)
- year = sprintf("%.04i", creditcard.year.to_i)
- month = sprintf("%.02i", creditcard.month.to_i)
+ year = sprintf('%.04i', creditcard.year)
+ month = sprintf('%.02i', creditcard.month)
"#{month}#{year[-2..-1]}"
end
-
def message_from(response)
- case response["responsetext"]
- when "SUCCESS", "Approved", nil # This is dubious, but responses from UPDATE are nil.
- "This transaction has been approved"
- when "DECLINE"
- "This transaction has been declined"
+ case response['responsetext']
+ when 'SUCCESS', 'Approved', nil # This is dubious, but responses from UPDATE are nil.
+ 'This transaction has been approved'
+ when 'DECLINE'
+ 'This transaction has been declined'
else
- response["responsetext"]
+ response['responsetext']
end
end
@@ -254,19 +263,18 @@ def post_data(action, parameters = {})
post[:password] = @options[:password]
post[:type] = action if action
- request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&")
+ request = post.merge(parameters).map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
request
end
def determine_funding_source(source)
case
when source.is_a?(String) then :vault
- when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card
+ when CreditCard.card_companies.include?(card_brand(source)) then :credit_card
when card_brand(source) == 'check' then :check
- else raise ArgumentError, "Unsupported funding source provided"
+ else raise ArgumentError, 'Unsupported funding source provided'
end
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/so_easy_pay.rb b/lib/active_merchant/billing/gateways/so_easy_pay.rb
new file mode 100644
index 00000000000..f0fa4523322
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/so_easy_pay.rb
@@ -0,0 +1,194 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SoEasyPayGateway < Gateway
+ self.live_url = self.test_url = 'https://secure.soeasypay.com/gateway.asmx'
+ self.money_format = :cents
+
+ self.supported_countries = [
+ 'US', 'CA', 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE',
+ 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU',
+ 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB',
+ 'IS', 'NO', 'CH'
+ ]
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :diners_club]
+ self.homepage_url = 'http://www.soeasypay.com/'
+ self.display_name = 'SoEasyPay'
+
+ def initialize(options = {})
+ requires!(options, :login, :password)
+ super
+ end
+
+ def authorize(money, payment_source, options = {})
+ if payment_source.respond_to?(:number)
+ commit('AuthorizeTransaction', do_authorization(money, payment_source, options), options)
+ else
+ commit('ReauthorizeTransaction', do_reauthorization(money, payment_source, options), options)
+ end
+ end
+
+ def purchase(money, payment_source, options = {})
+ if payment_source.respond_to?(:number)
+ commit('SaleTransaction', do_sale(money, payment_source, options), options)
+ else
+ commit('RebillTransaction', do_rebill(money, payment_source, options), options)
+ end
+ end
+
+ def capture(money, authorization, options = {})
+ commit('CaptureTransaction', do_capture(money, authorization, options), options)
+ end
+
+ def refund(money, authorization, options={})
+ commit('RefundTransaction', do_refund(money, authorization, options), options)
+ end
+
+ def void(authorization, options={})
+ commit('CancelTransaction', do_void(authorization, options), options)
+ end
+
+ private
+
+ def do_authorization(money, card, options)
+ build_soap('AuthorizeTransaction') do |soap|
+ fill_credentials(soap, options)
+ fill_order_info(soap, money, options)
+ fill_cardholder(soap, card, options)
+ fill_card(soap, card)
+ end
+ end
+
+ def do_sale(money, card, options)
+ build_soap('SaleTransaction') do |soap|
+ fill_credentials(soap, options)
+ fill_order_info(soap, money, options)
+ fill_cardholder(soap, card, options)
+ fill_card(soap, card)
+ end
+ end
+
+ def do_reauthorization(money, authorization, options)
+ build_soap('ReauthorizeTransaction') do |soap|
+ fill_credentials(soap, options)
+ fill_order_info(soap, money, options)
+ fill_transaction_id(soap, authorization)
+ end
+ end
+
+ def do_rebill(money, authorization, options)
+ build_soap('RebillTransaction') do |soap|
+ fill_credentials(soap, options)
+ fill_order_info(soap, money, options)
+ fill_transaction_id(soap, authorization)
+ end
+ end
+
+ def do_capture(money, authorization, options)
+ build_soap('CaptureTransaction') do |soap|
+ fill_credentials(soap, options)
+ fill_order_info(soap, money, options, :no_currency)
+ fill_transaction_id(soap, authorization)
+ end
+ end
+
+ def do_refund(money, authorization, options)
+ build_soap('RefundTransaction') do |soap|
+ fill_credentials(soap, options)
+ fill_order_info(soap, money, options, :no_currency)
+ fill_transaction_id(soap, authorization)
+ end
+ end
+
+ def do_void(authorization, options)
+ build_soap('CancelTransaction') do |soap|
+ fill_credentials(soap, options)
+ fill_transaction_id(soap, authorization)
+ end
+ end
+
+ def fill_credentials(soap, options)
+ soap.tag!('websiteID', @options[:login].to_s)
+ soap.tag!('password', @options[:password].to_s)
+ end
+
+ def fill_cardholder(soap, card, options)
+ ch_info = options[:billing_address] || options[:address]
+
+ soap.tag!('customerIP', options[:ip].to_s)
+ name = card.name || ch_info[:name]
+ soap.tag!('cardHolderName', name.to_s)
+ address = ch_info[:address1] || ''
+ address << ch_info[:address2] if ch_info[:address2]
+ soap.tag!('cardHolderAddress', address.to_s)
+ soap.tag!('cardHolderZipcode', ch_info[:zip].to_s)
+ soap.tag!('cardHolderCity', ch_info[:city].to_s)
+ soap.tag!('cardHolderState', ch_info[:state].to_s)
+ soap.tag!('cardHolderCountryCode', ch_info[:country].to_s)
+ soap.tag!('cardHolderPhone', ch_info[:phone].to_s)
+ soap.tag!('cardHolderEmail', options[:email].to_s)
+ end
+
+ def fill_transaction_id(soap, transaction_id)
+ soap.tag!('transactionID', transaction_id.to_s)
+ end
+
+ def fill_card(soap, card)
+ soap.tag!('cardNumber', card.number.to_s)
+ soap.tag!('cardSecurityCode', card.verification_value.to_s)
+ soap.tag!('cardExpireMonth', card.month.to_s.rjust(2, '0'))
+ soap.tag!('cardExpireYear', card.year.to_s)
+ end
+
+ def fill_order_info(soap, money, options, skip_currency=false)
+ soap.tag!('orderID', options[:order_id].to_s)
+ soap.tag!('orderDescription', "Order #{options[:order_id]}")
+ soap.tag!('amount', amount(money).to_s)
+ soap.tag!('currency', (options[:currency] || currency(money)).to_s) unless skip_currency
+ end
+
+ def parse(response, action)
+ result = {}
+ document = REXML::Document.new(response)
+ response_element = document.root.get_elements("//[@xsi:type='tns:#{action}Response']").first
+ response_element.elements.each do |element|
+ result[element.name.underscore] = element.text
+ end
+ result
+ end
+
+ def commit(soap_action, soap, options)
+ headers = {'SOAPAction' => "\"urn:Interface##{soap_action}\"",
+ 'Content-Type' => 'text/xml; charset=utf-8'}
+ response_string = ssl_post(test? ? self.test_url : self.live_url, soap, headers)
+ response = parse(response_string, soap_action)
+ return Response.new(response['errorcode'] == '000',
+ response['errormessage'],
+ response,
+ :test => test?,
+ :authorization => response['transaction_id'])
+ end
+
+ def build_soap(request)
+ retval = Builder::XmlMarkup.new(:indent => 2)
+ retval.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
+ retval.tag!('soap:Envelope', {
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
+ 'xmlns:soapenc' => 'http://schemas.xmlsoap.org/soap/encoding/',
+ 'xmlns:tns' => 'urn:Interface',
+ 'xmlns:types' => 'urn:Interface/encodedTypes',
+ 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/'}) do
+ retval.tag!('soap:Body', {'soap:encodingStyle'=>'http://schemas.xmlsoap.org/soap/encoding/'}) do
+ retval.tag!("tns:#{request}") do
+ retval.tag!("#{request}Request", {'xsi:type'=>"tns:#{request}Request"}) do
+ yield retval
+ end
+ end
+ end
+ end
+ retval.target!
+ end
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/spreedly_core.rb b/lib/active_merchant/billing/gateways/spreedly_core.rb
index 1a07ab0620a..92e3f5ccbcc 100644
--- a/lib/active_merchant/billing/gateways/spreedly_core.rb
+++ b/lib/active_merchant/billing/gateways/spreedly_core.rb
@@ -35,33 +35,25 @@ def initialize(options = {})
# Public: Run a purchase transaction.
#
# money - The monetary amount of the transaction in cents.
- # payment_method - The CreditCard or the Spreedly payment method token.
- # options - A standard ActiveMerchant options hash
+ # payment_method - The CreditCard or Check or the Spreedly payment method token.
+ # options - A hash of options:
+ # :store - Retain the payment method if the purchase
+ # succeeds. Defaults to false. (optional)
def purchase(money, payment_method, options = {})
- if payment_method.is_a?(String)
- purchase_with_token(money, payment_method, options)
- else
- MultiResponse.run do |r|
- r.process { save_card(false, payment_method, options) }
- r.process { purchase_with_token(money, r.authorization, options) }
- end
- end
+ request = build_transaction_request(money, payment_method, options)
+ commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request)
end
# Public: Run an authorize transaction.
#
# money - The monetary amount of the transaction in cents.
# payment_method - The CreditCard or the Spreedly payment method token.
- # options - A standard ActiveMerchant options hash
+ # options - A hash of options:
+ # :store - Retain the payment method if the authorize
+ # succeeds. Defaults to false. (optional)
def authorize(money, payment_method, options = {})
- if payment_method.is_a?(String)
- authorize_with_token(money, payment_method, options)
- else
- MultiResponse.run do |r|
- r.process { save_card(false, payment_method, options) }
- r.process { authorize_with_token(money, r.authorization, options) }
- end
- end
+ request = build_transaction_request(money, payment_method, options)
+ commit("gateways/#{@options[:gateway_token]}/authorize.xml", request)
end
def capture(money, authorization, options={})
@@ -75,6 +67,7 @@ def capture(money, authorization, options={})
def refund(money, authorization, options={})
request = build_xml_request('transaction') do |doc|
add_invoice(doc, money, options)
+ add_extra_options(:gateway_specific_fields, doc, options)
end
commit("transactions/#{authorization}/credit.xml", request)
@@ -84,12 +77,30 @@ def void(authorization, options={})
commit("transactions/#{authorization}/void.xml", '')
end
+ # Public: Determine whether a credit card is chargeable card and available for purchases.
+ #
+ # payment_method - The CreditCard or the Spreedly payment method token.
+ # options - A hash of options:
+ # :store - Retain the payment method if the verify
+ # succeeds. Defaults to false. (optional)
+ def verify(payment_method, options = {})
+ if payment_method.is_a?(String)
+ verify_with_token(payment_method, options)
+ else
+ MultiResponse.run do |r|
+ r.process { save_card(options[:store], payment_method, options) }
+ r.process { verify_with_token(r.authorization, options) }
+ end
+ end
+ end
+
# Public: Store a credit card in the Spreedly vault and retain it.
#
# credit_card - The CreditCard to store
# options - A standard ActiveMerchant options hash
def store(credit_card, options={})
- save_card(true, credit_card, options)
+ retain = (options.has_key?(:retain) ? options[:retain] : true)
+ save_card(retain, credit_card, options)
end
# Public: Redact the CreditCard in Spreedly. This wipes the sensitive
@@ -101,42 +112,93 @@ def unstore(authorization, options={})
commit("payment_methods/#{authorization}/redact.xml", '', :put)
end
+ # Public: Get the transaction with the given token.
+ def find(transaction_token)
+ commit("transactions/#{transaction_token}.xml", nil, :get)
+ end
+
+ alias_method :status, :find
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2').
+ gsub(%r(().+()), '\1[FILTERED]\2')
+ end
+
private
+
def save_card(retain, credit_card, options)
request = build_xml_request('payment_method') do |doc|
add_credit_card(doc, credit_card, options)
- add_data(doc, options)
+ add_extra_options(:data, doc, options)
doc.retained(true) if retain
end
- commit("payment_methods.xml", request, :post, :payment_method_token)
+ commit('payment_methods.xml', request, :post, :payment_method_token)
end
def purchase_with_token(money, payment_method_token, options)
- request = auth_purchase_request(money, payment_method_token, options)
- commit("gateways/#{@options[:gateway_token]}/purchase.xml", request)
+ request = build_transaction_request(money, payment_method_token, options)
+ commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request)
end
def authorize_with_token(money, payment_method_token, options)
- request = auth_purchase_request(money, payment_method_token, options)
+ request = build_transaction_request(money, payment_method_token, options)
commit("gateways/#{@options[:gateway_token]}/authorize.xml", request)
end
- def auth_purchase_request(money, payment_method_token, options)
+ def verify_with_token(payment_method_token, options)
+ request = build_transaction_request(nil, payment_method_token, options)
+ commit("gateways/#{@options[:gateway_token]}/verify.xml", request)
+ end
+
+ def build_transaction_request(money, payment_method, options)
build_xml_request('transaction') do |doc|
add_invoice(doc, money, options)
- doc.payment_method_token(payment_method_token)
+ add_payment_method(doc, payment_method, options)
+ add_extra_options(:gateway_specific_fields, doc, options)
end
end
def add_invoice(doc, money, options)
- doc.amount amount(money)
+ doc.amount amount(money) unless money.nil?
doc.currency_code(options[:currency] || currency(money) || default_currency)
+ doc.order_id(options[:order_id])
+ doc.ip(options[:ip]) if options[:ip]
+ doc.description(options[:description]) if options[:description]
+
+ if options[:merchant_name_descriptor]
+ doc.merchant_name_descriptor(options[:merchant_name_descriptor])
+ end
+ if options[:merchant_location_descriptor]
+ doc.merchant_location_descriptor(options[:merchant_location_descriptor])
+ end
+ end
+
+ def add_payment_method(doc, payment_method, options)
+ doc.retain_on_success(true) if options[:store]
+
+ if payment_method.is_a?(String)
+ doc.payment_method_token(payment_method)
+ elsif payment_method.is_a?(CreditCard)
+ add_credit_card(doc, payment_method, options)
+ elsif payment_method.is_a?(Check)
+ add_bank_account(doc, payment_method, options)
+ else
+ raise TypeError, 'Payment method not supported'
+ end
end
def add_credit_card(doc, credit_card, options)
doc.credit_card do
doc.number(credit_card.number)
+ doc.verification_value(credit_card.verification_value)
doc.first_name(credit_card.first_name)
doc.last_name(credit_card.last_name)
doc.month(credit_card.month)
@@ -147,20 +209,32 @@ def add_credit_card(doc, credit_card, options)
doc.city(options[:billing_address].try(:[], :city))
doc.state(options[:billing_address].try(:[], :state))
doc.zip(options[:billing_address].try(:[], :zip))
+ doc.country(options[:billing_address].try(:[], :country))
end
end
- def add_data(doc, options)
- doc.data do
- data_to_doc(doc, options[:data])
+ def add_bank_account(doc, bank_account, options)
+ doc.bank_account do
+ doc.first_name(bank_account.first_name)
+ doc.last_name(bank_account.last_name)
+ doc.bank_routing_number(bank_account.routing_number)
+ doc.bank_account_number(bank_account.account_number)
+ doc.bank_account_type(bank_account.account_type)
+ doc.bank_account_holder_type(bank_account.account_holder_type)
end
end
- def data_to_doc(doc, value)
+ def add_extra_options(type, doc, options)
+ doc.send(type) do
+ extra_options_to_doc(doc, options[type])
+ end
+ end
+
+ def extra_options_to_doc(doc, value)
return doc.text value unless value.kind_of? Hash
value.each do |k, v|
doc.send(k) do
- data_to_doc(doc, v)
+ extra_options_to_doc(doc, v)
end
end
end
@@ -170,7 +244,7 @@ def parse(xml)
doc = Nokogiri::XML(xml)
doc.root.xpath('*').each do |node|
- if (node.elements.empty?)
+ if node.elements.empty?
response[node.name.downcase.to_sym] = node.text
else
node.elements.each do |childnode|
@@ -230,4 +304,3 @@ def headers
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/stripe.rb b/lib/active_merchant/billing/gateways/stripe.rb
index f1feebeadab..ffa022c58d7 100644
--- a/lib/active_merchant/billing/gateways/stripe.rb
+++ b/lib/active_merchant/billing/gateways/stripe.rb
@@ -2,6 +2,8 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
+ # This gateway uses an older version of the Stripe API.
+ # To utilize the updated {Payment Intents API}[https://stripe.com/docs/api/payment_intents], integrate with the StripePaymentIntents gateway
class StripeGateway < Gateway
self.live_url = 'https://api.stripe.com/v1/'
@@ -21,14 +23,55 @@ class StripeGateway < Gateway
'unchecked' => 'P'
}
- self.supported_countries = %w(US CA GB AU IE FR NL BE DE ES)
+ DEFAULT_API_VERSION = '2015-04-07'
+
+ self.supported_countries = %w(AT AU BE BR CA CH DE DK ES FI FR GB HK IE IT JP LU MX NL NO NZ PT SE SG US)
self.default_currency = 'USD'
self.money_format = :cents
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
+ self.currencies_without_fractions = %w(BIF CLP DJF GNF JPY KMF KRW MGA PYG RWF VND VUV XAF XOF XPF UGX)
self.homepage_url = 'https://stripe.com/'
self.display_name = 'Stripe'
+ STANDARD_ERROR_CODE_MAPPING = {
+ 'incorrect_number' => STANDARD_ERROR_CODE[:incorrect_number],
+ 'invalid_number' => STANDARD_ERROR_CODE[:invalid_number],
+ 'invalid_expiry_month' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ 'invalid_expiry_year' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ 'invalid_cvc' => STANDARD_ERROR_CODE[:invalid_cvc],
+ 'expired_card' => STANDARD_ERROR_CODE[:expired_card],
+ 'incorrect_cvc' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ 'incorrect_zip' => STANDARD_ERROR_CODE[:incorrect_zip],
+ 'card_declined' => STANDARD_ERROR_CODE[:card_declined],
+ 'call_issuer' => STANDARD_ERROR_CODE[:call_issuer],
+ 'processing_error' => STANDARD_ERROR_CODE[:processing_error],
+ 'incorrect_pin' => STANDARD_ERROR_CODE[:incorrect_pin],
+ 'test_mode_live_card' => STANDARD_ERROR_CODE[:test_mode_live_card],
+ 'pickup_card' => STANDARD_ERROR_CODE[:pickup_card]
+ }
+
+ BANK_ACCOUNT_HOLDER_TYPE_MAPPING = {
+ 'personal' => 'individual',
+ 'business' => 'company',
+ }
+
+ MINIMUM_AUTHORIZE_AMOUNTS = {
+ 'USD' => 100,
+ 'CAD' => 100,
+ 'GBP' => 60,
+ 'EUR' => 100,
+ 'DKK' => 500,
+ 'NOK' => 600,
+ 'SEK' => 600,
+ 'CHF' => 100,
+ 'AUD' => 100,
+ 'JPY' => 100,
+ 'MXN' => 2000,
+ 'SGD' => 100,
+ 'HKD' => 800
+ }
+
def initialize(options = {})
requires!(options, :login)
@api_key = options[:login]
@@ -36,11 +79,22 @@ def initialize(options = {})
super
end
- def authorize(money, creditcard, options = {})
- post = create_post_for_auth_or_purchase(money, creditcard, options)
- post[:capture] = "false"
-
- commit(:post, 'charges', post, generate_meta(options))
+ def authorize(money, payment, options = {})
+ MultiResponse.run do |r|
+ if payment.is_a?(ApplePayPaymentToken)
+ r.process { tokenize_apple_pay_token(payment) }
+ payment = StripePaymentToken.new(r.params['token']) if r.success?
+ end
+ r.process do
+ post = create_post_for_auth_or_purchase(money, payment, options)
+ if emv_payment?(payment)
+ add_application_fee(post, options)
+ else
+ post[:capture] = 'false'
+ end
+ commit(:post, 'charges', post, options)
+ end
+ end.responses.last
end
# To create a charge on a card or a token, call
@@ -50,102 +104,331 @@ def authorize(money, creditcard, options = {})
# To create a charge on a customer, call
#
# purchase(money, nil, { :customer => id, ... })
- def purchase(money, creditcard, options = {})
- post = create_post_for_auth_or_purchase(money, creditcard, options)
+ def purchase(money, payment, options = {})
+ if ach?(payment)
+ direct_bank_error = 'Direct bank account transactions are not supported. Bank accounts must be stored and verified before use.'
+ return Response.new(false, direct_bank_error)
+ end
- commit(:post, 'charges', post, generate_meta(options))
+ MultiResponse.run do |r|
+ if payment.is_a?(ApplePayPaymentToken)
+ r.process { tokenize_apple_pay_token(payment) }
+ payment = StripePaymentToken.new(r.params['token']) if r.success?
+ end
+ r.process do
+ post = create_post_for_auth_or_purchase(money, payment, options)
+ post[:card][:processing_method] = 'quick_chip' if quickchip_payment?(payment)
+ commit(:post, 'charges', post, options)
+ end
+ end.responses.last
end
def capture(money, authorization, options = {})
- post = {:amount => amount(money)}
- add_application_fee(post, options)
+ post = {}
- commit(:post, "charges/#{CGI.escape(authorization)}/capture", post)
+ if emv_tc_response = options.delete(:icc_data)
+ post[:card] = { emv_approval_data: emv_tc_response }
+ commit(:post, "charges/#{CGI.escape(authorization)}", post, options)
+ else
+ add_application_fee(post, options)
+ add_amount(post, money, options)
+ add_exchange_rate(post, options)
+ commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options)
+ end
end
def void(identification, options = {})
- commit(:post, "charges/#{CGI.escape(identification)}/refund", {})
+ post = {}
+ post[:metadata] = options[:metadata] if options[:metadata]
+ post[:reason] = options[:reason] if options[:reason]
+ post[:expand] = [:charge]
+ commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options)
end
def refund(money, identification, options = {})
- post = {:amount => amount(money)}
- commit_options = generate_meta(options)
+ post = {}
+ add_amount(post, money, options)
+ post[:refund_application_fee] = true if options[:refund_application_fee]
+ post[:reverse_transfer] = options[:reverse_transfer] if options[:reverse_transfer]
+ post[:metadata] = options[:metadata] if options[:metadata]
+ post[:reason] = options[:reason] if options[:reason]
+ post[:expand] = [:charge]
+
+ response = commit(:post, "charges/#{CGI.escape(identification)}/refunds", post, options)
+
+ if response.success? && options[:refund_fee_amount] && options[:refund_fee_amount].to_s != '0'
+ charge = api_request(:get, "charges/#{CGI.escape(identification)}", nil, options)
+
+ if application_fee = charge['application_fee']
+ fee_refund_options = {
+ currency: options[:currency], # currency isn't used by Stripe here, but we need it for #add_amount
+ key: @fee_refund_api_key
+ }
+ refund_application_fee(options[:refund_fee_amount].to_i, application_fee, fee_refund_options)
+ end
+ end
- MultiResponse.run(:first) do |r|
- r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, commit_options) }
+ response
+ end
+
+ def verify(payment, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(auth_minimum_amount(options), payment, options) }
+ options[:idempotency_key] = nil
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ def refund_application_fee(money, identification, options = {})
+ post = {}
+ add_amount(post, money, options)
+ commit(:post, "application_fees/#{CGI.escape(identification)}/refunds", post, options)
+ end
+
+ # Note: creating a new credit card will not change the customer's existing default credit card (use :set_default => true)
+ def store(payment, options = {})
+ params = {}
+ post = {}
- return r unless options[:refund_fee_amount]
+ if payment.is_a?(ApplePayPaymentToken)
+ token_exchange_response = tokenize_apple_pay_token(payment)
+ params = { card: token_exchange_response.params['token']['id'] } if token_exchange_response.success?
+ elsif payment.is_a?(StripePaymentToken)
+ add_payment_token(params, payment, options)
+ elsif payment.is_a?(Check)
+ bank_token_response = tokenize_bank_account(payment)
+ return bank_token_response unless bank_token_response.success?
+ params = { source: bank_token_response.params['token']['id'] }
+ else
+ add_creditcard(params, payment, options)
+ end
+
+ post[:validate] = options[:validate] unless options[:validate].nil?
+ post[:description] = options[:description] if options[:description]
+ post[:email] = options[:email] if options[:email]
+
+ if options[:account]
+ add_external_account(post, params, payment)
+ commit(:post, "accounts/#{CGI.escape(options[:account])}/external_accounts", post, options)
+ elsif options[:customer]
+ MultiResponse.run(:first) do |r|
+ # The /cards endpoint does not update other customer parameters.
+ r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", params, options) }
+
+ if options[:set_default] and r.success? and !r.params['id'].blank?
+ post[:default_card] = r.params['id']
+ end
- r.process { fetch_application_fees(identification, commit_options) }
- r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r), commit_options) }
+ if post.count > 0
+ r.process { update_customer(options[:customer], post) }
+ end
+ end
+ else
+ commit(:post, 'customers', post.merge(params), options)
end
end
- def application_fee_from_response(response)
- return unless response.success?
+ def update(customer_id, card_id, options = {})
+ commit(:post, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", options, options)
+ end
- application_fees = response.params["data"].select { |fee| fee["object"] == "application_fee" }
- application_fees.first["id"] unless application_fees.empty?
+ def update_customer(customer_id, options = {})
+ commit(:post, "customers/#{CGI.escape(customer_id)}", options, options)
end
- def refund_application_fee(money, identification, options = {})
- return Response.new(false, "Application fee id could not be found") unless identification
+ def unstore(identification, options = {}, deprecated_options = {})
+ customer_id, card_id = identification.split('|')
- post = {:amount => amount(money)}
- options.merge!(:key => @fee_refund_api_key)
+ if options.kind_of?(String)
+ ActiveMerchant.deprecated 'Passing the card_id as the 2nd parameter is deprecated. The response authorization includes both the customer_id and the card_id.'
+ card_id ||= options
+ options = deprecated_options
+ end
- commit(:post, "application_fees/#{CGI.escape(identification)}/refund", post, options)
+ commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", nil, options)
end
- def store(creditcard, options = {})
- post = {}
- add_creditcard(post, creditcard, options)
- post[:description] = options[:description]
- post[:email] = options[:email]
+ def tokenize_apple_pay_token(apple_pay_payment_token, options = {})
+ token_response = api_request(:post, "tokens?pk_token=#{CGI.escape(apple_pay_payment_token.payment_data.to_json)}")
+ success = !token_response.key?('error')
- path = if options[:customer]
- "customers/#{CGI.escape(options[:customer])}"
+ if success && token_response.key?('id')
+ Response.new(success, nil, token: token_response)
else
- 'customers'
+ Response.new(success, token_response['error']['message'])
+ end
+ end
+
+ def verify_credentials
+ begin
+ ssl_get(live_url + 'charges/nonexistent', headers)
+ rescue ResponseError => e
+ return false if e.response.code.to_i == 401
end
- commit(:post, path, post, generate_meta(options))
+ true
+ end
+
+ def supports_scrubbing?
+ true
end
- def update(customer_id, creditcard, options = {})
- options = options.merge(:customer => customer_id)
- store(creditcard, options)
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?three_d_secure\[cryptogram\]=)[\w=]*(&?)), '\1[FILTERED]\2').
+ gsub(%r((card\[cryptogram\]=)[^&]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]').
+ gsub(%r((card\[emv_approval_data\]=)[^&]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((card\[emv_auth_data\]=)[^&]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((card\[encrypted_pin\]=)[^&]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((card\[encrypted_pin_key_id\]=)[\w=]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((card\[number\]=)\d+), '\1[FILTERED]').
+ gsub(%r((card\[swipe_data\]=)[^&]+(&?)), '\1[FILTERED]\2')
end
- def unstore(customer_id, options = {})
- commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, generate_meta(options))
+ def supports_network_tokenization?
+ true
end
private
- def create_post_for_auth_or_purchase(money, creditcard, options)
+ class StripePaymentToken < PaymentToken
+ def type
+ 'stripe'
+ end
+ end
+
+ def create_source(money, payment, type, options = {})
post = {}
- add_amount(post, money, options)
- add_creditcard(post, creditcard, options)
- add_customer(post, options)
- add_customer_data(post,options)
- post[:description] = options[:description] || options[:email]
- add_flags(post, options)
+ add_amount(post, money, options, true)
+ post[:type] = type
+ if type == 'card'
+ add_creditcard(post, payment, options, true)
+ add_source_owner(post, payment, options)
+ elsif type == 'three_d_secure'
+ post[:three_d_secure] = {card: payment}
+ post[:redirect] = {return_url: options[:redirect_url]}
+ end
+ commit(:post, 'sources', post, options)
+ end
+
+ def show_source(source_id, options)
+ commit(:get, "sources/#{source_id}", nil, options)
+ end
+
+ def create_webhook_endpoint(options, events)
+ post = {}
+ post[:url] = options[:callback_url]
+ post[:enabled_events] = events
+ post[:connect] = true if options[:stripe_account]
+ options.delete(:stripe_account)
+ commit(:post, 'webhook_endpoints', post, options)
+ end
+
+ def delete_webhook_endpoint(options)
+ commit(:delete, "webhook_endpoints/#{options[:webhook_id]}", {}, options)
+ end
+
+ def show_webhook_endpoint(options)
+ options.delete(:stripe_account)
+ commit(:get, "webhook_endpoints/#{options[:webhook_id]}", nil, options)
+ end
+
+ def list_webhook_endpoints(options)
+ params = {}
+ params[:limit] = options[:limit] if options[:limit]
+ options.delete(:stripe_account)
+ commit(:get, "webhook_endpoints?#{post_data(params)}", nil, options)
+ end
+
+ def create_post_for_auth_or_purchase(money, payment, options)
+ post = {}
+
+ if payment.is_a?(StripePaymentToken)
+ add_payment_token(post, payment, options)
+ else
+ add_creditcard(post, payment, options)
+ end
+
+ add_charge_details(post, money, payment, options)
+ post
+ end
+
+ # Used internally by Spreedly to populate the charge object for 3DS 1.0 transactions
+ def add_charge_details(post, money, payment, options)
+ if emv_payment?(payment)
+ add_statement_address(post, options)
+ add_emv_metadata(post, payment)
+ else
+ add_amount(post, money, options, true)
+ add_customer_data(post, options)
+ post[:description] = options[:description]
+ post[:statement_descriptor] = options[:statement_description]
+ post[:receipt_email] = options[:receipt_email] if options[:receipt_email]
+ add_customer(post, payment, options)
+ add_flags(post, options)
+ end
+
+ add_metadata(post, options)
add_application_fee(post, options)
+ add_exchange_rate(post, options)
+ add_destination(post, options)
+ add_level_three(post, options)
post
end
- def add_amount(post, money, options)
- post[:amount] = amount(money)
- post[:currency] = (options[:currency] || currency(money)).downcase
+ def add_amount(post, money, options, include_currency = false)
+ currency = options[:currency] || currency(money)
+ post[:amount] = localized_amount(money, currency)
+ post[:currency] = currency.downcase if include_currency
end
def add_application_fee(post, options)
post[:application_fee] = options[:application_fee] if options[:application_fee]
end
+ def add_exchange_rate(post, options)
+ post[:exchange_rate] = options[:exchange_rate] if options[:exchange_rate]
+ end
+
+ def add_destination(post, options)
+ if options[:destination]
+ post[:destination] = {}
+ post[:destination][:account] = options[:destination]
+ post[:destination][:amount] = options[:destination_amount] if options[:destination_amount]
+ end
+ end
+
+ def add_level_three(post, options)
+ level_three = {}
+
+ copy_when_present(level_three, [:merchant_reference], options)
+ copy_when_present(level_three, [:customer_reference], options)
+ copy_when_present(level_three, [:shipping_address_zip], options)
+ copy_when_present(level_three, [:shipping_from_zip], options)
+ copy_when_present(level_three, [:shipping_amount], options)
+ copy_when_present(level_three, [:line_items], options)
+
+ unless level_three.empty?
+ post[:level3] = level_three
+ end
+ end
+
+ def add_expand_parameters(post, options)
+ post[:expand] ||= []
+ post[:expand].concat(Array.wrap(options[:expand]).map(&:to_sym)).uniq!
+ end
+
+ def add_external_account(post, card_params, payment)
+ external_account = {}
+ external_account[:object] ='card'
+ external_account[:currency] = (options[:currency] || currency(payment)).downcase
+ post[:external_account] = external_account.merge(card_params[:card])
+ end
+
def add_customer_data(post, options)
- metadata_options = [:description,:browser_ip,:user_agent,:referrer]
+ metadata_options = [:description, :ip, :user_agent, :referrer]
post.update(options.slice(*metadata_options))
post[:external_id] = options[:order_id]
@@ -153,7 +436,7 @@ def add_customer_data(post, options)
end
def add_address(post, options)
- return unless post[:card] && post[:card].kind_of?(Hash)
+ return unless post[:card]&.kind_of?(Hash)
if address = options[:billing_address] || options[:address]
post[:card][:address_line1] = address[:address1] if address[:address1]
post[:card][:address_line2] = address[:address2] if address[:address2]
@@ -164,43 +447,116 @@ def add_address(post, options)
end
end
- def add_creditcard(post, creditcard, options)
+ def add_statement_address(post, options)
+ return unless statement_address = options[:statement_address]
+ return unless [:address1, :city, :zip, :state].all? { |key| statement_address[key].present? }
+
+ post[:statement_address] = {}
+ post[:statement_address][:line1] = statement_address[:address1]
+ post[:statement_address][:line2] = statement_address[:address2] if statement_address[:address2].present?
+ post[:statement_address][:city] = statement_address[:city]
+ post[:statement_address][:postal_code] = statement_address[:zip]
+ post[:statement_address][:state] = statement_address[:state]
+ end
+
+ def add_creditcard(post, creditcard, options, use_sources = false)
card = {}
- if creditcard.respond_to?(:number)
+ if emv_payment?(creditcard)
+ add_emv_creditcard(post, creditcard.icc_data)
+ post[:card][:read_method] = 'contactless' if creditcard.read_method == 'contactless'
+ post[:card][:read_method] = 'contactless_magstripe_mode' if creditcard.read_method == 'contactless_magstripe'
+ if creditcard.encrypted_pin_cryptogram.present? && creditcard.encrypted_pin_ksn.present?
+ post[:card][:encrypted_pin] = creditcard.encrypted_pin_cryptogram
+ post[:card][:encrypted_pin_key_id] = creditcard.encrypted_pin_ksn
+ end
+ elsif creditcard.respond_to?(:number)
if creditcard.respond_to?(:track_data) && creditcard.track_data.present?
card[:swipe_data] = creditcard.track_data
+ if creditcard.respond_to?(:read_method)
+ card[:fallback_reason] = 'no_chip' if creditcard.read_method == 'fallback_no_chip'
+ card[:fallback_reason] = 'chip_error' if creditcard.read_method == 'fallback_chip_error'
+ card[:read_method] = 'contactless_magstripe_mode' if creditcard.read_method == 'contactless_magstripe'
+ end
else
card[:number] = creditcard.number
card[:exp_month] = creditcard.month
card[:exp_year] = creditcard.year
card[:cvc] = creditcard.verification_value if creditcard.verification_value?
- card[:name] = creditcard.name if creditcard.name
+ card[:name] = creditcard.name if creditcard.name && !use_sources
end
+ if creditcard.is_a?(NetworkTokenizationCreditCard)
+ card[:cryptogram] = creditcard.payment_cryptogram
+ card[:eci] = creditcard.eci.rjust(2, '0') if creditcard.eci =~ /^[0-9]+$/
+ card[:tokenization_method] = creditcard.source.to_s
+ end
post[:card] = card
- add_address(post, options)
+
+ add_address(post, options) unless use_sources
elsif creditcard.kind_of?(String)
if options[:track_data]
card[:swipe_data] = options[:track_data]
+ elsif creditcard.include?('|')
+ customer_id, card_id = creditcard.split('|')
+ card = card_id
+ post[:customer] = customer_id
else
- card[:number] = creditcard
+ card = creditcard
end
post[:card] = card
end
end
- def add_customer(post, options)
- post[:customer] = options[:customer] if options[:customer] && post[:card].blank?
+ def add_emv_creditcard(post, icc_data, options = {})
+ post[:card] = { emv_auth_data: icc_data }
+ end
+
+ def add_payment_token(post, token, options = {})
+ post[:card] = token.payment_data['id']
+ end
+
+ def add_customer(post, payment, options)
+ if options[:customer] && !payment.respond_to?(:number)
+ ActiveMerchant.deprecated 'Passing the customer in the options is deprecated. Just use the response.authorization instead.'
+ post[:customer] = options[:customer]
+ end
end
def add_flags(post, options)
post[:uncaptured] = true if options[:uncaptured]
+ post[:recurring] = true if options[:eci] == 'recurring' || options[:recurring]
end
- def fetch_application_fees(identification, options = {})
- options.merge!(:key => @fee_refund_api_key)
+ def add_metadata(post, options = {})
+ post[:metadata] ||= {}
+ post[:metadata].merge!(options[:metadata]) if options[:metadata]
+ post[:metadata][:email] = options[:email] if options[:email]
+ post[:metadata][:order_id] = options[:order_id] if options[:order_id]
+ post.delete(:metadata) if post[:metadata].empty?
+ end
- commit(:get, "application_fees?charge=#{identification}", nil, options)
+ def add_emv_metadata(post, creditcard)
+ post[:metadata] ||= {}
+ post[:metadata][:card_read_method] = creditcard.read_method if creditcard.respond_to?(:read_method)
+ end
+
+ def add_source_owner(post, creditcard, options)
+ post[:owner] = {}
+ post[:owner][:name] = creditcard.name if creditcard.name
+ post[:owner][:email] = options[:email] if options[:email]
+
+ if address = options[:billing_address] || options[:address]
+ owner_address = {}
+ owner_address[:line1] = address[:address1] if address[:address1]
+ owner_address[:line2] = address[:address2] if address[:address2]
+ owner_address[:country] = address[:country] if address[:country]
+ owner_address[:postal_code] = address[:zip] if address[:zip]
+ owner_address[:state] = address[:state] if address[:state]
+ owner_address[:city] = address[:city] if address[:city]
+
+ post[:owner][:phone] = address[:phone] if address[:phone]
+ post[:owner][:address] = owner_address
+ end
end
def parse(body)
@@ -209,89 +565,226 @@ def parse(body)
def post_data(params)
return nil unless params
+ flatten_params([], params).join('&')
+ end
- params.map do |key, value|
- next if value.blank?
+ def flatten_params(flattened, params, prefix = nil)
+ params.each do |key, value|
+ next if value != false && value.blank?
+ flattened_key = prefix.nil? ? key : "#{prefix}[#{key}]"
if value.is_a?(Hash)
- h = {}
- value.each do |k, v|
- h["#{key}[#{k}]"] = v unless v.blank?
- end
- post_data(h)
+ flatten_params(flattened, value, flattened_key)
+ elsif value.is_a?(Array)
+ flatten_array(flattened, value, flattened_key)
else
- "#{key}=#{CGI.escape(value.to_s)}"
+ flattened << "#{flattened_key}=#{CGI.escape(value.to_s)}"
end
- end.compact.join("&")
+ end
+ flattened
end
- def generate_meta(options)
- {:meta => {:ip => options[:ip]}}
+ def flatten_array(flattened, array, prefix)
+ array.each_with_index do |item, idx|
+ key = "#{prefix}[#{idx}]"
+ if item.is_a?(Hash)
+ flatten_params(flattened, item, key)
+ elsif item.is_a?(Array)
+ flatten_array(flattened, item, key)
+ else
+ flattened << "#{key}=#{CGI.escape(item.to_s)}"
+ end
+ end
end
def headers(options = {})
- @@ua ||= JSON.dump({
- :bindings_version => ActiveMerchant::VERSION,
- :lang => 'ruby',
- :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
- :platform => RUBY_PLATFORM,
- :publisher => 'active_merchant',
- :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil)
- })
-
key = options[:key] || @api_key
-
- {
- "Authorization" => "Basic " + Base64.encode64(key.to_s + ":").strip,
- "User-Agent" => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
- "X-Stripe-Client-User-Agent" => @@ua,
- "X-Stripe-Client-User-Metadata" => options[:meta].to_json
+ idempotency_key = options[:idempotency_key]
+
+ headers = {
+ 'Authorization' => 'Basic ' + Base64.encode64(key.to_s + ':').strip,
+ 'User-Agent' => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ 'Stripe-Version' => api_version(options),
+ 'X-Stripe-Client-User-Agent' => stripe_client_user_agent(options),
+ 'X-Stripe-Client-User-Metadata' => {:ip => options[:ip]}.to_json
}
+ headers['Idempotency-Key'] = idempotency_key if idempotency_key
+ headers['Stripe-Account'] = options[:stripe_account] if options[:stripe_account]
+ headers
end
- def commit(method, url, parameters=nil, options = {})
+ def stripe_client_user_agent(options)
+ return user_agent unless options[:application]
+ JSON.dump(JSON.parse(user_agent).merge!({application: options[:application]}))
+ end
+
+ def api_version(options)
+ options[:version] || @options[:version] || self.class::DEFAULT_API_VERSION
+ end
+
+ def api_request(method, endpoint, parameters = nil, options = {})
raw_response = response = nil
- success = false
begin
- raw_response = ssl_request(method, self.live_url + url, post_data(parameters), headers(options))
+ raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options))
response = parse(raw_response)
- success = !response.key?("error")
rescue ResponseError => e
raw_response = e.response.body
response = response_error(raw_response)
rescue JSON::ParserError
response = json_error(raw_response)
end
+ response
+ end
+
+ def commit(method, url, parameters = nil, options = {})
+ add_expand_parameters(parameters, options) if parameters
+ response = api_request(method, url, parameters, options)
+ response['webhook_id'] = options[:webhook_id] if options[:webhook_id]
+ success = success_from(response)
- card = response["card"] || response["active_card"] || {}
+ card = card_from_response(response)
avs_code = AVS_CODE_TRANSLATOR["line1: #{card["address_line1_check"]}, zip: #{card["address_zip_check"]}"]
- cvc_code = CVC_CODE_TRANSLATOR[card["cvc_check"]]
+ cvc_code = CVC_CODE_TRANSLATOR[card['cvc_check']]
+
Response.new(success,
- success ? "Transaction approved" : response["error"]["message"],
+ message_from(success, response),
response,
- :test => response.has_key?("livemode") ? !response["livemode"] : false,
- :authorization => response["id"],
+ :test => response_is_test?(response),
+ :authorization => authorization_from(success, url, method, response),
:avs_result => { :code => avs_code },
- :cvv_result => cvc_code
+ :cvv_result => cvc_code,
+ :emv_authorization => emv_authorization_from_response(response),
+ :error_code => success ? nil : error_code_from(response)
)
end
- def response_error(raw_response)
- begin
- parse(raw_response)
- rescue JSON::ParserError
- json_error(raw_response)
+ def authorization_from(success, url, method, response)
+ return response.fetch('error', {})['charge'] unless success
+
+ if url == 'customers'
+ [response['id'], response.dig('sources', 'data').first&.dig('id')].join('|')
+ elsif method == :post && (url.match(/customers\/.*\/cards/) || url.match(/payment_methods\/.*\/attach/))
+ [response['customer'], response['id']].join('|')
+ else
+ response['id']
end
end
+ def message_from(success, response)
+ success ? 'Transaction approved' : response.fetch('error', {'message' => 'No error details'})['message']
+ end
+
+ def success_from(response)
+ !response.key?('error') && response['status'] != 'failed'
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
def json_error(raw_response)
msg = 'Invalid response received from the Stripe API. Please contact support@stripe.com if you continue to receive this message.'
msg += " (The raw response returned by the API was #{raw_response.inspect})"
{
- "error" => {
- "message" => msg
+ 'error' => {
+ 'message' => msg
}
}
end
+
+ def response_is_test?(response)
+ if response.has_key?('livemode')
+ !response['livemode']
+ elsif response['charge'].is_a?(Hash) && response['charge'].has_key?('livemode')
+ !response['charge']['livemode']
+ else
+ false
+ end
+ end
+
+ def emv_payment?(payment)
+ payment.respond_to?(:emv?) && payment.emv?
+ end
+
+ def quickchip_payment?(payment)
+ payment.respond_to?(:read_method) && payment.read_method == 'contact_quickchip'
+ end
+
+ def card_from_response(response)
+ response['card'] || response['active_card'] || response['source'] || {}
+ end
+
+ def emv_authorization_from_response(response)
+ return response['error']['emv_auth_data'] if response['error']
+
+ card_from_response(response)['emv_auth_data']
+ end
+
+ def error_code_from(response)
+ return STANDARD_ERROR_CODE_MAPPING['processing_error'] unless response['error']
+
+ code = response['error']['code']
+ decline_code = response['error']['decline_code'] if code == 'card_declined'
+
+ error_code = STANDARD_ERROR_CODE_MAPPING[decline_code]
+ error_code ||= STANDARD_ERROR_CODE_MAPPING[code]
+ error_code
+ end
+
+ def tokenize_bank_account(bank_account, options = {})
+ account_holder_type = BANK_ACCOUNT_HOLDER_TYPE_MAPPING[bank_account.account_holder_type]
+
+ post = {
+ bank_account: {
+ account_number: bank_account.account_number,
+ country: 'US',
+ currency: 'usd',
+ routing_number: bank_account.routing_number,
+ name: bank_account.name,
+ account_holder_type: account_holder_type,
+ }
+ }
+
+ token_response = api_request(:post, "tokens?#{post_data(post)}")
+ success = token_response['error'].nil?
+
+ if success && token_response['id']
+ Response.new(success, nil, token: token_response)
+ else
+ Response.new(success, token_response['error']['message'])
+ end
+ end
+
+ def ach?(payment_method)
+ case payment_method
+ when String, nil
+ false
+ else
+ card_brand(payment_method) == 'check'
+ end
+ end
+
+ def auth_minimum_amount(options)
+ return 100 unless options[:currency]
+ return MINIMUM_AUTHORIZE_AMOUNTS[options[:currency].upcase] || 100
+ end
+
+ def copy_when_present(dest, dest_path, source, source_path = nil)
+ source_path ||= dest_path
+ source_path.each do |key|
+ return nil unless source[key]
+ source = source[key]
+ end
+
+ if source
+ dest_path.first(dest_path.size - 1).each do |key|
+ dest[key] ||= {}
+ dest = dest[key]
+ end
+ dest[dest_path.last] = source
+ end
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/stripe_payment_intents.rb b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
new file mode 100644
index 00000000000..6469e671113
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/stripe_payment_intents.rb
@@ -0,0 +1,267 @@
+require 'active_support/core_ext/hash/slice'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ # This gateway uses the current Stripe {Payment Intents API}[https://stripe.com/docs/api/payment_intents].
+ # For the legacy API, see the Stripe gateway
+ class StripePaymentIntentsGateway < StripeGateway
+
+ self.supported_countries = %w(AT AU BE BR CA CH DE DK ES FI FR GB HK IE IT JP LU MX NL NO NZ PT SE SG US)
+
+ ALLOWED_METHOD_STATES = %w[automatic manual].freeze
+ ALLOWED_CANCELLATION_REASONS = %w[duplicate fraudulent requested_by_customer abandoned].freeze
+ CREATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email save_payment_method]
+ CONFIRM_INTENT_ATTRIBUTES = %i[receipt_email return_url save_payment_method setup_future_usage off_session]
+ UPDATE_INTENT_ATTRIBUTES = %i[description statement_descriptor receipt_email setup_future_usage]
+ DEFAULT_API_VERSION = '2019-05-16'
+
+ def create_intent(money, payment_method, options = {})
+ post = {}
+ add_amount(post, money, options, true)
+ add_capture_method(post, options)
+ add_confirmation_method(post, options)
+ add_customer(post, options)
+ add_payment_method_token(post, payment_method, options)
+ add_metadata(post, options)
+ add_return_url(post, options)
+ add_connected_account(post, options)
+ add_shipping_address(post, options)
+ setup_future_usage(post, options)
+ add_exemption(post, options)
+
+ CREATE_INTENT_ATTRIBUTES.each do |attribute|
+ add_whitelisted_attribute(post, options, attribute)
+ end
+
+ commit(:post, 'payment_intents', post, options)
+ end
+
+ def show_intent(intent_id, options)
+ commit(:get, "payment_intents/#{intent_id}", nil, options)
+ end
+
+ def confirm_intent(intent_id, payment_method, options = {})
+ post = {}
+ add_payment_method_token(post, payment_method, options)
+ CONFIRM_INTENT_ATTRIBUTES.each do |attribute|
+ add_whitelisted_attribute(post, options, attribute)
+ end
+
+ commit(:post, "payment_intents/#{intent_id}/confirm", post, options)
+ end
+
+ def create_payment_method(payment_method, options = {})
+ post = {}
+ post[:type] = 'card'
+ post[:card] = {}
+ post[:card][:number] = payment_method.number
+ post[:card][:exp_month] = payment_method.month
+ post[:card][:exp_year] = payment_method.year
+ post[:card][:cvc] = payment_method.verification_value if payment_method.verification_value
+ add_billing_address(post, options)
+
+ commit(:post, 'payment_methods', post, options)
+ end
+
+ def update_intent(money, intent_id, payment_method, options = {})
+ post = {}
+ post[:amount] = money if money
+
+ add_payment_method_token(post, payment_method, options)
+ add_payment_method_types(post, options)
+ add_customer(post, options)
+ add_metadata(post, options)
+ add_shipping_address(post, options)
+ add_connected_account(post, options)
+
+ UPDATE_INTENT_ATTRIBUTES.each do |attribute|
+ add_whitelisted_attribute(post, options, attribute)
+ end
+
+ commit(:post, "payment_intents/#{intent_id}", post, options)
+ end
+
+ def authorize(money, payment_method, options = {})
+ create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'manual'))
+ end
+
+ def purchase(money, payment_method, options = {})
+ create_intent(money, payment_method, options.merge!(confirm: true, capture_method: 'automatic'))
+ end
+
+ def capture(money, intent_id, options = {})
+ post = {}
+ post[:amount_to_capture] = money
+ add_connected_account(post, options)
+ commit(:post, "payment_intents/#{intent_id}/capture", post, options)
+ end
+
+ def void(intent_id, options = {})
+ post = {}
+ post[:cancellation_reason] = options[:cancellation_reason] if ALLOWED_CANCELLATION_REASONS.include?(options[:cancellation_reason])
+ commit(:post, "payment_intents/#{intent_id}/cancel", post, options)
+ end
+
+ def refund(money, intent_id, options = {})
+ intent = commit(:get, "payment_intents/#{intent_id}", nil, options)
+ charge_id = intent.params.dig('charges', 'data')[0].dig('id')
+ super(money, charge_id, options)
+ end
+
+ # Note: Not all payment methods are currently supported by the {Payment Methods API}[https://stripe.com/docs/payments/payment-methods]
+ # Current implementation will create a PaymentMethod object if the method is a token or credit card
+ # All other types will default to legacy Stripe store
+ def store(payment_method, options = {})
+ params = {}
+ post = {}
+
+ # If customer option is provided, create a payment method and attach to customer id
+ # Otherwise, create a customer, then attach
+ if payment_method.is_a?(StripePaymentToken) || payment_method.is_a?(ActiveMerchant::Billing::CreditCard)
+ add_payment_method_token(params, payment_method, options)
+ if options[:customer]
+ customer_id = options[:customer]
+ else
+ post[:validate] = options[:validate] unless options[:validate].nil?
+ post[:description] = options[:description] if options[:description]
+ post[:email] = options[:email] if options[:email]
+ customer = commit(:post, 'customers', post, options)
+ customer_id = customer.params['id']
+ end
+ commit(:post, "payment_methods/#{params[:payment_method]}/attach", { customer: customer_id }, options)
+ else
+ super(payment, options)
+ end
+ end
+
+ def unstore(identification, options = {}, deprecated_options = {})
+ if identification.include?('pm_')
+ _, payment_method = identification.split('|')
+ commit(:post, "payment_methods/#{payment_method}/detach", nil, options)
+ else
+ super(identification, options, deprecated_options)
+ end
+ end
+
+ private
+
+ def add_whitelisted_attribute(post, options, attribute)
+ post[attribute] = options[attribute] if options[attribute]
+ post
+ end
+
+ def add_capture_method(post, options)
+ capture_method = options[:capture_method].to_s
+ post[:capture_method] = capture_method if ALLOWED_METHOD_STATES.include?(capture_method)
+ post
+ end
+
+ def add_confirmation_method(post, options)
+ confirmation_method = options[:confirmation_method].to_s
+ post[:confirmation_method] = confirmation_method if ALLOWED_METHOD_STATES.include?(confirmation_method)
+ post
+ end
+
+ def add_customer(post, options)
+ customer = options[:customer].to_s
+ post[:customer] = customer if customer.start_with?('cus_')
+ post
+ end
+
+ def add_return_url(post, options)
+ return unless options[:confirm]
+ post[:confirm] = options[:confirm]
+ post[:return_url] = options[:return_url] if options[:return_url]
+ post
+ end
+
+ def add_payment_method_token(post, payment_method, options)
+ return if payment_method.nil?
+
+ if payment_method.is_a?(ActiveMerchant::Billing::CreditCard)
+ p = create_payment_method(payment_method, options)
+ payment_method = p.params['id']
+ end
+
+ if payment_method.is_a?(StripePaymentToken)
+ post[:payment_method] = payment_method.payment_data['id']
+ elsif payment_method.is_a?(String)
+ if payment_method.include?('|')
+ customer_id, payment_method_id = payment_method.split('|')
+ token = payment_method_id
+ post[:customer] = customer_id
+ else
+ token = payment_method
+ end
+ post[:payment_method] = token
+ end
+ end
+
+ def add_payment_method_types(post, options)
+ payment_method_types = options[:payment_method_types] if options[:payment_method_types]
+ return if payment_method_types.nil?
+
+ post[:payment_method_types] = Array(payment_method_types)
+ post
+ end
+
+ def add_exemption(post, options = {})
+ return unless options[:confirm]
+ post[:payment_method_options] ||= {}
+ post[:payment_method_options][:card] ||= {}
+ post[:payment_method_options][:card][:moto] = true if options[:moto]
+ end
+
+ def setup_future_usage(post, options = {})
+ post[:setup_future_usage] = options[:setup_future_usage] if %w( on_session off_session ).include?(options[:setup_future_usage])
+ post[:off_session] = options[:off_session] if options[:off_session] && options[:confirm] == true
+ post
+ end
+
+ def add_connected_account(post, options = {})
+ return unless options[:transfer_destination]
+ post[:transfer_data] = {}
+ post[:transfer_data][:destination] = options[:transfer_destination]
+ post[:transfer_data][:amount] = options[:transfer_amount] if options[:transfer_amount]
+ post[:on_behalf_of] = options[:on_behalf_of] if options[:on_behalf_of]
+ post[:transfer_group] = options[:transfer_group] if options[:transfer_group]
+ post[:application_fee_amount] = options[:application_fee] if options[:application_fee]
+ post
+ end
+
+ def add_billing_address(post, options = {})
+ return unless billing = options[:billing_address] || options[:address]
+ post[:billing_details] = {}
+ post[:billing_details][:address] = {}
+ post[:billing_details][:address][:city] = billing[:city] if billing[:city]
+ post[:billing_details][:address][:country] = billing[:country] if billing[:country]
+ post[:billing_details][:address][:line1] = billing[:address1] if billing[:address1]
+ post[:billing_details][:address][:line2] = billing[:address2] if billing[:address2]
+ post[:billing_details][:address][:postal_code] = billing[:zip] if billing[:zip]
+ post[:billing_details][:address][:state] = billing[:state] if billing[:state]
+ post[:billing_details][:email] = billing[:email] if billing[:email]
+ post[:billing_details][:name] = billing[:name] if billing[:name]
+ post[:billing_details][:phone] = billing[:phone] if billing[:phone]
+ post
+ end
+
+ def add_shipping_address(post, options = {})
+ return unless shipping = options[:shipping]
+ post[:shipping] = {}
+ post[:shipping][:address] = {}
+ post[:shipping][:address][:line1] = shipping[:address][:line1]
+ post[:shipping][:address][:city] = shipping[:address][:city] if shipping[:address][:city]
+ post[:shipping][:address][:country] = shipping[:address][:country] if shipping[:address][:country]
+ post[:shipping][:address][:line2] = shipping[:address][:line2] if shipping[:address][:line2]
+ post[:shipping][:address][:postal_code] = shipping[:address][:postal_code] if shipping[:address][:postal_code]
+ post[:shipping][:address][:state] = shipping[:address][:state] if shipping[:address][:state]
+
+ post[:shipping][:name] = shipping[:name]
+ post[:shipping][:carrier] = shipping[:carrier] if shipping[:carrier]
+ post[:shipping][:phone] = shipping[:phone] if shipping[:phone]
+ post[:shipping][:tracking_number] = shipping[:tracking_number] if shipping[:tracking_number]
+ post
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/swipe_checkout.rb b/lib/active_merchant/billing/gateways/swipe_checkout.rb
new file mode 100644
index 00000000000..aaf4002d355
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/swipe_checkout.rb
@@ -0,0 +1,152 @@
+require 'json'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class SwipeCheckoutGateway < Gateway
+ TRANSACTION_APPROVED_MSG = 'Transaction approved'
+ TRANSACTION_DECLINED_MSG = 'Transaction declined'
+
+ self.live_url = 'https://api.swipehq.com'
+ self.test_url = 'https://api.swipehq.com'
+
+ TRANSACTION_API = '/createShopifyTransaction.php'
+
+ self.supported_countries = %w[ NZ CA ]
+ self.default_currency = 'NZD'
+ self.supported_cardtypes = [:visa, :master]
+ self.homepage_url = 'https://www.swipehq.com/checkout'
+ self.display_name = 'Swipe Checkout'
+ self.money_format = :dollars
+
+ # Swipe Checkout requires the merchant's email and API key for authorization.
+ # This can be found under Settings > API Credentials after logging in to your
+ # Swipe Checkout merchant console at https://merchant.swipehq.[com|ca]
+ #
+ # :region determines which Swipe URL is used, this can be one of "NZ" or "CA".
+ # Currently Swipe Checkout has New Zealand and Canadian domains (swipehq.com
+ # and swipehq.ca respectively). Merchants must use the region that they
+ # signed up in for authentication with their merchant ID and API key to succeed.
+ def initialize(options = {})
+ requires!(options, :login, :api_key, :region)
+ super
+ end
+
+ # Transfers funds immediately.
+ # Note that Swipe Checkout only supports purchase at this stage
+ def purchase(money, creditcard, options = {})
+ post = {}
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_customer_data(post, creditcard, options)
+ add_amount(post, money, options)
+
+ commit('sale', money, post)
+ end
+
+ private
+
+ def add_customer_data(post, creditcard, options)
+ post[:email] = options[:email]
+ post[:ip_address] = options[:ip]
+
+ address = options[:billing_address] || options[:address]
+ return if address.nil?
+
+ post[:company] = address[:company]
+
+ post[:first_name], post[:last_name] = split_names(address[:name])
+ post[:address] = "#{address[:address1]}, #{address[:address2]}"
+ post[:city] = address[:city]
+ post[:country] = address[:country]
+ post[:mobile] = address[:phone] # API only has a "mobile" field, no "phone"
+ end
+
+ def add_invoice(post, options)
+ # store shopping-cart order ID in Swipe for merchant's records
+ post[:td_user_data] = options[:order_id] if options[:order_id]
+ post[:td_item] = options[:description] if options[:description]
+ post[:td_description] = options[:description] if options[:description]
+ post[:item_quantity] = '1'
+ end
+
+ def add_creditcard(post, creditcard)
+ post[:card_number] = creditcard.number
+ post[:card_type] = creditcard.brand
+ post[:name_on_card] = "#{creditcard.first_name} #{creditcard.last_name}"
+ post[:card_expiry] = expdate(creditcard)
+ post[:secure_number] = creditcard.verification_value
+ end
+
+ def expdate(creditcard)
+ year = format(creditcard.year, :two_digits)
+ month = format(creditcard.month, :two_digits)
+
+ "#{month}#{year}"
+ end
+
+ def add_amount(post, money, options)
+ post[:amount] = money.to_s
+
+ post[:currency] = (options[:currency] || currency(money))
+ end
+
+ def commit(action, money, parameters)
+ case action
+ when 'sale'
+ begin
+ response = call_api(TRANSACTION_API, parameters)
+
+ # response code and message params should always be present
+ code = response['response_code']
+ message = response['message']
+
+ if code == 200
+ result = response['data']['result']
+ success = (result == 'accepted' || (test? && result == 'test-accepted'))
+
+ Response.new(success,
+ success ?
+ TRANSACTION_APPROVED_MSG :
+ TRANSACTION_DECLINED_MSG,
+ response,
+ :test => test?
+ )
+ else
+ build_error_response(message, response)
+ end
+ rescue ResponseError => e
+ build_error_response("ssl_post() with url #{url} raised ResponseError: #{e}")
+ rescue JSON::ParserError => e
+ msg = 'Invalid response received from the Swipe Checkout API. ' \
+ 'Please contact support@optimizerhq.com if you continue to receive this message.' \
+ " (Full error message: #{e})"
+ build_error_response(msg)
+ end
+ end
+ end
+
+ def call_api(api, params=nil)
+ params ||= {}
+ params[:merchant_id] = @options[:login]
+ params[:api_key] = @options[:api_key]
+
+ # ssl_post() returns the response body as a string on success,
+ # or raises a ResponseError exception on failure
+ JSON.parse(ssl_post(url(api), params.to_query))
+ end
+
+ def url(api)
+ (test? ? self.test_url : self.live_url) + api
+ end
+
+ def build_error_response(message, params={})
+ Response.new(
+ false,
+ message,
+ params,
+ :test => test?
+ )
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/telr.rb b/lib/active_merchant/billing/gateways/telr.rb
new file mode 100644
index 00000000000..c39b0a17e34
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/telr.rb
@@ -0,0 +1,274 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class TelrGateway < Gateway
+ self.display_name = 'Telr'
+ self.homepage_url = 'http://www.telr.com/'
+
+ self.live_url = 'https://secure.telr.com/gateway/remote.xml'
+
+ self.supported_countries = ['AE', 'IN', 'SA']
+ self.default_currency = 'AED'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :maestro, :jcb]
+
+ CVC_CODE_TRANSLATOR = {
+ 'Y' => 'M',
+ 'N' => 'N',
+ 'X' => 'P',
+ 'E' => 'U',
+ }
+
+ AVS_CODE_TRANSLATOR = {
+ 'Y' => 'M',
+ 'P' => 'A',
+ 'N' => 'N',
+ 'X' => 'I',
+ 'E' => 'R'
+ }
+
+ def initialize(options={})
+ requires!(options, :merchant_id, :api_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ commit(:purchase, amount, options[:currency]) do |doc|
+ add_invoice(doc, 'sale', amount, payment_method, options)
+ add_payment_method(doc, payment_method, options)
+ add_customer_data(doc, payment_method, options)
+ end
+ end
+
+ def authorize(amount, payment_method, options={})
+ commit(:authorize, amount, options[:currency]) do |doc|
+ add_invoice(doc, 'auth', amount, payment_method, options)
+ add_payment_method(doc, payment_method, options)
+ add_customer_data(doc, payment_method, options)
+ end
+ end
+
+ def capture(amount, authorization, options={})
+ commit(:capture) do |doc|
+ add_invoice(doc, 'capture', amount, authorization, options)
+ end
+ end
+
+ def void(authorization, options={})
+ _, amount, currency = split_authorization(authorization)
+ commit(:void) do |doc|
+ add_invoice(doc, 'void', amount.to_i, authorization, options.merge(currency: currency))
+ end
+ end
+
+ def refund(amount, authorization, options={})
+ commit(:refund) do |doc|
+ add_invoice(doc, 'refund', amount, authorization, options)
+ end
+ end
+
+ def verify(credit_card, options={})
+ commit(:verify) do |doc|
+ add_invoice(doc, 'verify', 100, credit_card, options)
+ add_payment_method(doc, credit_card, options)
+ add_customer_data(doc, credit_card, options)
+ end
+ end
+
+ def verify_credentials
+ response = void('0')
+ !['01', '04'].include?(response.error_code)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r(()[^<]+(<))i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def add_invoice(doc, action, money, payment_method, options)
+ doc.tran do
+ doc.type(action)
+ doc.amount(amount(money))
+ doc.currency(options[:currency] || currency(money))
+ doc.cartid(options[:order_id])
+ doc.class_(transaction_class(action, payment_method))
+ doc.description(options[:description] || 'Description')
+ doc.test_(test_mode)
+ add_ref(doc, action, payment_method)
+ end
+ end
+
+ def add_payment_method(doc, payment_method, options)
+ return if payment_method.is_a?(String)
+ doc.card do
+ doc.number(payment_method.number)
+ doc.cvv(payment_method.verification_value)
+ doc.expiry do
+ doc.month(format(payment_method.month, :two_digits))
+ doc.year(format(payment_method.year, :four_digits))
+ end
+ end
+ end
+
+ def add_customer_data(doc, payment_method, options)
+ return if payment_method.is_a?(String)
+ doc.billing do
+ doc.name do
+ doc.first(payment_method.first_name)
+ doc.last(payment_method.last_name)
+ end
+ doc.email(options[:email] || 'unspecified@email.com')
+ doc.ip(options[:ip]) if options[:ip]
+ doc.address do
+ add_address(doc, options)
+ end
+ end
+ end
+
+ def add_address(doc, options)
+ address = options[:billing_address] || {}
+ doc.country(address[:country] ? lookup_country_code(address[:country]) : 'NA')
+ doc.city(address[:city] || 'City')
+ doc.line1(address[:address1] || 'Address')
+ return unless address
+ doc.line2(address[:address2]) if address[:address2]
+ doc.zip(address[:zip]) if address[:zip]
+ doc.region(address[:state]) if address[:state]
+ end
+
+ def add_ref(doc, action, payment_method)
+ if ['capture', 'refund', 'void'].include?(action) || payment_method.is_a?(String)
+ doc.ref(split_authorization(payment_method)[0])
+ end
+ end
+
+ def add_authentication(doc)
+ doc.store(@options[:merchant_id])
+ doc.key(@options[:api_key])
+ end
+
+ def lookup_country_code(code)
+ country = Country.find(code) rescue nil
+ country.code(:alpha2)
+ end
+
+ def commit(action, amount=nil, currency=nil)
+ currency = default_currency if currency == nil
+ request = build_xml_request { |doc| yield(doc) }
+ response = ssl_post(live_url, request, headers)
+ parsed = parse(response)
+
+ succeeded = success_from(parsed)
+ Response.new(
+ succeeded,
+ message_from(succeeded, parsed),
+ parsed,
+ authorization: authorization_from(action, parsed, amount, currency),
+ avs_result: avs_result(parsed),
+ cvv_result: cvv_result(parsed),
+ error_code: error_code_from(succeeded, parsed),
+ test: test?
+ )
+ end
+
+ def root_attributes
+ {
+ store: @options[:merchant_id],
+ key: @options[:api_key]
+ }
+ end
+
+ def build_xml_request
+ builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
+ xml.remote do |doc|
+ add_authentication(doc)
+ yield(doc)
+ end
+ end
+
+ builder.doc.to_xml
+ end
+
+ def test_mode
+ test? ? '1' : '0'
+ end
+
+ def transaction_class(action, payment_method)
+ if payment_method.is_a?(String) && action == 'sale'
+ return 'cont'
+ else
+ return 'moto'
+ end
+ end
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.root&.xpath('*')&.each do |node|
+ if node.elements.size == 0
+ response[node.name.downcase.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ name = childnode.name.downcase
+ response[name.to_sym] = childnode.text
+ end
+ end
+ end
+
+ response
+ end
+
+ def authorization_from(action, response, amount, currency)
+ auth = response[:tranref]
+ auth = [auth, amount, currency].join('|')
+ auth
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def success_from(response)
+ response[:status] == 'A'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ response[:message]
+ end
+ end
+
+ def error_code_from(succeeded, response)
+ unless succeeded
+ response[:code]
+ end
+ end
+
+ def cvv_result(parsed)
+ CVVResult.new(CVC_CODE_TRANSLATOR[parsed[:cvv]])
+ end
+
+ def avs_result(parsed)
+ AVSResult.new(code: AVS_CODE_TRANSLATOR[parsed[:avs]])
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'text/xml'
+ }
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/tns.rb b/lib/active_merchant/billing/gateways/tns.rb
new file mode 100644
index 00000000000..25ed79306a9
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/tns.rb
@@ -0,0 +1,22 @@
+module ActiveMerchant
+ module Billing
+ class TnsGateway < Gateway
+ include MastercardGateway
+
+ class_attribute :live_na_url, :live_ap_url, :test_na_url, :test_ap_url
+
+ self.live_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/'
+ self.test_na_url = 'https://secure.na.tnspayments.com/api/rest/version/36/'
+
+ self.live_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/'
+ self.test_ap_url = 'https://secure.ap.tnspayments.com/api/rest/version/36/'
+
+ self.display_name = 'TNS'
+ self.homepage_url = 'http://www.tnsi.com/'
+ self.supported_countries = %w(AR AU BR FR DE HK MX NZ SG GB US)
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro]
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/trans_first.rb b/lib/active_merchant/billing/gateways/trans_first.rb
index 013fdf5813b..8b2c579683f 100644
--- a/lib/active_merchant/billing/gateways/trans_first.rb
+++ b/lib/active_merchant/billing/gateways/trans_first.rb
@@ -1,66 +1,146 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class TransFirstGateway < Gateway
- self.live_url = self.test_url = 'https://webservices.primerchants.com/creditcard.asmx/CCSale'
+ self.test_url = 'https://ws.cert.transfirst.com'
+ self.live_url = 'https://webservices.primerchants.com'
self.supported_countries = ['US']
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.homepage_url = 'http://www.transfirst.com/'
self.display_name = 'TransFirst'
- UNUSED_FIELDS = %w(ECIValue UserId CAVVData TrackData POSInd EComInd MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSEntryMode POSConditionCode AuthCharInd CardCertData)
+ UNUSED_CREDIT_CARD_FIELDS = %w(UserId TrackData MerchZIP MerchCustPNum MCC InstallmentNum InstallmentOf POSInd POSEntryMode POSConditionCode EComInd AuthCharInd CardCertData CAVVData)
DECLINED = 'The transaction was declined'
+ ACTIONS = {
+ purchase: 'CCSale',
+ purchase_echeck: 'ACHDebit',
+ refund: 'CreditCardCredit',
+ refund_echeck: 'ACHVoidTransaction',
+ void: 'CreditCardAutoRefundorVoid',
+ }
+
+ ENDPOINTS = {
+ purchase: 'creditcard.asmx',
+ purchase_echeck: 'checkverifyws/checkverifyws.asmx',
+ refund: 'creditcard.asmx',
+ refund_echeck: 'checkverifyws/checkverifyws.asmx',
+ void: 'creditcard.asmx'
+ }
+
def initialize(options = {})
requires!(options, :login, :password)
super
end
- def purchase(money, credit_card, options = {})
+ def purchase(money, payment, options = {})
post = {}
add_amount(post, money)
- add_invoice(post, options)
- add_credit_card(post, credit_card)
+ add_payment(post, payment)
add_address(post, options)
+ add_invoice(post, options) if payment.credit_card?
+ add_pair(post, :RefID, options[:order_id], required: true)
+
+ commit((payment.is_a?(Check) ? :purchase_echeck : :purchase), post)
+ end
+
+ def refund(money, authorization, options={})
+ post = {}
+
+ transaction_id, payment_type = split_authorization(authorization)
+ add_amount(post, money)
+ add_pair(post, :TransID, transaction_id)
+ add_pair(post, :RefID, options[:order_id], required: true)
+
+ commit((payment_type == 'check' ? :refund_echeck : :refund), post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+
+ transaction_id, _ = split_authorization(authorization)
+ add_pair(post, :TransID, transaction_id)
+
+ commit(:void, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
- commit(post)
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?RegKey=)\w*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?CardNumber=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?CVV2=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?TransRoute=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?BankAccountNo=)\d*(&?)), '\1[FILTERED]\2')
end
private
+
def add_amount(post, money)
- add_pair(post, :Amount, amount(money), :required => true)
+ add_pair(post, :Amount, amount(money), required: true)
end
def add_address(post, options)
address = options[:billing_address] || options[:address]
if address
- add_pair(post, :Address, address[:address1])
- add_pair(post, :ZipCode, address[:zip])
+ add_pair(post, :Address, address[:address1], required: true)
+ add_pair(post, :ZipCode, address[:zip], required: true)
+ else
+ add_pair(post, :Address, '', required: true)
+ add_pair(post, :ZipCode, '', required: true)
end
end
def add_invoice(post, options)
- add_pair(post, :RefID, options[:order_id], :required => true)
- add_pair(post, :PONumber, options[:invoice], :required => true)
+ add_pair(post, :SECCCode, options[:invoice], required: true)
+ add_pair(post, :PONumber, options[:invoice], required: true)
add_pair(post, :SaleTaxAmount, amount(options[:tax] || 0))
- add_pair(post, :PaymentDesc, options[:description], :required => true)
add_pair(post, :TaxIndicator, 0)
+ add_pair(post, :PaymentDesc, options[:description] || '', required: true)
+ add_pair(post, :CompanyName, options[:company_name] || '', required: true)
+ end
+
+ def add_payment(post, payment)
+ if payment.is_a?(Check)
+ add_echeck(post, payment)
+ else
+ add_credit_card(post, payment)
+ end
end
- def add_credit_card(post, credit_card)
- add_pair(post, :CardHolderName, credit_card.name, :required => true)
- add_pair(post, :CardNumber, credit_card.number, :required => true)
+ def add_credit_card(post, payment)
+ add_pair(post, :CardHolderName, payment.name, required: true)
+ add_pair(post, :CardNumber, payment.number, required: true)
+ add_pair(post, :Expiration, expdate(payment), required: true)
+ add_pair(post, :CVV2, payment.verification_value, required: true)
+ end
+
+ def add_echeck(post, payment)
+ add_pair(post, :TransRoute, payment.routing_number, required: true)
+ add_pair(post, :BankAccountNo, payment.account_number, required: true)
+ add_pair(post, :BankAccountType, add_or_use_default(payment.account_type, 'Checking'), required: true)
+ add_pair(post, :CheckType, add_or_use_default(payment.account_holder_type, 'Personal'), required: true)
+ add_pair(post, :Name, payment.name, required: true)
+ add_pair(post, :ProcessDate, Time.now.strftime('%m%d%y'), required: true)
+ add_pair(post, :Description, '', required: true)
+ end
- add_pair(post, :Expiration, expdate(credit_card), :required => true)
- add_pair(post, :CVV2, credit_card.verification_value)
+ def add_or_use_default(payment_data, default_value)
+ return payment_data.capitalize if payment_data
+ return default_value
end
- def add_unused_fields(post)
- UNUSED_FIELDS.each do |f|
- post[f] = ""
+ def add_unused_fields(action, post)
+ return unless action == :purchase
+
+ UNUSED_CREDIT_CARD_FIELDS.each do |f|
+ post[f] = ''
end
end
@@ -75,7 +155,7 @@ def parse(data)
response = {}
xml = REXML::Document.new(data)
- root = REXML::XPath.first(xml, "//CCSaleDebitResponse")
+ root = REXML::XPath.first(xml, '*')
if root.nil?
response[:message] = data.to_s.strip
@@ -88,17 +168,42 @@ def parse(data)
response
end
- def commit(params)
- response = parse( ssl_post(self.live_url, post_data(params)) )
-
- Response.new(response[:status] == "Authorized", message_from(response), response,
+ def commit(action, params)
+ response = parse(ssl_post(url(action), post_data(action, params)))
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
:test => test?,
- :authorization => response[:trans_id],
+ :authorization => authorization_from(response),
:avs_result => { :code => response[:avs_code] },
:cvv_result => response[:cvv2_code]
)
end
+ def authorization_from(response)
+ if response[:status] == 'APPROVED'
+ "#{response[:trans_id]}|check"
+ else
+ "#{response[:trans_id]}|creditcard"
+ end
+ end
+
+ def success_from(response)
+ case response[:status]
+ when 'Authorized'
+ true
+ when 'Voided'
+ true
+ when 'APPROVED'
+ true
+ when 'VOIDED'
+ true
+ else
+ false
+ end
+ end
+
def message_from(response)
case response[:message]
when 'Call Voice Center'
@@ -108,19 +213,27 @@ def message_from(response)
end
end
- def post_data(params = {})
- add_unused_fields(params)
+ def post_data(action, params = {})
+ add_unused_fields(action, params)
params[:MerchantID] = @options[:login]
params[:RegKey] = @options[:password]
- request = params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ request = params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
request
end
def add_pair(post, key, value, options = {})
post[key] = value if !value.blank? || options[:required]
end
+
+ def url(action)
+ base_url = test? ? test_url : live_url
+ "#{base_url}/#{ENDPOINTS[action]}/#{ACTIONS[action]}"
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb
new file mode 100644
index 00000000000..f35fe0d9b78
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb
@@ -0,0 +1,608 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class TransFirstTransactionExpressGateway < Gateway
+ self.display_name = 'TransFirst Transaction Express'
+ self.homepage_url = 'http://transactionexpress.com/'
+
+ self.test_url = 'https://ws.cert.transactionexpress.com/portal/merchantframework/MerchantWebServices-v1?wsdl'
+ self.live_url = 'https://ws.transactionexpress.com/portal/merchantframework/MerchantWebServices-v1?wsdl'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
+
+ V1_NAMESPACE = 'http://postilion/realtime/merchantframework/xsd/v1/'
+ SOAPENV_NAMESPACE = 'http://schemas.xmlsoap.org/soap/envelope/'
+ AUTHORIZATION_FIELD_SEPARATOR = '|'
+
+ APPROVAL_CODES = %w(00 10)
+
+ RESPONSE_MESSAGES = {
+ '00' => 'Approved',
+ '01' => 'Refer to card issuer',
+ '02' => 'Refer to card issuer, special condition',
+ '03' => 'Invalid merchant',
+ '04' => 'Pick-up card',
+ '05' => 'Do not honor',
+ '06' => 'Error',
+ '07' => 'Pick-up card, special condition',
+ '08' => 'Honor with identification',
+ '09' => 'Request in progress',
+ '10' => 'Approved, partial authorization',
+ '11' => 'VIP Approval',
+ '12' => 'Invalid transaction',
+ '13' => 'Invalid amount',
+ '14' => 'Invalid card number',
+ '15' => 'No such issuer',
+ '16' => 'Approved, update track 3',
+ '17' => 'Customer cancellation',
+ '18' => 'Customer dispute',
+ '19' => 'Re-enter transaction',
+ '20' => 'Invalid response',
+ '21' => 'No action taken',
+ '22' => 'Suspected malfunction',
+ '23' => 'Unacceptable transaction fee',
+ '24' => 'File update not supported',
+ '25' => 'Unable to locate record',
+ '26' => 'Duplicate record',
+ '27' => 'File update field edit error',
+ '28' => 'File update file locked',
+ '29' => 'File update failed',
+ '30' => 'Format error',
+ '31' => 'Bank not supported',
+ '33' => 'Expired card, pick-up',
+ '34' => 'Suspected fraud, pick-up',
+ '35' => 'Contact acquirer, pick-up',
+ '36' => 'Restricted card, pick-up',
+ '37' => 'Call acquirer security, pick-up',
+ '38' => 'PIN tries exceeded, pick-up',
+ '39' => 'No credit account',
+ '40' => 'Function not supported',
+ '41' => 'Lost card, pick-up',
+ '42' => 'No universal account',
+ '43' => 'Stolen card, pick-up',
+ '44' => 'No investment account',
+ '45' => 'Account closed',
+ '46' => 'Identification required',
+ '47' => 'Identification cross-check required',
+ '48' => 'No customer record',
+ '49' => 'Reserved for future Realtime use',
+ '50' => 'Reserved for future Realtime use',
+ '51' => 'Not sufficient funds',
+ '52' => 'No checking account',
+ '53' => 'No savings account',
+ '54' => 'Expired card',
+ '55' => 'Incorrect PIN',
+ '56' => 'No card record',
+ '57' => 'Transaction not permitted to cardholder',
+ '58' => 'Transaction not permitted on terminal',
+ '59' => 'Suspected fraud',
+ '60' => 'Contact acquirer',
+ '61' => 'Exceeds withdrawal limit',
+ '62' => 'Restricted card',
+ '63' => 'Security violation',
+ '64' => 'Original amount incorrect',
+ '65' => 'Exceeds withdrawal frequency',
+ '66' => 'Call acquirer security',
+ '67' => 'Hard capture',
+ '68' => 'Response received too late',
+ '69' => 'Advice received too late (the response from a request was received too late )',
+ '70' => 'Reserved for future use',
+ '71' => 'Reserved for future Realtime use',
+ '72' => 'Reserved for future Realtime use',
+ '73' => 'Reserved for future Realtime use',
+ '74' => 'Reserved for future Realtime use',
+ '75' => 'PIN tries exceeded',
+ '76' => 'Reversal: Unable to locate previous message (no match on Retrieval Reference Number)/ Reserved for future Realtime use',
+ '77' => 'Previous message located for a repeat or reversal, but repeat or reversal data is inconsistent with original message/ Intervene, bank approval required',
+ '78' => 'Invalid/non-existent account – Decline (MasterCard specific)/ Intervene, bank approval required for partial amount',
+ '79' => 'Already reversed (by Switch)/ Reserved for client-specific use (declined)',
+ '80' => 'No financial Impact (Reserved for declined debit)/ Reserved for client-specific use (declined)',
+ '81' => 'PIN cryptographic error found by the Visa security module during PIN decryption/ Reserved for client-specific use (declined)',
+ '82' => 'Incorrect CVV/ Reserved for client-specific use (declined)',
+ '83' => 'Unable to verify PIN/ Reserved for client-specific use (declined)',
+ '84' => 'Invalid Authorization Life Cycle – Decline (MasterCard) or Duplicate Transaction Detected (Visa)/ Reserved for client-specific use (declined)',
+ '85' => 'No reason to decline a request for Account Number Verification or Address Verification/ Reserved for client-specific use (declined)',
+ '86' => 'Cannot verify PIN/ Reserved for client-specific use (declined)',
+ '87' => 'Reserved for client-specific use (declined)',
+ '88' => 'Reserved for client-specific use (declined)',
+ '89' => 'Reserved for client-specific use (declined)',
+ '90' => 'Cut-off in progress',
+ '91' => 'Issuer or switch inoperative',
+ '92' => 'Routing error',
+ '93' => 'Violation of law',
+ '94' => 'Duplicate Transmission (Integrated Debit and MasterCard)',
+ '95' => 'Reconcile error',
+ '96' => 'System malfunction',
+ '97' => 'Reserved for future Realtime use',
+ '98' => 'Exceeds cash limit',
+ '99' => 'Reserved for future Realtime use',
+ '1106' => 'Reserved for future Realtime use',
+ '0A' => 'Reserved for future Realtime use',
+ 'A0' => 'Reserved for future Realtime use',
+ 'A1' => 'ATC not incremented',
+ 'A2' => 'ATC limit exceeded',
+ 'A3' => 'ATC configuration error',
+ 'A4' => 'CVR check failure',
+ 'A5' => 'CVR configuration error',
+ 'A6' => 'TVR check failure',
+ 'A7' => 'TVR configuration error',
+ 'A8' => 'Reserved for future Realtime use',
+ 'B1' => 'Surcharge amount not permitted on Visa cards or EBT Food Stamps/ Reserved for future Realtime use',
+ 'B2' => 'Surcharge amount not supported by debit network issuer/ Reserved for future Realtime use',
+ 'C1' => 'Unacceptable PIN',
+ 'C2' => 'PIN Change failed',
+ 'C3' => 'PIN Unblock failed',
+ 'D1' => 'MAC Error',
+ 'E1' => 'Prepay error',
+ 'N1' => 'Network Error within the TXP platform',
+ 'N0' => 'Force STIP/ Reserved for client-specific use (declined)',
+ 'N3' => 'Cash service not available/ Reserved for client-specific use (declined)',
+ 'N4' => 'Cash request exceeds Issuer limit/ Reserved for client-specific use (declined)',
+ 'N5' => 'Ineligible for re-submission/ Reserved for client-specific use (declined)',
+ 'N7' => 'Decline for CVV2 failure/ Reserved for client-specific use (declined)',
+ 'N8' => 'Transaction amount exceeds preauthorized approval amount/ Reserved for client-specific use (declined)',
+ 'P0' => 'Approved; PVID code is missing, invalid, or has expired',
+ 'P1' => 'Declined; PVID code is missing, invalid, or has expired/ Reserved for client-specific use (declined)',
+ 'P2' => 'Invalid biller Information/ Reserved for client-specific use (declined)/ Reserved for client-specific use (declined)',
+ 'R0' => 'The transaction was declined or returned, because the cardholder requested that payment of a specific recurring or installment payment transaction be stopped/ Reserved for client-specific use (declined)',
+ 'R1' => 'The transaction was declined or returned, because the cardholder requested that payment of all recurring or installment payment transactions for a specific merchant account be stopped/ Reserved for client-specific use (declined)',
+ 'Q1' => 'Card Authentication failed/ Reserved for client-specific use (declined)',
+ 'XA' => 'Forward to Issuer/ Reserved for client-specific use (declined)',
+ 'XD' => 'Forward to Issuer/ Reserved for client-specific use (declined)',
+ }
+
+ EXTENDED_RESPONSE_MESSAGES = {
+ 'B40K' => 'Declined Post – Credit linked to unextracted settle transaction'
+ }
+
+ TRANSACTION_CODES = {
+ authorize: 0,
+ void_authorize: 2,
+
+ purchase: 1,
+ capture: 3,
+ void_purchase: 6,
+ void_capture: 6,
+
+ refund: 4,
+ credit: 5,
+ void_refund: 13,
+ void_credit: 13,
+
+ verify: 9,
+
+ purchase_echeck: 11,
+ refund_echeck: 16,
+ void_echeck: 16,
+
+ wallet_sale: 14,
+ }
+
+ def initialize(options={})
+ requires!(options, :gateway_id, :reg_key)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ if credit_card?(payment_method)
+ action = :purchase
+ request = build_xml_transaction_request do |doc|
+ add_credit_card(doc, payment_method)
+ add_contact(doc, payment_method.name, options)
+ add_amount(doc, amount)
+ add_order_number(doc, options)
+ end
+ elsif echeck?(payment_method)
+ action = :purchase_echeck
+ request = build_xml_transaction_request do |doc|
+ add_echeck(doc, payment_method)
+ add_contact(doc, payment_method.name, options)
+ add_amount(doc, amount)
+ add_order_number(doc, options)
+ end
+ else
+ action = :wallet_sale
+ wallet_id = split_authorization(payment_method).last
+ request = build_xml_transaction_request do |doc|
+ add_amount(doc, amount)
+ add_wallet_id(doc, wallet_id)
+ end
+ end
+
+ commit(action, request)
+ end
+
+ def authorize(amount, payment_method, options={})
+ if credit_card?(payment_method)
+ request = build_xml_transaction_request do |doc|
+ add_credit_card(doc, payment_method)
+ add_contact(doc, payment_method.name, options)
+ add_amount(doc, amount)
+ end
+ else
+ wallet_id = split_authorization(payment_method).last
+ request = build_xml_transaction_request do |doc|
+ add_amount(doc, amount)
+ add_wallet_id(doc, wallet_id)
+ end
+ end
+
+ commit(:authorize, request)
+ end
+
+ def capture(amount, authorization, options={})
+ transaction_id = split_authorization(authorization)[1]
+ request = build_xml_transaction_request do |doc|
+ add_amount(doc, amount)
+ add_original_transaction_data(doc, transaction_id)
+ end
+
+ commit(:capture, request)
+ end
+
+ def void(authorization, options={})
+ action, transaction_id = split_authorization(authorization)
+
+ request = build_xml_transaction_request do |doc|
+ add_original_transaction_data(doc, transaction_id)
+ end
+
+ commit(void_type(action), request)
+ end
+
+ def refund(amount, authorization, options={})
+ action, transaction_id = split_authorization(authorization)
+
+ request = build_xml_transaction_request do |doc|
+ add_amount(doc, amount) unless action == 'purchase_echeck'
+ add_original_transaction_data(doc, transaction_id)
+ end
+
+ commit(refund_type(action), request)
+ end
+
+ def credit(amount, payment_method, options={})
+ request = build_xml_transaction_request do |doc|
+ add_pan(doc, payment_method)
+ add_amount(doc, amount)
+ end
+
+ commit(:credit, request)
+ end
+
+ def verify(credit_card, options={})
+ request = build_xml_transaction_request do |doc|
+ add_credit_card(doc, credit_card)
+ add_contact(doc, credit_card.name, options)
+ end
+
+ commit(:verify, request)
+ end
+
+ def store(payment_method, options={})
+ store_customer_request = build_xml_payment_storage_request do |doc|
+ store_customer_details(doc, payment_method.name, options)
+ end
+
+ MultiResponse.run do |r|
+ r.process { commit(:store, store_customer_request) }
+ return r unless r.success? && r.params['custId']
+ customer_id = r.params['custId']
+
+ store_payment_method_request = build_xml_payment_storage_request do |doc|
+ doc['v1'].cust do
+ add_customer_id(doc, customer_id)
+ doc['v1'].pmt do
+ doc['v1'].type 0 # add
+ add_credit_card(doc, payment_method)
+ end
+ end
+ end
+
+ r.process { commit(:store, store_payment_method_request) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((<[^>]+pan>)[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r((<[^>]+sec>)[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r((<[^>]+id>)[^<]+(<))i, '\1[FILTERED]\2').
+ gsub(%r((<[^>]+regKey>)[^<]+(<))i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") }
+ CURRENCY_CODES['USD'] = '840'
+
+ def headers
+ {
+ 'Content-Type' => 'text/xml'
+ }
+ end
+
+ def commit(action, request)
+ request = add_transaction_code_to_request(request, action)
+
+ raw_response = begin
+ ssl_post(url, request, headers)
+ rescue ActiveMerchant::ResponseError => e
+ e.response.body
+ end
+
+ response = parse(raw_response)
+
+ succeeded = success_from(response)
+
+ Response.new(
+ succeeded,
+ message_from(succeeded, response),
+ response,
+ error_code: error_code_from(succeeded, response),
+ authorization: authorization_from(action, response),
+ avs_result: AVSResult.new(code: response['avsRslt']),
+ cvv_result: CVVResult.new(response['secRslt']),
+ test: test?
+ )
+ end
+
+ def url
+ test? ? test_url : live_url
+ end
+
+ def parse(xml)
+ response = {}
+ doc = Nokogiri::XML(xml).remove_namespaces!
+
+ doc.css('Envelope Body *').each do |node|
+ # node.name is more readable, but uniq_name is occasionally necessary
+ uniq_name = [node.parent.name, node.name].join('_')
+ response[uniq_name] = node.text
+ response[node.name] = node.text
+ end
+
+ response
+ end
+
+ def success_from(response)
+ fault = response['Fault']
+ approved_transaction = APPROVAL_CODES.include?(response['rspCode'])
+ found_contact = response['FndRecurrProfResponse']
+
+ return !fault && (approved_transaction || found_contact)
+ end
+
+ def error_code_from(succeeded, response)
+ return if succeeded
+ response['errorCode'] || response['rspCode']
+ end
+
+ def message_from(succeeded, response)
+ return 'Succeeded' if succeeded
+
+ if response['rspCode']
+ code = response['rspCode']
+ extended_code = response['extRspCode']
+
+ message = RESPONSE_MESSAGES[code]
+ extended = EXTENDED_RESPONSE_MESSAGES[extended_code]
+ ach_response = response['achResponse']
+
+ [message, extended, ach_response].compact.join('. ')
+ else
+ response['faultstring']
+ end
+ end
+
+ def authorization_from(action, response)
+ authorization = response['tranNr'] || response['pmtId']
+
+ # guard so we don't return something like "purchase|"
+ return unless authorization
+
+ [action, authorization].join(AUTHORIZATION_FIELD_SEPARATOR)
+ end
+
+ # -- helper methods ----------------------------------------------------
+ def credit_card?(payment_method)
+ payment_method.respond_to?(:verification_value)
+ end
+
+ def echeck?(payment_method)
+ payment_method.respond_to?(:routing_number)
+ end
+
+ def split_authorization(authorization)
+ authorization.split(AUTHORIZATION_FIELD_SEPARATOR)
+ end
+
+ def void_type(action)
+ action == 'purchase_echeck' ? :void_echeck : :"void_#{action}"
+ end
+
+ def refund_type(action)
+ action == 'purchase_echeck' ? :refund_echeck : :refund
+ end
+
+ # -- request methods ---------------------------------------------------
+ def build_xml_transaction_request
+ build_xml_request('SendTranRequest') do |doc|
+ yield doc
+ end
+ end
+
+ def build_xml_payment_storage_request
+ build_xml_request('UpdtRecurrProfRequest') do |doc|
+ yield doc
+ end
+ end
+
+ def build_xml_payment_update_request
+ merchant_product_type = 5 # credit card
+ build_xml_request('UpdtRecurrProfRequest', merchant_product_type) do |doc|
+ yield doc
+ end
+ end
+
+ def build_xml_payment_search_request
+ build_xml_request('FndRecurrProfRequest') do |doc|
+ yield doc
+ end
+ end
+
+ def build_xml_request(wrapper, merchant_product_type=nil)
+ Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
+ xml['soapenv'].Envelope('xmlns:soapenv' => SOAPENV_NAMESPACE) do
+ xml['soapenv'].Body do
+ xml['v1'].send(wrapper, 'xmlns:v1' => V1_NAMESPACE) do
+ add_merchant(xml)
+ yield(xml)
+ end
+ end
+ end
+ end.doc.root.to_xml
+ end
+
+ def add_transaction_code_to_request(request, action)
+ # store requests don't get a transaction code
+ return request if action == :store
+
+ doc = Nokogiri::XML::Document.parse(request)
+ merc_nodeset = doc.xpath('//v1:merc', 'v1' => V1_NAMESPACE)
+ merc_nodeset.after "#{TRANSACTION_CODES[action]}"
+ doc.root.to_xml
+ end
+
+ def add_merchant(doc, product_type=nil)
+ doc['v1'].merc do
+ doc['v1'].id @options[:gateway_id]
+ doc['v1'].regKey @options[:reg_key]
+ doc['v1'].inType '1'
+ doc['v1'].prodType product_type if product_type
+ end
+ end
+
+ def add_amount(doc, money)
+ doc['v1'].reqAmt amount(money)
+ end
+
+ def add_order_number(doc, options)
+ return unless options[:order_id]
+
+ doc['v1'].authReq {
+ doc['v1'].ordNr options[:order_id]
+ }
+ end
+
+ def add_credit_card(doc, payment_method)
+ doc['v1'].card {
+ doc['v1'].pan payment_method.number
+ doc['v1'].sec payment_method.verification_value if payment_method.verification_value?
+ doc['v1'].xprDt expiration_date(payment_method)
+ }
+ end
+
+ def add_echeck(doc, payment_method)
+ doc['v1'].achEcheck {
+ doc['v1'].bankRtNr payment_method.routing_number
+ doc['v1'].acctNr payment_method.account_number
+ }
+ end
+
+ def expiration_date(payment_method)
+ yy = format(payment_method.year, :two_digits)
+ mm = format(payment_method.month, :two_digits)
+ yy + mm
+ end
+
+ def add_pan(doc, payment_method)
+ doc['v1'].card do
+ doc['v1'].pan payment_method.number
+ end
+ end
+
+ def add_contact(doc, fullname, options)
+ doc['v1'].contact do
+ doc['v1'].fullName fullname unless fullname.blank?
+ doc['v1'].coName options[:company_name] if options[:company_name]
+ doc['v1'].title options[:title] if options[:title]
+
+ if (billing_address = options[:billing_address])
+ if billing_address[:phone]
+ doc['v1'].phone do
+ doc['v1'].type(options[:phone_number_type] || '4')
+ doc['v1'].nr billing_address[:phone].gsub(/\D/, '')
+ end
+ end
+ doc['v1'].addrLn1 billing_address[:address1] if billing_address[:address1]
+ doc['v1'].addrLn2 billing_address[:address2] unless billing_address[:address2].blank?
+ doc['v1'].city billing_address[:city] if billing_address[:city]
+ doc['v1'].state billing_address[:state] if billing_address[:state]
+ doc['v1'].zipCode billing_address[:zip] if billing_address[:zip]
+ doc['v1'].ctry 'US'
+ end
+
+ doc['v1'].email options[:email] if options[:email]
+ doc['v1'].type options[:contact_type] if options[:contact_type]
+ doc['v1'].stat options[:contact_stat] if options[:contact_stat]
+
+ if (shipping_address = options[:shipping_address])
+ doc['v1'].ship do
+ doc['v1'].fullName fullname unless fullname.blank?
+ doc['v1'].addrLn1 shipping_address[:address1] if shipping_address[:address1]
+ doc['v1'].addrLn2 shipping_address[:address2] unless shipping_address[:address2].blank?
+ doc['v1'].city shipping_address[:city] if shipping_address[:city]
+ doc['v1'].state shipping_address[:state] if shipping_address[:state]
+ doc['v1'].zipCode shipping_address[:zip] if shipping_address[:zip]
+ doc['v1'].phone shipping_address[:phone].gsub(/\D/, '') if shipping_address[:phone]
+ doc['v1'].email shipping_address[:email] if shipping_address[:email]
+ end
+ end
+ end
+ end
+
+ def add_name(doc, payment_method)
+ doc['v1'].contact do
+ doc['v1'].fullName payment_method.name unless payment_method.name.blank?
+ end
+ end
+
+ def add_original_transaction_data(doc, authorization)
+ doc['v1'].origTranData do
+ doc['v1'].tranNr authorization
+ end
+ end
+
+ def store_customer_details(doc, fullname, options)
+ options[:contact_type] = 1 # recurring
+ options[:contact_stat] = 1 # active
+
+ doc['v1'].cust do
+ doc['v1'].type 0 # add
+ add_contact(doc, fullname, options)
+ end
+ end
+
+ def add_customer_id(doc, customer_id)
+ doc['v1'].contact do
+ doc['v1'].id customer_id
+ end
+ end
+
+ def add_wallet_id(doc, wallet_id)
+ doc['v1'].recurMan do
+ doc['v1'].id wallet_id
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/transact_pro.rb b/lib/active_merchant/billing/gateways/transact_pro.rb
new file mode 100644
index 00000000000..5aaa36d756d
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/transact_pro.rb
@@ -0,0 +1,224 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ # For more information visit {Transact Pro Services}[https://www.transactpro.lv/business/]
+ #
+ # This gateway was formerly associated with www.1stpayments.net
+ #
+ # Written by Piers Chambers (Varyonic.com)
+ class TransactProGateway < Gateway
+ self.test_url = 'https://gw2sandbox.tpro.lv:8443/gw2test/gwprocessor2.php'
+ self.live_url = 'https://www2.1stpayments.net/gwprocessor2.php'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'https://www.transactpro.lv/business/online-payments-acceptance'
+ self.display_name = 'Transact Pro'
+
+ def initialize(options={})
+ requires!(options, :guid, :password, :terminal)
+ super
+ end
+
+ def purchase(amount, payment, options={})
+ post = PostData.new
+ add_invoice(post, amount, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+ add_credentials(post)
+ post[:rs] = @options[:terminal]
+
+ MultiResponse.run do |r|
+ r.process { commit('init', post) }
+ r.process do
+ post = PostData.new
+ post[:init_transaction_id] = r.authorization
+ add_payment_cc(post, payment)
+ post[:f_extended] = '4'
+
+ commit('charge', post, amount)
+ end
+ end
+ end
+
+ def authorize(amount, payment, options={})
+ post = PostData.new
+ add_invoice(post, amount, options)
+ add_payment(post, payment)
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+ add_credentials(post)
+ post[:rs] = @options[:terminal]
+
+ MultiResponse.run do |r|
+ r.process { commit('init_dms', post) }
+ r.process do
+ post = PostData.new
+ post[:init_transaction_id] = r.authorization
+ add_payment_cc(post, payment)
+ post[:f_extended] = '4'
+
+ commit('make_hold', post, amount)
+ end
+ end
+ end
+
+ def capture(amount, authorization, options={})
+ identifier, original_amount = split_authorization(authorization)
+ if amount && (amount != original_amount)
+ raise ArgumentError.new("Partial capture is not supported, and #{amount.inspect} != #{original_amount.inspect}")
+ end
+
+ post = PostData.new
+ add_credentials(post)
+ post[:init_transaction_id] = identifier
+ post[:f_extended] = '4'
+
+ commit('charge_hold', post, original_amount)
+ end
+
+ def refund(amount, authorization, options={})
+ identifier, original_amount = split_authorization(authorization)
+
+ post = PostData.new
+ add_credentials(post, :account_guid)
+ post[:init_transaction_id] = identifier
+ post[:amount_to_refund] = amount(amount || original_amount)
+
+ commit('refund', post)
+ end
+
+ def void(authorization, options={})
+ identifier, amount = split_authorization(authorization)
+
+ post = PostData.new
+ add_credentials(post, :account_guid)
+ post[:init_transaction_id] = identifier
+ post[:amount_to_refund] = amount(amount)
+ commit('cancel_dms', 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
+
+ private
+
+ def add_customer_data(post, options)
+ post[:email] = (options[:email] || 'noone@example.com')
+ post[:user_ip] = (options[:ip] || '127.0.0.1')
+ end
+
+ def add_address(post, creditcard, options)
+ if address = options[:billing_address]
+ post[:street] = address[:address1].to_s
+ post[:city] = address[:city].to_s
+ post[:state] = (address[:state].blank? ? 'NA' : address[:state].to_s)
+ post[:zip] = address[:zip].to_s
+ post[:country] = address[:country].to_s
+ post[:phone] = (address[:phone].to_s.gsub(/[^0-9]/, '') || '0000000')
+ end
+
+ if address = options[:shipping_address]
+ post[:shipping_name] = "#{address.first_name} #{address.last_name}"
+ post[:shipping_street] = address[:address1].to_s
+ post[:shipping_phone] = address[:phone].to_s
+ post[:shipping_zip] = address[:zip].to_s
+ post[:shipping_city] = address[:city].to_s
+ post[:shipping_country] = address[:country].to_s
+ post[:shipping_state] = (address[:state].blank? ? 'NA' : address[:state].to_s)
+ post[:shipping_email] = (options[:email] || 'noone@example.com')
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:merchant_transaction_id] = options[:order_id] if options[:order_id]
+ post[:amount] = amount(money)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:description] = options[:description]
+ post[:merchant_site_url] = options[:merchant]
+ end
+
+ def add_payment(post, payment)
+ post[:name_on_card] = "#{payment.first_name} #{payment.last_name}"
+ post[:card_bin] = payment.first_digits
+ end
+
+ def add_payment_cc(post, credit_card)
+ post[:cc] = credit_card.number
+ post[:cvv] = credit_card.verification_value if credit_card.verification_value?
+ year = sprintf('%.4i', credit_card.year)
+ month = sprintf('%.2i', credit_card.month)
+ post[:expire] = "#{month}/#{year[2..3]}"
+ end
+
+ def add_credentials(post, key=:guid)
+ post[key] = @options[:guid]
+ post[:pwd] = Digest::SHA1.hexdigest(@options[:password])
+ end
+
+ def parse(body)
+ if body =~ /^ID:/
+ body.split('~').reduce(Hash.new) { |h, v|
+ m = v.match('(.*?):(.*)')
+ h.merge!(m[1].underscore.to_sym => m[2])
+ }
+ elsif (m = body.match('(.*?):(.*)'))
+ m[1] == 'OK' ?
+ { status: 'success', id: m[2] } :
+ { status: 'failure', message: m[2] }
+ else
+ Hash[status: body]
+ end
+ end
+
+ def commit(action, parameters, amount=nil)
+ url = (test? ? test_url : live_url)
+ response = parse(ssl_post(url, post_data(action, parameters)))
+
+ Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(parameters, response, amount),
+ test: test?
+ )
+ end
+
+ def authorization_from(parameters, response, amount)
+ identifier = (response[:id] || parameters[:init_transaction_id])
+ authorization = [identifier]
+ authorization << amount if amount
+ authorization.join('|')
+ end
+
+ def split_authorization(authorization)
+ if authorization =~ /|/
+ identifier, amount = authorization.split('|')
+ [identifier, amount.to_i]
+ else
+ authorization
+ end
+ end
+
+ def success_from(response)
+ (response[:status] =~ /success/i || response[:status] =~ /ok/i)
+ end
+
+ def message_from(response)
+ (response[:message] || response[:status])
+ end
+
+ def post_data(action, parameters = {})
+ parameters[:a] = action
+ parameters.to_s
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/transax.rb b/lib/active_merchant/billing/gateways/transax.rb
index c11c830bac9..336a7fb31c3 100644
--- a/lib/active_merchant/billing/gateways/transax.rb
+++ b/lib/active_merchant/billing/gateways/transax.rb
@@ -1,23 +1,22 @@
-require File.join(File.dirname(__FILE__),'smart_ps.rb')
+require File.join(File.dirname(__FILE__), 'smart_ps.rb')
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class TransaxGateway < SmartPs
self.live_url = self.test_url = 'https://secure.nelixtransax.net/api/transact.php'
-
+
# The countries the gateway supports merchants from as 2 digit ISO country codes
self.supported_countries = ['US']
-
+
# The card types supported by the payment gateway
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
-
+
# The homepage URL of the gateway
self.homepage_url = 'https://www.nelixtransax.com/'
-
+
# The name of the gateway
self.display_name = 'NELiX TransaX'
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/transnational.rb b/lib/active_merchant/billing/gateways/transnational.rb
index 5c49489ba80..350a2e91857 100644
--- a/lib/active_merchant/billing/gateways/transnational.rb
+++ b/lib/active_merchant/billing/gateways/transnational.rb
@@ -1,239 +1,9 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- class TransnationalGateway < Gateway
- self.live_url = self.test_url = 'https://secure.networkmerchants.com/api/transact.php'
-
- self.supported_countries = ['US']
- self.supported_cardtypes = [:visa, :master, :american_express, :discover]
-
+ class TransnationalGateway < NetworkMerchantsGateway
self.homepage_url = 'http://www.tnbci.com/'
self.display_name = 'Transnational'
-
- self.money_format = :dollars
- self.default_currency = 'USD'
-
- def initialize(options = {})
- requires!(options, :login, :password)
- super
- end
-
- def authorize(money, creditcard_or_vault_id, options = {})
- post = build_auth_post(money, creditcard_or_vault_id, options)
- commit('auth', post)
- end
-
- def purchase(money, creditcard_or_vault_id, options = {})
- post = build_purchase_post(money, creditcard_or_vault_id, options)
- commit('sale', post)
- end
-
- def capture(money, authorization, options = {})
- post = build_capture_post(money, authorization, options)
- commit('capture', post)
- end
-
- def void(authorization, options = {})
- post = build_void_post(authorization, options)
- commit('void', post)
- end
-
- def refund(money, authorization, options = {})
- post = build_refund_post(money, authorization, options)
- commit('refund', post)
- end
-
- def store(creditcard, options = {})
- post = build_store_post(creditcard, options)
- commit_vault('add_customer', post)
- end
-
- def unstore(customer_vault_id, options = {})
- post = build_unstore_post(customer_vault_id, options)
- commit_vault('delete_customer', post)
- end
-
- private
-
- def build_auth_post(money, creditcard_or_vault_id, options)
- post = {}
- add_order(post, options)
- add_address(post, options)
- add_shipping_address(post, options)
- add_payment_method(post, creditcard_or_vault_id, options)
- add_amount(post, money)
- post
- end
-
- def build_purchase_post(money, creditcard, options)
- build_auth_post(money, creditcard, options)
- end
-
- def build_capture_post(money, authorization, option)
- post = {}
- post[:transactionid] = authorization
- add_amount(post, money)
- post
- end
-
- def build_void_post(authorization, options)
- post = {}
- post[:transactionid] = authorization
- post
- end
-
- def build_refund_post(money, authorization, options)
- post = {}
- post[:transactionid] = authorization
- add_amount(post, money)
- post
- end
-
- def build_store_post(creditcard_or_check, options)
- post = {}
- add_address(post, options)
- add_shipping_address(post, options)
- add_payment_method(post, creditcard_or_check, options)
- post
- end
-
- def build_unstore_post(customer_vault_id, options)
- post = {}
- post['customer_vault_id'] = customer_vault_id
- post
- end
-
- def add_order(post, options)
- post[:orderid] = options[:order_id]
- post[:orderdescription] = options[:description]
- end
-
- def add_address(post, options)
- post[:email] = options[:email]
- post[:ipaddress] = options[:ip]
-
- address = options[:billing_address] || options[:address] || {}
- post[:address1] = address[:address1]
- post[:address2] = address[:address2]
- post[:city] = address[:city]
- post[:state] = address[:state]
- post[:zip] = address[:zip]
- post[:country] = address[:country]
- post[:phone] = address[:phone]
- end
-
- def add_shipping_address(post, options)
- shipping_address = options[:shipping_address] || {}
- post[:shipping_address1] = shipping_address[:address1]
- post[:shipping_address2] = shipping_address[:address2]
- post[:shipping_city] = shipping_address[:city]
- post[:shipping_state] = shipping_address[:state]
- post[:shipping_zip] = shipping_address[:zip]
- post[:shipping_country] = shipping_address[:country]
- end
-
- def add_swipe_data(post, options)
- # unencrypted tracks
- post[:track_1] = options[:track_1]
- post[:track_2] = options[:track_2]
- post[:track_3] = options[:track_3]
-
- # encrypted tracks
- post[:magnesafe_track_1] = options[:magnesafe_track_1]
- post[:magnesafe_track_2] = options[:magnesafe_track_2]
- post[:magnesafe_track_3] = options[:magnesafe_track_3]
- post[:magnesafe_magneprint] = options[:magnesafe_magneprint]
- post[:magnesafe_ksn] = options[:magnesafe_ksn]
- post[:magnesafe_magneprint_status] = options[:magnesafe_magneprint_status]
- end
-
- def add_payment_method(post, creditcard_or_check_or_vault_id, options)
- post[:processor_id] = options[:processor_id]
- post[:customer_vault] = 'add_customer' if options[:store]
-
- add_swipe_data(post, options)
-
- # creditcard_or_check can be blank if using swipe data
- if creditcard_or_check_or_vault_id.is_a?(CreditCard) # creditcard or check
- creditcard = creditcard_or_check_or_vault_id
- post[:firstname] = creditcard.first_name
- post[:lastname] = creditcard.last_name
- post[:ccnumber] = creditcard.number
- post[:ccexp] = format(creditcard.month, :two_digits) + format(creditcard.year, :two_digits)
- post[:cvv] = creditcard.verification_value
- post[:payment] = 'creditcard'
- elsif creditcard_or_check_or_vault_id.is_a?(Check)
- check = creditcard_or_check_or_vault_id
- post[:firstname] = check.first_name
- post[:lastname] = check.last_name
- post[:checkname] = check.name
- post[:checkaba] = check.routing_number
- post[:checkaccount] = check.account_number
- post[:account_type] = check.account_type
- post[:account_holder_type] = check.account_holder_type
- post[:payment] = 'check'
- else
- post[:customer_vault_id] = creditcard_or_check_or_vault_id
- end
- end
-
- def add_login(post)
- post[:username] = @options[:login]
- post[:password] = @options[:password]
- end
-
- def add_amount(post, money)
- post[:currency] = options[:currency] || currency(money)
- post[:amount] = amount(money)
- end
-
- def commit_vault(action, parameters)
- commit(nil, parameters.merge(:customer_vault => action))
- end
-
- def commit(action, parameters)
- raw = parse(ssl_post(self.live_url, build_request(action, parameters)))
-
- success = (raw['response'] == ResponseCodes::APPROVED)
-
- authorization = authorization_from(success, parameters, raw)
-
- Response.new(success, raw['responsetext'], raw,
- :test => test?,
- :authorization => authorization,
- :avs_result => { :code => raw['avsresponse']},
- :cvv_result => raw['cvvresponse']
- )
- end
-
- def build_request(action, parameters)
- parameters[:type] = action if action
- add_login(parameters)
- parameters.to_query
- end
-
- def authorization_from(success, parameters, response)
- return nil unless success
-
- authorization = response['transactionid']
- if(parameters[:customer_vault] && (authorization.nil? || authorization.empty?))
- authorization = response['customer_vault_id']
- end
-
- authorization
- end
-
- class ResponseCodes
- APPROVED = '1'
- DENIED = '2'
- ERROR = '3'
- end
-
- def parse(raw_response)
- rsp = CGI.parse(raw_response)
- rsp.keys.each { |k| rsp[k] = rsp[k].first } # flatten out the values
- rsp
- end
+ self.supported_countries = ['US']
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/trexle.rb b/lib/active_merchant/billing/gateways/trexle.rb
new file mode 100644
index 00000000000..42451539b2c
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/trexle.rb
@@ -0,0 +1,218 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class TrexleGateway < Gateway
+ self.test_url = 'https://core.trexle.com/api/v1'
+ self.live_url = 'https://core.trexle.com/api/v1'
+
+ self.default_currency = 'USD'
+ self.money_format = :cents
+ self.supported_countries = %w(AD AE AT AU BD BE BG BN CA CH CY CZ DE DK EE EG ES FI FR GB
+ GI GR HK HU ID IE IL IM IN IS IT JO KW LB LI LK LT LU LV MC
+ MT MU MV MX MY NL NO NZ OM PH PL PT QA RO SA SE SG SI SK SM
+ TR TT UM US VA VN ZA)
+ self.supported_cardtypes = [:visa, :master, :american_express]
+ self.homepage_url = 'https://trexle.com'
+ self.display_name = 'Trexle'
+
+ def initialize(options = {})
+ requires!(options, :api_key)
+ super
+ end
+
+ # Create a charge using a credit card, card token or customer token
+ #
+ # To charge a credit card: purchase([money], [creditcard hash], ...)
+ # To charge a customer: purchase([money], [token], ...)
+ def purchase(money, creditcard, options = {})
+ post = {}
+
+ add_amount(post, money, options)
+ add_customer_data(post, options)
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_address(post, creditcard, options)
+ commit(:post, 'charges', post, options)
+ end
+
+ # Create a customer and associated credit card. The token that is returned
+ # can be used instead of a credit card parameter in the purchase method
+ def store(creditcard, options = {})
+ post = {}
+
+ add_creditcard(post, creditcard)
+ add_customer_data(post, options)
+ add_address(post, creditcard, options)
+ commit(:post, 'customers', post, options)
+ end
+
+ # Refund a transaction
+ def refund(money, token, options = {})
+ commit(:post, "charges/#{CGI.escape(token)}/refunds", { amount: amount(money) }, options)
+ end
+
+ # Authorize an amount on a credit card. Once authorized, you can later
+ # capture this charge using the charge token that is returned.
+ def authorize(money, creditcard, options = {})
+ post = {}
+
+ add_amount(post, money, options)
+ add_customer_data(post, options)
+ add_invoice(post, options)
+ add_creditcard(post, creditcard)
+ add_address(post, creditcard, options)
+ post[:capture] = false
+ commit(:post, 'charges', post, options)
+ end
+
+ # Captures a previously authorized charge. Capturing only part of the original
+ # authorization is currently not supported.
+ def capture(money, token, options = {})
+ commit(:put, "charges/#{CGI.escape(token)}/capture", { amount: amount(money) }, options)
+ end
+
+ # Updates the credit card for the customer.
+ def update(token, creditcard, options = {})
+ post = {}
+
+ add_creditcard(post, creditcard)
+ add_customer_data(post, options)
+ add_address(post, creditcard, options)
+ commit(:put, "customers/#{CGI.escape(token)}", post, options)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(/(number\\?":\\?")(\d*)/, '\1[FILTERED]').
+ gsub(/(cvc\\?":\\?")(\d*)/, '\1[FILTERED]')
+ end
+
+ private
+
+ def add_amount(post, money, options)
+ post[:amount] = amount(money)
+ post[:currency] = (options[:currency] || currency(money))
+ post[:currency] = post[:currency].upcase if post[:currency]
+ end
+
+ def add_customer_data(post, options)
+ post[:email] = options[:email] if options[:email]
+ post[:ip_address] = options[:ip] if options[:ip]
+ end
+
+ def add_address(post, creditcard, options)
+ return if creditcard.kind_of?(String)
+ address = (options[:billing_address] || options[:address])
+ return unless address
+
+ post[:card] ||= {}
+ post[:card].merge!(
+ address_line1: address[:address1],
+ address_line2: address[:address_line2],
+ address_city: address[:city],
+ address_postcode: address[:zip],
+ address_state: address[:state],
+ address_country: address[:country]
+ )
+ end
+
+ def add_invoice(post, options)
+ post[:description] = options[:description] || 'Active Merchant Purchase'
+ end
+
+ def add_creditcard(post, creditcard)
+ if creditcard.respond_to?(:number)
+ post[:card] ||= {}
+
+ post[:card].merge!(
+ number: creditcard.number,
+ expiry_month: creditcard.month,
+ expiry_year: creditcard.year,
+ cvc: creditcard.verification_value,
+ name: creditcard.name
+ )
+ elsif creditcard.kind_of?(String)
+ if creditcard =~ /^token_/
+ post[:card_token] = creditcard
+ else
+ post[:customer_token] = creditcard
+ end
+ end
+ end
+
+ def headers(params = {})
+ result = {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Basic #{Base64.strict_encode64(options[:api_key] + ':').strip}"
+ }
+
+ result['X-Partner-Key'] = params[:partner_key] if params[:partner_key]
+ result['X-Safe-Card'] = params[:safe_card] if params[:safe_card]
+ result
+ end
+
+ def commit(method, action, params, options)
+ url = "#{test? ? test_url : live_url}/#{action}"
+ raw_response = ssl_request(method, url, post_data(params), headers(options))
+ parsed_response = parse(raw_response)
+ success_response(parsed_response)
+ rescue ResponseError => e
+ error_response(parse(e.response.body))
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ end
+
+ def success_response(body)
+ return invalid_response unless body['response']
+
+ response = body['response']
+ Response.new(
+ true,
+ response['status_message'],
+ body,
+ authorization: token(response),
+ test: test?
+ )
+ end
+
+ def error_response(body)
+ return invalid_response unless body['error']
+ Response.new(
+ false,
+ body['error'],
+ body,
+ authorization: nil,
+ test: test?
+ )
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from Trexle. Please contact support@trexle.com if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+
+ def invalid_response
+ message = 'Invalid response.'
+ return Response.new(false, message)
+ end
+
+ def token(response)
+ response['token']
+ end
+
+ def parse(body)
+ return {} if body.blank?
+ JSON.parse(body)
+ end
+
+ def post_data(parameters = {})
+ parameters.to_json
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/trust_commerce.rb b/lib/active_merchant/billing/gateways/trust_commerce.rb
index e0a5b9c27dd..3018d9d1690 100644
--- a/lib/active_merchant/billing/gateways/trust_commerce.rb
+++ b/lib/active_merchant/billing/gateways/trust_commerce.rb
@@ -19,10 +19,10 @@ module Billing #:nodoc:
# Next, create a credit card object using a TC approved test card.
#
# creditcard = ActiveMerchant::Billing::CreditCard.new(
- # :number => '4111111111111111',
- # :month => 8,
- # :year => 2006,
- # :first_name => 'Longbob',
+ # :number => '4111111111111111',
+ # :month => 8,
+ # :year => 2006,
+ # :first_name => 'Longbob',
# :last_name => 'Longsen'
# )
#
@@ -67,43 +67,45 @@ module Billing #:nodoc:
class TrustCommerceGateway < Gateway
self.live_url = self.test_url = 'https://vault.trustcommerce.com/trans/'
- SUCCESS_TYPES = ["approved", "accepted"]
+ SUCCESS_TYPES = ['approved', 'accepted']
DECLINE_CODES = {
- "decline" => "The credit card was declined",
- "avs" => "AVS failed; the address entered does not match the billing address on file at the bank",
- "cvv" => "CVV failed; the number provided is not the correct verification number for the card",
- "call" => "The card must be authorized manually over the phone",
- "expiredcard" => "Issuer was not certified for card verification",
- "carderror" => "Card number is invalid",
- "authexpired" => "Attempt to postauth an expired (more than 14 days old) preauth",
- "fraud" => "CrediGuard fraud score was below requested threshold",
- "blacklist" => "CrediGuard blacklist value was triggered",
- "velocity" => "CrediGuard velocity control value was triggered",
- "dailylimit" => "Daily limit in transaction count or amount as been reached",
- "weeklylimit" => "Weekly limit in transaction count or amount as been reached",
- "monthlylimit" => "Monthly limit in transaction count or amount as been reached"
+ 'decline' => 'The credit card was declined',
+ 'avs' => 'AVS failed; the address entered does not match the billing address on file at the bank',
+ 'cvv' => 'CVV failed; the number provided is not the correct verification number for the card',
+ 'call' => 'The card must be authorized manually over the phone',
+ 'expiredcard' => 'Issuer was not certified for card verification',
+ 'carderror' => 'Card number is invalid',
+ 'authexpired' => 'Attempt to postauth an expired (more than 14 days old) preauth',
+ 'fraud' => 'CrediGuard fraud score was below requested threshold',
+ 'blacklist' => 'CrediGuard blacklist value was triggered',
+ 'velocity' => 'CrediGuard velocity control value was triggered',
+ 'dailylimit' => 'Daily limit in transaction count or amount as been reached',
+ 'weeklylimit' => 'Weekly limit in transaction count or amount as been reached',
+ 'monthlylimit' => 'Monthly limit in transaction count or amount as been reached'
}
BADDATA_CODES = {
- "missingfields" => "One or more parameters required for this transaction type were not sent",
- "extrafields" => "Parameters not allowed for this transaction type were sent",
- "badformat" => "A field was improperly formatted, such as non-digit characters in a number field",
- "badlength" => "A field was longer or shorter than the server allows",
- "merchantcantaccept" => "The merchant can't accept data passed in this field",
- "mismatch" => "Data in one of the offending fields did not cross-check with the other offending field"
+ 'missingfields' => 'One or more parameters required for this transaction type were not sent',
+ 'extrafields' => 'Parameters not allowed for this transaction type were sent',
+ 'badformat' => 'A field was improperly formatted, such as non-digit characters in a number field',
+ 'badlength' => 'A field was longer or shorter than the server allows',
+ 'merchantcantaccept' => "The merchant can't accept data passed in this field",
+ 'mismatch' => 'Data in one of the offending fields did not cross-check with the other offending field'
}
ERROR_CODES = {
- "cantconnect" => "Couldn't connect to the TrustCommerce gateway",
- "dnsfailure" => "The TCLink software was unable to resolve DNS hostnames",
- "linkfailure" => "The connection was established, but was severed before the transaction could complete",
- "failtoprocess" => "The bank servers are offline and unable to authorize transactions"
+ 'cantconnect' => "Couldn't connect to the TrustCommerce gateway",
+ 'dnsfailure' => 'The TCLink software was unable to resolve DNS hostnames',
+ 'linkfailure' => 'The connection was established, but was severed before the transaction could complete',
+ 'failtoprocess' => 'The bank servers are offline and unable to authorize transactions'
}
TEST_LOGIN = 'TestMerchant'
TEST_PASSWORD = 'password'
+ VOIDABLE_ACTIONS = %w(preauth sale postauth credit)
+
self.money_format = :cents
self.supported_cardtypes = [:visa, :master, :discover, :american_express, :diners_club, :jcb]
self.supported_countries = ['US']
@@ -153,9 +155,12 @@ def authorize(money, creditcard_or_billing_id, options = {})
}
add_order_id(parameters, options)
+ add_aggregator(parameters, options)
add_customer_data(parameters, options)
add_payment_source(parameters, creditcard_or_billing_id)
add_addresses(parameters, options)
+ add_custom_fields(parameters, options)
+
commit('preauth', parameters)
end
@@ -167,9 +172,12 @@ def purchase(money, creditcard_or_billing_id, options = {})
}
add_order_id(parameters, options)
+ add_aggregator(parameters, options)
add_customer_data(parameters, options)
add_payment_source(parameters, creditcard_or_billing_id)
add_addresses(parameters, options)
+ add_custom_fields(parameters, options)
+
commit('sale', parameters)
end
@@ -177,10 +185,13 @@ def purchase(money, creditcard_or_billing_id, options = {})
# postauth, we preserve active_merchant's nomenclature of capture() for consistency with the rest of the library. To process
# a postauthorization with TC, you need an amount in cents or a money object, and a TC transid.
def capture(money, authorization, options = {})
+ transaction_id, _ = split_authorization(authorization)
parameters = {
:amount => amount(money),
- :transid => authorization,
+ :transid => transaction_id,
}
+ add_aggregator(parameters, options)
+ add_custom_fields(parameters, options)
commit('postauth', parameters)
end
@@ -188,16 +199,21 @@ def capture(money, authorization, options = {})
# refund() allows you to return money to a card that was previously billed. You need to supply the amount, in cents or a money object,
# that you want to refund, and a TC transid for the transaction that you are refunding.
def refund(money, identification, options = {})
+ transaction_id, _ = split_authorization(identification)
+
parameters = {
:amount => amount(money),
- :transid => identification
+ :transid => transaction_id
}
+ add_aggregator(parameters, options)
+ add_custom_fields(parameters, options)
+
commit('credit', parameters)
end
def credit(money, identification, options = {})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -210,20 +226,29 @@ def credit(money, identification, options = {})
# TrustCommerce to allow for reversal transactions before you can use this
# method.
#
+ # void() is also used to to cancel a capture (postauth), purchase (sale),
+ # or refund (credit) or a before it is sent for settlement.
+ #
# NOTE: AMEX preauth's cannot be reversed. If you want to clear it more
# quickly than the automatic expiration (7-10 days), you will have to
# capture it and then immediately issue a credit for the same amount
# which should clear the customers credit card with 48 hours according to
# TC.
def void(authorization, options = {})
+ transaction_id, original_action = split_authorization(authorization)
+ action = (VOIDABLE_ACTIONS - ['preauth']).include?(original_action) ? 'void' : 'reversal'
+
parameters = {
- :transid => authorization,
+ :transid => transaction_id,
}
- commit('reversal', parameters)
+ add_aggregator(parameters, options)
+ add_custom_fields(parameters, options)
+
+ commit(action, parameters)
end
- # recurring() a TrustCommerce account that is activated for Citatdel, TrustCommerce's
+ # recurring() a TrustCommerce account that is activated for Citadel, TrustCommerce's
# hosted customer billing info database.
#
# Recurring billing uses the same TC action as a plain-vanilla 'store', but we have a separate method for clarity. It can be called
@@ -235,7 +260,9 @@ def void(authorization, options = {})
#
# You can optionally specify how long you want payments to continue using 'payments'
def recurring(money, creditcard, options = {})
- requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily] )
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
+
+ requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily])
cycle = case options[:periodicity]
when :monthly
@@ -265,7 +292,7 @@ def recurring(money, creditcard, options = {})
commit('store', parameters)
end
- # store() requires a TrustCommerce account that is activated for Citatdel. You can call it with a credit card and a billing ID
+ # store() requires a TrustCommerce account that is activated for Citadel. You can call it with a credit card and a billing ID
# you would like to use to reference the stored credit card info for future captures. Use 'verify' to specify whether you want
# to simply store the card in the DB, or you want TC to verify the data first.
@@ -277,6 +304,8 @@ def store(creditcard, options = {})
add_creditcard(parameters, creditcard)
add_addresses(parameters, options)
+ add_custom_fields(parameters, options)
+
commit('store', parameters)
end
@@ -287,27 +316,58 @@ def unstore(identification, options = {})
:billingid => identification,
}
+ add_custom_fields(parameters, options)
+
commit('unstore', parameters)
end
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((&?cc=)\d*(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?password=)[^&]+(&?)), '\1[FILTERED]\2').
+ gsub(%r((&?cvv=)\d*(&?)), '\1[FILTERED]\2')
+ end
+
private
+
+ def add_custom_fields(params, options)
+ options[:custom_fields]&.each do |key, value|
+ params[key.to_sym] = value
+ end
+ end
+
+ def add_aggregator(params, options)
+ if @options[:aggregator_id] || application_id != Gateway.application_id
+ params[:aggregators] = 1
+ params[:aggregator1] = @options[:aggregator_id] || application_id
+ end
+ end
+
def add_payment_source(params, source)
if source.is_a?(String)
add_billing_id(params, source)
+ elsif card_brand(source) == 'check'
+ add_check(params, source)
else
add_creditcard(params, source)
end
end
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
-
- "#{month}#{year[-2..-1]}"
+ def add_check(params, check)
+ params[:media] = 'ach'
+ params[:routing] = check.routing_number
+ params[:account] = check.account_number
+ params[:savings] = 'y' if check.account_type == 'savings'
+ params[:name] = check.name
end
def add_creditcard(params, creditcard)
- params[:media] = "cc"
+ params[:media] = 'cc'
params[:name] = creditcard.name
params[:cc] = creditcard.number
params[:exp] = expdate(creditcard)
@@ -346,7 +406,7 @@ def add_addresses(params, options)
params[:shipto_address2] = shipping_address[:address2] unless shipping_address[:address2].blank?
params[:shipto_city] = shipping_address[:city] unless shipping_address[:city].blank?
params[:shipto_state] = shipping_address[:state] unless shipping_address[:state].blank?
- params[:shipto_zip] = shipping_address[:zip] unless shipping_address[:zip].blank?
+ params[:shipto_zip] = shipping_address[:zip] unless shipping_address[:zip].blank?
params[:shipto_country] = shipping_address[:country] unless shipping_address[:country].blank?
end
end
@@ -355,7 +415,7 @@ def clean_and_stringify_params(parameters)
# TCLink wants us to send a hash with string keys, and activemerchant pushes everything around with
# symbol keys. Before sending our input to TCLink, we convert all our keys to strings and dump the symbol keys.
# We also remove any pairs with nil values, as these confuse TCLink.
- parameters.keys.reverse.each do |key|
+ parameters.keys.reverse_each do |key|
if parameters[key]
parameters[key.to_s] = parameters[key]
end
@@ -364,7 +424,7 @@ def clean_and_stringify_params(parameters)
end
def post_data(parameters)
- parameters.collect { |key, value| "#{key}=#{ CGI.escape(value.to_s)}" }.join("&")
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
def commit(action, parameters)
@@ -376,19 +436,19 @@ def commit(action, parameters)
clean_and_stringify_params(parameters)
data = if tclink?
- TCLink.send(parameters)
- else
- parse( ssl_post(self.live_url, post_data(parameters)) )
- end
+ TCLink.send(parameters)
+ else
+ parse(ssl_post(self.live_url, post_data(parameters)))
+ end
# to be considered successful, transaction status must be either "approved" or "accepted"
- success = SUCCESS_TYPES.include?(data["status"])
+ success = SUCCESS_TYPES.include?(data['status'])
message = message_from(data)
Response.new(success, message, data,
:test => test?,
- :authorization => data["transid"],
- :cvv_result => data["cvv"],
- :avs_result => { :code => data["avs"] }
+ :authorization => authorization_from(action, data),
+ :cvv_result => data['cvv'],
+ :avs_result => { :code => data['avs'] }
)
end
@@ -396,7 +456,7 @@ def parse(body)
results = {}
body.split(/\n/).each do |pair|
- key,val = pair.split(/=/)
+ key, val = pair.split(/=/)
results[key] = val
end
@@ -404,18 +464,27 @@ def parse(body)
end
def message_from(data)
- status = case data["status"]
- when "decline"
- return DECLINE_CODES[data["declinetype"]]
- when "baddata"
- return BADDATA_CODES[data["error"]]
- when "error"
- return ERROR_CODES[data["errortype"]]
+ case data['status']
+ when 'decline'
+ return DECLINE_CODES[data['declinetype']]
+ when 'baddata'
+ return BADDATA_CODES[data['error']]
+ when 'error'
+ return ERROR_CODES[data['errortype']]
else
- return "The transaction was successful"
+ return 'The transaction was successful'
end
end
+ def authorization_from(action, data)
+ authorization = data['transid']
+ authorization = "#{authorization}|#{action}" if authorization && VOIDABLE_ACTIONS.include?(action)
+ authorization
+ end
+
+ def split_authorization(authorization)
+ authorization&.split('|')
+ end
end
end
end
diff --git a/lib/active_merchant/billing/gateways/usa_epay.rb b/lib/active_merchant/billing/gateways/usa_epay.rb
index 2ca57cdd0cf..0558311bc11 100644
--- a/lib/active_merchant/billing/gateways/usa_epay.rb
+++ b/lib/active_merchant/billing/gateways/usa_epay.rb
@@ -9,15 +9,15 @@ class UsaEpayGateway < Gateway
self.abstract_class = true
##
- # Creates an instance of UsaEpayTransactionGateway by default, but if
+ # Creates an instance of UsaEpayTransactionGateway by default, but if
# :software id or :live_url are passed in the options hash it will
# create an instance of UsaEpayAdvancedGateway.
#
def self.new(options={})
- unless options.has_key?(:software_id) || options.has_key?(:live_url)
- UsaEpayTransactionGateway.new(options)
- else
+ if options.has_key?(:software_id) || options.has_key?(:live_url)
UsaEpayAdvancedGateway.new(options)
+ else
+ UsaEpayTransactionGateway.new(options)
end
end
end
diff --git a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
index 6b3faeb312b..23077a0fd87 100644
--- a/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
+++ b/lib/active_merchant/billing/gateways/usa_epay_advanced.rb
@@ -5,7 +5,7 @@ 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
+ # This class encapsulates 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.
@@ -62,7 +62,7 @@ module Billing #:nodoc:
# * {USA ePay Developer Login}[https://www.usaepay.com/developer/login]
#
class UsaEpayAdvancedGateway < Gateway
- API_VERSION = "1.4"
+ API_VERSION = '1.4'
TEST_URL_BASE = 'https://sandbox.usaepay.com/soap/gate/' #:nodoc:
LIVE_URL_BASE = 'https://www.usaepay.com/soap/gate/' #:nodoc:
@@ -70,19 +70,21 @@ class UsaEpayAdvancedGateway < Gateway
self.test_url = TEST_URL_BASE
self.live_url = LIVE_URL_BASE
- FAILURE_MESSAGE = "Default Failure" #:nodoc:
+ 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 = {
+ CUSTOMER_PROFILE_OPTIONS = {
:id => [:string, 'CustomerID'], # merchant assigned number
:notes => [:string, 'Notes'],
:data => [:string, 'CustomData'],
- :url => [:string, 'URL'],
- # Recurring Billing
+ :url => [:string, 'URL']
+ } #:nodoc:
+
+ CUSTOMER_RECURRING_BILLING_OPTIONS = {
:enabled => [:boolean, 'Enabled'],
:schedule => [:string, 'Schedule'],
:number_left => [:integer, 'NumLeft'],
@@ -92,18 +94,24 @@ class UsaEpayAdvancedGateway < Gateway
:user => [:string, 'User'],
:source => [:string, 'Source'],
:send_receipt => [:boolean, 'SendReceipt'],
- :receipt_note => [:string, 'ReceiptNote'],
- # Point of Sale
+ :receipt_note => [:string, 'ReceiptNote']
+ } #:nodoc:
+
+ CUSTOMER_POINT_OF_SALE_OPTIONS = {
:price_tier => [:string, 'PriceTier'],
:tax_class => [:string, 'TaxClass'],
:lookup_code => [:string, 'LookupCode']
} #:nodoc:
- ADDRESS_OPTIONS = {
+ CUSTOMER_OPTIONS = [
+ CUSTOMER_PROFILE_OPTIONS,
+ CUSTOMER_RECURRING_BILLING_OPTIONS,
+ CUSTOMER_POINT_OF_SALE_OPTIONS
+ ].inject(:merge) #:nodoc:
+
+ COMMON_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'],
@@ -114,6 +122,32 @@ class UsaEpayAdvancedGateway < Gateway
:company => [:string, 'Company']
} #:nodoc:
+ ADDRESS_OPTIONS = [
+ COMMON_ADDRESS_OPTIONS,
+ {
+ :address1 => [:string, 'Street'],
+ :address2 => [:string, 'Street2'],
+ }
+ ].inject(:merge) #:nodoc
+
+ CUSTOMER_UPDATE_DATA_FIELDS = [
+ CUSTOMER_PROFILE_OPTIONS,
+ CUSTOMER_RECURRING_BILLING_OPTIONS,
+ COMMON_ADDRESS_OPTIONS,
+ {
+ :address1 => [:string, 'Address'],
+ :address2 => [:string, 'Address2'],
+ },
+ {
+ :card_number => [:string, 'CardNumber'],
+ :card_exp => [:string, 'CardExp'],
+ :account => [:string, 'Account'],
+ :routing => [:string, 'Routing'],
+ :check_format => [:string, 'CheckFormat'],
+ :record_type => [:string, 'RecordType'],
+ }
+ ].inject(:merge) #:nodoc
+
CUSTOMER_TRANSACTION_REQUEST_OPTIONS = {
:command => [:string, 'Command'],
:ignore_duplicate => [:boolean, 'IgnoreDuplicate'],
@@ -124,7 +158,8 @@ class UsaEpayAdvancedGateway < Gateway
:merchant_receipt => [:boolean, 'MerchReceipt'],
:merchant_email => [:boolean, 'MerchReceiptEmail'],
:merchant_template => [:boolean, 'MerchReceiptName'],
- :verification_value => [:boolean, 'isRecurring'],
+ :recurring => [:boolean, 'isRecurring'],
+ :verification_value => [:string, 'CardCode'],
:software => [:string, 'Software']
} #:nodoc:
@@ -179,7 +214,6 @@ class UsaEpayAdvancedGateway < Gateway
} #:nodoc:
CHECK_DATA_OPTIONS = {
- :check_number => [:integer, 'CheckNumber'],
:drivers_license => [:string, 'DriversLicense'],
:drivers_license_state => [:string, 'DriversLicenseState'],
:record_type => [:string, 'RecordType'],
@@ -238,8 +272,8 @@ 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}"
+ self.live_url = "#{LIVE_URL_BASE}#{options[:software_id]}"
+ self.test_url = "#{TEST_URL_BASE}#{options[:software_id]}"
else
self.live_url = options[:live_url].to_s
self.test_url = options[:test_url].to_s if options[:test_url]
@@ -292,7 +326,7 @@ def refund(money, identification, options={})
end
def credit(money, identification, options={})
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
@@ -339,9 +373,10 @@ def add_customer(options={})
commit(__method__, request)
end
- # Update a customer by replacing all of the customer details..
+ # Update a customer by replacing all of the customer details.
#
- # Use quickUpdateCustomer to just update a few attributes.
+ # ==== Required
+ # * :customer_number -- customer to update
#
# ==== Options
# * Same as add_customer
@@ -353,9 +388,58 @@ def update_customer(options={})
commit(__method__, request)
end
+ # Update a customer by replacing only the provided fields.
+ #
+ # ==== Required
+ # * :customer_number -- customer to update
+ # * :update_data -- FieldValue array of fields to retrieve
+ # * :first_name
+ # * :last_name
+ # * :id
+ # * :company
+ # * :address
+ # * :address2
+ # * :city
+ # * :state
+ # * :zip
+ # * :country
+ # * :phone
+ # * :fax
+ # * :email
+ # * :url
+ # * :receipt_note
+ # * :send_receipt
+ # * :notes
+ # * :description
+ # * :order_id
+ # * :enabled
+ # * :schedule
+ # * :next
+ # * :num_left
+ # * :amount
+ # * :custom_data
+ # * :source
+ # * :user
+ # * :card_number
+ # * :card_exp
+ # * :account
+ # * :routing
+ # * :check_format or :record_type
+ #
+ # ==== Response
+ # * #message -- boolean; Returns true if successful. Exception thrown all failures.
+ #
+ def quick_update_customer(options={})
+ requires! options, :customer_number
+ requires! options, :update_data
+
+ request = build_request(__method__, options)
+ commit(__method__, request)
+ end
+
# Enable a customer for recurring billing.
#
- # Note: Customer does not need to have all recurring paramerters to succeed.
+ # Note: Customer does not need to have all recurring parameters to succeed.
#
# ==== Required
# * :customer_number
@@ -402,7 +486,7 @@ def add_customer_payment_method(options={})
commit(__method__, request)
end
- # Retrive all of the payment methods belonging to a customer
+ # Retrieve all of the payment methods belonging to a customer
#
# ==== Required
# * :customer_number
@@ -417,7 +501,7 @@ def get_customer_payment_methods(options={})
commit(__method__, request)
end
- # Retrive one of the payment methods belonging to a customer
+ # Retrieve one of the payment methods belonging to a customer
#
# ==== Required
# * :customer_number
@@ -454,7 +538,7 @@ def update_customer_payment_method(options={})
commit(__method__, request)
end
- # Delete one the payment methods beloning to a customer
+ # Delete one the payment methods belonging to a customer
#
# ==== Required
# * :customer_number
@@ -543,11 +627,11 @@ def run_customer_transaction(options={})
# will be returned in the response.
#
# ==== Options
- # * :method -- credit_card or check
+ # * :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 tranaction handling
+ # * :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
@@ -680,7 +764,7 @@ def refund_transaction(options={})
commit(__method__, request)
end
- # Override transaction flagged for mananager approval.
+ # Override transaction flagged for manager approval.
#
# Note: Checks only!
#
@@ -948,14 +1032,14 @@ def get_account_details
def build_request(action, options = {})
soap = Builder::XmlMarkup.new
soap.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
- soap.tag! "SOAP-ENV:Envelope",
+ 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|
+ 'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' do
+ soap.tag! 'SOAP-ENV:Body' do
send("build_#{action}", soap, options)
end
end
@@ -971,12 +1055,12 @@ def build_tag(soap, type, tag, value)
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|
+ soap.Token 'xsi:type' => 'ns1:ueSecurityToken' do
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'
+ soap.PinHash 'xsi:type' => 'ns1:ueHash' do
+ 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
@@ -985,17 +1069,17 @@ def build_token(soap, options)
# Customer ======================================================
def build_add_customer(soap, options)
- soap.tag! "ns1:addCustomer" do |soap|
+ soap.tag! 'ns1:addCustomer' do
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]
+ 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|
+ soap.tag! "ns1:#{type}" do
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_customer_data soap, options if add_customer_data
@@ -1018,8 +1102,16 @@ def build_delete_customer(soap, options)
build_customer(soap, options, 'deleteCustomer')
end
+ def build_quick_update_customer(soap, options)
+ soap.tag! 'ns1:quickUpdateCustomer' do
+ build_token soap, options
+ build_tag soap, :integer, 'CustNum', options[:customer_number]
+ build_field_value_array soap, 'UpdateData', 'FieldValue', options[:update_data], CUSTOMER_UPDATE_DATA_FIELDS
+ end
+ end
+
def build_add_customer_payment_method(soap, options)
- soap.tag! "ns1:addCustomerPaymentMethod" do |soap|
+ soap.tag! 'ns1:addCustomerPaymentMethod' do
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_customer_payment_methods soap, options
@@ -1029,7 +1121,7 @@ def build_add_customer_payment_method(soap, options)
end
def build_get_customer_payment_method(soap, options)
- soap.tag! 'ns1:getCustomerPaymentMethod' do |soap|
+ soap.tag! 'ns1:getCustomerPaymentMethod' do
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_tag soap, :integer, 'MethodID', options[:method_id]
@@ -1041,7 +1133,7 @@ def build_get_customer_payment_methods(soap, options)
end
def build_update_customer_payment_method(soap, options)
- soap.tag! 'ns1:updateCustomerPaymentMethod' do |soap|
+ soap.tag! 'ns1:updateCustomerPaymentMethod' do
build_token soap, options
build_customer_payment_methods soap, options
build_tag soap, :boolean, 'Verify', options[:verify]
@@ -1049,7 +1141,7 @@ def build_update_customer_payment_method(soap, options)
end
def build_delete_customer_payment_method(soap, options)
- soap.tag! "ns1:deleteCustomerPaymentMethod" do |soap|
+ soap.tag! 'ns1:deleteCustomerPaymentMethod' do
build_token soap, options
build_tag soap, :integer, 'Custnum', options[:customer_number]
build_tag soap, :integer, 'PaymentMethodID', options[:method_id]
@@ -1057,7 +1149,7 @@ def build_delete_customer_payment_method(soap, options)
end
def build_run_customer_transaction(soap, options)
- soap.tag! "ns1:runCustomerTransaction" do |soap|
+ soap.tag! 'ns1:runCustomerTransaction' do
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_tag soap, :integer, 'PaymentMethodID', options[:method_id] || 0
@@ -1068,56 +1160,56 @@ def build_run_customer_transaction(soap, options)
# Transactions ==================================================
def build_run_transaction(soap, options)
- soap.tag! 'ns1:runTransaction' do |soap|
+ soap.tag! 'ns1:runTransaction' do
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|
+ soap.tag! 'ns1:runSale' do
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|
+ soap.tag! 'ns1:runAuthOnly' do
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_run_credit(soap, options)
- soap.tag! 'ns1:runCredit' do |soap|
+ soap.tag! 'ns1:runCredit' do
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|
+ soap.tag! 'ns1:runCheckSale' do
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|
+ soap.tag! 'ns1:runCheckCredit' do
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_post_auth(soap, options)
- soap.tag! 'ns1:postAuth' do |soap|
+ soap.tag! 'ns1:postAuth' do
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|
+ soap.tag! 'ns1:runQuickSale' do
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_transaction_detail soap, options
@@ -1126,7 +1218,7 @@ def build_run_quick_sale(soap, options)
end
def build_run_quick_credit(soap, options)
- soap.tag! 'ns1:runQuickCredit' do |soap|
+ soap.tag! 'ns1:runQuickCredit' do
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_transaction_detail soap, options
@@ -1134,21 +1226,21 @@ def build_run_quick_credit(soap, options)
end
def build_get_transaction(soap, options)
- soap.tag! "ns1:getTransaction" do |soap|
+ soap.tag! 'ns1:getTransaction' do
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|
+ soap.tag! 'ns1:getTransactionStatus' do
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|
+ soap.tag! 'ns1:getTransactionCustom' do
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_transaction_field_array soap, options
@@ -1156,29 +1248,29 @@ def build_get_transaction_custom(soap, options)
end
def build_get_check_trace(soap, options)
- soap.tag! "ns1:getCheckTrace" do |soap|
+ soap.tag! 'ns1:getCheckTrace' do
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|
+ soap.tag! 'ns1:captureTransaction' do
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
- build_tag soap, :double, 'RefNum', amount(options[:amount])
+ build_tag soap, :double, 'Amount', amount(options[:amount])
end
end
def build_void_transaction(soap, options)
- soap.tag! "ns1:voidTransaction" do |soap|
+ soap.tag! 'ns1:voidTransaction' do
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|
+ soap.tag! 'ns1:refundTransaction' do
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_tag soap, :integer, 'Amount', amount(options[:amount])
@@ -1186,7 +1278,7 @@ def build_refund_transaction(soap, options)
end
def build_override_transaction(soap, options)
- soap.tag! "ns1:overrideTransaction" do |soap|
+ soap.tag! 'ns1:overrideTransaction' do
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_tag soap, :string, 'Reason', options[:reason]
@@ -1196,7 +1288,7 @@ def build_override_transaction(soap, options)
# Account =======================================================
def build_get_account_details(soap, options)
- soap.tag! "ns1:getAccountDetails" do |soap|
+ soap.tag! 'ns1:getAccountDetails' do
build_token soap, options
end
end
@@ -1205,7 +1297,7 @@ def build_get_account_details(soap, options)
def build_customer_data(soap, options)
soap.CustomerData 'xsi:type' => 'ns1:CustomerObject' do
- CUSTOMER_OPTIONS.each do |k,v|
+ CUSTOMER_OPTIONS.each do |k, v|
build_tag soap, v[0], v[1], options[k]
end
build_billing_address soap, options
@@ -1218,7 +1310,7 @@ 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|
+ 'xsi:type' =>'ns1:PaymentMethodArray' do
build_customer_payment_methods soap, options
end
end
@@ -1244,16 +1336,18 @@ def build_credit_card_or_check(soap, payment_method)
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}"
+ "#{"%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].number
+ build_tag soap, :string, 'Account', payment_method[:method].account_number
build_tag soap, :string, 'Routing', payment_method[:method].routing_number
- build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize
+ 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]
@@ -1263,7 +1357,7 @@ def build_credit_card_or_check(soap, payment_method)
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|
+ soap.tag! tag_name, 'xsi:type' => 'ns1:PaymentMethod' do
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]
@@ -1274,9 +1368,9 @@ def build_customer_payment_methods(soap, options)
end
def build_customer_transaction(soap, options)
- soap.Parameters 'xsi:type' => "ns1:CustomerTransactionRequest" do |soap|
+ soap.Parameters 'xsi:type' => 'ns1:CustomerTransactionRequest' do
build_transaction_detail soap, options
- CUSTOMER_TRANSACTION_REQUEST_OPTIONS.each do |k,v|
+ CUSTOMER_TRANSACTION_REQUEST_OPTIONS.each do |k, v|
build_tag soap, v[0], v[1], options[k]
end
build_custom_fields soap, options
@@ -1287,12 +1381,13 @@ def build_customer_transaction(soap, options)
# 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|
+ soap.tag! name, 'xsi:type' => 'ns1:TransactionRequestObject' do
+ 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].nil?
+ 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)
@@ -1310,39 +1405,47 @@ def build_transaction_request_object(soap, options, name='Params')
end
def build_transaction_detail(soap, options)
- soap.Details 'xsi:type' => "ns1:TransactionDetail" do |soap|
- TRANSACTION_DETAIL_OPTIONS.each do |k,v|
+ soap.Details 'xsi:type' => 'ns1:TransactionDetail' do
+ 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|
+ 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|
+ soap.CreditCardData 'xsi:type' => 'ns1:CreditCardData' do
build_tag soap, :string, 'CardNumber', options[:payment_method].number
- build_tag soap, :string, 'CardExpiration',
- "#{"%02d" % options[:payment_method].month}#{options[:payment_method].year}"
+ 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|
+ 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|
+ soap.CheckData 'xsi:type' => 'ns1:CheckData' do
+ 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|
+ CHECK_DATA_OPTIONS.each do |k, v|
build_tag soap, v[0], v[1], options[k]
end
end
@@ -1350,11 +1453,11 @@ def build_check_data(soap, options)
def build_recurring_billing(soap, options)
if options[:recurring]
- soap.RecurringBilling 'xsi:type' => "ns1:RecurringBilling" do |soap|
+ soap.RecurringBilling 'xsi:type' => 'ns1:RecurringBilling' do
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, :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
@@ -1362,7 +1465,7 @@ def build_recurring_billing(soap, options)
end
def build_transaction_field_array(soap, options)
- soap.Fields 'SOAP-ENC:arryType' => "xsd:string[#{options[:fields].length}]", 'xsi:type' => 'ns1:stringArray' do |soap|
+ soap.Fields 'SOAP-ENC:arryType' => "xsd:string[#{options[:fields].length}]", 'xsi:type' => 'ns1:stringArray' do
options[:fields].each do |field|
build_tag soap, :string, 'item', field
end
@@ -1374,11 +1477,10 @@ def build_transaction_field_array(soap, options)
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]
+ options[:billing_address][:first_name], options[:billing_address][:last_name] = split_names(options[:billing_address][:name])
end
- soap.BillingAddress 'xsi:type' => "ns1:Address" do
- ADDRESS_OPTIONS.each do |k,v|
+ 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
@@ -1388,17 +1490,31 @@ def build_billing_address(soap, options)
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]
+ options[:shipping_address][:first_name], options[:shipping_address][:last_name] = split_names(options[:shipping_address][:name])
end
- soap.ShippingAddress 'xsi:type' => "ns1:Address" do
- ADDRESS_OPTIONS.each do |k,v|
+ 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_field_value_array(soap, tag_name, type, custom_data, fields)
+ soap.tag! tag_name, 'SOAP-ENC:arryType' => "xsd:#{type}[#{options.length}]", 'xsi:type' => "ns1:#{type}Array" do
+ custom_data.each do |k, v|
+ build_field_value soap, fields[k][1], v, fields[k][0] if fields.key?(k)
+ end
+ end
+ end
+
+ def build_field_value(soap, field, value, value_type)
+ soap.FieldValue 'xsi:type' => 'ns1:FieldValue' do
+ build_tag soap, :string, 'Field', field
+ build_tag soap, value_type, 'Value', value
+ end
+ end
+
def build_line_items(soap, options) # TODO
end
@@ -1411,22 +1527,25 @@ def commit(action, request)
url = test? ? test_url : live_url
begin
- soap = ssl_post(url, request, "Content-Type" => "text/xml")
+ soap = ssl_post(url, request, 'Content-Type' => 'text/xml')
rescue ActiveMerchant::ResponseError => error
soap = error.response.body
end
- response = build_response(action, soap)
+ 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_params['soap_response'] = soap if @options[:soap_response]
- response = Response.new(
- success, message, response_params,
- :test => test?, :authorization => authorization,
+ Response.new(
+ success,
+ message,
+ response_params,
+ :test => test?,
+ :authorization => authorization,
:avs_result => avs_from(avs),
:cvv_result => cvv
)
@@ -1434,23 +1553,23 @@ def build_response(action, soap)
def avs_from(avs)
avs_params = { :code => avs }
- avs_params.merge!(:message => AVS_CUSTOM_MESSAGES[avs]) if AVS_CUSTOM_MESSAGES.key?(avs)
+ avs_params[: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")
+ 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')
+ 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
+ success = p['result_code'] == 'A'
authorization = p['ref_num']
avs = AVS_RESULTS[p['avs_result_code']]
cvv = p['card_code_result_code']
@@ -1461,7 +1580,8 @@ def parse(action, soap)
when :get_customer_payment_methods
p['item']
when :get_transaction_custom
- p['item'].inject({}) { |map, field| map[field['field']] = field['value']; map }
+ items = p['item'].kind_of?(Array) ? p['item'] : [p['item']]
+ items.inject({}) { |hash, item| hash[item['field']] = item['value']; hash }
else
p
end
@@ -1498,4 +1618,3 @@ def parse_element(node)
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
index 8adfeb5167f..e1cade9a52a 100644
--- a/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
+++ b/lib/active_merchant/billing/gateways/usa_epay_transaction.rb
@@ -1,20 +1,41 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
-
class UsaEpayTransactionGateway < Gateway
- self.test_url = self.live_url = 'https://www.usaepay.com/gate.php'
+ self.live_url = 'https://www.usaepay.com/gate'
+ self.test_url = 'https://sandbox.usaepay.com/gate'
- self.supported_cardtypes = [:visa, :master, :american_express]
- self.supported_countries = ['US']
- self.homepage_url = 'http://www.usaepay.com/'
- self.display_name = 'USA ePay'
+ self.supported_cardtypes = [:visa, :master, :american_express]
+ self.supported_countries = ['US']
+ self.homepage_url = 'http://www.usaepay.com/'
+ self.display_name = 'USA ePay'
TRANSACTIONS = {
- :authorization => 'authonly',
- :purchase => 'sale',
- :capture => 'capture',
- :refund => 'refund',
- :void => 'void'
+ :authorization => 'cc:authonly',
+ :purchase => 'cc:sale',
+ :capture => 'cc:capture',
+ :refund => 'cc:refund',
+ :void => 'cc:void',
+ :void_release => 'cc:void:release',
+ :check_purchase => 'check:sale'
+ }
+
+ STANDARD_ERROR_CODE_MAPPING = {
+ '00011' => STANDARD_ERROR_CODE[:incorrect_number],
+ '00012' => STANDARD_ERROR_CODE[:incorrect_number],
+ '00013' => STANDARD_ERROR_CODE[:incorrect_number],
+ '00014' => STANDARD_ERROR_CODE[:invalid_number],
+ '00015' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '00016' => STANDARD_ERROR_CODE[:invalid_expiry_date],
+ '00017' => STANDARD_ERROR_CODE[:expired_card],
+ '10116' => STANDARD_ERROR_CODE[:incorrect_cvc],
+ '10107' => STANDARD_ERROR_CODE[:incorrect_zip],
+ '10109' => STANDARD_ERROR_CODE[:incorrect_address],
+ '10110' => STANDARD_ERROR_CODE[:incorrect_address],
+ '10111' => STANDARD_ERROR_CODE[:incorrect_address],
+ '10127' => STANDARD_ERROR_CODE[:card_declined],
+ '00043' => STANDARD_ERROR_CODE[:call_issuer],
+ '10205' => STANDARD_ERROR_CODE[:card_declined],
+ '10204' => STANDARD_ERROR_CODE[:pickup_card]
}
def initialize(options = {})
@@ -27,29 +48,44 @@ def authorize(money, credit_card, options = {})
add_amount(post, money)
add_invoice(post, options)
- add_credit_card(post, credit_card)
- add_address(post, credit_card, options)
- add_customer_data(post, options)
+ add_payment(post, credit_card)
+ unless credit_card.track_data.present?
+ add_address(post, credit_card, options)
+ add_customer_data(post, options)
+ end
+ add_split_payments(post, options)
+ add_recurring_fields(post, options)
+ add_custom_fields(post, options)
+ add_line_items(post, options)
+ add_test_mode(post, options)
commit(:authorization, post)
end
- def purchase(money, credit_card, options = {})
+ def purchase(money, payment, options = {})
post = {}
add_amount(post, money)
add_invoice(post, options)
- add_credit_card(post, credit_card)
- add_address(post, credit_card, options)
- add_customer_data(post, options)
+ add_payment(post, payment, options)
+ unless payment.respond_to?(:track_data) && payment.track_data.present?
+ add_address(post, payment, options)
+ add_customer_data(post, options)
+ end
+ add_split_payments(post, options)
+ add_recurring_fields(post, options)
+ add_custom_fields(post, options)
+ add_line_items(post, options)
+ add_test_mode(post, options)
- commit(:purchase, post)
+ payment.respond_to?(:routing_number) ? commit(:check_purchase, post) : commit(:purchase, post)
end
def capture(money, authorization, options = {})
post = { :refNum => authorization }
add_amount(post, money)
+ add_test_mode(post, options)
commit(:capture, post)
end
@@ -57,12 +93,36 @@ def refund(money, authorization, options = {})
post = { :refNum => authorization }
add_amount(post, money)
+ add_test_mode(post, options)
commit(:refund, post)
end
+ def verify(creditcard, options = {})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(1, creditcard, options) }
+ r.process(:ignore_result) { void(r.authorization, options) }
+ end
+ end
+
+ # Pass `no_release: true` to keep the void from immediately settling
def void(authorization, options = {})
+ command = (options[:no_release] ? :void : :void_release)
post = { :refNum => authorization }
- commit(:void, post)
+ add_test_mode(post, options)
+ commit(command, post)
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?UMcard=)\d*(&?))i, '\1[FILTERED]\2').
+ gsub(%r((&?UMcvv2=)\d*(&?))i, '\1[FILTERED]\2').
+ gsub(%r((&?UMmagstripe=)[^&]*)i, '\1[FILTERED]\2').
+ gsub(%r((&?UMaccount=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?UMkey=)[^&]*)i, '\1[FILTERED]')
end
private
@@ -85,7 +145,12 @@ def add_customer_data(post, options)
if options.has_key? :email
post[:custemail] = options[:email]
- post[:custreceipt] = 'No'
+ if options[:cust_receipt]
+ post[:custreceipt] = options[:cust_receipt]
+ post[:custreceiptname] = options[:cust_receipt_name] if options[:cust_receipt_name]
+ else
+ post[:custreceipt] = 'No'
+ end
end
if options.has_key? :customer
@@ -97,26 +162,27 @@ def add_customer_data(post, options)
end
end
- def add_address(post, credit_card, options)
+ def add_address(post, payment, options)
billing_address = options[:billing_address] || options[:address]
- add_address_for_type(:billing, post, credit_card, billing_address) if billing_address
- add_address_for_type(:shipping, post, credit_card, options[:shipping_address]) if options[:shipping_address]
+ add_address_for_type(:billing, post, payment, billing_address) if billing_address
+ add_address_for_type(:shipping, post, payment, options[:shipping_address]) if options[:shipping_address]
end
- def add_address_for_type(type, post, credit_card, address)
+ def add_address_for_type(type, post, payment, address)
prefix = address_key_prefix(type)
-
- post[address_key(prefix, 'fname')] = credit_card.first_name
- post[address_key(prefix, 'lname')] = credit_card.last_name
- post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank?
- post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank?
- post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank?
- post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank?
- post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank?
- post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank?
- post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank?
- post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank?
+ first_name, last_name = split_names(address[:name])
+
+ post[address_key(prefix, 'fname')] = first_name.blank? && last_name.blank? ? payment.first_name : first_name
+ post[address_key(prefix, 'lname')] = first_name.blank? && last_name.blank? ? payment.last_name : last_name
+ post[address_key(prefix, 'company')] = address[:company] unless address[:company].blank?
+ post[address_key(prefix, 'street')] = address[:address1] unless address[:address1].blank?
+ post[address_key(prefix, 'street2')] = address[:address2] unless address[:address2].blank?
+ post[address_key(prefix, 'city')] = address[:city] unless address[:city].blank?
+ post[address_key(prefix, 'state')] = address[:state] unless address[:state].blank?
+ post[address_key(prefix, 'zip')] = address[:zip] unless address[:zip].blank?
+ post[address_key(prefix, 'country')] = address[:country] unless address[:country].blank?
+ post[address_key(prefix, 'phone')] = address[:phone] unless address[:phone].blank?
end
def address_key_prefix(type)
@@ -131,55 +197,141 @@ def address_key(prefix, key)
end
def add_invoice(post, options)
- post[:invoice] = options[:order_id]
- post[:description] = options[:description]
+ post[:invoice] = options[:invoice]
+ post[:orderid] = options[:order_id]
+ post[:description] = options[:description]
+ end
+
+ def add_payment(post, payment, options={})
+ if payment.respond_to?(:routing_number)
+ post[:checkformat] = options[:check_format] if options[:check_format]
+ if payment.account_type
+ account_type = payment.account_type.to_s.capitalize
+ raise ArgumentError, 'account_type must be checking or savings' unless %w(Checking Savings).include?(account_type)
+ post[:accounttype] = account_type
+ end
+ post[:account] = payment.account_number
+ post[:routing] = payment.routing_number
+ post[:name] = payment.name unless payment.name.blank?
+ elsif payment.respond_to?(:track_data) && payment.track_data.present?
+ post[:magstripe] = payment.track_data
+ post[:cardpresent] = true
+ else
+ post[:card] = payment.number
+ post[:cvv2] = payment.verification_value if payment.verification_value?
+ post[:expir] = expdate(payment)
+ post[:name] = payment.name unless payment.name.blank?
+ post[:cardpresent] = true if payment.manual_entry
+ end
+ end
+
+ def add_test_mode(post, options)
+ post[:testmode] = (options[:test_mode] ? 1 : 0) if options.has_key?(:test_mode)
+ end
+
+ # see: http://wiki.usaepay.com/developer/transactionapi#split_payments
+ def add_split_payments(post, options)
+ return unless options[:split_payments].is_a?(Array)
+ options[:split_payments].each_with_index do |payment, index|
+ prefix = '%02d' % (index + 2)
+ post["#{prefix}key"] = payment[:key]
+ post["#{prefix}amount"] = amount(payment[:amount])
+ post["#{prefix}description"] = payment[:description]
+ end
+
+ # When blank it's 'Stop'. 'Continue' is another one
+ post['onError'] = options[:on_error] || 'Void'
+ end
+
+ def add_recurring_fields(post, options)
+ return unless options[:recurring_fields].is_a?(Hash)
+ options[:recurring_fields].each do |key, value|
+ if value == true
+ value = 'yes'
+ elsif value == false
+ next
+ end
+
+ if key == :bill_amount
+ value = amount(value)
+ end
+
+ post[key.to_s.delete('_')] = value
+ end
end
- def add_credit_card(post, credit_card)
- post[:card] = credit_card.number
- post[:cvv2] = credit_card.verification_value if credit_card.verification_value?
- post[:expir] = expdate(credit_card)
- post[:name] = credit_card.name
+ # see: https://wiki.usaepay.com/developer/transactionapi#merchant_defined_custom_fields
+ def add_custom_fields(post, options)
+ return unless options[:custom_fields].is_a?(Hash)
+
+ options[:custom_fields].each do |index, custom|
+ raise ArgumentError.new('Cannot specify custom field with index 0') if index.to_s.to_i.zero?
+
+ post["custom#{index}"] = custom
+ end
+ end
+
+ # see: https://wiki.usaepay.com/developer/transactionapi#line_item_details
+ def add_line_items(post, options)
+ return unless options[:line_items].is_a?(Array)
+ options[:line_items].each_with_index do |line_item, index|
+ %w(product_ref_num sku qty name description taxable tax_rate tax_amount commodity_code discount_rate discount_amount).each do |key|
+ post["line#{index}#{key.delete('_')}"] = line_item[key.to_sym] if line_item.has_key?(key.to_sym)
+ end
+
+ {
+ quantity: 'qty',
+ unit: 'um',
+ }.each do |key, umkey|
+ post["line#{index}#{umkey}"] = line_item[key.to_sym] if line_item.has_key?(key.to_sym)
+ end
+
+ post["line#{index}cost"] = amount(line_item[:cost])
+ end
end
def parse(body)
fields = {}
for line in body.split('&')
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
+ key, value = *line.scan(%r{^(\w+)\=(.*)$}).flatten
fields[key] = CGI.unescape(value.to_s)
end
{
- :status => fields['UMstatus'],
- :auth_code => fields['UMauthCode'],
- :ref_num => fields['UMrefNum'],
- :batch => fields['UMbatch'],
- :avs_result => fields['UMavsResult'],
- :avs_result_code => fields['UMavsResultCode'],
- :cvv2_result => fields['UMcvv2Result'],
+ :status => fields['UMstatus'],
+ :auth_code => fields['UMauthCode'],
+ :ref_num => fields['UMrefNum'],
+ :batch => fields['UMbatch'],
+ :avs_result => fields['UMavsResult'],
+ :avs_result_code => fields['UMavsResultCode'],
+ :cvv2_result => fields['UMcvv2Result'],
:cvv2_result_code => fields['UMcvv2ResultCode'],
:vpas_result_code => fields['UMvpasResultCode'],
- :result => fields['UMresult'],
- :error => fields['UMerror'],
- :error_code => fields['UMerrorcode'],
- :acs_url => fields['UMacsurl'],
- :payload => fields['UMpayload']
- }.delete_if{|k, v| v.nil?}
+ :result => fields['UMresult'],
+ :error => fields['UMerror'],
+ :error_code => fields['UMerrorcode'],
+ :acs_url => fields['UMacsurl'],
+ :payload => fields['UMpayload']
+ }.delete_if { |k, v| v.nil? }
end
def commit(action, parameters)
- response = parse( ssl_post(self.live_url, post_data(action, parameters)) )
-
- Response.new(response[:status] == 'Approved', message_from(response), response,
- :test => test?,
- :authorization => response[:ref_num],
- :cvv_result => response[:cvv2_result_code],
- :avs_result => { :code => response[:avs_result_code] }
+ url = (test? ? self.test_url : self.live_url)
+ response = parse(ssl_post(url, post_data(action, parameters)))
+ approved = response[:status] == 'Approved'
+ error_code = nil
+ error_code = (STANDARD_ERROR_CODE_MAPPING[response[:error_code]] || STANDARD_ERROR_CODE[:processing_error]) unless approved
+ Response.new(approved, message_from(response), response,
+ :test => test?,
+ :authorization => response[:ref_num],
+ :cvv_result => response[:cvv2_result_code],
+ :avs_result => { :code => response[:avs_result_code] },
+ :error_code => error_code
)
end
def message_from(response)
- if response[:status] == "Approved"
+ if response[:status] == 'Approved'
return 'Success'
else
return 'Unspecified error' if response[:error].blank?
@@ -189,13 +341,15 @@ def message_from(response)
def post_data(action, parameters = {})
parameters[:command] = TRANSACTIONS[action]
- parameters[:key] = @options[:login]
+ parameters[:key] = @options[:login]
parameters[:software] = 'Active Merchant'
- parameters[:testmode] = (@options[:test] ? 1 : 0)
+ parameters[:testmode] = (@options[:test] ? 1 : 0) unless parameters.has_key?(:testmode)
+ seed = SecureRandom.hex(32).upcase
+ hash = Digest::SHA1.hexdigest("#{parameters[:command]}:#{@options[:password]}:#{parameters[:amount]}:#{parameters[:invoice]}:#{seed}")
+ parameters[:hash] = "s/#{seed}/#{hash}/n"
- parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join("&")
+ parameters.collect { |key, value| "UM#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/vanco.rb b/lib/active_merchant/billing/gateways/vanco.rb
new file mode 100644
index 00000000000..546eedbca4e
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/vanco.rb
@@ -0,0 +1,294 @@
+require 'nokogiri'
+
+module ActiveMerchant
+ module Billing
+ class VancoGateway < Gateway
+ include Empty
+
+ self.test_url = 'https://uat.vancopayments.com/cgi-bin/ws2.vps'
+ self.live_url = 'https://myvanco.vancopayments.com/cgi-bin/ws2.vps'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ self.homepage_url = 'http://vancopayments.com/'
+ self.display_name = 'Vanco Payment Solutions'
+
+ def initialize(options={})
+ requires!(options, :user_id, :password, :client_id)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ MultiResponse.run do |r|
+ r.process { login }
+ r.process { commit(purchase_request(money, payment_method, r.params['response_sessionid'], options), :response_transactionref) }
+ end
+ end
+
+ def refund(money, authorization, options={})
+ MultiResponse.run do |r|
+ r.process { login }
+ r.process { commit(refund_request(money, authorization, r.params['response_sessionid']), :response_creditrequestreceived) }
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r(().+())i, '\1[FILTERED]\2').
+ gsub(%r(().+())i, '\1[FILTERED]\2').
+ gsub(%r(().+())i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def parse(xml)
+ response = {}
+
+ doc = Nokogiri::XML(xml)
+ doc.root.xpath('*').each do |node|
+ if node.elements.empty?
+ response[node.name.downcase.to_sym] = node.text
+ else
+ node.elements.each do |childnode|
+ childnode_to_response(response, node, childnode)
+ end
+ end
+ end
+
+ response
+ end
+
+ def childnode_to_response(response, node, childnode)
+ name = "#{node.name.downcase}_#{childnode.name.downcase}"
+ if name == 'response_errors' && !childnode.elements.empty?
+ add_errors_to_response(response, childnode.to_s)
+ else
+ response[name.downcase.to_sym] = childnode.text
+ end
+ end
+
+ def add_errors_to_response(response, errors_xml)
+ errors_hash = Hash.from_xml(errors_xml).values.first
+ response[:response_errors] = errors_hash
+
+ error = errors_hash['Error']
+ if error.kind_of?(Hash)
+ response[:error_message] = error['ErrorDescription']
+ response[:error_codes] = error['ErrorCode']
+ elsif error.kind_of?(Array)
+ error_str = error.map { |e| e['ErrorDescription'] }.join('. ')
+ error_codes = error.map { |e| e['ErrorCode'] }.join(', ')
+ response[:error_message] = "#{error_str}."
+ response[:error_codes] = error_codes
+ end
+ end
+
+ def commit(request, success_field_name)
+ response = parse(ssl_post(url, request, headers))
+ succeeded = success_from(response, success_field_name)
+
+ Response.new(
+ succeeded,
+ message_from(succeeded, response),
+ response,
+ authorization: authorization_from(response),
+ test: test?
+ )
+ end
+
+ def success_from(response, success_field_name)
+ !empty?(response[success_field_name])
+ end
+
+ def message_from(succeeded, response)
+ return 'Success' if succeeded
+ response[:error_message]
+ end
+
+ def authorization_from(response)
+ [
+ response[:response_customerref],
+ response[:response_paymentmethodref],
+ response[:response_transactionref]
+ ].join('|')
+ end
+
+ def split_authorization(authorization)
+ authorization.to_s.split('|')
+ end
+
+ def purchase_request(money, payment_method, session_id, options)
+ build_xml_request do |doc|
+ add_auth(doc, 'EFTAddCompleteTransaction', session_id)
+
+ doc.Request do
+ doc.RequestVars do
+ add_client_id(doc)
+ add_amount(doc, money, options)
+ add_payment_method(doc, payment_method, options)
+ add_options(doc, options)
+ add_purchase_noise(doc)
+ end
+ end
+ end
+ end
+
+ def refund_request(money, authorization, session_id)
+ build_xml_request do |doc|
+ add_auth(doc, 'EFTAddCredit', session_id)
+
+ doc.Request do
+ doc.RequestVars do
+ add_client_id(doc)
+ add_amount(doc, money, options)
+ add_reference(doc, authorization)
+ add_refund_noise(doc)
+ end
+ end
+ end
+ end
+
+ def add_request(doc, request_type)
+ doc.RequestType(request_type)
+ doc.RequestID(SecureRandom.hex(15))
+ doc.RequestTime(Time.now)
+ doc.Version(2)
+ end
+
+ def add_auth(doc, request_type, session_id)
+ doc.Auth do
+ add_request(doc, request_type)
+ doc.SessionID(session_id)
+ end
+ end
+
+ def add_reference(doc, authorization)
+ customer_ref, payment_method_ref, transaction_ref = split_authorization(authorization)
+ doc.CustomerRef(customer_ref)
+ doc.PaymentMethodRef(payment_method_ref)
+ doc.TransactionRef(transaction_ref)
+ end
+
+ def add_amount(doc, money, options)
+ if empty?(options[:fund_id])
+ doc.Amount(amount(money))
+ else
+ doc.Funds do
+ doc.Fund do
+ doc.FundID(options[:fund_id])
+ doc.FundAmount(amount(money))
+ end
+ end
+ end
+ end
+
+ def add_payment_method(doc, payment_method, options)
+ if card_brand(payment_method) == 'check'
+ add_echeck(doc, payment_method)
+ else
+ add_credit_card(doc, payment_method, options)
+ end
+ end
+
+ def add_credit_card(doc, credit_card, options)
+ doc.AccountNumber(credit_card.number)
+ doc.CustomerName("#{credit_card.last_name}, #{credit_card.first_name}")
+ doc.CardExpMonth(format(credit_card.month, :two_digits))
+ doc.CardExpYear(format(credit_card.year, :two_digits))
+ doc.CardCVV2(credit_card.verification_value)
+ doc.CardBillingName(credit_card.name)
+ doc.AccountType('CC')
+ add_billing_address(doc, options)
+ end
+
+ def add_billing_address(doc, options)
+ address = options[:billing_address]
+ return unless address
+
+ doc.CardBillingAddr1(address[:address1])
+ doc.CardBillingAddr2(address[:address2])
+ doc.CardBillingCity(address[:city])
+ doc.CardBillingState(address[:state])
+ doc.CardBillingZip(address[:zip])
+ doc.CardBillingCountryCode(address[:country])
+ end
+
+ def add_echeck(doc, echeck)
+ if echeck.account_type == 'savings'
+ doc.AccountType('S')
+ else
+ doc.AccountType('C')
+ end
+
+ doc.CustomerName("#{echeck.last_name}, #{echeck.first_name}")
+ doc.AccountNumber(echeck.account_number)
+ doc.RoutingNumber(echeck.routing_number)
+ doc.TransactionTypeCode('WEB')
+ end
+
+ def add_purchase_noise(doc)
+ doc.StartDate('0000-00-00')
+ doc.FrequencyCode('O')
+ end
+
+ def add_refund_noise(doc)
+ doc.ContactName('Bilbo Baggins')
+ doc.ContactPhone('1234567890')
+ doc.ContactExtension('None')
+ doc.ReasonForCredit('Refund requested')
+ end
+
+ def add_options(doc, options)
+ doc.CustomerIPAddress(options[:ip]) if options[:ip]
+ end
+
+ def add_client_id(doc)
+ doc.ClientID(@options[:client_id])
+ end
+
+ def login
+ commit(login_request, :response_sessionid)
+ end
+
+ def login_request
+ build_xml_request do |doc|
+ doc.Auth do
+ add_request(doc, 'Login')
+ end
+
+ doc.Request do
+ doc.RequestVars do
+ doc.UserID(@options[:user_id])
+ doc.Password(@options[:password])
+ end
+ end
+ end
+ end
+
+ def build_xml_request
+ builder = Nokogiri::XML::Builder.new
+ builder.__send__('VancoWS') do |doc|
+ yield(doc)
+ end
+ builder.to_xml
+ end
+
+ def url
+ (test? ? test_url : live_url)
+ end
+
+ def headers
+ {
+ 'Content-Type' => 'text/xml'
+ }
+ end
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/verifi.rb b/lib/active_merchant/billing/gateways/verifi.rb
index c0ff6765c10..e435505d8be 100644
--- a/lib/active_merchant/billing/gateways/verifi.rb
+++ b/lib/active_merchant/billing/gateways/verifi.rb
@@ -5,46 +5,46 @@ module Billing #:nodoc:
class VerifiGateway < Gateway
class VerifiPostData < PostData
# Fields that will be sent even if they are blank
- self.required_fields = [ :amount, :type, :ccnumber, :ccexp, :firstname, :lastname,
- :company, :address1, :address2, :city, :state, :zip, :country, :phone ]
+ self.required_fields = [:amount, :type, :ccnumber, :ccexp, :firstname, :lastname,
+ :company, :address1, :address2, :city, :state, :zip, :country, :phone]
end
self.live_url = self.test_url = 'https://secure.verifi.com/gw/api/transact.php'
RESPONSE_CODE_MESSAGES = {
- "100" => "Transaction was Approved",
- "200" => "Transaction was Declined by Processor",
- "201" => "Do Not Honor",
- "202" => "Insufficient Funds",
- "203" => "Over Limit",
- "204" => "Transaction not allowed",
- "220" => "Incorrect payment Data",
- "221" => "No Such Card Issuer",
- "222" => "No Card Number on file with Issuer",
- "223" => "Expired Card",
- "224" => "Invalid Expiration Date",
- "225" => "Invalid Card Security Code",
- "240" => "Call Issuer for Further Information",
- "250" => "Pick Up Card",
- "251" => "Lost Card",
- "252" => "Stolen Card",
- "253" => "Fraudulent Card",
- "260" => "Declined With further Instructions Available (see response text)",
- "261" => "Declined - Stop All Recurring Payments",
- "262" => "Declined - Stop this Recurring Program",
- "263" => "Declined - Update Cardholder Data Available",
- "264" => "Declined - Retry in a few days",
- "300" => "Transaction was Rejected by Gateway",
- "400" => "Transaction Error Returned by Processor",
- "410" => "Invalid Merchant Configuration",
- "411" => "Merchant Account is Inactive",
- "420" => "Communication Error",
- "421" => "Communication Error with Issuer",
- "430" => "Duplicate Transaction at Processor",
- "440" => "Processor Format Error",
- "441" => "Invalid Transaction Information",
- "460" => "Processor Feature Not Available",
- "461" => "Unsupported Card Type"
+ '100' => 'Transaction was Approved',
+ '200' => 'Transaction was Declined by Processor',
+ '201' => 'Do Not Honor',
+ '202' => 'Insufficient Funds',
+ '203' => 'Over Limit',
+ '204' => 'Transaction not allowed',
+ '220' => 'Incorrect payment Data',
+ '221' => 'No Such Card Issuer',
+ '222' => 'No Card Number on file with Issuer',
+ '223' => 'Expired Card',
+ '224' => 'Invalid Expiration Date',
+ '225' => 'Invalid Card Security Code',
+ '240' => 'Call Issuer for Further Information',
+ '250' => 'Pick Up Card',
+ '251' => 'Lost Card',
+ '252' => 'Stolen Card',
+ '253' => 'Fraudulent Card',
+ '260' => 'Declined With further Instructions Available (see response text)',
+ '261' => 'Declined - Stop All Recurring Payments',
+ '262' => 'Declined - Stop this Recurring Program',
+ '263' => 'Declined - Update Cardholder Data Available',
+ '264' => 'Declined - Retry in a few days',
+ '300' => 'Transaction was Rejected by Gateway',
+ '400' => 'Transaction Error Returned by Processor',
+ '410' => 'Invalid Merchant Configuration',
+ '411' => 'Merchant Account is Inactive',
+ '420' => 'Communication Error',
+ '421' => 'Communication Error with Issuer',
+ '430' => 'Duplicate Transaction at Processor',
+ '440' => 'Processor Format Error',
+ '441' => 'Invalid Transaction Information',
+ '460' => 'Processor Feature Not Available',
+ '461' => 'Unsupported Card Type'
}
SUCCESS = 1
@@ -86,7 +86,7 @@ def void(authorization, options = {})
def credit(money, credit_card_or_authorization, options = {})
if credit_card_or_authorization.is_a?(String)
- deprecated CREDIT_DEPRECATION_MESSAGE
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, credit_card_or_authorization, options)
else
sale_authorization_or_credit_template(:credit, money, credit_card_or_authorization, options)
@@ -125,13 +125,6 @@ def add_credit_card(post, credit_card)
post[:cvv] = credit_card.verification_value
end
- def expdate(credit_card)
- year = sprintf("%.4i", credit_card.year)
- month = sprintf("%.2i", credit_card.month)
-
- "#{month}#{year[-2..-1]}"
- end
-
def add_addresses(post, options)
if billing_address = options[:billing_address] || options[:address]
post[:company] = billing_address[:company]
@@ -187,10 +180,10 @@ def add_security_key_data(post, options, money)
# MD5(username|password|orderid|amount|time)
now = Time.now.to_i.to_s
md5 = Digest::MD5.new
- md5 << @options[:login].to_s + "|"
- md5 << @options[:password].to_s + "|"
- md5 << options[:order_id].to_s + "|"
- md5 << amount(money).to_s + "|"
+ md5 << @options[:login].to_s + '|'
+ md5 << @options[:password].to_s + '|'
+ md5 << options[:order_id].to_s + '|'
+ md5 << amount(money).to_s + '|'
md5 << now
post[:key] = md5.hexdigest
post[:time] = now
@@ -199,7 +192,7 @@ def add_security_key_data(post, options, money)
def commit(trx_type, money, post)
post[:amount] = amount(money)
- response = parse( ssl_post(self.live_url, post_data(trx_type, post)) )
+ response = parse(ssl_post(self.live_url, post_data(trx_type, post)))
Response.new(response[:response].to_i == SUCCESS, message_from(response), response,
:test => test?,
@@ -210,7 +203,7 @@ def commit(trx_type, money, post)
end
def message_from(response)
- response[:response_code_message] ? response[:response_code_message] : ""
+ response[:response_code_message] || ''
end
def parse(body)
diff --git a/lib/active_merchant/billing/gateways/viaklix.rb b/lib/active_merchant/billing/gateways/viaklix.rb
index 307933e6506..b68a8ec3ac9 100644
--- a/lib/active_merchant/billing/gateways/viaklix.rb
+++ b/lib/active_merchant/billing/gateways/viaklix.rb
@@ -50,7 +50,7 @@ def purchase(money, creditcard, options = {})
# Viaklix does not support credits by reference. You must pass in the credit card
def credit(money, creditcard, options = {})
if creditcard.is_a?(String)
- raise ArgumentError, "Reference credits are not supported. Please supply the original credit card"
+ raise ArgumentError, 'Reference credits are not supported. Please supply the original credit card'
end
form = {}
@@ -63,6 +63,7 @@ def credit(money, creditcard, options = {})
end
private
+
def add_test_mode(form, options)
form[:test_mode] = 'TRUE' if options[:test_mode]
end
@@ -72,12 +73,12 @@ def add_customer_data(form, options)
form[:customer_code] = options[:customer].to_s.slice(0, 10) unless options[:customer].blank?
end
- def add_invoice(form,options)
+ def add_invoice(form, options)
form[:invoice_number] = (options[:order_id] || options[:invoice]).to_s.slice(0, 10)
form[:description] = options[:description].to_s.slice(0, 255)
end
- def add_address(form,options)
+ def add_address(form, options)
billing_address = options[:billing_address] || options[:address]
if billing_address
@@ -92,7 +93,7 @@ def add_address(form,options)
end
if shipping_address = options[:shipping_address]
- first_name, last_name = parse_first_and_last_name(shipping_address[:name])
+ first_name, last_name = split_names(shipping_address[:name])
form[:ship_to_first_name] = first_name.to_s.slice(0, 20)
form[:ship_to_last_name] = last_name.to_s.slice(0, 30)
form[:ship_to_address] = shipping_address[:address1].to_s.slice(0, 30)
@@ -104,14 +105,6 @@ def add_address(form,options)
end
end
- def parse_first_and_last_name(value)
- name = value.to_s.split(' ')
-
- last_name = name.pop || ''
- first_name = name.join(' ')
- [ first_name, last_name ]
- end
-
def add_creditcard(form, creditcard)
form[:card_number] = creditcard.number
form[:exp_date] = expdate(creditcard)
@@ -145,7 +138,7 @@ def commit(action, money, parameters)
parameters[:amount] = amount(money)
parameters[:transaction_type] = self.actions[action]
- response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)) )
+ response = parse(ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)))
Response.new(response['result'] == APPROVED, message_from(response), response,
:test => @options[:test] || test?,
@@ -166,22 +159,16 @@ def message_from(response)
def post_data(parameters)
result = preamble
result.merge!(parameters)
- result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
- end
-
- def expdate(creditcard)
- year = sprintf("%.4i", creditcard.year)
- month = sprintf("%.2i", creditcard.month)
- "#{month}#{year[2..3]}"
+ result.collect { |key, value| "ssl_#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
# Parse the response message
def parse(msg)
resp = {}
- msg.split(self.delimiter).collect{|li|
- key, value = li.split("=")
- resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip
- }
+ msg.split(self.delimiter).collect { |li|
+ key, value = li.split('=')
+ resp[key.strip.gsub(/^ssl_/, '')] = value.to_s.strip
+ }
resp
end
end
diff --git a/lib/active_merchant/billing/gateways/vindicia.rb b/lib/active_merchant/billing/gateways/vindicia.rb
deleted file mode 100644
index 1d091ef00e2..00000000000
--- a/lib/active_merchant/billing/gateways/vindicia.rb
+++ /dev/null
@@ -1,361 +0,0 @@
-begin
- require "vindicia-api"
-rescue LoadError
- raise "Could not load the vindicia-api gem. Use `gem install vindicia-api` to install it."
-end
-
-require 'i18n/core_ext/string/interpolate'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
-
- # For more information on the Vindicia Gateway please visit their {website}[http://vindicia.com/]
- #
- # The login and password are not the username and password you use to
- # login to the Vindicia Merchant Portal.
- #
- # ==== Recurring Billing
- #
- # AutoBills are an feature of Vindicia's API that allows for creating and managing subscriptions.
- #
- # For more information about Vindicia's API and various other services visit their {Resource Center}[http://www.vindicia.com/resources/index.html]
- class VindiciaGateway < Gateway
- self.supported_countries = %w{US CA GB AU MX BR DE KR CN HK}
- self.supported_cardtypes = [:visa, :master, :american_express, :discover]
- self.homepage_url = 'http://www.vindicia.com/'
- self.display_name = 'Vindicia'
-
- class_attribute :test_url, :live_url
-
- self.test_url = "https://soap.prodtest.sj.vindicia.com/soap.pl"
- self.live_url = "http://soap.vindicia.com/soap.pl"
-
- # Creates a new VindiciaGateway
- #
- # The gateway requires that a valid login and password be passed
- # in the +options+ hash.
- #
- # ==== Options
- #
- # * :login -- Vindicia SOAP login (REQUIRED)
- # * :password -- Vindicia SOAP password (REQUIRED)
- # * :api_version -- Vindicia API Version - defaults to 3.6 (OPTIONAL)
- # * :account_id -- Account Id which all transactions will be run against. (REQUIRED)
- # * :transaction_prefix -- Prefix to order id for one-time transactions - defaults to 'X' (OPTIONAL
- # * :min_chargeback_probability -- Minimum score for chargebacks - defaults to 65 (OPTIONAL)
- # * :cvn_success -- Array of valid CVN Check return values - defaults to [M, P] (OPTIONAL)
- # * :avs_success -- Array of valid AVS Check return values - defaults to [X, Y, A, W, Z] (OPTIONAL)
- def initialize(options = {})
- requires!(options, :login, :password)
- super
-
- config = lambda do |config|
- config.login = options[:login]
- config.password = options[:password]
- config.api_version = options[:api_version] || "3.6"
- config.endpoint = test? ? self.test_url : self.live_url
- config.namespace = "http://soap.vindicia.com"
- end
-
- if Vindicia.config.is_configured?
- config.call(Vindicia.config)
- else
- Vindicia.configure(&config)
- end
-
- requires!(options, :account_id)
- @account_id = options[:account_id]
-
- @transaction_prefix = options[:transaction_prefix] || "X"
-
- @min_chargeback_probability = options[:min_chargeback_probability] || 65
- @cvn_success = options[:cvn_success] || %w{M P}
- @avs_success = options[:avs_success] || %w{X Y A W Z}
-
- @allowed_authorization_statuses = %w{Authorized}
- end
-
- # Perform a purchase, which is essentially an authorization and capture in a single operation.
- #
- # ==== Parameters
- #
- # * money -- The amount to be purchased as an Integer value in cents.
- # * creditcard -- The CreditCard details for the transaction.
- # * options -- A hash of optional parameters.
- def purchase(money, creditcard, options = {})
- response = authorize(money, creditcard, options)
- return response if !response.success? || response.fraud_review?
-
- capture(money, response.authorization, options)
- end
-
- # Performs an authorization, which reserves the funds on the customer's credit card, but does not
- # charge the card.
- #
- # ==== Parameters
- #
- # * money -- The amount to be authorized as an Integer value in cents.
- # * creditcard -- The CreditCard details for the transaction.
- # * options -- A hash of optional parameters.
- def authorize(money, creditcard, options = {})
- vindicia_transaction = authorize_transaction(money, creditcard, options)
- response = check_transaction(vindicia_transaction)
-
- # if this response is under fraud review because of our AVS/CVV checks void the transaction
- if !response.success? && response.fraud_review? && !response.authorization.blank?
- void_response = void([vindicia_transaction[:transaction][:merchantTransactionId]], options)
- if void_response.success?
- return response
- else
- return void_response
- end
- end
-
- response
- end
-
- # Captures the funds from an authorized transaction.
- #
- # ==== Parameters
- #
- # * money -- The amount to be captured as an Integer value in cents.
- # * identification -- The authorization returned from the previous authorize request.
- def capture(money, identification, options = {})
- response = post(Vindicia::Transaction.capture({
- :transactions => [{ :merchantTransactionId => identification }]
- }))
-
- if response[:return][:returnCode] != '200' || response[:qtyFail].to_i > 0
- return fail(response)
- end
-
- success(response, identification)
- end
-
- # Void a previous transaction
- #
- # ==== Parameters
- #
- # * identification - The authorization returned from the previous authorize request.
- # * options - Extra options (currently only :ip used)
- def void(identification, options = {})
- response = post(Vindicia::Transaction.cancel({
- :transactions => [{
- :account => { :merchantAccountId => @account_id },
- :merchantTransactionId => identification,
- :sourceIp => options[:ip]
- }]
- }))
-
- if response[:return][:returnCode] == '200' && response[:qtyFail].to_i == 0
- success(response, identification)
- else
- fail(response)
- end
- end
-
- # Perform a recurring billing, which is essentially a purchase and autobill setup in a single operation.
- #
- # ==== Parameters
- #
- # * money -- The amount to be purchased as an Integer value in cents.
- # * creditcard -- The CreditCard details for the transaction.
- # * options -- A hash of parameters.
- #
- # ==== Options
- #
- # * :product_sku -- The subscription product's sku
- # * :autobill_prefix -- Prefix to order id for subscriptions - defaults to 'A' (OPTIONAL)
- def recurring(money, creditcard, options={})
- options[:recurring] = true
- @autobill_prefix = options[:autobill_prefix] || "A"
-
- response = authorize(money, creditcard, options)
- return response if !response.success? || response.fraud_review?
-
- capture_resp = capture(money, response.authorization, options)
- return capture_resp if !response.success?
-
- # Setting up a recurring AutoBill requires an associated product
- requires!(options, :product_sku)
- autobill_response = check_subscription(authorize_subscription(options.merge(:product_sku => options[:product_sku])))
-
- if autobill_response.success?
- autobill_response
- else
- # If the AutoBill fails to set-up, void the transaction and return it as the response
- void_response = void(capture_resp.authorization, options)
- if void_response.success?
- return autobill_response
- else
- return void_response
- end
- end
- end
-
- protected
-
- def post(body)
- parse(ssl_post(Vindicia.config.endpoint, body, "Content-Type" => "text/xml"))
- end
-
- def parse(response)
- # Vindicia always returns in the form of request_type_response => { actual_response }
- Hash.from_xml(response)["Envelope"]["Body"].values.first.with_indifferent_access
- end
-
- def check_transaction(vindicia_transaction)
- if vindicia_transaction[:return][:returnCode] == '200'
- status_log = vindicia_transaction[:transaction][:statusLog].first
- if status_log[:creditCardStatus]
- avs = status_log[:creditCardStatus][:avsCode]
- cvn = status_log[:creditCardStatus][:cvnCode]
- end
-
- if @allowed_authorization_statuses.include?(status_log[:status]) &&
- check_cvn(cvn) && check_avs(avs)
-
- success(vindicia_transaction,
- vindicia_transaction[:transaction][:merchantTransactionId],
- avs, cvn)
- else
- # If the transaction is authorized, but it didn't pass our AVS/CVV checks send the authorization along so
- # that is gets voided. Otherwise, send no authorization.
- fail(vindicia_transaction, avs, cvn, false,
- @allowed_authorization_statuses.include?(status_log[:status]) ? vindicia_transaction[:transaction][:merchantTransactionId] : "")
- end
- else
- # 406 = Chargeback risk score is higher than minChargebackProbability, transaction not authorized.
- fail(vindicia_transaction, nil, nil, vindicia_transaction[:return][:return_code] == '406')
- end
- end
-
- def authorize_transaction(money, creditcard, options)
- parameters = {
- :amount => amount(money),
- :currency => options[:currency] || currency(money)
- }
-
- add_account_data(parameters, options)
- add_customer_data(parameters, options)
- add_payment_source(parameters, creditcard, options)
-
- post(Vindicia::Transaction.auth({
- :transaction => parameters,
- :minChargebackProbability => @min_chargeback_probability
- }))
- end
-
- def add_account_data(parameters, options)
- parameters[:account] = { :merchantAccountId => @account_id }
- parameters[:sourceIp] = options[:ip] if options[:ip]
- end
-
- def add_customer_data(parameters, options)
- parameters[:merchantTransactionId] = transaction_id(options[:order_id])
- parameters[:shippingAddress] = convert_am_address_to_vindicia(options[:shipping_address])
-
- # Transaction items must be provided for tax purposes
- requires!(options, :line_items)
- parameters[:transactionItems] = options[:line_items]
-
- if options[:recurring]
- parameters[:nameValues] = [{:name => 'merchantAutoBillIdentifier', :value => autobill_id(options[:order_id])}]
- end
- end
-
- def add_payment_source(parameters, creditcard, options)
- parameters[:sourcePaymentMethod] = {
- :type => 'CreditCard',
- :creditCard => { :account => creditcard.number, :expirationDate => "%4d%02d" % [creditcard.year, creditcard.month] },
- :accountHolderName => creditcard.name,
- :nameValues => [{ :name => 'CVN', :value => creditcard.verification_value }],
- :billingAddress => convert_am_address_to_vindicia(options[:billing_address] || options[:address]),
- :customerSpecifiedType => creditcard.brand.capitalize,
- :active => !!options[:recurring]
- }
- end
-
- def authorize_subscription(options)
- parameters = {}
-
- add_account_data(parameters, options)
- add_subscription_information(parameters, options)
-
- post(Vindicia::AutoBill.update({
- :autobill => parameters,
- :validatePaymentMethod => false,
- :minChargebackProbability => 100
- }))
- end
-
- def check_subscription(vindicia_transaction)
- if vindicia_transaction[:return][:returnCode] == '200'
- if vindicia_transaction[:autobill] && vindicia_transaction[:autobill][:status] == "Active"
- success(vindicia_transaction,
- vindicia_transaction[:autobill][:merchantAutoBillId])
- else
- fail(vindicia_transaction)
- end
- else
- fail(vindicia_transaction)
- end
- end
-
- def add_subscription_information(parameters, options)
- requires!(options, :product_sku)
-
- if options[:shipping_address]
- parameters[:account][:shipping_address] = options[:shipping_address]
- end
-
- parameters[:merchantAutoBillId] = autobill_id(options[:order_id])
- parameters[:product] = { :merchantProductId => options[:product_sku] }
- end
-
- def check_avs(avs)
- avs.blank? || @avs_success.include?(avs)
- end
-
- def check_cvn(cvn)
- cvn.blank? || @cvn_success.include?(cvn)
- end
-
- def success(response, authorization, avs_code = nil, cvn_code = nil)
- ActiveMerchant::Billing::Response.new(true, response[:return][:returnString], response,
- { :fraud_review => false, :authorization => authorization, :test => test?,
- :avs_result => { :code => avs_code }, :cvv_result => cvn_code })
- end
-
- def fail(response, avs_code = nil, cvn_code = nil, fraud_review = false, authorization = "")
- ActiveMerchant::Billing::Response.new(false, response[:return][:returnString], response,
- { :fraud_review => fraud_review || !authorization.blank?,
- :authorization => authorization, :test => test?,
- :avs_result => { :code => avs_code }, :cvv_result => cvn_code })
-
- end
-
- def autobill_id(order_id)
- "#{@autobill_prefix}#{order_id}"
- end
-
- def transaction_id(order_id)
- "#{@transaction_prefix}#{order_id}"
- end
-
- # Converts valid ActiveMerchant address hash to proper Vindicia format
- def convert_am_address_to_vindicia(address)
- return if address.nil?
-
- convs = { :address1 => :addr1, :address2 => :addr2,
- :state => :district, :zip => :postalCode }
-
- vindicia_address = {}
- address.each do |key, val|
- vindicia_address[convs[key] || key] = val
- end
- vindicia_address
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/gateways/visanet_peru.rb b/lib/active_merchant/billing/gateways/visanet_peru.rb
new file mode 100644
index 00000000000..49473abc2f6
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/visanet_peru.rb
@@ -0,0 +1,245 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class VisanetPeruGateway < Gateway
+ include Empty
+ self.display_name = 'VisaNet Peru Gateway'
+ self.homepage_url = 'http://www.visanet.com.pe'
+
+ self.test_url = 'https://devapi.vnforapps.com/api.tokenization/api/v2/merchant'
+ self.live_url = 'https://api.vnforapps.com/api.tokenization/api/v2/merchant'
+
+ self.supported_countries = ['US', 'PE']
+ self.default_currency = 'PEN'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+
+ def initialize(options={})
+ requires!(options, :access_key_id, :secret_access_key, :merchant_id)
+ super
+ end
+
+ def purchase(amount, payment_method, options={})
+ MultiResponse.run() do |r|
+ r.process { authorize(amount, payment_method, options) }
+ r.process { capture(r.authorization, options) }
+ end
+ end
+
+ def authorize(amount, payment_method, options={})
+ params = {}
+
+ add_invoice(params, amount, options)
+ add_payment_method(params, payment_method)
+ add_antifraud_data(params, options)
+ params[:email] = options[:email] || 'unknown@email.com'
+ params[:createAlias] = false
+
+ commit('authorize', params, options)
+ end
+
+ def capture(authorization, options={})
+ params = {}
+ options[:id_unico] = split_authorization(authorization)[1]
+ add_auth_order_id(params, authorization, options)
+ commit('deposit', params, options)
+ end
+
+ def void(authorization, options={})
+ params = {}
+ add_auth_order_id(params, authorization, options)
+ commit('void', params, options)
+ end
+
+ def refund(amount, authorization, options={})
+ params = {}
+ params[:amount] = amount(amount) if amount
+ add_auth_order_id(params, authorization, options)
+ response = commit('cancelDeposit', params, options)
+ return response if response.success? || split_authorization(authorization).length == 1 || !options[:force_full_refund_if_unsettled]
+
+ # Attempt RefundSingleTransaction if unsettled (and stash the original
+ # response message so it will be included it in the follow-up response
+ # message)
+ options[:error_message] = response.message
+ prepare_refund_data(params, authorization, options)
+ commit('refund', params, options)
+ 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((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r((\"cardNumber\\\":\\\")\d+), '\1[FILTERED]').
+ gsub(%r((\"cvv2Code\\\":\\\")\d+), '\1[FILTERED]')
+ end
+
+ private
+
+ CURRENCY_CODES = Hash.new { |h, k| raise ArgumentError.new("Unsupported currency: #{k}") }
+ CURRENCY_CODES['USD'] = 840
+ CURRENCY_CODES['PEN'] = 604
+
+ def add_invoice(params, money, options)
+ # Visanet Peru expects a 9-digit numeric purchaseNumber
+ params[:purchaseNumber] = (SecureRandom.random_number(900_000_000) + 100_000_000).to_s
+ params[:externalTransactionId] = options[:order_id]
+ params[:amount] = amount(money)
+ params[:currencyId] = CURRENCY_CODES[options[:currency] || currency(money)]
+ end
+
+ def add_auth_order_id(params, authorization, options)
+ purchase_number, _ = split_authorization(authorization)
+ params[:purchaseNumber] = purchase_number
+ params[:externalTransactionId] = options[:order_id]
+ end
+
+ def add_payment_method(params, payment_method)
+ params[:firstName] = payment_method.first_name
+ params[:lastName] = payment_method.last_name
+ params[:cardNumber] = payment_method.number
+ params[:cvv2Code] = payment_method.verification_value
+ params[:expirationYear] = format(payment_method.year, :four_digits)
+ params[:expirationMonth] = format(payment_method.month, :two_digits)
+ end
+
+ def add_antifraud_data(params, options)
+ antifraud = {}
+
+ if billing_address = options[:billing_address] || options[:address]
+ antifraud[:billTo_street1] = billing_address[:address1]
+ antifraud[:billTo_city] = billing_address[:city]
+ antifraud[:billTo_state] = billing_address[:state]
+ antifraud[:billTo_country] = billing_address[:country]
+ antifraud[:billTo_postalCode] = billing_address[:zip]
+ end
+
+ antifraud[:deviceFingerprintId] = options[:device_fingerprint_id] || SecureRandom.hex(16)
+ antifraud[:merchantDefineData] = options[:merchant_define_data] if options[:merchant_define_data]
+
+ params[:antifraud] = antifraud
+ end
+
+ def prepare_refund_data(params, authorization, options)
+ params.delete(:purchaseNumber)
+ params[:externalReferenceId] = params.delete(:externalTransactionId)
+ _, transaction_id = split_authorization(authorization)
+
+ options.update(transaction_id: transaction_id)
+ params[:ruc] = options[:ruc]
+ end
+
+ def split_authorization(authorization)
+ authorization.split('|')
+ end
+
+ def commit(action, params, options={})
+ raw_response = ssl_request(method(action), url(action, params, options), params.to_json, headers)
+ response = parse(raw_response)
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response_error(raw_response, options, action)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ else
+ Response.new(
+ success_from(response),
+ message_from(response, options, action),
+ response,
+ :test => test?,
+ :authorization => authorization_from(params, response, options),
+ :error_code => response['errorCode']
+ )
+ end
+
+ def headers
+ {
+ 'Authorization' => 'Basic ' + Base64.strict_encode64("#{@options[:access_key_id]}:#{@options[:secret_access_key]}").strip,
+ 'Content-Type' => 'application/json'
+ }
+ end
+
+ def url(action, params, options={})
+ if action == 'authorize'
+ "#{base_url}/#{@options[:merchant_id]}"
+ elsif action == 'refund'
+ "#{base_url}/#{@options[:merchant_id]}/#{action}/#{options[:transaction_id]}"
+ else
+ "#{base_url}/#{@options[:merchant_id]}/#{action}/#{params[:purchaseNumber]}"
+ end
+ end
+
+ def method(action)
+ %w(authorize refund).include?(action) ? :post : :put
+ end
+
+ def authorization_from(params, response, options)
+ id_unico = response['data']['ID_UNICO'] || options[:id_unico]
+ "#{params[:purchaseNumber]}|#{id_unico}"
+ end
+
+ def base_url
+ test? ? test_url : live_url
+ end
+
+ def parse(body)
+ JSON.parse(body)
+ end
+
+ def success_from(response)
+ response['errorCode'] == 0
+ end
+
+ def message_from(response, options, action)
+ message_from_messages(
+ response['errorMessage'],
+ action_code_description(response),
+ options[:error_message]
+ )
+ end
+
+ def message_from_messages(*args)
+ args.reject { |m| error_message_empty?(m) }.join(' | ')
+ end
+
+ def action_code_description(response)
+ return nil unless response['data']
+ response['data']['DSC_COD_ACCION']
+ end
+
+ def error_message_empty?(error_message)
+ empty?(error_message) || error_message == '[ ]'
+ end
+
+ def response_error(raw_response, options, action)
+ response = parse(raw_response)
+ rescue JSON::ParserError
+ unparsable_response(raw_response)
+ else
+ return Response.new(
+ false,
+ message_from(response, options, action),
+ response,
+ :test => test?,
+ :authorization => response['transactionUUID'],
+ :error_code => response['errorCode']
+ )
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from VisanetPeruGateway. Please contact VisanetPeruGateway if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/webpay.rb b/lib/active_merchant/billing/gateways/webpay.rb
index 07f8a82f500..29636897892 100644
--- a/lib/active_merchant/billing/gateways/webpay.rb
+++ b/lib/active_merchant/billing/gateways/webpay.rb
@@ -13,25 +13,23 @@ class WebpayGateway < StripeGateway
self.homepage_url = 'https://webpay.jp/'
self.display_name = 'WebPay'
- def authorize(money, credit_card, options = {})
- raise NotImplementedError.new
- end
-
- def capture(money, credit_card, options = {})
- raise NotImplementedError.new
+ def capture(money, authorization, options = {})
+ post = {}
+ add_amount(post, money, options)
+ add_application_fee(post, options)
+ commit(:post, "charges/#{CGI.escape(authorization)}/capture", post)
end
def refund(money, identification, options = {})
- post = {:amount => localized_amount(money)}
- commit_options = generate_meta(options)
-
+ post = {}
+ add_amount(post, money, options)
MultiResponse.run do |r|
- r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, commit_options) }
+ r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, options) }
return r unless options[:refund_fee_amount]
- r.process { fetch_application_fees(identification, commit_options) }
- r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r), commit_options) }
+ r.process { fetch_application_fees(identification, options) }
+ r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r), options) }
end
end
@@ -39,40 +37,59 @@ def refund_fee(identification, options, meta)
raise NotImplementedError.new
end
- def localized_amount(money, currency = self.default_currency)
- non_fractional_currency?(currency) ? (amount(money).to_f / 100).floor : amount(money)
+ def add_customer(post, creditcard, options)
+ post[:customer] = options[:customer] if options[:customer] && !creditcard.respond_to?(:number)
end
- def add_amount(post, money, options)
- post[:currency] = (options[:currency] || currency(money)).downcase
- post[:amount] = localized_amount(money, post[:currency].upcase)
+ def store(creditcard, options = {})
+ post = {}
+ add_creditcard(post, creditcard, options)
+ post[:description] = options[:description]
+ post[:email] = options[:email]
+
+ if options[:customer]
+ MultiResponse.run(:first) do |r|
+ r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/", post, options) }
+
+ return r unless options[:set_default] and r.success? and !r.params['id'].blank?
+
+ r.process { update_customer(options[:customer], :default_card => r.params['id']) }
+ end
+ else
+ commit(:post, 'customers', post, options)
+ end
+ end
+
+ def update(customer_id, creditcard, options = {})
+ post = {}
+ add_creditcard(post, creditcard, options)
+ commit(:post, "customers/#{CGI.escape(customer_id)}", post, options)
+ end
+
+ private
+
+ def create_post_for_auth_or_purchase(money, creditcard, options)
+ stripe_post = super
+ stripe_post[:description] ||= stripe_post.delete(:metadata).try(:[], :email)
+ stripe_post
end
def json_error(raw_response)
msg = 'Invalid response received from the WebPay API. Please contact support@webpay.jp if you continue to receive this message.'
msg += " (The raw response returned by the API was #{raw_response.inspect})"
{
- "error" => {
- "message" => msg
+ 'error' => {
+ 'message' => msg
}
}
end
def headers(options = {})
- @@ua ||= JSON.dump({
- :bindings_version => ActiveMerchant::VERSION,
- :lang => 'ruby',
- :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
- :platform => RUBY_PLATFORM,
- :publisher => 'active_merchant',
- :uname => (RUBY_PLATFORM =~ /linux|darwin/i ? `uname -a 2>/dev/null`.strip : nil)
- })
-
{
- "Authorization" => "Basic " + Base64.encode64(@api_key.to_s + ":").strip,
- "User-Agent" => "Webpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
- "X-Webpay-Client-User-Agent" => @@ua,
- "X-Webpay-Client-User-Metadata" => options[:meta].to_json
+ 'Authorization' => 'Basic ' + Base64.encode64(@api_key.to_s + ':').strip,
+ 'User-Agent' => "Webpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ 'X-Webpay-Client-User-Agent' => user_agent,
+ 'X-Webpay-Client-User-Metadata' => {:ip => options[:ip]}.to_json
}
end
end
diff --git a/lib/active_merchant/billing/gateways/wepay.rb b/lib/active_merchant/billing/gateways/wepay.rb
new file mode 100644
index 00000000000..9ec05da7f7f
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/wepay.rb
@@ -0,0 +1,237 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class WepayGateway < Gateway
+ self.test_url = 'https://stage.wepayapi.com/v2'
+ self.live_url = 'https://wepayapi.com/v2'
+
+ self.supported_countries = ['US', 'CA']
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
+ self.homepage_url = 'https://www.wepay.com/'
+ self.default_currency = 'USD'
+ self.display_name = 'WePay'
+
+ def initialize(options = {})
+ requires!(options, :client_id, :account_id, :access_token)
+ super(options)
+ end
+
+ def purchase(money, payment_method, options = {})
+ post = {}
+ if payment_method.is_a?(String)
+ MultiResponse.run do |r|
+ r.process { authorize_with_token(post, money, payment_method, options) }
+ r.process { capture(money, r.authorization, options) }
+ end
+ else
+ MultiResponse.run do |r|
+ r.process { store(payment_method, options) }
+ r.process { authorize_with_token(post, money, r.authorization, options) }
+ r.process { capture(money, r.authorization, options) }
+ end
+ end
+ end
+
+ def authorize(money, payment_method, options = {})
+ post = {}
+ if payment_method.is_a?(String)
+ authorize_with_token(post, money, payment_method, options)
+ else
+ MultiResponse.run do |r|
+ r.process { store(payment_method, options) }
+ r.process { authorize_with_token(post, money, r.authorization, options) }
+ end
+ end
+ end
+
+ def capture(money, identifier, options = {})
+ checkout_id, original_amount = split_authorization(identifier)
+
+ post = {}
+ post[:checkout_id] = checkout_id
+ if(money && (original_amount != amount(money)))
+ post[:amount] = amount(money)
+ end
+ commit('/checkout/capture', post, options)
+ end
+
+ def void(identifier, options = {})
+ post = {}
+ post[:checkout_id] = split_authorization(identifier).first
+ post[:cancel_reason] = (options[:description] || 'Void')
+ commit('/checkout/cancel', post, options)
+ end
+
+ def refund(money, identifier, options = {})
+ checkout_id, original_amount = split_authorization(identifier)
+
+ post = {}
+ post[:checkout_id] = checkout_id
+ if(money && (original_amount != amount(money)))
+ post[:amount] = amount(money)
+ end
+ post[:refund_reason] = (options[:description] || 'Refund')
+ post[:payer_email_message] = options[:payer_email_message] if options[:payer_email_message]
+ post[:payee_email_message] = options[:payee_email_message] if options[:payee_email_message]
+ commit('/checkout/refund', post, options)
+ end
+
+ def store(creditcard, options = {})
+ post = {}
+ post[:client_id] = @options[:client_id]
+ post[:user_name] = "#{creditcard.first_name} #{creditcard.last_name}"
+ post[:email] = options[:email] || 'unspecified@example.com'
+ post[:cc_number] = creditcard.number
+ post[:cvv] = creditcard.verification_value unless options[:recurring]
+ post[:expiration_month] = creditcard.month
+ post[:expiration_year] = creditcard.year
+
+ if(billing_address = (options[:billing_address] || options[:address]))
+ post[:address] = {}
+ post[:address]['address1'] = billing_address[:address1] if billing_address[:address1]
+ post[:address]['city'] = billing_address[:city] if billing_address[:city]
+ post[:address]['country'] = billing_address[:country] if billing_address[:country]
+ post[:address]['region'] = billing_address[:state] if billing_address[:state]
+ post[:address]['postal_code'] = billing_address[:zip]
+ end
+
+ if options[:recurring] == true
+ post[:client_secret] = @options[:client_secret]
+ commit('/credit_card/transfer', post, options)
+ else
+ post[:original_device] = options[:device_fingerprint] if options[:device_fingerprint]
+ post[:original_ip] = options[:ip] if options[:ip]
+ commit('/credit_card/create', post, options)
+ end
+ end
+
+ def supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((\\?"cc_number\\?":\\?")[^\\"]+(\\?"))i, '\1[FILTERED]\2').
+ gsub(%r((\\?"cvv\\?":\\?")[^\\"]+(\\?"))i, '\1[FILTERED]\2').
+ gsub(%r((Authorization: Bearer )\w+)i, '\1[FILTERED]\2')
+ end
+
+ private
+
+ def authorize_with_token(post, money, token, options)
+ add_token(post, token)
+ add_product_data(post, money, options)
+ commit('/checkout/create', post, options)
+ end
+
+ def add_product_data(post, money, options)
+ post[:account_id] = @options[:account_id]
+ post[:amount] = amount(money)
+ post[:short_description] = (options[:description] || 'Purchase')
+ post[:type] = (options[:type] || 'goods')
+ post[:currency] = (options[:currency] || currency(money))
+ post[:long_description] = options[:long_description] if options[:long_description]
+ post[:payer_email_message] = options[:payer_email_message] if options[:payer_email_message]
+ post[:payee_email_message] = options[:payee_email_message] if options[:payee_email_message]
+ post[:reference_id] = options[:order_id] if options[:order_id]
+ post[:unique_id] = options[:unique_id] if options[:unique_id]
+ post[:redirect_uri] = options[:redirect_uri] if options[:redirect_uri]
+ post[:callback_uri] = options[:callback_uri] if options[:callback_uri]
+ post[:fallback_uri] = options[:fallback_uri] if options[:fallback_uri]
+ post[:require_shipping] = options[:require_shipping] if options[:require_shipping]
+ post[:shipping_fee] = options[:shipping_fee] if options[:shipping_fee]
+ post[:charge_tax] = options[:charge_tax] if options[:charge_tax]
+ post[:mode] = options[:mode] if options[:mode]
+ post[:preapproval_id] = options[:preapproval_id] if options[:preapproval_id]
+ post[:prefill_info] = options[:prefill_info] if options[:prefill_info]
+ post[:funding_sources] = options[:funding_sources] if options[:funding_sources]
+ post[:payer_rbits] = options[:payer_rbits] if options[:payer_rbits]
+ post[:transaction_rbits] = options[:transaction_rbits] if options[:transaction_rbits]
+ add_fee(post, options)
+ end
+
+ def add_token(post, token)
+ payment_method = {}
+ payment_method[:type] = 'credit_card'
+ payment_method[:credit_card] = {
+ id: token,
+ auto_capture: false
+ }
+
+ post[:payment_method] = payment_method
+ end
+
+ def add_fee(post, options)
+ if options[:application_fee] || options[:fee_payer]
+ post[:fee] = {}
+ post[:fee][:app_fee] = options[:application_fee] if options[:application_fee]
+ post[:fee][:fee_payer] = options[:fee_payer] if options[:fee_payer]
+ end
+ end
+
+ def parse(response)
+ JSON.parse(response)
+ end
+
+ def commit(action, params, options={})
+ begin
+ response = parse(ssl_post(
+ ((test? ? test_url : live_url) + action),
+ params.to_json,
+ headers(options)
+ ))
+ rescue ResponseError => e
+ response = parse(e.response.body)
+ end
+
+ return Response.new(
+ success_from(response),
+ message_from(response),
+ response,
+ authorization: authorization_from(response, params),
+ test: test?
+ )
+ rescue JSON::ParserError
+ return unparsable_response(response)
+ end
+
+ def success_from(response)
+ (!response['error'])
+ end
+
+ def message_from(response)
+ (response['error'] ? response['error_description'] : 'Success')
+ end
+
+ def authorization_from(response, params)
+ return response['credit_card_id'].to_s if response['credit_card_id']
+
+ original_amount = response['amount'].nil? ? nil : sprintf('%0.02f', response['amount'])
+ [response['checkout_id'], original_amount].join('|')
+ end
+
+ def split_authorization(authorization)
+ auth, original_amount = authorization.to_s.split('|')
+ [auth, original_amount]
+ end
+
+ def unparsable_response(raw_response)
+ message = 'Invalid JSON response received from WePay. Please contact WePay support if you continue to receive this message.'
+ message += " (The raw response returned by the API was #{raw_response.inspect})"
+ return Response.new(false, message)
+ end
+
+ def headers(options)
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => "ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ 'Authorization' => "Bearer #{@options[:access_token]}"
+ }
+ headers['Api-Version'] = options[:version] if options[:version]
+ headers['Client-IP'] = options[:ip] if options[:ip]
+ headers['WePay-Risk-Token'] = options[:risk_token] if options[:risk_token]
+
+ headers
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/wirecard.rb b/lib/active_merchant/billing/gateways/wirecard.rb
index b1f84d536cc..21218896b6a 100644
--- a/lib/active_merchant/billing/gateways/wirecard.rb
+++ b/lib/active_merchant/billing/gateways/wirecard.rb
@@ -3,10 +3,7 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class WirecardGateway < Gateway
- # Test server location
self.test_url = 'https://c3-test.wirecard.com/secure/ssl-gateway'
-
- # Live server location
self.live_url = 'https://c3.wirecard.com/secure/ssl-gateway'
# The Namespaces are not really needed, because it just tells the System, that there's actually no namespace used.
@@ -29,57 +26,123 @@ class WirecardGateway < Gateway
# number 5551234 within area code 202 (country code 1).
VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/
- # The countries the gateway supports merchants from as 2 digit ISO country codes
+ self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ]
self.supported_countries = %w(AD CY GI IM MT RO CH AT DK GR IT MC SM TR BE EE HU LV NL SK GB BG FI IS LI NO SI VA FR IL LT PL ES CZ DE IE LU PT SE)
-
- # Wirecard supports all major credit and debit cards:
- # Visa, Mastercard, American Express, Diners Club,
- # JCB, Switch, VISA Carte Bancaire, Visa Electron and UATP cards.
- # They also support the latest anti-fraud systems such as Verified by Visa or Master Secure Code.
- self.supported_cardtypes = [
- :visa, :master, :american_express, :diners_club, :jcb, :switch
- ]
-
- # The homepage URL of the gateway
self.homepage_url = 'http://www.wirecard.com'
-
- # The name of the gateway
self.display_name = 'Wirecard'
-
- # The currency should normally be EUROs
self.default_currency = 'EUR'
-
- # 100 is 1.00 Euro
self.money_format = :cents
+ # Public: Create a new Wirecard gateway.
+ #
+ # options - A hash of options:
+ # :login - The username
+ # :password - The password
+ # :signature - The BusinessCaseSignature
def initialize(options = {})
- # verify that username and password are supplied
- requires!(options, :login, :password)
- # unfortunately Wirecard also requires a BusinessCaseSignature in the XML request
- requires!(options, :signature)
+ requires!(options, :login, :password, :signature)
super
end
- # Authorization
- def authorize(money, creditcard, options = {})
- options[:credit_card] = creditcard
+ # Authorization - the second parameter may be a CreditCard or
+ # a String which represents a GuWID reference to an earlier
+ # transaction. If a GuWID is given, rather than a CreditCard,
+ # then then the :recurring option will be forced to "Repeated"
+ def authorize(money, payment_method, options = {})
+ if payment_method.respond_to?(:number)
+ options[:credit_card] = payment_method
+ else
+ options[:preauthorization] = payment_method
+ end
commit(:preauthorization, money, options)
end
- # Capture Authorization
def capture(money, authorization, options = {})
options[:preauthorization] = authorization
commit(:capture, money, options)
end
- # Purchase
- def purchase(money, creditcard, options = {})
- options[:credit_card] = creditcard
+ # Purchase - the second parameter may be a CreditCard or
+ # a String which represents a GuWID reference to an earlier
+ # transaction. If a GuWID is given, rather than a CreditCard,
+ # then then the :recurring option will be forced to "Repeated"
+ def purchase(money, payment_method, options = {})
+ if payment_method.respond_to?(:number)
+ options[:credit_card] = payment_method
+ else
+ options[:preauthorization] = payment_method
+ end
commit(:purchase, money, options)
end
+ def void(identification, options = {})
+ options[:preauthorization] = identification
+ commit(:reversal, nil, options)
+ end
+
+ def refund(money, identification, options = {})
+ options[:preauthorization] = identification
+ commit(:bookback, money, options)
+ end
+
+ # Store card - Wirecard supports the notion of "Recurring
+ # Transactions" by allowing the merchant to provide a reference
+ # to an earlier transaction (the GuWID) rather than a credit
+ # card. A reusable reference (GuWID) can be obtained by sending
+ # a purchase or authorization transaction with the element
+ # "RECURRING_TRANSACTION/Type" set to "Initial". Subsequent
+ # transactions can then use the GuWID in place of a credit
+ # card by setting "RECURRING_TRANSACTION/Type" to "Repeated".
+ #
+ # This implementation of card store utilizes a Wirecard
+ # "Authorization Check" (a Preauthorization that is automatically
+ # reversed). It defaults to a check amount of "100" (i.e.
+ # $1.00) but this can be overriden (see below).
+ #
+ # IMPORTANT: In order to reuse the stored reference, the
+ # +authorization+ from the response should be saved by
+ # your application code.
+ #
+ # ==== Options specific to +store+
+ #
+ # * :amount -- The amount, in cents, that should be
+ # "validated" by the Authorization Check. This amount will
+ # be reserved and then reversed. Default is 100.
+ #
+ # Note: This is not the only way to achieve a card store
+ # operation at Wirecard. Any +purchase+ or +authorize+
+ # can be sent with +options[:recurring] = 'Initial'+ to make
+ # the returned authorization/GuWID usable in later transactions
+ # with +options[:recurring] = 'Repeated'+.
+ def store(creditcard, options = {})
+ options[:credit_card] = creditcard
+ options[:recurring] = 'Initial'
+ money = options.delete(:amount) || 100
+ # Amex does not support authorization_check
+ if creditcard.brand == 'american_express'
+ commit(:preauthorization, money, options)
+ else
+ commit(:authorization_check, money, options)
+ end
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(()\d+()), '\1[FILTERED]\2').
+ gsub(%r(()[^<]+()), '\1[FILTERED]\2')
+ end
+
private
+ def clean_description(description)
+ description.to_s.slice(0, 32).encode('US-ASCII', invalid: :replace, undef: :replace, replace: '?')
+ end
+
def prepare_options_hash(options)
result = @options.merge(options)
setup_address_hash!(result)
@@ -95,6 +158,13 @@ def setup_address_hash!(options)
options[:billing_address][:email] = options[:email] if options[:email]
end
+ # If a GuWID (string-based reference) is passed rather than a
+ # credit card, then the :recurring type needs to be forced to
+ # "Repeated"
+ def setup_recurring_flag(options)
+ options[:recurring] = 'Repeated' if options[:preauthorization].present?
+ end
+
# Contact WireCard, make the XML request, and parse the
# reply into a Response object
def commit(action, money, options)
@@ -105,19 +175,19 @@ def commit(action, money, options)
response = parse(ssl_post(test? ? self.test_url : self.live_url, request, headers))
# Pending Status also means Acknowledged (as stated in their specification)
- success = response[:FunctionResult] == "ACK" || response[:FunctionResult] == "PENDING"
+ success = response[:FunctionResult] == 'ACK' || response[:FunctionResult] == 'PENDING'
message = response[:Message]
authorization = response[:GuWID]
Response.new(success, message, response,
:test => test?,
:authorization => authorization,
- :avs_result => { :code => response[:avsCode] },
- :cvv_result => response[:cvCode]
+ :avs_result => { :code => avs_code(response, options) },
+ :cvv_result => response[:CVCResponseCode]
)
rescue ResponseError => e
- if e.response.code == "401"
- return Response.new(false, "Invalid Login")
+ if e.response.code == '401'
+ return Response.new(false, 'Invalid Login')
else
raise
end
@@ -131,7 +201,7 @@ def build_request(action, money, options)
xml.instruct!
xml.tag! 'WIRECARD_BXML' do
xml.tag! 'W_REQUEST' do
- xml.tag! 'W_JOB' do
+ xml.tag! 'W_JOB' do
xml.tag! 'JobID', ''
# UserID for this transaction
xml.tag! 'BusinessCaseSignature', options[:signature] || options[:login]
@@ -148,25 +218,36 @@ def add_transaction_data(xml, money, options)
options[:order_id] ||= generate_unique_id
xml.tag! "FNC_CC_#{options[:action].to_s.upcase}" do
- xml.tag! 'FunctionID', options[:description].to_s.slice(0,32)
+ xml.tag! 'FunctionID', clean_description(options[:description])
xml.tag! 'CC_TRANSACTION' do
xml.tag! 'TransactionID', options[:order_id]
+ xml.tag! 'CommerceType', options[:commerce_type] if options[:commerce_type]
case options[:action]
- when :preauthorization, :purchase
+ when :preauthorization, :purchase, :authorization_check
+ setup_recurring_flag(options)
add_invoice(xml, money, options)
- add_creditcard(xml, options[:credit_card])
+
+ if options[:credit_card]
+ add_creditcard(xml, options[:credit_card])
+ else
+ xml.tag! 'GuWID', options[:preauthorization]
+ end
+
add_address(xml, options[:billing_address])
- when :capture
+ when :capture, :bookback
+ xml.tag! 'GuWID', options[:preauthorization]
+ add_amount(xml, money, options)
+ when :reversal
xml.tag! 'GuWID', options[:preauthorization]
- add_amount(xml, money)
end
+ add_customer_data(xml, options)
end
end
end
# Includes the payment (amount, currency, country) to the transaction-xml
def add_invoice(xml, money, options)
- add_amount(xml, money)
+ add_amount(xml, money, options)
xml.tag! 'Currency', options[:currency] || currency(money)
xml.tag! 'CountryCode', options[:billing_address][:country]
xml.tag! 'RECURRING_TRANSACTION' do
@@ -175,13 +256,13 @@ def add_invoice(xml, money, options)
end
# Include the amount in the transaction-xml
- def add_amount(xml, money)
- xml.tag! 'Amount', amount(money)
+ def add_amount(xml, money, options)
+ xml.tag! 'Amount', localized_amount(money, options[:currency] || currency(money))
end
# Includes the credit-card data to the transaction-xml
def add_creditcard(xml, creditcard)
- raise "Creditcard must be supplied!" if creditcard.nil?
+ raise 'Creditcard must be supplied!' if creditcard.nil?
xml.tag! 'CREDIT_CARD_DATA' do
xml.tag! 'CreditCardNumber', creditcard.number
xml.tag! 'CVC2', creditcard.verification_value
@@ -229,8 +310,8 @@ def parse(xml)
xml = REXML::Document.new(xml)
if root = REXML::XPath.first(xml, "#{basepath}/W_JOB")
parse_response(response, root)
- elsif root = REXML::XPath.first(xml, "//ERROR")
- parse_error(response, root)
+ elsif root = REXML::XPath.first(xml, '//ERROR')
+ parse_error_only_response(response, root)
else
response[:Message] = "No valid XML response message received. \
Propably wrong credentials supplied with HTTP header."
@@ -239,31 +320,49 @@ def parse(xml)
response
end
- # Parse the Element which containts all important information
+ def parse_error_only_response(response, root)
+ error_code = REXML::XPath.first(root, 'Number')
+ response[:ErrorCode] = error_code.text if error_code
+ response[:Message] = parse_error(root)
+ end
+
+ # Parse the Element which contains all important information
def parse_response(response, root)
status = nil
- # get the root element for this Transaction
+
root.elements.to_a.each do |node|
if node.name =~ /FNC_CC_/
- status = REXML::XPath.first(node, "CC_TRANSACTION/PROCESSING_STATUS")
+ status = REXML::XPath.first(node, 'CC_TRANSACTION/PROCESSING_STATUS')
end
end
- message = ""
+
+ message = ''
if status
if info = status.elements['Info']
message << info.text
end
- # Get basic response information
+
status.elements.to_a.each do |node|
- response[node.name.to_sym] = (node.text || '').strip
+ if node.elements.size == 0
+ response[node.name.to_sym] = (node.text || '').strip
+ else
+ node.elements.each do |childnode|
+ name = "#{node.name}_#{childnode.name}"
+ response[name.to_sym] = (childnode.text || '').strip
+ end
+ end
end
+
+ error_code = REXML::XPath.first(status, 'ERROR/Number')
+ response['ErrorCode'] = error_code.text if error_code
end
+
parse_error(root, message)
response[:Message] = message
end
# Parse a generic error response from the gateway
- def parse_error(root, message = "")
+ def parse_error(root, message = '')
# Get errors if available and append them to the message
errors = errors_to_string(root)
unless errors.strip.blank?
@@ -278,7 +377,7 @@ def parse_error(root, message = "")
def errors_to_string(root)
# Get context error messages (can be 0..*)
errors = []
- REXML::XPath.each(root, "//ERROR") do |error_elem|
+ REXML::XPath.each(root, '//ERROR') do |error_elem|
error = {}
error[:Advice] = []
error[:Message] = error_elem.elements['Message'].text
@@ -290,7 +389,7 @@ def errors_to_string(root)
# Convert all messages to a single string
string = ''
errors.each do |error|
- string << error[:Message]
+ string << error[:Message] if error[:Message]
error[:Advice].each_with_index do |advice, index|
string << ' (' if index == 0
string << "#{index+1}. #{advice}"
@@ -301,13 +400,33 @@ def errors_to_string(root)
string
end
+ # Amex have different AVS response codes
+ AMEX_TRANSLATED_AVS_CODES = {
+ 'A' => 'B', # CSC and Address Matched
+ 'F' => 'D', # All Data Matched
+ 'N' => 'I', # CSC Match
+ 'U' => 'U', # Data Not Checked
+ 'Y' => 'D', # All Data Matched
+ 'Z' => 'P', # CSC and Postcode Matched
+ }
+
+ # Amex have different AVS response codes to visa etc
+ def avs_code(response, options)
+ if response.has_key?(:AVS_ProviderResultCode)
+ if options[:credit_card].present? && ActiveMerchant::Billing::CreditCard.brand?(options[:credit_card].number) == 'american_express'
+ AMEX_TRANSLATED_AVS_CODES[response[:AVS_ProviderResultCode]]
+ else
+ response[:AVS_ProviderResultCode]
+ end
+ end
+ end
+
# Encode login and password in Base64 to supply as HTTP header
# (for http basic authentication)
def encoded_credentials
credentials = [@options[:login], @options[:password]].join(':')
- "Basic " << Base64.encode64(credentials).strip
+ 'Basic ' << Base64.encode64(credentials).strip
end
end
end
end
-
diff --git a/lib/active_merchant/billing/gateways/world_net.rb b/lib/active_merchant/billing/gateways/world_net.rb
new file mode 100644
index 00000000000..70699b1a139
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/world_net.rb
@@ -0,0 +1,344 @@
+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{(\d{6})\d+(\d{4})}, '\1...\2').
+ gsub(%r{()\d+( 'VISA-SSL',
@@ -18,8 +20,31 @@ class WorldpayGateway < Gateway
'american_express' => 'AMEX-SSL',
'jcb' => 'JCB-SSL',
'maestro' => 'MAESTRO-SSL',
- 'laser' => 'LASER-SSL',
- 'diners_club' => 'DINERS-SSL'
+ 'diners_club' => 'DINERS-SSL',
+ 'elo' => 'ELO-SSL',
+ 'naranja' => 'NARANJA-SSL',
+ 'cabal' => 'CABAL-SSL',
+ 'unknown' => 'CARD-SSL'
+ }
+
+ AVS_CODE_MAP = {
+ 'A' => 'M', # Match
+ 'B' => 'P', # Postcode matches, address not verified
+ 'C' => 'Z', # Postcode matches, address does not match
+ 'D' => 'B', # Address matched; postcode not checked
+ 'E' => 'I', # Address and postal code not checked
+ 'F' => 'A', # Address matches, postcode does not match
+ 'G' => 'C', # Address does not match, postcode not checked
+ 'H' => 'I', # Address and postcode not provided
+ 'I' => 'C', # Address not checked postcode does not match
+ 'J' => 'C', # Address and postcode does not match
+ }
+
+ CVC_CODE_MAP = {
+ 'A' => 'M', # CVV matches
+ 'B' => 'P', # Not provided
+ 'C' => 'P', # Not checked
+ 'D' => 'N', # Does not match
}
def initialize(options = {})
@@ -29,68 +54,117 @@ def initialize(options = {})
def purchase(money, payment_method, options = {})
MultiResponse.run do |r|
- r.process{authorize(money, payment_method, options)}
- r.process{capture(money, r.authorization, options.merge(:authorization_validated => true))}
+ r.process { authorize(money, payment_method, options) }
+ r.process { capture(money, r.authorization, options.merge(:authorization_validated => true)) }
end
end
def authorize(money, payment_method, options = {})
requires!(options, :order_id)
- authorize_request(money, payment_method, options)
+ payment_details = payment_details_from(payment_method)
+ authorize_request(money, payment_method, payment_details.merge(options))
end
def capture(money, authorization, options = {})
+ authorization = order_id_from_authorization(authorization.to_s)
MultiResponse.run do |r|
- r.process{inquire_request(authorization, options, "AUTHORISED")} unless options[:authorization_validated]
+ r.process { inquire_request(authorization, options, 'AUTHORISED') } unless options[:authorization_validated]
if r.params
authorization_currency = r.params['amount_currency_code']
options = options.merge(:currency => authorization_currency) if authorization_currency.present?
end
- r.process{capture_request(money, authorization, options)}
+ r.process { capture_request(money, authorization, options) }
end
end
def void(authorization, options = {})
+ authorization = order_id_from_authorization(authorization.to_s)
MultiResponse.run do |r|
- r.process{inquire_request(authorization, options, "AUTHORISED")}
- r.process{cancel_request(authorization, options)}
+ r.process { inquire_request(authorization, options, 'AUTHORISED') } unless options[:authorization_validated]
+ r.process { cancel_request(authorization, options) }
end
end
def refund(money, authorization, options = {})
- MultiResponse.run do |r|
- r.process{inquire_request(authorization, options, "CAPTURED", "SETTLED")}
- r.process{refund_request(money, authorization, options)}
+ authorization = order_id_from_authorization(authorization.to_s)
+ response = MultiResponse.run do |r|
+ r.process { inquire_request(authorization, options, 'CAPTURED', 'SETTLED', 'SETTLED_BY_MERCHANT') }
+ r.process { refund_request(money, authorization, options) }
+ end
+
+ return response if response.success?
+ return response unless options[:force_full_refund_if_unsettled]
+
+ void(authorization, options) if response.params['last_event'] == 'AUTHORISED'
+ end
+
+ # Credits only function on a Merchant ID/login/profile flagged for Payouts
+ # aka Credit Fund Transfers (CFT), whereas normal purchases, refunds,
+ # and other transactions should be performed on a normal eCom-flagged
+ # merchant ID.
+ def credit(money, payment_method, options = {})
+ payment_details = payment_details_from(payment_method)
+ credit_request(money, payment_method, payment_details.merge(:credit => true, **options))
+ end
+
+ def verify(payment_method, options={})
+ MultiResponse.run(:use_first_response) do |r|
+ r.process { authorize(100, payment_method, options) }
+ r.process(:ignore_result) { void(r.authorization, options.merge(:authorization_validated => true)) }
end
end
+ def store(credit_card, options={})
+ requires!(options, :customer)
+ store_request(credit_card, options)
+ end
+
+ def supports_scrubbing
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
+ gsub(%r(()\d+()), '\1[FILTERED]\2').
+ gsub(%r(()[^<]+()), '\1[FILTERED]\2')
+ end
+
private
def authorize_request(money, payment_method, options)
- commit('authorize', build_authorization_request(money, payment_method, options), "AUTHORISED")
+ commit('authorize', build_authorization_request(money, payment_method, options), 'AUTHORISED', options)
end
def capture_request(money, authorization, options)
- commit('capture', build_capture_request(money, authorization, options), :ok)
+ commit('capture', build_capture_request(money, authorization, options), :ok, options)
end
def cancel_request(authorization, options)
- commit('cancel', build_void_request(authorization, options), :ok)
+ commit('cancel', build_void_request(authorization, options), :ok, options)
end
def inquire_request(authorization, options, *success_criteria)
- commit('inquiry', build_order_inquiry_request(authorization, options), *success_criteria)
+ commit('inquiry', build_order_inquiry_request(authorization, options), *success_criteria, options)
end
def refund_request(money, authorization, options)
- commit('refund', build_refund_request(money, authorization, options), :ok)
+ commit('refund', build_refund_request(money, authorization, options), :ok, options)
+ end
+
+ def credit_request(money, payment_method, options)
+ commit('credit', build_authorization_request(money, payment_method, options), :ok, 'SENT_FOR_REFUND', options)
+ end
+
+ def store_request(credit_card, options)
+ commit('store', build_store_request(credit_card, options), options)
end
def build_request
xml = Builder::XmlMarkup.new :indent => 2
- xml.instruct! :xml, :encoding => 'ISO-8859-1'
- xml.declare! :DOCTYPE, :paymentService, :PUBLIC, "-//WorldPay//DTD WorldPay PaymentService v1//EN", "http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"
- xml.tag! 'paymentService', 'version' => "1.4", 'merchantCode' => @options[:login] do
+ xml.instruct! :xml, :encoding => 'UTF-8'
+ xml.declare! :DOCTYPE, :paymentService, :PUBLIC, '-//WorldPay//DTD WorldPay PaymentService v1//EN', 'http://dtd.worldpay.com/paymentService_v1.dtd'
+ xml.tag! 'paymentService', 'version' => '1.4', 'merchantCode' => @options[:login] do
yield xml
end
xml.target!
@@ -117,8 +191,8 @@ def build_order_inquiry_request(authorization, options)
def build_authorization_request(money, payment_method, options)
build_request do |xml|
xml.tag! 'submit' do
- xml.tag! 'order', {'orderCode' => options[:order_id], 'installationId' => @options[:inst_id]}.reject{|_,v| !v} do
- xml.description(options[:description].blank? ? "Purchase" : options[:description])
+ xml.tag! 'order', order_tag_attributes(options) do
+ xml.description(options[:description].blank? ? 'Purchase' : options[:description])
add_amount(xml, money, options)
if options[:order_content]
xml.tag! 'orderContent' do
@@ -126,11 +200,22 @@ def build_authorization_request(money, payment_method, options)
end
end
add_payment_method(xml, money, payment_method, options)
+ add_shopper(xml, options)
+ if options[:hcg_additional_data]
+ add_hcg_additional_data(xml, options)
+ end
+ if options[:instalments]
+ add_instalments_data(xml, options)
+ end
end
end
end
end
+ def order_tag_attributes(options)
+ { 'orderCode' => options[:order_id], 'installationId' => options[:inst_id] || @options[:inst_id] }.reject { |_, v| !v }
+ end
+
def build_capture_request(money, authorization, options)
build_order_modify_request(authorization) do |xml|
xml.tag! 'capture' do
@@ -150,108 +235,264 @@ def build_void_request(authorization, options)
def build_refund_request(money, authorization, options)
build_order_modify_request(authorization) do |xml|
xml.tag! 'refund' do
- add_amount(xml, money, options.merge(:debit_credit_indicator => "credit"))
+ add_amount(xml, money, options.merge(:debit_credit_indicator => 'credit'))
+ end
+ end
+ end
+
+ def build_store_request(credit_card, options)
+ build_request do |xml|
+ xml.tag! 'submit' do
+ xml.tag! 'paymentTokenCreate' do
+ add_authenticated_shopper_id(xml, options)
+ xml.tag! 'createToken'
+ xml.tag! 'paymentInstrument' do
+ xml.tag! 'cardDetails' do
+ add_card(xml, credit_card, options)
+ end
+ end
+ end
end
end
end
def add_amount(xml, money, options)
currency = options[:currency] || currency(money)
- amount = localized_amount(money, currency)
amount_hash = {
- :value => amount,
+ :value => localized_amount(money, currency),
'currencyCode' => currency,
- 'exponent' => 2
+ 'exponent' => currency_exponent(currency)
}
if options[:debit_credit_indicator]
- amount_hash.merge!('debitCreditIndicator' => options[:debit_credit_indicator])
+ amount_hash['debitCreditIndicator'] = options[:debit_credit_indicator]
end
xml.tag! 'amount', amount_hash
end
def add_payment_method(xml, amount, payment_method, options)
- if payment_method.is_a?(String)
- xml.tag! 'payAsOrder', 'orderCode' => payment_method do
- add_amount(xml, amount, options)
+ if options[:payment_type] == :pay_as_order
+ if options[:merchant_code]
+ xml.tag! 'payAsOrder', 'orderCode' => payment_method, 'merchantCode' => options[:merchant_code] do
+ add_amount(xml, amount, options)
+ end
+ else
+ xml.tag! 'payAsOrder', 'orderCode' => payment_method do
+ add_amount(xml, amount, options)
+ end
end
else
- xml.tag! 'paymentDetails' do
- xml.tag! CARD_CODES[card_brand(payment_method)] do
- xml.tag! 'cardNumber', payment_method.number
- xml.tag! 'expiryDate' do
- xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits)
+ xml.tag! 'paymentDetails', credit_fund_transfer_attribute(options) do
+ if options[:payment_type] == :token
+ xml.tag! 'TOKEN-SSL', 'tokenScope' => options[:token_scope] do
+ xml.tag! 'paymentTokenID', options[:token_id]
end
+ else
+ xml.tag! card_code_for(payment_method) do
+ add_card(xml, payment_method, options)
+ end
+ end
+ add_stored_credential_options(xml, options)
+ if options[:ip] && options[:session_id]
+ xml.tag! 'session', 'shopperIPAddress' => options[:ip], 'id' => options[:session_id]
+ else
+ xml.tag! 'session', 'shopperIPAddress' => options[:ip] if options[:ip]
+ xml.tag! 'session', 'id' => options[:session_id] if options[:session_id]
+ end
- xml.tag! 'cardHolderName', payment_method.name
- xml.tag! 'cvc', payment_method.verification_value
-
- add_address(xml, 'cardAddress', (options[:billing_address] || options[:address]))
+ if three_d_secure = options[:three_d_secure]
+ add_three_d_secure(three_d_secure, xml)
end
end
end
end
- def add_address(xml, element, address)
- return if address.nil?
+ def add_three_d_secure(three_d_secure, xml)
+ xml.tag! 'info3DSecure' do
+ xml.tag! 'threeDSVersion', three_d_secure[:version]
+ if three_d_secure[:version] =~ /^2/
+ xml.tag! 'dsTransactionId', three_d_secure[:ds_transaction_id]
+ else
+ xml.tag! 'xid', three_d_secure[:xid]
+ end
+ xml.tag! 'cavv', three_d_secure[:cavv]
+ xml.tag! 'eci', three_d_secure[:eci]
+ end
+ end
+
+ def add_card(xml, payment_method, options)
+ xml.tag! 'cardNumber', payment_method.number
+ xml.tag! 'expiryDate' do
+ xml.tag! 'date', 'month' => format(payment_method.month, :two_digits), 'year' => format(payment_method.year, :four_digits)
+ end
+
+ xml.tag! 'cardHolderName', options[:execute_threed] ? '3D' : payment_method.name
+ xml.tag! 'cvc', payment_method.verification_value
+
+ add_address(xml, (options[:billing_address] || options[:address]))
+ end
+
+ def add_stored_credential_options(xml, options={})
+ if options[:stored_credential]
+ add_stored_credential_using_normalized_fields(xml, options)
+ else
+ add_stored_credential_using_gateway_specific_fields(xml, options)
+ end
+ end
+
+ def add_stored_credential_using_normalized_fields(xml, options)
+ if options[:stored_credential][:initial_transaction]
+ xml.tag! 'storedCredentials', 'usage' => 'FIRST'
+ else
+ reason = case options[:stored_credential][:reason_type]
+ when 'installment' then 'INSTALMENT'
+ when 'recurring' then 'RECURRING'
+ when 'unscheduled' then 'UNSCHEDULED'
+ end
+
+ xml.tag! 'storedCredentials', 'usage' => 'USED', 'merchantInitiatedReason' => reason do
+ xml.tag! 'schemeTransactionIdentifier', options[:stored_credential][:network_transaction_id] if options[:stored_credential][:network_transaction_id]
+ end
+ end
+ end
+
+ def add_stored_credential_using_gateway_specific_fields(xml, options)
+ return unless options[:stored_credential_usage]
- xml.tag! element do
+ if options[:stored_credential_initiated_reason]
+ xml.tag! 'storedCredentials', 'usage' => options[:stored_credential_usage], 'merchantInitiatedReason' => options[:stored_credential_initiated_reason] do
+ xml.tag! 'schemeTransactionIdentifier', options[:stored_credential_transaction_id] if options[:stored_credential_transaction_id]
+ end
+ else
+ xml.tag! 'storedCredentials', 'usage' => options[:stored_credential_usage]
+ end
+ end
+
+ def add_shopper(xml, options)
+ return unless options[:execute_threed] || options[:email] || options[:customer]
+ xml.tag! 'shopper' do
+ xml.tag! 'shopperEmailAddress', options[:email] if options[:email]
+ add_authenticated_shopper_id(xml, options)
+ xml.tag! 'browser' do
+ xml.tag! 'acceptHeader', options[:accept_header]
+ xml.tag! 'userAgentHeader', options[:user_agent]
+ end
+ end
+ end
+
+ def add_authenticated_shopper_id(xml, options)
+ xml.tag!('authenticatedShopperID', options[:customer]) if options[:customer]
+ end
+
+ def add_address(xml, address)
+ return unless address
+
+ address = address_with_defaults(address)
+
+ xml.tag! 'cardAddress' do
xml.tag! 'address' do
if m = /^\s*([^\s]+)\s+(.+)$/.match(address[:name])
xml.tag! 'firstName', m[1]
xml.tag! 'lastName', m[2]
end
- if m = /^\s*(\d+)\s+(.+)$/.match(address[:address1])
- xml.tag! 'street', m[2]
- house_number = m[1]
- else
- xml.tag! 'street', address[:address1]
- end
- xml.tag! 'houseName', address[:address2] if address[:address2]
- xml.tag! 'houseNumber', house_number if house_number.present?
- xml.tag! 'postalCode', (address[:zip].present? ? address[:zip] : "0000")
- xml.tag! 'city', address[:city] if address[:city]
- xml.tag! 'state', (address[:state].present? ? address[:state] : 'N/A')
+ xml.tag! 'address1', address[:address1]
+ xml.tag! 'address2', address[:address2] if address[:address2]
+ xml.tag! 'postalCode', address[:zip]
+ xml.tag! 'city', address[:city]
+ xml.tag! 'state', address[:state]
xml.tag! 'countryCode', address[:country]
xml.tag! 'telephoneNumber', address[:phone] if address[:phone]
end
end
end
+ def add_hcg_additional_data(xml, options)
+ xml.tag! 'hcgAdditionalData' do
+ options[:hcg_additional_data].each do |k, v|
+ xml.tag! 'param', {name: k.to_s}, v
+ end
+ end
+ end
+
+ def add_instalments_data(xml, options)
+ xml.tag! 'thirdPartyData' do
+ xml.tag! 'instalments', options[:instalments]
+ xml.tag! 'cpf', options[:cpf] if options[:cpf]
+ end
+ end
+
+ def address_with_defaults(address)
+ address ||= {}
+ address.delete_if { |_, v| v.blank? }
+ address.reverse_merge!(default_address)
+ end
+
+ def default_address
+ {
+ address1: 'N/A',
+ zip: '0000',
+ city: 'N/A',
+ state: 'N/A',
+ country: 'US'
+ }
+ end
+
def parse(action, xml)
parse_element({:action => action}, REXML::Document.new(xml))
end
def parse_element(raw, node)
+ node_name = node.name.underscore
node.attributes.each do |k, v|
- raw["#{node.name.underscore}_#{k.underscore}".to_sym] = v
+ raw["#{node_name}_#{k.underscore}".to_sym] = v
end
if node.has_elements?
- raw[node.name.underscore.to_sym] = true unless node.name.blank?
- node.elements.each{|e| parse_element(raw, e) }
+ raw[node_name.to_sym] = true unless node.name.blank?
+ node.elements.each { |e| parse_element(raw, e) }
+ elsif node.children.count > 1
+ raw[node_name.to_sym] = node.children.join(' ').strip
else
- raw[node.name.underscore.to_sym] = node.text unless node.text.nil?
+ raw[node_name.to_sym] = node.text unless node.text.nil?
end
raw
end
- def commit(action, request, *success_criteria)
- xmr = ssl_post(url, request, 'Content-Type' => 'text/xml', 'Authorization' => encoded_credentials)
- raw = parse(action, xmr)
- success, message = success_and_message_from(raw, success_criteria)
+ def headers(options)
+ headers = {
+ 'Content-Type' => 'text/xml',
+ 'Authorization' => encoded_credentials
+ }
+ if options[:cookie]
+ headers['Cookie'] = options[:cookie] if options[:cookie]
+ end
+ headers
+ end
+
+ def commit(action, request, *success_criteria, options)
+ xml = ssl_post(url, request, headers(options))
+ raw = parse(action, xml)
+ if options[:execute_threed]
+ raw[:cookie] = @cookie
+ raw[:session_id] = options[:session_id]
+ end
+ success = success_from(action, raw, success_criteria)
+ message = message_from(success, raw, success_criteria)
Response.new(
success,
message,
raw,
- :authorization => authorization_from(raw),
- :test => test?)
-
+ :authorization => authorization_from(action, raw, options),
+ :error_code => error_code_from(success, raw),
+ :test => test?,
+ :avs_result => AVSResult.new(code: AVS_CODE_MAP[raw[:avs_result_code_description]]),
+ :cvv_result => CVVResult.new(CVC_CODE_MAP[raw[:cvc_result_code_description]])
+ )
rescue ActiveMerchant::ResponseError => e
- if e.response.code.to_s == "401"
- return Response.new(false, "Invalid credentials", {}, :test => test?)
+ if e.response.code.to_s == '401'
+ return Response.new(false, 'Invalid credentials', {}, :test => test?)
else
raise e
end
@@ -261,42 +502,132 @@ def url
test? ? self.test_url : self.live_url
end
+ # Override the regular handle response so we can access the headers
+ # Set-Cookie value is needed for 3DS transactions
+ def handle_response(response)
+ case response.code.to_i
+ when 200...300
+ @cookie = response['Set-Cookie']
+ response.body
+ else
+ raise ResponseError.new(response)
+ end
+ end
+
+ def success_from(action, raw, success_criteria)
+ success_criteria_success?(raw, success_criteria) || action_success?(action, raw)
+ end
+
+ def message_from(success, raw, success_criteria)
+ return 'SUCCESS' if success
+ raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria)
+ end
+
# success_criteria can be:
# - a string or an array of strings (if one of many responses)
# - An array of strings if one of many responses could be considered a
# success.
- def success_and_message_from(raw, success_criteria)
- success = (success_criteria.include?(raw[:last_event]) || raw[:ok].present?)
- if success
- message = "SUCCESS"
+ def success_criteria_success?(raw, success_criteria)
+ success_criteria.include?(raw[:last_event]) || raw[:ok].present?
+ end
+
+ def action_success?(action, raw)
+ case action
+ when 'store'
+ raw[:token].present?
else
- message = (raw[:iso8583_return_code_description] || raw[:error] || required_status_message(raw, success_criteria))
+ false
end
+ end
- [ success, message ]
+ def error_code_from(success, raw)
+ unless success == 'SUCCESS'
+ raw[:iso8583_return_code_code] || raw[:error_code] || nil
+ end
end
def required_status_message(raw, success_criteria)
if(!success_criteria.include?(raw[:last_event]))
- "A transaction status of #{success_criteria.collect{|c| "'#{c}'"}.join(" or ")} is required."
+ "A transaction status of #{success_criteria.collect { |c| "'#{c}'" }.join(" or ")} is required."
+ end
+ end
+
+ def authorization_from(action, raw, options)
+ order_id = order_id_from(raw)
+
+ case action
+ when 'store'
+ authorization_from_token_details(
+ order_id: order_id,
+ token_id: raw[:payment_token_id],
+ token_scope: 'shopper',
+ customer: options[:customer]
+ )
+ else
+ order_id
end
end
- def authorization_from(raw)
- pair = raw.detect{|k,v| k.to_s =~ /_order_code$/}
+ def order_id_from(raw)
+ pair = raw.detect { |k, v| k.to_s =~ /_order_code$/ }
(pair ? pair.last : nil)
end
+ def authorization_from_token_details(options={})
+ [options[:order_id], options[:token_id], options[:token_scope], options[:customer]].join('|')
+ end
+
+ def order_id_from_authorization(authorization)
+ token_details_from_authorization(authorization)[:order_id]
+ end
+
+ def token_details_from_authorization(authorization)
+ order_id, token_id, token_scope, customer = authorization.split('|')
+
+ token_details = {}
+ token_details[:order_id] = order_id if order_id.present?
+ token_details[:token_id] = token_id if token_id.present?
+ token_details[:token_scope] = token_scope if token_scope.present?
+ token_details[:customer] = customer if customer.present?
+
+ token_details
+ end
+
+ def payment_details_from(payment_method)
+ payment_details = {}
+ if payment_method.respond_to?(:number)
+ payment_details[:payment_type] = :credit
+ else
+ token_details = token_details_from_authorization(payment_method)
+ payment_details.merge!(token_details)
+ if token_details.has_key?(:token_id)
+ payment_details[:payment_type] = :token
+ else
+ payment_details[:payment_type] = :pay_as_order
+ end
+ end
+
+ payment_details
+ end
+
+ def credit_fund_transfer_attribute(options)
+ return unless options[:credit]
+ {'action' => 'REFUND'}
+ end
+
def encoded_credentials
credentials = "#{@options[:login]}:#{@options[:password]}"
"Basic #{[credentials].pack('m').strip}"
end
- def localized_amount(money, currency)
- amount = amount(money)
- return amount unless CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
+ def currency_exponent(currency)
+ return 0 if non_fractional_currency?(currency)
+ return 3 if three_decimal_currency?(currency)
+ return 2
+ end
- amount.to_i / 100 * 100
+ def card_code_for(payment_method)
+ CARD_CODES[card_brand(payment_method)] || CARD_CODES['unknown']
end
end
end
diff --git a/lib/active_merchant/billing/gateways/worldpay_online_payments.rb b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb
new file mode 100644
index 00000000000..d3a03ffbeec
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/worldpay_online_payments.rb
@@ -0,0 +1,215 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class WorldpayOnlinePaymentsGateway < Gateway
+ self.live_url = 'https://api.worldpay.com/v1/'
+
+ self.default_currency = 'GBP'
+
+ self.money_format = :cents
+
+ self.supported_countries = %w(HK US GB BE CH CZ DE DK ES FI FR GR HU IE IT LU MT NL NO PL PT SE SG TR)
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro]
+
+ self.homepage_url = 'http://online.worldpay.com'
+ self.display_name = 'Worldpay Online Payments'
+
+ def initialize(options={})
+ requires!(options, :client_key, :service_key)
+ @client_key = options[:client_key]
+ @service_key = options[:service_key]
+ super
+ end
+
+ def authorize(money, credit_card, options={})
+ response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value)
+ if response.success?
+ options[:authorizeOnly] = true
+ post = create_post_for_auth_or_purchase(response.authorization, money, options)
+ response = commit(:post, 'orders', post, {}, 'authorize')
+ end
+ response
+ end
+
+ def capture(money, authorization, options={})
+ if authorization
+ commit(:post, "orders/#{CGI.escape(authorization)}/capture", {'captureAmount'=>money}, options, 'capture')
+ else
+ Response.new(false,
+ 'FAILED',
+ 'FAILED',
+ :test => test?,
+ :authorization => false,
+ :avs_result => {},
+ :cvv_result => {},
+ :error_code => false
+ )
+ end
+ end
+
+ def purchase(money, credit_card, options={})
+ response = create_token(true, credit_card.first_name+' '+credit_card.last_name, credit_card.month, credit_card.year, credit_card.number, credit_card.verification_value)
+ if response.success?
+ post = create_post_for_auth_or_purchase(response.authorization, money, options)
+ response = commit(:post, 'orders', post, options, 'purchase')
+ end
+ response
+ end
+
+ def refund(money, orderCode, options={})
+ obj = money ? {'refundAmount' => money} : {}
+ commit(:post, "orders/#{CGI.escape(orderCode)}/refund", obj, options, 'refund')
+ end
+
+ def void(orderCode, options={})
+ response = commit(:delete, "orders/#{CGI.escape(orderCode)}", nil, options, 'void')
+ if !response.success? && (response.params && response.params['customCode'] != 'ORDER_NOT_FOUND')
+ response = refund(nil, orderCode)
+ end
+ response
+ end
+
+ def verify(credit_card, options={})
+ authorize(0, credit_card, options)
+ end
+
+ private
+
+ def create_token(reusable, name, exp_month, exp_year, number, cvc)
+ obj = {
+ 'reusable'=> reusable,
+ 'paymentMethod'=> {
+ 'type'=> 'Card',
+ 'name'=> name,
+ 'expiryMonth'=> exp_month,
+ 'expiryYear'=> exp_year,
+ 'cardNumber'=> number,
+ 'cvc'=> cvc
+ },
+ 'clientKey'=> @client_key
+ }
+ token_response = commit(:post, 'tokens', obj, {'Authorization' => @service_key}, 'token')
+ token_response
+ end
+
+ def create_post_for_auth_or_purchase(token, money, options)
+ {
+ 'token' => token,
+ 'orderDescription' => options[:description] || 'Worldpay Order',
+ 'amount' => money,
+ 'currencyCode' => options[:currency] || default_currency,
+ 'name' => options[:billing_address]&&options[:billing_address][:name] ? options[:billing_address][:name] : '',
+ 'billingAddress' => {
+ 'address1'=>options[:billing_address]&&options[:billing_address][:address1] ? options[:billing_address][:address1] : '',
+ 'address2'=>options[:billing_address]&&options[:billing_address][:address2] ? options[:billing_address][:address2] : '',
+ 'address3'=>'',
+ 'postalCode'=>options[:billing_address]&&options[:billing_address][:zip] ? options[:billing_address][:zip] : '',
+ 'city'=>options[:billing_address]&&options[:billing_address][:city] ? options[:billing_address][:city] : '',
+ 'state'=>options[:billing_address]&&options[:billing_address][:state] ? options[:billing_address][:state] : '',
+ 'countryCode'=>options[:billing_address]&&options[:billing_address][:country] ? options[:billing_address][:country] : ''
+ },
+ 'customerOrderCode' => options[:order_id],
+ 'orderType' => 'ECOM',
+ 'authorizeOnly' => options[:authorizeOnly] ? true : false
+ }
+ end
+
+ def parse(body)
+ body ? JSON.parse(body) : {}
+ end
+
+ def headers(options = {})
+ headers = {
+ 'Authorization' => @service_key,
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => "Worldpay/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
+ 'X-Worldpay-Client-User-Agent' => user_agent,
+ 'X-Worldpay-Client-User-Metadata' => {:ip => options[:ip]}.to_json
+ }
+ if options['Authorization']
+ headers['Authorization'] = options['Authorization']
+ end
+ headers
+ end
+
+ def commit(method, url, parameters=nil, options = {}, type = false)
+ raw_response = response = nil
+ success = false
+ begin
+ json = parameters ? parameters.to_json : nil
+
+ raw_response = ssl_request(method, self.live_url + url, json, headers(options))
+
+ if raw_response != ''
+ response = parse(raw_response)
+ if type == 'token'
+ success = response.key?('token')
+ else
+ if response.key?('httpStatusCode')
+ success = false
+ else
+ if type == 'authorize' && response['paymentStatus'] == 'AUTHORIZED'
+ success = true
+ elsif type == 'purchase' && response['paymentStatus'] == 'SUCCESS'
+ success = true
+ elsif type == 'capture' || type=='refund' || type=='void'
+ success = true
+ end
+ end
+ end
+ else
+ success = true
+ response = {}
+ end
+ rescue ResponseError => e
+ raw_response = e.response.body
+ response = response_error(raw_response)
+ rescue JSON::ParserError
+ response = json_error(raw_response)
+ end
+
+ if response['orderCode']
+ authorization = response['orderCode']
+ elsif response['token']
+ authorization = response['token']
+ else
+ authorization = response['message']
+ end
+
+ Response.new(success,
+ success ? 'SUCCESS' : response['message'],
+ response,
+ :test => test?,
+ :authorization => authorization,
+ :avs_result => {},
+ :cvv_result => {},
+ :error_code => success ? nil : response['customCode']
+ )
+ end
+
+ def test?
+ @service_key[0] == 'T'
+ end
+
+ def response_error(raw_response)
+ parse(raw_response)
+ rescue JSON::ParserError
+ json_error(raw_response)
+ end
+
+ def json_error(raw_response)
+ msg = 'Invalid response received from the Worldpay Online Payments API. Please contact techsupport.online@worldpay.com if you continue to receive this message.'
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
+ {
+ 'error' => {
+ 'message' => msg
+ }
+ }
+ end
+
+ def handle_response(response)
+ response.body
+ end
+
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/gateways/worldpay_us.rb b/lib/active_merchant/billing/gateways/worldpay_us.rb
new file mode 100644
index 00000000000..303b5b1767e
--- /dev/null
+++ b/lib/active_merchant/billing/gateways/worldpay_us.rb
@@ -0,0 +1,221 @@
+require 'nokogiri'
+
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class WorldpayUsGateway < Gateway
+ class_attribute :backup_url
+
+ self.display_name = 'Worldpay US'
+ self.homepage_url = 'http://www.worldpay.com/us'
+
+ # No sandbox, just use test cards.
+ self.live_url = 'https://trans.worldpay.us/cgi-bin/process.cgi'
+ self.backup_url = 'https://trans.gwtx01.com/cgi-bin/process.cgi'
+
+ self.supported_countries = ['US']
+ self.default_currency = 'USD'
+ self.money_format = :dollars
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb]
+
+ def initialize(options={})
+ requires!(options, :acctid, :subid, :merchantpin)
+ super
+ end
+
+ def purchase(money, payment_method, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_payment_method(post, payment_method)
+ add_customer_data(post, options)
+
+ commit('purchase', options, post)
+ end
+
+ def authorize(money, payment, options={})
+ post = {}
+ add_invoice(post, money, options)
+ add_credit_card(post, payment)
+ add_customer_data(post, options)
+
+ commit('authorize', options, post)
+ end
+
+ def capture(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit('capture', options, post)
+ end
+
+ def refund(amount, authorization, options={})
+ post = {}
+ add_invoice(post, amount, options)
+ add_reference(post, authorization)
+ add_customer_data(post, options)
+
+ commit('refund', options, post)
+ end
+
+ def void(authorization, options={})
+ post = {}
+ add_reference(post, authorization)
+
+ commit('void', options, 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 supports_scrubbing?
+ true
+ end
+
+ def scrub(transcript)
+ transcript.
+ gsub(%r((&?merchantpin=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?ccnum=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?ckacct=)[^&]*)i, '\1[FILTERED]').
+ gsub(%r((&?cvv2=)[^&]*)i, '\1[FILTERED]')
+ end
+
+ private
+
+ def url(options)
+ options[:use_backup_url].to_s == 'true' ? self.backup_url : self.live_url
+ end
+
+ def add_customer_data(post, options)
+ if(billing_address = (options[:billing_address] || options[:address]))
+ post[:ci_companyname] = billing_address[:company]
+ post[:ci_billaddr1] = billing_address[:address1]
+ post[:ci_billaddr2] = billing_address[:address2]
+ post[:ci_billcity] = billing_address[:city]
+ post[:ci_billstate] = billing_address[:state]
+ post[:ci_billzip] = billing_address[:zip]
+ post[:ci_billcountry] = billing_address[:country]
+
+ post[:ci_phone] = billing_address[:phone]
+ post[:ci_email] = billing_address[:email]
+ post[:ci_ipaddress] = billing_address[:ip]
+ end
+
+ if(shipping_address = options[:shipping_address])
+ post[:ci_shipaddr1] = shipping_address[:address1]
+ post[:ci_shipaddr2] = shipping_address[:address2]
+ post[:ci_shipcity] = shipping_address[:city]
+ post[:ci_shipstate] = shipping_address[:state]
+ post[:ci_shipzip] = shipping_address[:zip]
+ post[:ci_shipcountry] = shipping_address[:country]
+ end
+ end
+
+ def add_invoice(post, money, options)
+ post[:amount] = amount(money)
+ post[:currencycode] = (options[:currency] || currency(money))
+ post[:merchantordernumber] = options[:order_id] if options[:order_id]
+ end
+
+ def add_payment_method(post, payment_method)
+ if card_brand(payment_method) == 'check'
+ add_check(post, payment_method)
+ else
+ add_credit_card(post, payment_method)
+ end
+ end
+
+ def add_credit_card(post, payment_method)
+ post[:ccname] = payment_method.name
+ post[:ccnum] = payment_method.number
+ post[:cvv2] = payment_method.verification_value
+ post[:expyear] = format(payment_method.year, :four_digits)
+ post[:expmon] = format(payment_method.month, :two_digits)
+ end
+
+ ACCOUNT_TYPES = {
+ 'checking' => '1',
+ 'savings' => '2',
+ }
+
+ def add_check(post, payment_method)
+ post[:action] = 'ns_quicksale_check'
+ post[:ckacct] = payment_method.account_number
+ post[:ckaba] = payment_method.routing_number
+ post[:ckno] = payment_method.number
+ post[:ckaccttype] = ACCOUNT_TYPES[payment_method.account_type] if ACCOUNT_TYPES[payment_method.account_type]
+ end
+
+ def split_authorization(authorization)
+ historyid, orderid = authorization.split('|')
+ [historyid, orderid]
+ end
+
+ def add_reference(post, authorization)
+ historyid, orderid = split_authorization(authorization)
+ post[:postonly] = historyid
+ post[:historykeyid] = historyid
+ post[:orderkeyid] = orderid
+ end
+
+ def parse(xml)
+ response = {}
+ doc = Nokogiri::XML(xml)
+ message = doc.xpath('//plaintext')
+ message.text.split(/\r?\n/).each do |line|
+ key, value = line.split(%r{=})
+ response[key] = value if key
+ end
+ response
+ end
+
+ ACTIONS = {
+ 'purchase' => 'ns_quicksale_cc',
+ 'refund' => 'ns_credit',
+ 'authorize' => 'ns_quicksale_cc',
+ 'capture' => 'ns_quicksale_cc',
+ 'void' => 'ns_void',
+ }
+
+ def commit(action, options, post)
+ post[:action] = ACTIONS[action] unless post[:action]
+ post[:acctid] = @options[:acctid]
+ post[:subid] = @options[:subid]
+ post[:merchantpin] = @options[:merchantpin]
+
+ post[:authonly] = '1' if action == 'authorize'
+
+ raw = parse(ssl_post(url(options), post.to_query))
+
+ succeeded = success_from(raw['result'])
+ Response.new(
+ succeeded,
+ message_from(succeeded, raw),
+ raw,
+ :authorization => authorization_from(raw),
+ :test => test?
+ )
+ end
+
+ def success_from(result)
+ result == '1'
+ end
+
+ def message_from(succeeded, response)
+ if succeeded
+ 'Succeeded'
+ else
+ (response['transresult'] || response['Reason'] || 'Unable to read error message')
+ end
+ end
+
+ def authorization_from(response)
+ [response['historyid'], response['orderid']].join('|')
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/integrations.rb b/lib/active_merchant/billing/integrations.rb
deleted file mode 100644
index 27d8bd2201a..00000000000
--- a/lib/active_merchant/billing/integrations.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module ActiveMerchant
- module Billing
- module Integrations
-
- Dir[File.dirname(__FILE__) + '/integrations/*.rb'].each do |f|
-
- # Get camelized class name
- filename = File.basename(f, '.rb')
- # Camelize the string to get the class name
- gateway_class = filename.camelize.to_sym
-
- # Register for autoloading
- autoload gateway_class, f
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/a1agregator.rb b/lib/active_merchant/billing/integrations/a1agregator.rb
deleted file mode 100644
index 9ac093aed24..00000000000
--- a/lib/active_merchant/billing/integrations/a1agregator.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require File.dirname(__FILE__) + '/a1agregator/helper.rb'
-require File.dirname(__FILE__) + '/a1agregator/notification.rb'
-require File.dirname(__FILE__) + '/a1agregator/status.rb'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module A1agregator
-
- mattr_accessor :service_url
- self.service_url = 'https://partner.a1agregator.ru/a1lite/input/'
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'check'
-
- def self.notification(*args)
- Notification.new(*args)
- end
-
- def self.status(login, password)
- Status.new(login, password)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/a1agregator/helper.rb b/lib/active_merchant/billing/integrations/a1agregator/helper.rb
deleted file mode 100644
index cddf1f853fa..00000000000
--- a/lib/active_merchant/billing/integrations/a1agregator/helper.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module A1agregator
- class Helper < ActiveMerchant::Billing::Integrations::Helper
-
- # public key
- mapping :account, 'key'
-
- mapping :amount, 'cost'
-
- mapping :order, 'order_id'
-
- mapping :customer, :email => 'email',
- :phone => 'phone_number'
-
- # payment description
- mapping :credential2, 'name'
-
- mapping :credential3, 'comment'
-
- # on error
- # 1 - raise error
- # 0 - redirect
- mapping :credential4, 'verbose'
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/a1agregator/notification.rb b/lib/active_merchant/billing/integrations/a1agregator/notification.rb
deleted file mode 100644
index d714f3d489a..00000000000
--- a/lib/active_merchant/billing/integrations/a1agregator/notification.rb
+++ /dev/null
@@ -1,186 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module A1agregator
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- self.production_ips = [
- '78.108.178.206',
- '79.137.235.129',
- '95.163.96.79',
- '212.24.38.100'
- ]
-
- def initialize(*args)
- super
- guess_notification_type
- end
-
- # Simple notification request params:
- # tid
- # name
- # comment
- # partner_id
- # service_id
- # order_id
- # type
- # partner_income
- # system_income
-
- def complete?
- true
- end
-
- def transaction_id
- params['tid']
- end
-
- def title
- params['name']
- end
-
- def comment
- params['comment']
- end
-
- def partner_id
- params['partner_id']
- end
-
- def service_id
- params['service_id']
- end
-
- def item_id
- params['order_id']
- end
-
- def type
- params['type']
- end
-
- def partner_income
- params['partner_income']
- end
-
- def system_income
- params['system_income']
- end
-
- # Additional notification request params:
- # tid
- # name
- # comment
- # partner_id
- # service_id
- # order_id
- # type
- # cost
- # income_total
- # income
- # partner_income
- # system_income
- # command
- # phone_number
- # email
- # resultStr
- # date_created
- # version
- # check
-
- def inclome_total
- params['income_total']
- end
-
- def income
- params['income']
- end
-
- def partner_income
- params['partner_income']
- end
-
- def system_income
- params['system_income']
- end
-
- def command
- params['command']
- end
-
- def phone_number
- params['phone_number']
- end
-
- def payer_email
- params['email']
- end
-
- def result_string
- params['resultStr']
- end
-
- def received_at
- params['date_created']
- end
-
- def version
- params['version']
- end
-
- def security_key
- params[A1agregator.signature_parameter_name].to_s.downcase
- end
-
- # the money amount we received in X.2 decimal.
- alias_method :gross, :system_income
-
- def currency
- 'RUB'
- end
-
- # Was this a test transaction?
- def test?
- params['test'] == '1'
- end
-
- def simple_notification?
- @notification_type == :simple
- end
-
- def additional_notification?
- @notification_type == :additional
- end
-
- def acknowledge
- security_key == signature
- end
-
- private
-
- def signature
- data = "#{params['tid']}\
-#{params['name']}\
-#{params['comment']}\
-#{params['partner_id']}\
-#{params['service_id']}\
-#{params['order_id']}\
-#{params['type']}\
-#{params['partner_income']}\
-#{params['system_income']}\
-#{params['test']}\
-#{@options[:secret]}"
- Digest::MD5.hexdigest(data)
- end
-
- def guess_notification_type
- @notification_type = params['version'] ? :additional : :simple
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/a1agregator/status.rb b/lib/active_merchant/billing/integrations/a1agregator/status.rb
deleted file mode 100644
index 264db72da29..00000000000
--- a/lib/active_merchant/billing/integrations/a1agregator/status.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module A1agregator
-
- class Status
- include PostsData
-
- STATUS_TEST_URL = 'https://partner.a1pay.ru/a1lite/info/'
-
- attr_accessor :login, :password
-
- def initialize(login, password)
- @login, @password = login, password
- end
-
- # agregator provides two methods:
- # by tid - transaction id
- # by order_id & service_id
- def update(options = {})
- data = PostData.new
- data[:user] = @login
- data[:pass] = @password
- if options[:tid]
- data[:tid] = options[:tid]
- else
- data[:ord_id] = options[:ord_id]
- data[:service_id] = options[:service_id]
- end
-
- ssl_post(STATUS_TEST_URL, data.to_post_data)
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/action_view_helper.rb b/lib/active_merchant/billing/integrations/action_view_helper.rb
deleted file mode 100644
index 7fed361e757..00000000000
--- a/lib/active_merchant/billing/integrations/action_view_helper.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-require 'action_pack'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module ActionViewHelper
- # This helper allows the usage of different payment integrations
- # through a single form helper. Payment integrations are the
- # type of service where the user is redirected to the secure
- # site of the service, like Paypal or Chronopay.
- #
- # The helper creates a scope around a payment service helper
- # which provides the specific mapping for that service.
- #
- # <% payment_service_for 1000, 'paypalemail@mystore.com',
- # :amount => 50.00,
- # :currency => 'CAD',
- # :service => :paypal,
- # :html => { :id => 'payment-form' } do |service| %>
- #
- # <% service.customer :first_name => 'Cody',
- # :last_name => 'Fauser',
- # :phone => '(555)555-5555',
- # :email => 'cody@example.com' %>
- #
- # <% service.billing_address :city => 'Ottawa',
- # :address1 => '21 Snowy Brook Lane',
- # :address2 => 'Apt. 36',
- # :state => 'ON',
- # :country => 'CA',
- # :zip => 'K1J1E5' %>
- #
- # <% service.invoice '#1000' %>
- # <% service.shipping '0.00' %>
- # <% service.tax '0.00' %>
- #
- # <% service.notify_url url_for(:only_path => false, :action => 'notify') %>
- # <% service.return_url url_for(:only_path => false, :action => 'done') %>
- # <% service.cancel_return_url 'http://mystore.com' %>
- # <% end %>
- #
- def payment_service_for(order, account, options = {}, &proc)
- raise ArgumentError, "Missing block" unless block_given?
-
- integration_module = ActiveMerchant::Billing::Integrations.const_get(options.delete(:service).to_s.camelize)
- service_class = integration_module.const_get('Helper')
-
- form_options = options.delete(:html) || {}
- service = service_class.new(order, account, options)
- form_options[:method] = service.form_method
- result = []
- result << form_tag(integration_module.service_url, form_options)
-
- result << capture(service, &proc)
-
- service.form_fields.each do |field, value|
- result << hidden_field_tag(field, value)
- end
-
- service.raw_html_fields.each do |field, value|
- result << "\n"
- end
-
- result << ''
- result= result.join("\n")
-
- concat(result.respond_to?(:html_safe) ? result.html_safe : result)
- nil
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/authorize_net_sim.rb b/lib/active_merchant/billing/integrations/authorize_net_sim.rb
deleted file mode 100644
index e4a56576ef2..00000000000
--- a/lib/active_merchant/billing/integrations/authorize_net_sim.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module AuthorizeNetSim
- autoload :Helper, 'active_merchant/billing/integrations/authorize_net_sim/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/authorize_net_sim/notification.rb'
-
- # Overwrite this if you want to change the ANS test url
- mattr_accessor :test_url
- self.test_url = 'https://test.authorize.net/gateway/transact.dll'
-
- # Overwrite this if you want to change the ANS production url
- mattr_accessor :production_url
- self.production_url = 'https://secure.authorize.net/gateway/transact.dll'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification(post)
- Notification.new(post)
- end
-
- def self.return(query_string)
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb b/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb
deleted file mode 100644
index 0290de8c74a..00000000000
--- a/lib/active_merchant/billing/integrations/authorize_net_sim/helper.rb
+++ /dev/null
@@ -1,229 +0,0 @@
-require 'active_support/version' # for ActiveSupport2.3
-require 'active_support/core_ext/float/rounding.rb' unless ActiveSupport::VERSION::MAJOR > 3 # Float#round(precision)
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module AuthorizeNetSim
- # An example. Note the username as a parameter and transaction key you
- # will want to use later. The amount that you pass in will be *rounded*,
- # so preferably pass in X.2 decimal so that no rounding occurs. It is
- # rounded because if it looks like 00.000 Authorize.Net fails the
- # transaction as incorrectly formatted.
- #
- # payment_service_for('order_id', 'authorize_net_account', :service => :authorize_net_sim, :amount => 157.0) do |service|
- #
- # # You must call setup_hash and invoice
- #
- # service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY',
- # :order_timestamp => 1206836763
- # service.customer_id 8
- # service.customer :first_name => 'g',
- # :last_name => 'g',
- # :email => 'g@g.com',
- # :phone => '3'
- # service.billing_address :zip => 'g',
- # :country => 'United States of America',
- # :address => 'g'
- #
- # service.ship_to_address :first_name => 'g',
- # :last_name => 'g',
- # :city => '',
- # :address => 'g',
- # :address2 => '',
- # :state => address.state,
- # :country => 'United States of America',
- # :zip => 'g'
- #
- # service.invoice "516428355" # your invoice number
- # # The end-user is presented with the HTML produced by the notify_url.
- # service.notify_url "http://t/authorize_net_sim/payment_received_notification_sub_step"
- # service.payment_header 'My store name'
- # service.add_line_item :name => 'item name', :quantity => 1, :unit_price => 0
- # service.test_request 'true' # only if it's just a test
- # service.shipping '25.0'
- # # Tell it to display a "0" line item for shipping, with the price in
- # # the name, otherwise it isn't shown at all, leaving the end user to
- # # wonder why the total is different than the sum of the line items.
- # service.add_shipping_as_line_item
- # server.add_tax_as_line_item # same with tax
- # # See the helper.rb file for various custom fields
- # end
-
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :order, 'x_fp_sequence'
- mapping :account, 'x_login'
-
- mapping :customer, :first_name => 'x_first_name',
- :last_name => 'x_last_name',
- :email => 'x_email',
- :phone => 'x_phone'
-
- mapping :notify_url, 'x_relay_url'
- mapping :return_url, '' # unused
- mapping :cancel_return_url, '' # unused
-
- # Custom fields for Authorize.net SIM.
- # See http://www.Authorize.Net/support/SIM_guide.pdf for more descriptions.
- mapping :fax, 'x_fax'
- mapping :customer_id, 'x_cust_id'
- mapping :description, 'x_description'
- mapping :tax, 'x_tax'
- mapping :shipping, 'x_freight'
-
- # True or false, or 0 or 1 same effect [not required to send one,
- # defaults to false].
- mapping :test_request, 'x_test_request'
-
- # This one is necessary for the notify url to be able to parse its
- # information later! They also pass back customer id, if that's
- # useful.
- def invoice(number)
- add_field 'x_invoice_num', number
- end
-
- # Set the billing address. Call like service.billing_address {:city =>
- # 'provo, :state => 'UT'}...
- def billing_address(options)
- for setting in [:city, :state, :zip, :country, :po_num] do
- add_field 'x_' + setting.to_s, options[setting]
- end
- raise 'must use address1 and address2' if options[:address]
- add_field 'x_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip
- end
-
- # Adds a custom field which you submit to Authorize.Net. These fields
- # are all passed back to you verbatim when it does its relay
- # (callback) to you note that if you call it twice with the same name,
- # this function only uses keeps the second value you called it with.
- def add_custom_field(name, value)
- add_field name, value
- end
-
- # Displays tax as a line item, so they can see it. Otherwise it isn't
- # displayed.
- def add_tax_as_line_item
- raise unless @fields['x_tax']
- add_line_item :name => 'Total Tax', :quantity => 1, :unit_price => @fields['x_tax'], :tax => 0, :line_title => 'Tax'
- end
-
- # Displays shipping as a line item, so they can see it. Otherwise it
- # isn't displayed.
- def add_shipping_as_line_item(extra_options = {})
- raise 'must set shipping/freight before calling this' unless @fields['x_freight']
- add_line_item extra_options.merge({:name => 'Shipping and Handling Cost', :quantity => 1, :unit_price => @fields['x_freight'], :line_title => 'Shipping'})
- end
-
- # Add ship_to_address in the same format as the normal address is
- # added.
- def ship_to_address(options)
- for setting in [:first_name, :last_name, :company, :city, :state, :zip, :country] do
- if options[setting] then
- add_field 'x_ship_to_' + setting.to_s, options[setting]
- end
- end
- raise 'must use :address1 and/or :address2' if options[:address]
- add_field 'x_ship_to_address', (options[:address1].to_s + ' ' + options[:address2].to_s).strip
- end
-
- # These control the look of the SIM payment page. Note that you can
- # include a CSS header in descriptors, etc.
- mapping :color_link, 'x_color_link'
- mapping :color_text, 'x_color_text'
- mapping :logo_url, 'x_logo_url'
- mapping :background_url, 'x_background_url' # background image url for the page
- mapping :payment_header, 'x_header_html_payment_form'
- mapping :payment_footer, 'x_footer_html_payment_form'
-
- # For this to work you must have also passed in an email for the
- # purchaser.
- def yes_email_customer_from_authorizes_side
- add_field 'x_email_customer', 'TRUE'
- end
-
- # Add a line item to Authorize.Net.
- # Call line add_line_item {:name => 'orange', :unit_price => 30, :tax_value => 'Y', :quantity => 3, }
- # Note you can't pass in a negative unit price, and you can add an
- # optional :line_title => 'special name' if you don't want it to say
- # 'Item 1' or what not, the default coded here.
- # Cannot have a negative price, nor a name with "'s or $
- # You can use the :line_title for the product name and then :name for description, if desired
- def add_line_item(options)
- raise 'needs name' unless options[:name]
-
- if @line_item_count == 30
- # Add a note that we are not showing at least one -- AN doesn't
- # display more than 30 or so.
- description_of_last = @raw_html_fields[-1][1]
- # Pull off the second to last section, which is the description.
- description_of_last =~ />([^>]*)<\|>[YN]$/
- # Create a new description, which can't be too big, so truncate here.
- @raw_html_fields[-1][1] = description_of_last.gsub($1, $1[0..200] + ' + more unshown items after this one.')
- end
-
- name = options[:name]
- quantity = options[:quantity] || 1
- line_title = options[:line_title] || ('Item ' + (@line_item_count + 1).to_s) # left most field
- unit_price = options[:unit_price] || 0
- unit_price = unit_price.to_f.round(2)
- tax_value = options[:tax_value] || 'N'
-
- # Sanitization, in case they include a reserved word here, following
- # their guidelines; unfortunately, they require 'raw' fields here,
- # not CGI escaped, using their own delimiters.
- #
- # Authorize.net ignores the second field (sanitized_short_name)
- raise 'illegal char for line item <|>' if name.include? '<|>'
- raise 'illegal char for line item "' if name.include? '"'
- raise 'cannot pass in dollar sign' if unit_price.to_s.include? '$'
- raise 'must have positive or 0 unit price' if unit_price.to_f < 0
- # Using CGI::escape causes the output to be formated incorrectly in
- # the HTML presented to the end-user's browser (e.g., spaces turn
- # into +'s).
- sanitized_short_name = name[0..30]
- name = name[0..255]
-
- add_raw_html_field "x_line_item", "#{line_title}<|>#{sanitized_short_name}<|>#{name}<|>#{quantity}<|>#{unit_price}<|>#{tax_value}"
-
- @line_item_count += 1
- end
-
- # If you call this it will e-mail to this address a copy of a receipt
- # after successful, from Authorize.Net.
- def email_merchant_from_authorizes_side(to_this_email)
- add_field 'x_email_merchant', to_this_email
- end
-
- # You MUST call this at some point for it to actually work. Options
- # must include :transaction_key and :order_timestamp
- def setup_hash(options)
- raise unless options[:transaction_key]
- raise unless options[:order_timestamp]
- amount = @fields['x_amount']
- data = "#{@fields['x_login']}^#{@fields['x_fp_sequence']}^#{options[:order_timestamp].to_i}^#{amount}^#{@fields['x_currency_code']}"
- hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('md5'), options[:transaction_key], data)
- add_field 'x_fp_hash', hmac
- add_field 'x_fp_timestamp', options[:order_timestamp].to_i
- end
-
- # Note that you should call #invoice and #setup_hash as well, for the
- # response_url to actually work.
- def initialize(order, account, options = {})
- super
- raise 'missing parameter' unless order and account and options[:amount]
- raise 'error -- amount with no digits!' unless options[:amount].to_s =~ /\d/
- add_field('x_type', 'AUTH_CAPTURE') # the only one we deal with, for now. Not refunds or anything else, currently.
- add_field 'x_show_form', 'PAYMENT_FORM'
- add_field 'x_relay_response', 'TRUE'
- add_field 'x_duplicate_window', '28800' # large default duplicate window.
- add_field 'x_currency_code', currency_code
- add_field 'x_version' , '3.1' # version from doc
- add_field 'x_amount', options[:amount].to_f.round(2)
- @line_item_count = 0
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb b/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb
deleted file mode 100644
index d2e36ab5cb5..00000000000
--- a/lib/active_merchant/billing/integrations/authorize_net_sim/notification.rb
+++ /dev/null
@@ -1,340 +0,0 @@
-require 'net/http'
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- # # Example:
- # parser = AuthorizeNetSim::Notification.new(request.raw_post)
- # passed = parser.complete?
- #
- # order = Order.find_by_order_number(parser.invoice_num)
- #
- # unless order
- # @message = 'Error--unable to find your transaction! Please contact us directly.'
- # return render :partial => 'authorize_net_sim_payment_response'
- # end
- #
- # if order.total != parser.gross.to_f
- # logger.error "Authorize.Net sim said they paid for #{parser.gross} and it should have been #{order.total}!"
- # passed = false
- # end
- #
- # # Theoretically, Authorize.net will *never* pass us the same transaction
- # # ID twice, but we can double check that... by using
- # # parser.transaction_id, and checking against previous orders' transaction
- # # id's (which you can save when the order is completed)....
- # unless parser.acknowledge MD5_HASH_SET_IN_AUTHORIZE_NET, AUTHORIZE_LOGIN
- # passed = false
- # logger.error "ALERT POSSIBLE FRAUD ATTEMPT either that or you haven't setup your md5 hash setting right in #{__FILE__}
- # because a transaction came back from Authorize.Net with the wrong hash value--rejecting!"
- # end
- #
- # unless parser.cavv_matches? and parser.avs_code_matches?
- # logger.error 'Warning--non matching CC!' + params.inspect
- # # Could fail them here, as well (recommended)...
- # end
- #
- # if passed
- # # Set up your session, and render something that will redirect them to
- # # your site, most likely.
- # else
- # # Render failure or redirect them to your site where you will render failure
- # end
-
- module AuthorizeNetSim
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- def unescape(val) #:nodoc:
- if val
- CGI::unescape val
- else
- val
- end
- end
-
- # Passes a hash of the address the user entered in at Authorize.Net
- def billing_address
- all = {}
- [:fax, :city, :company, :last_name, :country, :zip, :first_name, :address, :email, :state].each do |key_out|
- all[key_out] = unescape params['x_' + key_out.to_s]
- end
- all
- end
-
- def customer_id
- unescape params['x_cust_id']
- end
-
- def auth_code
- unescape params['x_auth_code']
- end
-
- def po_num
- unescape params['x_po_num']
- end
-
- def ship_to_address
- all = {}
- [:city, :last_name, :first_name, :country, :zip, :address].each do |key_out|
- all[key_out] = unescape params['x_ship_to_' + key_out.to_s]
- end
- all
- end
-
- # Tax amount we sent them.
- def tax
- unescape params['x_tax']
- end
-
- # Transaction type (probably going to be auth_capture, since that's
- # all we set it as).
- def transaction_type
- unescape params['x_type']
- end
-
- # Payment method used--almost always CC (for credit card).
- def method
- unescape params['x_method']
- end
-
- # Ff our payment method is available. Almost always "true".
- def method_available
- params['x_method_available']
- end
-
- # Invoice num we passed in as invoice_num to them.
- def invoice_num
- item_id
- end
-
- # If you pass any values to authorize that aren't its expected, it
- # will pass them back to you verbatim, returned by this method.
- # custom values:
- def all_custom_values_passed_in_and_now_passed_back_to_us
- all = {}
- params.each do |key, value|
- if key[0..1] != 'x_'
- all[key] = unescape value
- end
- end
- all
- end
-
- def duty
- unescape params['x_duty']
- end
-
- # Shipping we sent them.
- def freight
- unescape params['x_freight']
- end
- alias_method :shipping, :freight
-
- def description
- unescape params['x_description']
- end
-
- # Returns the response code as a symbol.
- # {'1' => :approved, '2' => :declined, '3' => :error, '4' => :held_for_review}
- def response_code_as_ruby_symbol
- map = {'1' => :approved, '2' => :declined, '3' => :error, '4' => :held_for_review}
- map[params['x_response_code']]
- end
-
- def response_reason_text
- unescape params['x_response_reason_text']
- end
-
- # The response reason text's numeric id [equivalent--just a number]
- def response_reason_code
- unescape params['x_response_reason_code']
- end
-
- # 'used internally by their gateway'
- def response_subcode
- params['x_response_subcode']
- end
-
- # They pass back a tax_exempt value.
- def tax_exempt
- params['x_tax_exempt']
- end
-
- # avs [address verification] code
- # A = Address (Street)
- # matches, ZIP does not
- # B = Address information
- # not provided for AVS
- # check
- # E = AVS error
- # G = Non-U.S. Card Issuing
- # Bank
- # N = No Match on Address
- # (Street) or ZIP
- # P = AVS not applicable for
- # this transaction
- # R = Retry – System
- # unavailable or timed out
- # S = Service not supported
- # by issuer
- # U = Address information is
- # unavailable
- # W = Nine digit ZIP
- # matches, Address (Street)
- # does not
- # X = Address (Street) and
- # nine digit ZIP match
- # Y = Address (Street) and
- # five digit ZIP match
- # Z = Five digit ZIP matches
- # Address (Street) does not
- def avs_code
- params['x_avs_code']
- end
-
- # Returns true if their address completely matched [Y or X, P from
- # #avs_code, which mean 'add+zip match', 'address + 9-zip match', and
- # not applicable, respectively].
- def avs_code_matches?
- return ['Y', 'X', 'P'].include? params['x_avs_code']
- end
-
- # cvv2 response
- # M = Match
- # N = No Match
- # P = Not Processed
- # S = Should have been
- # present
- # U = Issuer unable to
- # process request
- def cvv2_resp_code
- params['x_cvv2_resp_code']
- end
-
- # check if #cvv2_resp_code == 'm' for Match. otherwise false
- def cvv2_resp_code_matches?
- return ['M'].include? cvv2_resp_code
- end
-
- # cavv_response--'cardholder authentication verification response code'--most likely not use for SIM
- # Blank or not present =
- # CAVV not validated
- # 0 = CAVV not validated
- # because erroneous data
- # was submitted
- # 1 = CAVV failed validation
- # 2 = CAVV passed
- # validation
- # 3 = CAVV validation could
- # not be performed; issuer
- # attempt incomplete
- # 4 = CAVV validation could
- # not be performed; issuer
- # system error
- # 5 = Reserved for future
- # use
- # 6 = Reserved for future
- # use
- # 7 = CAVV attempt – failed
- # validation – issuer
- # available (U.S.-issued
- # card/non-U.S acquirer)
- # 8 = CAVV attempt –
- # passed validation – issuer
- # available (U.S.-issued
- # card/non-U.S. acquirer)
- # 9 = CAVV attempt – failed
- # validation – issuer
- def cavv_response
- params['x_cavv_response']
- end
-
- # Check if #cavv_response == '', '2', '8' one of those [non failing]
- # [blank means no validated, 2 is passed, 8 is passed issuer
- # available]
- def cavv_matches?
- ['','2','8'].include? cavv_response
- end
-
- # Payment is complete -- returns true if x_response_code == '1'
- def complete?
- params["x_response_code"] == '1'
- end
-
- # Alias for invoice number--this is the only id they pass back to us
- # that we passed to them, except customer id is also passed back.
- def item_id
- unescape params['x_invoice_num']
- end
-
- # They return this number to us [it's unique to Authorize.net].
- def transaction_id
- params['x_trans_id']
- end
-
- # When was this payment was received by the client. --unimplemented --
- # always returns nil
- def received_at
- nil
- end
-
- # End-user's email
- def payer_email
- unescape params['x_email']
- end
-
- # They don't pass merchant email back to us -- unimplemented -- always
- # returns nil
- def receiver_email
- nil
- end
-
- # md5 hash used internally
- def security_key
- params['x_MD5_Hash']
- end
-
- # The money amount we received in X.2 decimal. Returns a string
- def gross
- unescape params['x_amount']
- end
-
- # Was this a test transaction?
- def test?
- params['x_test_request'] == 'true'
- end
-
- # #method_available alias
- def status
- complete?
- end
-
- # Called to request back and check if it was a valid request.
- # Authorize.net passes us back a hash that includes a hash of our
- # 'unique' MD5 value that we set within their system.
- #
- # Example:
- # acknowledge('my secret md5 hash that I set within Authorize.Net', 'authorize_login')
- #
- # Note this is somewhat unsafe unless you actually set that md5 hash
- # to something (defaults to '' in their system).
- def acknowledge(md5_hash_set_in_authorize_net, authorize_net_login_name)
- Digest::MD5.hexdigest(md5_hash_set_in_authorize_net + authorize_net_login_name + params['x_trans_id'] + gross) == params['x_MD5_Hash'].downcase
- end
-
- private
-
- # Take the posted data and move the relevant data into a hash.
- def parse(post)
- @raw = post
- post.split('&').each do |line|
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
- params[key] = value
- end
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/bogus.rb b/lib/active_merchant/billing/integrations/bogus.rb
deleted file mode 100644
index 6de2b0cc889..00000000000
--- a/lib/active_merchant/billing/integrations/bogus.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Bogus
- autoload :Return, 'active_merchant/billing/integrations/bogus/return.rb'
- autoload :Helper, 'active_merchant/billing/integrations/bogus/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/bogus/notification.rb'
-
- mattr_accessor :service_url
- self.service_url = 'http://www.bogus.com'
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/bogus/helper.rb b/lib/active_merchant/billing/integrations/bogus/helper.rb
deleted file mode 100644
index ffb5e214228..00000000000
--- a/lib/active_merchant/billing/integrations/bogus/helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Bogus
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :account, 'account'
- mapping :order, 'order'
- mapping :amount, 'amount'
- mapping :currency, 'currency'
- mapping :customer, :first_name => 'first_name',
- :last_name => 'last_name'
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/bogus/notification.rb b/lib/active_merchant/billing/integrations/bogus/notification.rb
deleted file mode 100644
index 77630ffad84..00000000000
--- a/lib/active_merchant/billing/integrations/bogus/notification.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Bogus
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/bogus/return.rb b/lib/active_merchant/billing/integrations/bogus/return.rb
deleted file mode 100644
index d41ef806104..00000000000
--- a/lib/active_merchant/billing/integrations/bogus/return.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Bogus
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/chronopay.rb b/lib/active_merchant/billing/integrations/chronopay.rb
deleted file mode 100644
index 6137772ac71..00000000000
--- a/lib/active_merchant/billing/integrations/chronopay.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Chronopay
- autoload :Return, 'active_merchant/billing/integrations/chronopay/return.rb'
- autoload :Helper, 'active_merchant/billing/integrations/chronopay/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/chronopay/notification.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://secure.chronopay.com/index_shop.cgi'
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/chronopay/helper.rb b/lib/active_merchant/billing/integrations/chronopay/helper.rb
deleted file mode 100644
index 4f62465cbf8..00000000000
--- a/lib/active_merchant/billing/integrations/chronopay/helper.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Chronopay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- # All currently supported checkout languages:
- # es (Spanish)
- # en (English)
- # de (German)
- # pt (Portuguese)
- # lv (Latvian)
- # cn1 (Chinese Version 1)
- # cn2 (Chinese version 2)
- # nl (Dutch)
- # ru (Russian)
- COUNTRIES_FOR_LANG = {
- 'ES' => %w( AR BO CL CO CR CU DO EC SV GQ GT HN MX NI PA PY PE ES UY VE),
- 'DE' => %w( DE AT CH LI ),
- 'PT' => %w( AO BR CV GW MZ PT ST TL),
- 'RU' => %w( BY KG KZ RU ),
- 'LV' => %w( LV ),
- 'CN1' => %w( CN ),
- 'NL' => %w( NL )
- }
-
- LANG_FOR_COUNTRY = COUNTRIES_FOR_LANG.inject(Hash.new("EN")) do |memo, (lang, countries)|
- countries.each do |code|
- memo[code] = lang
- end
- memo
- end
-
-
- self.country_format = :alpha3
-
- def initialize(order, account, options = {})
- super
- add_field('cb_type', 'p')
- end
-
- # product_id
- mapping :account, 'product_id'
- # product_name
- mapping :invoice, 'product_name'
- # product_price
- mapping :amount, 'product_price'
- # product_price_currency
- mapping :currency, 'product_price_currency'
-
- # f_name
- # s_name
- # email
- mapping :customer, :first_name => 'f_name',
- :last_name => 's_name',
- :phone => 'phone',
- :email => 'email'
-
- # city
- # street
- # state
- # zip
- # country - The country must be a 3 digit country code
- # phone
-
- mapping :billing_address, :city => 'city',
- :address1 => 'street',
- :state => 'state',
- :zip => 'zip',
- :country => 'country'
-
- def billing_address(mapping = {})
- # Gets the country code in the appropriate format or returns what we were given
- # The appropriate format for Chronopay is the alpha 3 country code
- country_code = lookup_country_code(mapping.delete(:country))
- add_field(mappings[:billing_address][:country], country_code)
-
- countries_with_supported_states = ['USA', 'CAN']
- if !countries_with_supported_states.include?(country_code)
- mapping.delete(:state)
- add_field(mappings[:billing_address][:state], 'XX')
- end
- mapping.each do |k, v|
- field = mappings[:billing_address][k]
- add_field(field, v) unless field.nil?
- end
- add_field('language', checkout_language_from_country(country_code))
- end
-
- # card_no
- # exp_month
- # exp_year
- mapping :credit_card, :number => 'card_no',
- :expiry_month => 'exp_month',
- :expiry_year => 'exp_year'
-
- # cb_url
- mapping :notify_url, 'cb_url'
-
- # cs1
- mapping :order, 'cs1'
-
- # cs2
- # cs3
- # decline_url
-
-
- private
-
- def checkout_language_from_country(country_code)
- country = Country.find(country_code)
- short_code = country.code(:alpha2).to_s
- LANG_FOR_COUNTRY[short_code]
- rescue InvalidCountryCodeError
- 'EN'
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/chronopay/notification.rb b/lib/active_merchant/billing/integrations/chronopay/notification.rb
deleted file mode 100644
index 4942c50a183..00000000000
--- a/lib/active_merchant/billing/integrations/chronopay/notification.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Chronopay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- def complete?
- status == 'Completed'
- end
-
- # Status of transaction. List of possible values:
- # onetime – one time payment has been made, no repayment required;::
- # initial – first payment has been made, repayment required in corresponding period;::
- # decline – charge request has been rejected;::
- # rebill – repayment has been made together with initial transaction;::
- # cancel – repayments has been disabled;::
- # expire – customer’s access to restricted zone membership has been expired;::
- # refund – request to refund has been received;::
- # chargeback – request to chargeback has been received.::
- #
- # This implementation of Chronopay does not support subscriptions.
- # The status codes used are matched to the status codes that Paypal
- # sends. See Paypal::Notification#status for more details
- def status
- case params['transaction_type']
- when 'onetime'
- 'Completed'
- when 'refund'
- 'Refunded'
- when 'chargeback'
- 'Reversed'
- else
- 'Failed'
- end
- end
-
- # Unique ID of transaction
- def transaction_id
- params['transaction_id']
- end
-
- # Unique ID of customer
- def customer_id
- params['customer_id']
- end
-
- # Unique ID of Merchant’s web-site
- def site_id
- params['site_id']
- end
-
- # ID of a product that was purchased
- def product_id
- params['product_id']
- end
-
- # Language
- def language
- params['language']
- end
-
- def received_at
- # Date should be formatted "dd-mm-yy" to be parsed by 1.8 and 1.9 the same way
- formatted_date = Date.strptime(date, "%m/%d/%Y").strftime("%d-%m-%Y")
- Time.parse("#{formatted_date} #{time}") unless date.blank? || time.blank?
- end
-
- # Date of transaction in MM/DD/YYYY format
- def date
- params['date']
- end
-
- # Time of transaction in HH:MM:SS format
- def time
- params['time']
- end
-
- # The customer's full name
- def name
- params['name']
- end
-
- # The customer's email address
- def email
- params['email']
- end
-
- # The customer's street address
- def street
- params['street']
- end
-
- # The customer's country - 3 digit country code
- def country
- params['country']
- end
-
- # The customer's city
- def city
- params['city']
- end
-
- # The customer's zip
- def zip
- params['zip']
- end
-
- # The customer's state. Only useful for US Customers
- def state
- params['state']
- end
-
- # Customer’s login for restricted access zone of Merchant’s Web-site
- def username
- params['username']
- end
-
- # Customer's password for restricted access zone of Merchant’s Web-site, as chosen
- def password
- params['password']
- end
-
- # The item id passed in the first custom parameter
- def item_id
- params['cs1']
- end
-
- # Additional parameter
- def custom2
- params['cs2']
- end
-
- # Additional parameter
- def custom3
- params['cs3']
- end
-
- # The currency the purchase was made in
- def currency
- params['currency']
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- params['total']
- end
-
- def test?
- date.blank? && time.blank? && transaction_id.blank?
- end
-
- def acknowledge
- true
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/chronopay/return.rb b/lib/active_merchant/billing/integrations/chronopay/return.rb
deleted file mode 100644
index 399b258b3b8..00000000000
--- a/lib/active_merchant/billing/integrations/chronopay/return.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Chronopay
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/direc_pay.rb b/lib/active_merchant/billing/integrations/direc_pay.rb
deleted file mode 100644
index 62e3956ff15..00000000000
--- a/lib/active_merchant/billing/integrations/direc_pay.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module DirecPay
- autoload :Helper, File.dirname(__FILE__) + '/direc_pay/helper.rb'
- autoload :Return, File.dirname(__FILE__) + '/direc_pay/return.rb'
- autoload :Notification, File.dirname(__FILE__) + '/direc_pay/notification.rb'
- autoload :Status, File.dirname(__FILE__) + '/direc_pay/status.rb'
-
- mattr_accessor :production_url, :test_url
-
- self.production_url = "https://www.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp"
- self.test_url = "https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp"
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string, options)
- end
-
- def self.request_status_update(mid, transaction_id, notification_url)
- Status.new(mid).update(transaction_id, notification_url)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/direc_pay/helper.rb b/lib/active_merchant/billing/integrations/direc_pay/helper.rb
deleted file mode 100644
index dddbfd9a38c..00000000000
--- a/lib/active_merchant/billing/integrations/direc_pay/helper.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module DirecPay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :account, 'MID'
- mapping :order, 'Merchant Order No'
- mapping :amount, 'Amount'
- mapping :currency, 'Currency'
- mapping :country, 'Country'
-
- mapping :billing_address, :city => 'custCity',
- :address1 => 'custAddress',
- :state => 'custState',
- :zip => 'custPinCode',
- :country => 'custCountry',
- :phone => 'custMobileNo'
-
- mapping :shipping_address, :name => 'deliveryName',
- :city => 'deliveryCity',
- :address1 => 'deliveryAddress',
- :state => 'deliveryState',
- :zip => 'deliveryPinCode',
- :country => 'deliveryCountry',
- :phone => 'deliveryMobileNo'
-
- mapping :customer, :name => 'custName',
- :email => 'custEmailId'
-
- mapping :description, 'otherNotes'
- mapping :edit_allowed, 'editAllowed'
-
- mapping :return_url, 'Success URL'
- mapping :failure_url, 'Failure URL'
-
- mapping :operating_mode, 'Operating Mode'
- mapping :other_details, 'Other Details'
- mapping :collaborator, 'Collaborator'
-
- OPERATING_MODE = 'DOM'
- COUNTRY = 'IND'
- CURRENCY = 'INR'
- OTHER_DETAILS = 'NULL'
- EDIT_ALLOWED = 'Y'
-
- PHONE_CODES = {
- 'IN' => '91',
- 'US' => '01',
- 'CA' => '01'
- }
-
- ENCODED_PARAMS = [ :account, :operating_mode, :country, :currency, :amount, :order, :other_details, :return_url, :failure_url, :collaborator ]
-
-
- def initialize(order, account, options = {})
- super
- collaborator = ActiveMerchant::Billing::Base.integration_mode == :test || options[:test] ? 'TOML' : 'DirecPay'
- add_field(mappings[:collaborator], collaborator)
- add_field(mappings[:country], 'IND')
- add_field(mappings[:operating_mode], OPERATING_MODE)
- add_field(mappings[:other_details], OTHER_DETAILS)
- add_field(mappings[:edit_allowed], EDIT_ALLOWED)
- end
-
-
- def customer(params = {})
- add_field(mappings[:customer][:name], full_name(params))
- add_field(mappings[:customer][:email], params[:email])
- end
-
- # Need to format the amount to have 2 decimal places
- def amount=(money)
- cents = money.respond_to?(:cents) ? money.cents : money
- if money.is_a?(String) or cents.to_i <= 0
- raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
- end
- add_field(mappings[:amount], sprintf("%.2f", cents.to_f/100))
- end
-
- def shipping_address(params = {})
- super(update_address(:shipping_address, params))
- end
-
- def billing_address(params = {})
- super(update_address(:billing_address, params))
- end
-
- def form_fields
- add_failure_url
- add_request_parameters
-
- unencoded_parameters
- end
-
-
- private
-
- def add_request_parameters
- params = ENCODED_PARAMS.map{ |param| fields[mappings[param]] }
- encoded = encode_value(params.join('|'))
-
- add_field('requestparameter', encoded)
- end
-
- def unencoded_parameters
- params = fields.dup
- # remove all encoded params from exported fields
- ENCODED_PARAMS.each{ |param| params.delete(mappings[param]) }
- # remove all special characters from each field value
- params = params.collect{|name, value| [name, remove_special_characters(value)] }
- Hash[params]
- end
-
- def add_failure_url
- if fields[mappings[:failure_url]].nil?
- add_field(mappings[:failure_url], fields[mappings[:return_url]])
- end
- end
-
- def update_address(address_type, params)
- params = params.dup
- address = params[:address1]
- address = "#{address} #{params[:address2]}" if params[:address2].present?
- address = "#{params[:company]} #{address}" if params[:company].present?
- params[:address1] = address
-
- params[:phone] = normalize_phone_number(params[:phone])
- add_land_line_phone_for(address_type, params)
-
- if address_type == :shipping_address
- shipping_name = full_name(params) || fields[mappings[:customer][:name]]
- add_field(mappings[:shipping_address][:name], shipping_name)
- end
- params
- end
-
- # Split a single phone number into the country code, area code and local number as best as possible
- def add_land_line_phone_for(address_type, params)
- address_field = address_type == :billing_address ? 'custPhoneNo' : 'deliveryPhNo'
-
- if params.has_key?(:phone2)
- phone = normalize_phone_number(params[:phone2])
- phone_country_code, phone_area_code, phone_number = nil
-
- if params[:country] == 'IN' && phone =~ /(91)? *(\d{3}) *(\d{4,})$/
- phone_country_code, phone_area_code, phone_number = $1, $2, $3
- else
- numbers = phone.split(' ')
- case numbers.size
- when 3
- phone_country_code, phone_area_code, phone_number = numbers
- when 2
- phone_area_code, phone_number = numbers
- else
- phone =~ /(\d{3})(\d+)$/
- phone_area_code, phone_number = $1, $2
- end
- end
-
- add_field("#{address_field}1", phone_country_code || phone_code_for_country(params[:country]) || '91')
- add_field("#{address_field}2", phone_area_code)
- add_field("#{address_field}3", phone_number)
- end
- end
-
- def normalize_phone_number(phone)
- phone.gsub(/[^\d ]+/, '') if phone
- end
-
- # Special characters are NOT allowed while posting transaction parameters on DirecPay system
- def remove_special_characters(string)
- string.gsub(/[~"'%]/, '-')
- end
-
- def encode_value(value)
- encoded = Base64.strict_encode64(value)
- string_to_encode = encoded[0, 1] + "T" + encoded[1, encoded.length]
- Base64.strict_encode64(string_to_encode)
- end
-
- def decode_value(value)
- decoded = Base64.decode64(value)
- string_to_decode = decoded[0, 1] + decoded[2, decoded.length]
- Base64.decode64(string_to_decode)
- end
-
- def phone_code_for_country(country)
- PHONE_CODES[country]
- end
-
- def full_name(params)
- return if params[:name].blank? && params[:first_name].blank? && params[:last_name].blank?
-
- params[:name] || "#{params[:first_name]} #{params[:last_name]}"
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/direc_pay/notification.rb b/lib/active_merchant/billing/integrations/direc_pay/notification.rb
deleted file mode 100644
index bf37e9ad140..00000000000
--- a/lib/active_merchant/billing/integrations/direc_pay/notification.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module DirecPay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- RESPONSE_PARAMS = ['DirecPay Reference ID', 'Flag', 'Country', 'Currency', 'Other Details', 'Merchant Order No', 'Amount']
-
- def acknowledge
- true
- end
-
- def complete?
- status == 'Completed' || status == 'Pending'
- end
-
- def status
- case params['Flag']
- when 'SUCCESS'
- 'Completed'
- when 'PENDING'
- 'Pending'
- when 'FAIL'
- 'Failed'
- else
- 'Error'
- end
- end
-
- def item_id
- params['Merchant Order No']
- end
-
- def transaction_id
- params['DirecPay Reference ID']
- end
-
- # the money amount we received in X.2 decimal
- def gross
- params['Amount']
- end
-
- def currency
- params['Currency']
- end
-
- def country
- params['Country']
- end
-
- def other_details
- params['Other Details']
- end
-
- def test?
- false
- end
-
- # Take the posted data and move the relevant data into a hash
- def parse(post)
- super
-
- values = params['responseparams'].to_s.split('|')
- response_params = values.size == 3 ? ['DirecPay Reference ID', 'Flag', 'Error message'] : RESPONSE_PARAMS
- response_params.each_with_index do |name, index|
- params[name] = values[index]
- end
- params
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/direc_pay/return.rb b/lib/active_merchant/billing/integrations/direc_pay/return.rb
deleted file mode 100644
index 2eb2497daeb..00000000000
--- a/lib/active_merchant/billing/integrations/direc_pay/return.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- module DirecPay
- class Return < ActiveMerchant::Billing::Integrations::Return
-
- def initialize(post_data, options = {})
- @notification = Notification.new(treat_failure_as_pending(post_data), options)
- end
-
- def success?
- notification.complete?
- end
-
- def message
- notification.status
- end
-
-
- private
-
- # Work around the issue that the initial return from DirecPay is always either SUCCESS or FAIL, there is no PENDING
- def treat_failure_as_pending(post_data)
- post_data.sub(/FAIL/, 'PENDING')
- end
- end
- end
-
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/direc_pay/status.rb b/lib/active_merchant/billing/integrations/direc_pay/status.rb
deleted file mode 100644
index 5b9f85200fd..00000000000
--- a/lib/active_merchant/billing/integrations/direc_pay/status.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module DirecPay
-
- class Status
- include PostsData
-
- STATUS_TEST_URL = 'https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp'
- STATUS_LIVE_URL = 'https://www.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp'
-
- attr_reader :account, :options
-
- def initialize(account, options = {})
- @account, @options = account, options
- end
-
-
- # Use this method to manually request a status update to the provided notification_url
- def update(authorization, notification_url)
- url = test? ? STATUS_TEST_URL : STATUS_LIVE_URL
- parameters = [ authorization, account, notification_url ]
- data = PostData.new
- data[:requestparams] = parameters.join('|')
-
- response = ssl_get("#{url}?#{data.to_post_data}")
- end
-
- def test?
- ActiveMerchant::Billing::Base.integration_mode == :test || options[:test]
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/directebanking.rb b/lib/active_merchant/billing/integrations/directebanking.rb
deleted file mode 100644
index df9ad5b5d04..00000000000
--- a/lib/active_merchant/billing/integrations/directebanking.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Directebanking
- autoload :Return, File.dirname(__FILE__) + '/directebanking/return.rb'
- autoload :Helper, File.dirname(__FILE__) + '/directebanking/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/directebanking/notification.rb'
-
- # Supported countries:
- # Germany - DE
- # Austria - AT
- # Belgium - BE
- # Netherlands - NL
- # Switzerland - CH
- # Great Britain - GB
-
- # Overwrite this if you want to change the directebanking test url
- mattr_accessor :test_url
- self.test_url = 'https://www.directebanking.com/payment/start'
-
- # Overwrite this if you want to change the directebanking production url
- mattr_accessor :production_url
- self.production_url = 'https://www.directebanking.com/payment/start'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification(post, options = {})
- Notification.new(post, options)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/directebanking/helper.rb b/lib/active_merchant/billing/integrations/directebanking/helper.rb
deleted file mode 100644
index 5e33720b700..00000000000
--- a/lib/active_merchant/billing/integrations/directebanking/helper.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Directebanking
- class Helper < ActiveMerchant::Billing::Integrations::Helper
-
- # All credentials are mandatory and need to be set
- #
- # credential1: User ID
- # credential2: Project ID
- # credential3: Project Password (Algorithm: SH1)
- # credential4: Notification Password (Algorithm: SH1)
- def initialize(order, account, options = {})
- super
- add_field('user_variable_0', order)
- add_field('project_id', options[:credential2])
- @project_password = options[:credential3]
- end
-
- SIGNATURE_FIELDS = [
- :user_id,
- :project_id,
- :sender_holder,
- :sender_account_number,
- :sender_bank_code,
- :sender_country_id,
- :amount,
- :currency_id,
- :reason_1,
- :reason_2,
- :user_variable_0,
- :user_variable_1,
- :user_variable_2,
- :user_variable_3,
- :user_variable_4,
- :user_variable_5
- ]
-
- SIGNATURE_IGNORE_AT_METHOD_CREATION_FIELDS = [
- :user_id,
- :amount,
- :project_id,
- :currency_id,
- :user_variable_0,
- :user_variable_1,
- :user_variable_2,
- :user_variable_3
- ]
-
- SIGNATURE_FIELDS.each do |key|
- if !SIGNATURE_IGNORE_AT_METHOD_CREATION_FIELDS.include?(key)
- mapping "#{key}".to_sym, "#{key.to_s}"
- end
- end
-
- # Need to format the amount to have 2 decimal places
- def amount=(money)
- cents = money.respond_to?(:cents) ? money.cents : money
- if money.is_a?(String) or cents.to_i <= 0
- raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
- end
- add_field mappings[:amount], sprintf("%.2f", cents.to_f/100)
- end
-
- def generate_signature_string
- # format of signature: user_id|project_id|sender_holder|sender_account_number|sender_bank_code| sender_country_id|amount|currency_id|reason_1|reason_2|user_variable_0|user_variable_1|user_variable_2|user_variable_3|user_variable_4|user_variable_5|project_password
- SIGNATURE_FIELDS.map {|key| @fields[key.to_s]} * "|" + "|#{@project_password}"
- end
-
- def generate_signature
- Digest::SHA1.hexdigest(generate_signature_string)
- end
-
- def form_fields
- @fields.merge('hash' => generate_signature)
- end
-
- mapping :account, 'user_id'
- mapping :amount, 'amount'
- mapping :currency, 'currency_id'
- mapping :description, 'reason_1'
-
- mapping :return_url, 'user_variable_1'
- mapping :cancel_return_url, 'user_variable_2'
- mapping :notify_url, 'user_variable_3'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/directebanking/notification.rb b/lib/active_merchant/billing/integrations/directebanking/notification.rb
deleted file mode 100644
index 7b60290d8e8..00000000000
--- a/lib/active_merchant/billing/integrations/directebanking/notification.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Directebanking
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- def initialize(data, options)
- if options[:credential4].nil?
- raise ArgumentError, "You need to provide the notification password (SH1) as the option :credential4 to verify that the notification originated from Directebanking (Payment Networks AG)"
- end
- super
- end
-
- def complete?
- status == 'Completed'
- end
-
- def item_id
- params['user_variable_0']
- end
-
- def transaction_id
- params['transaction']
- end
-
- # When was this payment received by the client.
- def received_at
- Time.parse(params['created']) if params['created']
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- "%.2f" % params['amount'].to_f
- end
-
- def status
- 'Completed'
- end
-
- def currency
- params['currency_id']
- end
-
- def test?
- params['sender_bank_name'] == 'Testbank'
- end
-
- # for verifying the signature of the URL parameters
- PAYMENT_HOOK_SIGNATURE_FIELDS = [
- :transaction,
- :user_id,
- :project_id,
- :sender_holder,
- :sender_account_number,
- :sender_bank_code,
- :sender_bank_name,
- :sender_bank_bic,
- :sender_iban,
- :sender_country_id,
- :recipient_holder,
- :recipient_account_number,
- :recipient_bank_code,
- :recipient_bank_name,
- :recipient_bank_bic,
- :recipient_iban,
- :recipient_country_id,
- :international_transaction,
- :amount,
- :currency_id,
- :reason_1,
- :reason_2,
- :security_criteria,
- :user_variable_0,
- :user_variable_1,
- :user_variable_2,
- :user_variable_3,
- :user_variable_4,
- :user_variable_5,
- :created
- ]
-
- PAYMENT_HOOK_IGNORE_AT_METHOD_CREATION_FIELDS = [
- :transaction,
- :amount,
- :currency_id,
- :user_variable_0,
- :user_variable_1,
- :user_variable_2,
- :user_variable_3,
- :created
- ]
-
- # Provide access to raw fields
- PAYMENT_HOOK_SIGNATURE_FIELDS.each do |key|
- if !PAYMENT_HOOK_IGNORE_AT_METHOD_CREATION_FIELDS.include?(key)
- define_method(key.to_s) do
- params[key.to_s]
- end
- end
- end
-
- def generate_signature_string
- #format is: transaction|user_id|project_id|sender_holder|sender_account_number|sender_bank_code|sender_bank_name|sender_bank_bic|sender_iban|sender_country_id|recipient_holder|recipient_account_number|recipient_bank_code|recipient_bank_name|recipient_bank_bic|recipient_iban|recipient_country_id|international_transaction|amount|currency_id|reason_1|reason_2|security_criteria|user_variable_0|user_variable_1|user_variable_2|user_variable_3|user_variable_4|user_variable_5|created|notification_password
- PAYMENT_HOOK_SIGNATURE_FIELDS.map {|key| params[key.to_s]} * "|" + "|#{@options[:credential4]}"
- end
-
- def generate_signature
- Digest::SHA1.hexdigest(generate_signature_string)
- end
-
- def acknowledge
- # signature_is_valid?
- generate_signature.to_s == params['hash'].to_s
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/directebanking/return.rb b/lib/active_merchant/billing/integrations/directebanking/return.rb
deleted file mode 100644
index 8f8825ad210..00000000000
--- a/lib/active_merchant/billing/integrations/directebanking/return.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Directebanking
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
-
diff --git a/lib/active_merchant/billing/integrations/dotpay.rb b/lib/active_merchant/billing/integrations/dotpay.rb
deleted file mode 100644
index 5a130bd7cbb..00000000000
--- a/lib/active_merchant/billing/integrations/dotpay.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dotpay
- autoload :Return, File.dirname(__FILE__) + '/dotpay/return.rb'
- autoload :Helper, File.dirname(__FILE__) + '/dotpay/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/dotpay/notification.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://ssl.dotpay.pl'
-
- def self.notification(post, options = {})
- Notification.new(post, options)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/dotpay/helper.rb b/lib/active_merchant/billing/integrations/dotpay/helper.rb
deleted file mode 100644
index 464106cdcd1..00000000000
--- a/lib/active_merchant/billing/integrations/dotpay/helper.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dotpay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- def initialize(order, account, options = {})
- options = {:currency => 'PLN'}.merge options
-
- super
-
- add_field('channel', '0')
- add_field('ch_lock', '0')
- add_field('lang', 'PL')
- add_field('onlinetransfer', '0')
- add_field('tax', '0')
- add_field('type', '2')
- end
-
- mapping :account, 'id'
- mapping :amount, 'amount'
-
- mapping :billing_address, :street => 'street',
- :street_n1 => 'street_n1',
- :street_n2 => 'street_n2',
- :addr2 => 'addr2',
- :addr3 => 'addr3',
- :city => 'city',
- :postcode => 'postcode',
- :phone => 'phone',
- :country => 'country'
-
- mapping :buttontext, 'buttontext'
- mapping :channel, 'channel'
- mapping :ch_lock, 'ch_lock'
- mapping :code, 'code'
- mapping :control, 'control'
- mapping :currency, 'currency'
-
- mapping :customer, :firstname => 'firstname',
- :lastname => 'lastname',
- :email => 'email'
-
- mapping :description, 'description'
- mapping :lang, 'lang'
- mapping :onlinetransfer, 'onlinetransfer'
- mapping :order, 'description'
- mapping :p_email, 'p_email'
- mapping :p_info, 'p_info'
- mapping :tax, 'tax'
- mapping :type, 'type'
- mapping :url, 'url'
- mapping :urlc, 'urlc'
-
- def billing_address(params = {})
- country = lookup_country_code(params.delete(:country) { 'POL' }, :alpha3)
- add_field(mappings[:billing_address][:country], country)
-
- # Everything else
- params.each do |k, v|
- field = mappings[:billing_address][k]
- add_field(field, v) unless field.nil?
- end
- end
-
- private
-
- def lookup_country_code(name_or_code, format = country_format)
- country = Country.find(name_or_code)
- country.code(format).to_s
- rescue InvalidCountryCodeError
- name_or_code
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/dotpay/notification.rb b/lib/active_merchant/billing/integrations/dotpay/notification.rb
deleted file mode 100644
index a403b15c3ef..00000000000
--- a/lib/active_merchant/billing/integrations/dotpay/notification.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dotpay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- def complete?
- status == 'OK' && %w(2 4 5).include?(t_status)
- end
-
- def currency
- orginal_amount.split(' ')[1]
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- params['amount']
- end
-
- def pin=(value)
- @options[:pin] = value
- end
-
- def status
- params['status']
- end
-
- def test?
- params['t_id'].match('.*-TST\d+') ? true : false
- end
-
- PAYMENT_HOOK_FIELDS = [
- :id,
- :control,
- :t_id,
- :orginal_amount,
- :email,
- :service,
- :code,
- :username,
- :password,
- :t_status,
- :description,
- :md5,
- :p_info,
- :p_email,
- :t_date
- ]
-
- PAYMENT_HOOK_SIGNATURE_FIELDS = [
- :id,
- :control,
- :t_id,
- :amount,
- :email,
- :service,
- :code,
- :username,
- :password,
- :t_status
- ]
-
- # Provide access to raw fields
- PAYMENT_HOOK_FIELDS.each do |key|
- define_method(key.to_s) do
- params[key.to_s]
- end
- end
-
- def generate_signature_string
- "#{@options[:pin]}:" + PAYMENT_HOOK_SIGNATURE_FIELDS.map {|key| params[key.to_s]} * ":"
- end
-
- def generate_signature
- Digest::MD5.hexdigest(generate_signature_string)
- end
-
- def acknowledge
- generate_signature.to_s == md5.to_s
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/dotpay/return.rb b/lib/active_merchant/billing/integrations/dotpay/return.rb
deleted file mode 100644
index 236db3d9d8c..00000000000
--- a/lib/active_merchant/billing/integrations/dotpay/return.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dotpay
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
-
diff --git a/lib/active_merchant/billing/integrations/dwolla.rb b/lib/active_merchant/billing/integrations/dwolla.rb
deleted file mode 100644
index e154bddaee6..00000000000
--- a/lib/active_merchant/billing/integrations/dwolla.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dwolla
- autoload :Return, 'active_merchant/billing/integrations/dwolla/return.rb'
- autoload :Helper, 'active_merchant/billing/integrations/dwolla/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/dwolla/notification.rb'
- autoload :Common, 'active_merchant/billing/integrations/dwolla/common.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://www.dwolla.com/payment/pay'
-
- def self.notification(post, options={})
- Notification.new(post, options)
- end
-
- def self.return(query_string, options={})
- Return.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/dwolla/common.rb b/lib/active_merchant/billing/integrations/dwolla/common.rb
deleted file mode 100644
index fbbb7e79658..00000000000
--- a/lib/active_merchant/billing/integrations/dwolla/common.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require "openssl"
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dwolla
- module Common
- def verify_signature(checkoutId, amount, notification_signature, secret)
- if secret.nil?
- raise ArgumentError, "You need to provide the Application secret as the option :credential3 to verify that the notification originated from Dwolla"
- end
-
- expected_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, "%s&%.2f" % [checkoutId, amount])
-
- if notification_signature != expected_signature
- raise StandardError, "Dwolla signature verification failed."
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/dwolla/helper.rb b/lib/active_merchant/billing/integrations/dwolla/helper.rb
deleted file mode 100644
index 290b12d511e..00000000000
--- a/lib/active_merchant/billing/integrations/dwolla/helper.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require "openssl"
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dwolla
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- def initialize(order, account, options = {})
- super
- add_field('name', 'Store Purchase')
-
- timestamp = Time.now.to_i.to_s
-
- if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test]
- add_field('test', 'true')
- # timestamp used for test signature generation:
- timestamp = "1370726016"
- end
-
- add_field('timestamp', timestamp)
- add_field('allowFundingSources', 'true')
-
- key = options[:credential2].to_s
- secret = options[:credential3].to_s
- orderid = order.to_s
- signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, "#{key}{timestamp}{orderid}")
- add_field('signature', signature)
- end
-
- mapping :account, 'destinationid'
- mapping :credential2, 'key'
- mapping :notify_url, 'callback'
- mapping :return_url, 'redirect'
- mapping :description, 'description'
- mapping :amount, 'amount'
- mapping :tax, 'tax'
- mapping :shipping, 'shipping'
- mapping :order, 'orderid'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/dwolla/notification.rb b/lib/active_merchant/billing/integrations/dwolla/notification.rb
deleted file mode 100644
index 112657f27ff..00000000000
--- a/lib/active_merchant/billing/integrations/dwolla/notification.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'net/http'
-require 'digest/sha1'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dwolla
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include Common
-
- def initialize(data, options)
- super
- end
-
- def complete?
- (status == "Completed")
- end
-
- def status
- params["Status"]
- end
-
- def transaction_id
- params['TransactionId']
- end
-
- def item_id
- params['OrderId']
- end
-
- def currency
- "USD"
- end
-
- def gross
- params['Amount']
- end
-
- def error
- params['Error']
- end
-
- def test?
- params['TestMode'] != "false"
- end
-
- def acknowledge
- true
- end
-
- private
-
- def parse(post)
- @raw = post.to_s
- json_post = JSON.parse(post)
- verify_signature(json_post['CheckoutId'], json_post['Amount'], json_post['Signature'], @options[:credential3])
-
- params.merge!(json_post)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/dwolla/return.rb b/lib/active_merchant/billing/integrations/dwolla/return.rb
deleted file mode 100644
index 837dc545f26..00000000000
--- a/lib/active_merchant/billing/integrations/dwolla/return.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Dwolla
- class Return < ActiveMerchant::Billing::Integrations::Return
- include Common
-
- def initialize(data, options)
- params = parse(data)
-
- if params['error'] != 'failure'
- verify_signature(params['checkoutId'], params['amount'], params['signature'], options[:credential3])
- end
-
- super
- end
-
- def success?
- (self.error.nil? && self.callback_success?)
- end
-
- def error
- params['error']
- end
-
- def error_description
- params['error_description']
- end
-
- def checkout_id
- params['checkoutId']
- end
-
- def transaction
- params['transaction']
- end
-
- def test?
- params['test'] != nil
- end
-
- def callback_success?
- (params['postback'] != "failure")
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/e_payment_plans.rb b/lib/active_merchant/billing/integrations/e_payment_plans.rb
deleted file mode 100644
index 5614a0bac3b..00000000000
--- a/lib/active_merchant/billing/integrations/e_payment_plans.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module EPaymentPlans
- autoload :Helper, File.dirname(__FILE__) + '/e_payment_plans/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/e_payment_plans/notification.rb'
-
- mattr_accessor :production_url
- self.production_url = 'https://www.epaymentplans.com'
-
- mattr_accessor :test_url
- self.test_url = 'https://test.epaymentplans.com'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- "#{production_url}/order/purchase"
- when :test
- "#{test_url}/order/purchase"
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification_confirmation_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- "#{production_url}/order/confirmation"
- when :test
- "#{test_url}/order/confirmation"
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification(post, options = {})
- Notification.new(post, options)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb b/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb
deleted file mode 100644
index 76a56101450..00000000000
--- a/lib/active_merchant/billing/integrations/e_payment_plans/helper.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module EPaymentPlans
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :account, 'order[account]'
- mapping :amount, 'order[amount]'
-
- mapping :order, 'order[num]'
-
- mapping :customer, :first_name => 'order[first_name]',
- :last_name => 'order[last_name]',
- :email => 'order[email]',
- :phone => 'order[phone]'
-
- mapping :billing_address, :city => 'order[city]',
- :address1 => 'order[address1]',
- :address2 => 'order[address2]',
- :company => 'order[company]',
- :state => 'order[state]',
- :zip => 'order[zip]',
- :country => 'order[country]'
-
- mapping :notify_url, 'order[notify_url]'
- mapping :return_url, 'order[return_url]'
- mapping :cancel_return_url, 'order[cancel_return_url]'
- mapping :description, 'order[description]'
- mapping :tax, 'order[tax]'
- mapping :shipping, 'order[shipping]'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb b/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb
deleted file mode 100644
index 339e25ee74b..00000000000
--- a/lib/active_merchant/billing/integrations/e_payment_plans/notification.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module EPaymentPlans
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include ActiveMerchant::PostsData
- def complete?
- status == "Completed"
- end
-
- def transaction_id
- params['transaction_id']
- end
-
- def item_id
- params['item_id']
- end
-
- # When was this payment received by the client.
- def received_at
- Time.parse(params['received_at'].to_s).utc
- end
-
- def gross
- params['gross']
- end
-
- def currency
- params['currency']
- end
-
- def security_key
- params['security_key']
- end
-
- # Was this a test transaction?
- def test?
- params['test'] == 'test'
- end
-
- def status
- params['status'].capitalize
- end
-
- # Acknowledge the transaction to EPaymentPlans. This method has to be called after a new
- # apc arrives. EPaymentPlans will verify that all the information we received are correct
- # and will return ok or a fail.
- #
- # Example:
- #
- # def ipn
- # notify = EPaymentPlans.notification(request.raw_post)
- #
- # if notify.acknowledge
- # ... process order ... if notify.complete?
- # else
- # ... log possible hacking attempt ...
- # end
- def acknowledge
- payload = raw
-
- response = ssl_post(EPaymentPlans.notification_confirmation_url, payload)
-
- # Replace with the appropriate codes
- raise StandardError.new("Faulty EPaymentPlans result: #{response}") unless ["AUTHORISED", "DECLINED"].include?(response)
- response == "AUTHORISED"
- end
-
- private
- # Take the posted data and move the relevant data into a hash
- def parse(post)
- @raw = post
- for line in post.split('&')
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
- params[key] = value
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/easy_pay.rb b/lib/active_merchant/billing/integrations/easy_pay.rb
deleted file mode 100644
index 701f1d19b25..00000000000
--- a/lib/active_merchant/billing/integrations/easy_pay.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- # Documentation: https://ssl.easypay.by/light/
- module EasyPay
- autoload :Helper, File.dirname(__FILE__) + '/easy_pay/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/easy_pay/notification.rb'
- autoload :Common, File.dirname(__FILE__) + '/easy_pay/common.rb'
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'EP_Hash'
-
- mattr_accessor :notify_signature_parameter_name
- self.notify_signature_parameter_name = 'notify_signature'
-
- mattr_accessor :service_url
- self.service_url = 'https://ssl.easypay.by/weborder/'
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/easy_pay/common.rb b/lib/active_merchant/billing/integrations/easy_pay/common.rb
deleted file mode 100644
index 74a8e4b2278..00000000000
--- a/lib/active_merchant/billing/integrations/easy_pay/common.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module EasyPay
- module Common
- def generate_signature(type)
- string = case type
- when :request
- request_signature_string
- when :notify
- notify_signature_string
- end
-
- Digest::MD5.hexdigest(string)
- end
-
- def request_signature_string
- [
- @fields[mappings[:account]],
- @secret,
- @fields[mappings[:order]],
- @fields[mappings[:amount]]
- ].join
- end
-
- def notify_signature_string
- [
- params['order_mer_code'],
- params['sum'],
- params['mer_no'],
- params['card'],
- params['purch_date'],
- secret
- ].join
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/easy_pay/helper.rb b/lib/active_merchant/billing/integrations/easy_pay/helper.rb
deleted file mode 100644
index 5df2a4f403c..00000000000
--- a/lib/active_merchant/billing/integrations/easy_pay/helper.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module EasyPay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Common
-
- def initialize(order, account, options = {})
- super
- @secret = options[:credential2]
- end
-
- def form_fields
- @fields.merge(ActiveMerchant::Billing::Integrations::EasyPay.signature_parameter_name => generate_signature(:request))
- end
-
- def params
- @fields
- end
-
- mapping :account, 'EP_MerNo'
- mapping :amount, 'EP_Sum'
- mapping :order, 'EP_OrderNo'
- mapping :comment, 'EP_Comment'
- mapping :order_info, 'EP_OrderInfo'
- mapping :expires, 'EP_Expires'
- mapping :success_url, 'EP_Success_URL'
- mapping :cancel_url, 'EP_Cancel_URL'
- mapping :debug, 'EP_Debug'
- mapping :url_type, 'EP_URL_Type'
- mapping :encoding, 'EP_Encoding'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/easy_pay/notification.rb b/lib/active_merchant/billing/integrations/easy_pay/notification.rb
deleted file mode 100644
index 90ce7e629a4..00000000000
--- a/lib/active_merchant/billing/integrations/easy_pay/notification.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module EasyPay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include Common
-
- def initialize(data, options)
- if options[:credential2].nil?
- raise ArgumentError, "You need to provide the md5 secret as the option :credential2 to verify that the notification originated from EasyPay"
- end
-
- super
- end
-
- def self.recognizes?(params)
- params.has_key?('order_mer_code') && params.has_key?('sum')
- end
-
- def complete?
- true
- end
-
- def amount
- BigDecimal.new(gross)
- end
-
- def item_id
- params['order_mer_code']
- end
-
- def security_key
- params[ActiveMerchant::Billing::Integrations::EasyPay.notify_signature_parameter_name]
- end
-
- def gross
- params['sum']
- end
-
- def status
- 'Completed'
- end
-
- def secret
- @options[:credential2]
- end
-
- def acknowledge
- security_key == generate_signature(:notify)
- end
-
- def success_response(*args)
- { :nothing => true }
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/epay.rb b/lib/active_merchant/billing/integrations/epay.rb
deleted file mode 100644
index 24de6aae014..00000000000
--- a/lib/active_merchant/billing/integrations/epay.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Epay
- autoload :Helper, File.dirname(__FILE__) + '/epay/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/epay/notification.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://ssl.ditonlinebetalingssystem.dk/integration/ewindow/Default.aspx'
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/integrations/epay/helper.rb b/lib/active_merchant/billing/integrations/epay/helper.rb
deleted file mode 100644
index 650dcd661c0..00000000000
--- a/lib/active_merchant/billing/integrations/epay/helper.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Epay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
-
- def initialize(order, merchantnumber, options = {})
- super
- add_field('windowstate', 3)
- add_field('language', '0')
- add_field('orderid', format_order_number(order))
- @fields = Hash[@fields.sort]
- end
-
- def md5secret(value)
- @md5secret = value
- end
-
- def form_fields
- @fields.merge('hash' => generate_md5hash)
- end
-
- def generate_md5string
- @fields.sort.each.map { |key, value| key != 'hash' ? value.to_s : ''} * "" + @md5secret
- end
-
- def generate_md5hash
- Digest::MD5.hexdigest(generate_md5string)
- end
-
- # Limited to 20 digits max
- def format_order_number(number)
- number.to_s.gsub(/[^\w_]/, '').rjust(4, "0")[0...20]
- end
-
- mapping :account, 'merchantnumber'
- mapping :language, 'language'
- mapping :amount, 'amount'
- mapping :currency, 'currency'
- mapping :return_url, 'accepturl'
- mapping :cancel_return_url, 'cancelurl'
- mapping :notify_url, 'callbackurl'
- mapping :autocapture, 'instantcapture'
- mapping :description, 'description'
- mapping :credential3, 'md5secret'
- mapping :customer, ''
- mapping :billing_address, {}
- mapping :tax, ''
- mapping :shipping, ''
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/epay/notification.rb b/lib/active_merchant/billing/integrations/epay/notification.rb
deleted file mode 100644
index aa868ee51b7..00000000000
--- a/lib/active_merchant/billing/integrations/epay/notification.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Epay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- CURRENCY_CODES = {
- :ADP => '020', :AED => '784', :AFA => '004', :ALL => '008', :AMD => '051',
- :ANG => '532', :AOA => '973', :ARS => '032', :AUD => '036', :AWG => '533',
- :AZM => '031', :BAM => '977', :BBD => '052', :BDT => '050', :BGL => '100',
- :BGN => '975', :BHD => '048', :BIF => '108', :BMD => '060', :BND => '096',
- :BOB => '068', :BOV => '984', :BRL => '986', :BSD => '044', :BTN => '064',
- :BWP => '072', :BYR => '974', :BZD => '084', :CAD => '124', :CDF => '976',
- :CHF => '756', :CLF => '990', :CLP => '152', :CNY => '156', :COP => '170',
- :CRC => '188', :CUP => '192', :CVE => '132', :CYP => '196', :CZK => '203',
- :DJF => '262', :DKK => '208', :DOP => '214', :DZD => '012', :ECS => '218',
- :ECV => '983', :EEK => '233', :EGP => '818', :ERN => '232', :ETB => '230',
- :EUR => '978', :FJD => '242', :FKP => '238', :GBP => '826', :GEL => '981',
- :GHC => '288', :GIP => '292', :GMD => '270', :GNF => '324', :GTQ => '320',
- :GWP => '624', :GYD => '328', :HKD => '344', :HNL => '340', :HRK => '191',
- :HTG => '332', :HUF => '348', :IDR => '360', :ILS => '376', :INR => '356',
- :IQD => '368', :IRR => '364', :ISK => '352', :JMD => '388', :JOD => '400',
- :JPY => '392', :KES => '404', :KGS => '417', :KHR => '116', :KMF => '174',
- :KPW => '408', :KRW => '410', :KWD => '414', :KYD => '136', :KZT => '398',
- :LAK => '418', :LBP => '422', :LKR => '144', :LRD => '430', :LSL => '426',
- :LTL => '440', :LVL => '428', :LYD => '434', :MAD => '504', :MDL => '498',
- :MGF => '450', :MKD => '807', :MMK => '104', :MNT => '496', :MOP => '446',
- :MRO => '478', :MTL => '470', :MUR => '480', :MVR => '462', :MWK => '454',
- :MXN => '484', :MXV => '979', :MYR => '458', :MZM => '508', :NAD => '516',
- :NGN => '566', :NIO => '558', :NOK => '578', :NPR => '524', :NZD => '554',
- :OMR => '512', :PAB => '590', :PEN => '604', :PGK => '598', :PHP => '608',
- :PKR => '586', :PLN => '985', :PYG => '600', :QAR => '634', :ROL => '642',
- :RUB => '643', :RUR => '810', :RWF => '646', :SAR => '682', :SBD => '090',
- :SCR => '690', :SDD => '736', :SEK => '752', :SGD => '702', :SHP => '654',
- :SIT => '705', :SKK => '703', :SLL => '694', :SOS => '706', :SRG => '740',
- :STD => '678', :SVC => '222', :SYP => '760', :SZL => '748', :THB => '764',
- :TJS => '972', :TMM => '795', :TND => '788', :TOP => '776', :TPE => '626',
- :TRL => '792', :TRY => '949', :TTD => '780', :TWD => '901', :TZS => '834',
- :UAH => '980', :UGX => '800', :USD => '840', :UYU => '858', :UZS => '860',
- :VEB => '862', :VND => '704', :VUV => '548', :XAF => '950', :XCD => '951',
- :XOF => '952', :XPF => '953', :YER => '886', :YUM => '891', :ZAR => '710',
- :ZMK => '894', :ZWD => '716'
- }
-
- def complete?
- Integer(transaction_id) > 0
- end
-
- def item_id
- params['orderid']
- end
-
- def transaction_id
- params['txnid']
- end
-
- def received_at
- Time.mktime(params['date'][0..3], params['date'][4..5], params['date'][6..7], params['time'][0..1], params['time'][2..3])
- end
-
- def gross
- "%.2f" % (gross_cents / 100.0)
- end
-
- def gross_cents
- params['amount'].to_i
- end
-
- def test?
- return false
- end
-
- %w(txnid orderid amount currency date time hash fraud payercountry issuercountry txnfee subscriptionid paymenttype cardno).each do |attr|
- define_method(attr) do
- params[attr]
- end
- end
-
- def currency
- CURRENCY_CODES.invert[params['currency']].to_s
- end
-
- def amount
- Money.new(params['amount'].to_i, currency)
- end
-
- def generate_md5string
- md5string = String.new
- for line in @raw.split('&')
- key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten
- md5string += params[key] if key != 'hash'
- end
- return md5string + @options[:credential3]
- end
-
- def generate_md5hash
- Digest::MD5.hexdigest(generate_md5string)
- end
-
- def acknowledge
- generate_md5hash == params['hash']
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/first_data.rb b/lib/active_merchant/billing/integrations/first_data.rb
deleted file mode 100644
index 65e8f84743e..00000000000
--- a/lib/active_merchant/billing/integrations/first_data.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module FirstData
- autoload :Helper, 'active_merchant/billing/integrations/first_data/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/first_data/notification.rb'
-
- # Overwrite this if you want to change the ANS test url
- mattr_accessor :test_url
- self.test_url = 'https://demo.globalgatewaye4.firstdata.com/payment'
-
- # Overwrite this if you want to change the ANS production url
- mattr_accessor :production_url
- self.production_url = 'https://checkout.globalgatewaye4.firstdata.com/payment'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification(post)
- Notification.new(post)
- end
-
- def self.return(query_string)
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/first_data/helper.rb b/lib/active_merchant/billing/integrations/first_data/helper.rb
deleted file mode 100644
index ae314ae7e6f..00000000000
--- a/lib/active_merchant/billing/integrations/first_data/helper.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module FirstData
- # First Data payment pages emulates the Authorize.Net SIM API. See
- # ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Helper for
- # more details.
- #
- # An example. Note the username as a parameter and transaction key you
- # will want to use later.
- #
- # payment_service_for('order_id', 'first_data_payment_page_id', :service => :first_data, :amount => 157.0) do |service|
- #
- # # You must call setup_hash and invoice
- #
- # service.setup_hash :transaction_key => '8CP6zJ7uD875J6tY',
- # :order_timestamp => 1206836763
- # service.customer_id 8
- # service.customer :first_name => 'g',
- # :last_name => 'g',
- # :email => 'g@g.com',
- # :phone => '3'
- # service.billing_address :zip => 'g',
- # :country => 'United States of America',
- # :address => 'g'
- #
- # service.ship_to_address :first_name => 'g',
- # :last_name => 'g',
- # :city => '',
- # :address => 'g',
- # :address2 => '',
- # :state => address.state,
- # :country => 'United States of America',
- # :zip => 'g'
- #
- # service.invoice "516428355" # your invoice number
- # # The end-user is presented with the HTML produced by the notify_url
- # # (using the First Data Receipt Link feature).
- # service.return_url "http://mysite/first_data_receipt_generator_page"
- # service.payment_header 'My store name'
- # service.add_line_item :name => 'item name', :quantity => 1, :unit_price => 0
- # service.test_request 'true' # only if it's just a test
- # service.shipping '25.0'
- # # Tell it to display a "0" line item for shipping, with the price in
- # # the name, otherwise it isn't shown at all, leaving the end user to
- # # wonder why the total is different than the sum of the line items.
- # service.add_shipping_as_line_item
- # server.add_tax_as_line_item # same with tax
- # # See the helper.rb file for various custom fields
- # end
- class Helper < ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Helper
- # Configure notify_url to use the "Relay Response" feature
- mapping :notify_url, 'x_relay_url'
-
- # Configure return_url to use the "Receipt Link" feature
- mapping :return_url, 'x_receipt_link_url'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/first_data/notification.rb b/lib/active_merchant/billing/integrations/first_data/notification.rb
deleted file mode 100644
index 2e786ff3db8..00000000000
--- a/lib/active_merchant/billing/integrations/first_data/notification.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- # First Data payment pages emulates the Authorize.Net SIM API. See
- # ActiveMerchant::Billing::Integrations::FirstData::Notification for
- # more details.
- #
- # # Example:
- # parser = FirstData::Notification.new(request.raw_post)
- # passed = parser.complete?
- #
- # order = Order.find_by_order_number(parser.invoice_num)
- #
- # unless order
- # @message = 'Error--unable to find your transaction! Please contact us directly.'
- # return render :partial => 'first_data_payment_response'
- # end
- #
- # if order.total != parser.gross.to_f
- # logger.error "First Data said they paid for #{parser.gross} and it should have been #{order.total}!"
- # passed = false
- # end
- #
- # # Theoretically, First Data will *never* pass us the same transaction
- # # ID twice, but we can double check that... by using
- # # parser.transaction_id, and checking against previous orders' transaction
- # # id's (which you can save when the order is completed)....
- # unless parser.acknowledge FIRST_DATA_TRANSACTION_KEY, FIRST_DATA_RESPONSE_KEY
- # passed = false
- # logger.error "ALERT POSSIBLE FRAUD ATTEMPT"
- # end
- #
- # unless parser.cavv_matches? and parser.avs_code_matches?
- # logger.error 'Warning--non matching CC!' + params.inspect
- # # Could fail them here, as well (recommended)...
- # end
- #
- # if passed
- # # Set up your session, and render something that will redirect them to
- # # your site, most likely.
- # else
- # # Render failure or redirect them to your site where you will render failure
- # end
-
- module FirstData
- class Notification < ActiveMerchant::Billing::Integrations::AuthorizeNetSim::Notification
- def acknowledge(response_key, payment_page_id)
- Digest::MD5.hexdigest(response_key + payment_page_id + params['x_trans_id'] + sprintf('%.2f', gross)) == params['x_MD5_Hash'].downcase
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/gestpay.rb b/lib/active_merchant/billing/integrations/gestpay.rb
deleted file mode 100644
index fca773c78a0..00000000000
--- a/lib/active_merchant/billing/integrations/gestpay.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# With help from Giovanni Intini and his code for RGestPay - http://medlar.it/it/progetti/rgestpay
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Gestpay
- autoload :Return, File.dirname(__FILE__) + '/gestpay/return.rb'
- autoload :Common, File.dirname(__FILE__) + '/gestpay/common.rb'
- autoload :Helper, File.dirname(__FILE__) + '/gestpay/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/gestpay/notification.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://ecomm.sella.it/gestpay/pagam.asp'
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/gestpay/common.rb b/lib/active_merchant/billing/integrations/gestpay/common.rb
deleted file mode 100644
index 213e47a1248..00000000000
--- a/lib/active_merchant/billing/integrations/gestpay/common.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Gestpay
- module Common
- VERSION = "2.0"
- ENCRYPTION_PATH = "/CryptHTTPS/Encrypt.asp"
- DECRYPTION_PATH = "/CryptHTTPS/Decrypt.asp"
- DELIMITER = '*P1*'
-
- CURRENCY_MAPPING = {
- 'EUR' => '242',
- 'ITL' => '18',
- 'BRL' => '234',
- 'USD' => '1',
- 'JPY' => '71',
- 'HKD' => '103'
- }
-
- def parse_response(response)
- case response
- when /#cryptstring#(.*)#\/cryptstring#/, /#decryptstring#(.*)#\/decryptstring#/
- $1
- when /#error#(.*)#\/error#/
- raise StandardError, "An error occurred retrieving the encrypted string from GestPay: #{$1}"
- else
- raise StandardError, "No response was received by GestPay"
- end
- end
-
- def ssl_get(url, path)
- uri = URI.parse(url)
- site = Net::HTTP.new(uri.host, uri.port)
- site.use_ssl = true
- site.verify_mode = OpenSSL::SSL::VERIFY_NONE
- site.get(path).body
- end
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/integrations/gestpay/helper.rb b/lib/active_merchant/billing/integrations/gestpay/helper.rb
deleted file mode 100644
index 2cb4b04e903..00000000000
--- a/lib/active_merchant/billing/integrations/gestpay/helper.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Gestpay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Common
- # Valid language codes
- # Italian => 1
- # English => 2
- # Spanish => 3
- # French => 4
- # Tedesco => 5
- def initialize(order, account, options = {})
- super
- add_field('PAY1_IDLANGUAGE', 2)
- end
-
- mapping :account, 'ShopLogin'
-
- mapping :amount, 'PAY1_AMOUNT'
- mapping :currency, 'PAY1_UICCODE'
-
- mapping :order, 'PAY1_SHOPTRANSACTIONID'
-
- # Buyer name PAY1_CHNAME
- mapping :customer, :email => 'PAY1_CHEMAIL'
-
- mapping :credit_card, :number => 'PAY1_CARDNUMBER',
- :expiry_month => 'PAY1_EXPMONTH',
- :expiry_year => 'PAY1_EXPYEAR',
- :verification_value => 'PAY1_CVV'
-
- def customer(params = {})
- add_field(mappings[:customer][:email], params[:email])
- add_field('PAY1_CHNAME', "#{params[:first_name]} #{params[:last_name]}")
- end
-
- def currency=(currency_code)
- code = CURRENCY_MAPPING[currency_code]
- raise StandardError, "Invalid currency code #{currency_code} specified" if code.nil?
-
- add_field(mappings[:currency], code)
- end
-
- def form_fields
- @encrypted_data ||= get_encrypted_string
-
- {
- 'a' => @fields['ShopLogin'],
- 'b' => @encrypted_data
- }
- end
-
- def get_encrypted_string
- response = ssl_get(Gestpay.service_url, encryption_query_string)
- parse_response(response)
- end
-
- def encryption_query_string
- fields = ['PAY1_AMOUNT', 'PAY1_SHOPTRANSACTIONID', 'PAY1_UICCODE']
-
- encoded_params = fields.collect{ |field| "#{field}=#{CGI.escape(@fields[field])}" }.join(DELIMITER)
-
- "#{ENCRYPTION_PATH}?a=" + CGI.escape(@fields['ShopLogin']) + "&b=" + encoded_params + "&c=" + CGI.escape(VERSION)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/gestpay/notification.rb b/lib/active_merchant/billing/integrations/gestpay/notification.rb
deleted file mode 100644
index 27ed5773680..00000000000
--- a/lib/active_merchant/billing/integrations/gestpay/notification.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Gestpay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include Common
-
- def complete?
- status == 'Completed'
- end
-
- # The important param
- def item_id
- params['PAY1_SHOPTRANSACTIONID']
- end
-
- def transaction_id
- params['PAY1_BANKTRANSACTIONID']
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- params['PAY1_AMOUNT']
- end
-
- def currency
- # Ruby 1.9 compat
- method = CURRENCY_MAPPING.respond_to?(:key) ? :key : :index
- CURRENCY_MAPPING.send(method, params['PAY1_UICCODE'])
- end
-
- def test?
- false
- end
-
- def status
- case params['PAY1_TRANSACTIONRESULT']
- when 'OK'
- 'Completed'
- else
- 'Failed'
- end
- end
-
- def acknowledge
- true
- end
-
- private
- # Take the posted data and move the relevant data into a hash
- def parse(query_string)
- @raw = query_string
-
- return if query_string.blank?
- encrypted_params = parse_delimited_string(query_string)
-
- return if encrypted_params['a'].blank? || encrypted_params['b'].blank?
- @params = decrypt_data(encrypted_params['a'], encrypted_params['b'])
- end
-
- def parse_delimited_string(string, delimiter = '&', unencode_cgi = false)
- result = {}
- for line in string.split(delimiter)
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
- result[key] = unencode_cgi ? CGI.unescape(value) : value
- end
- result
- end
-
- def decrypt_data(shop_login, encrypted_string)
- response = ssl_get(Gestpay.service_url, decryption_query_string(shop_login, encrypted_string))
- encoded_response = parse_response(response)
- parse_delimited_string(encoded_response, DELIMITER, true)
- end
-
- def decryption_query_string(shop_login, encrypted_string)
- "#{DECRYPTION_PATH}?a=" + CGI.escape(shop_login) + "&b=" + encrypted_string + "&c=" + CGI.escape(VERSION)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/gestpay/return.rb b/lib/active_merchant/billing/integrations/gestpay/return.rb
deleted file mode 100644
index 0c01f286eff..00000000000
--- a/lib/active_merchant/billing/integrations/gestpay/return.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Gestpay
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/helper.rb b/lib/active_merchant/billing/integrations/helper.rb
deleted file mode 100644
index dc742de5597..00000000000
--- a/lib/active_merchant/billing/integrations/helper.rb
+++ /dev/null
@@ -1,117 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- class Helper #:nodoc:
- attr_reader :fields
- class_attribute :service_url
- class_attribute :mappings
- class_attribute :country_format
- self.country_format = :alpha2
-
- # The application making the calls to the gateway
- # Useful for things like the PayPal build notation (BN) id fields
- class_attribute :application_id
- self.application_id = 'ActiveMerchant'
-
- def initialize(order, account, options = {})
- options.assert_valid_keys([:amount, :currency, :test, :credential2, :credential3, :credential4, :country, :account_name, :transaction_type])
- @fields = {}
- @raw_html_fields = []
- @test = options[:test]
- self.order = order
- self.account = account
- self.amount = options[:amount]
- self.currency = options[:currency]
- self.credential2 = options[:credential2]
- self.credential3 = options[:credential3]
- self.credential4 = options[:credential4]
- end
-
- def self.mapping(attribute, options = {})
- self.mappings ||= {}
- self.mappings[attribute] = options
- end
-
- def add_field(name, value)
- return if name.blank? || value.blank?
- @fields[name.to_s] = value.to_s
- end
-
- def add_fields(subkey, params = {})
- params.each do |k, v|
- field = mappings[subkey][k]
- add_field(field, v) unless field.blank?
- end
- end
-
- # Add a field that has characters that CGI::escape would mangle. Allows
- # for multiple fields with the same name (e.g., to support line items).
- def add_raw_html_field(name, value)
- return if name.blank? || value.blank?
- @raw_html_fields << [name, value]
- end
-
- def raw_html_fields
- @raw_html_fields
- end
-
- def billing_address(params = {})
- add_address(:billing_address, params)
- end
-
- def shipping_address(params = {})
- add_address(:shipping_address, params)
- end
-
- def form_fields
- @fields
- end
-
- def test?
- @test_mode ||= ActiveMerchant::Billing::Base.integration_mode == :test || @test
- end
-
- def form_method
- "POST"
- end
-
- private
-
- def add_address(key, params)
- return if mappings[key].nil?
-
- code = lookup_country_code(params.delete(:country))
- add_field(mappings[key][:country], code)
- add_fields(key, params)
- end
-
- def lookup_country_code(name_or_code, format = country_format)
- country = Country.find(name_or_code)
- country.code(format).to_s
- rescue InvalidCountryCodeError
- name_or_code
- end
-
- def method_missing(method_id, *args)
- method_id = method_id.to_s.gsub(/=$/, '').to_sym
- # Return and do nothing if the mapping was not found. This allows
- # For easy substitution of the different integrations
- return if mappings[method_id].nil?
-
- mapping = mappings[method_id]
-
- case mapping
- when Array
- mapping.each{ |field| add_field(field, args.last) }
- when Hash
- options = args.last.is_a?(Hash) ? args.pop : {}
-
- mapping.each{ |key, field| add_field(field, options[key]) }
- else
- add_field(mapping, args.last)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/hi_trust.rb b/lib/active_merchant/billing/integrations/hi_trust.rb
deleted file mode 100644
index b5be207bbd0..00000000000
--- a/lib/active_merchant/billing/integrations/hi_trust.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module HiTrust
- autoload :Helper, File.dirname(__FILE__) + '/hi_trust/helper.rb'
- autoload :Return, File.dirname(__FILE__) + '/hi_trust/return.rb'
- autoload :Notification, File.dirname(__FILE__) + '/hi_trust/notification.rb'
-
- TEST_URL = 'https://testtrustlink.hitrust.com.tw/TrustLink/TrxReqForJava'
- LIVE_URL = 'https://trustlink.hitrust.com.tw/TrustLink/TrxReqForJava'
-
- def self.service_url
- ActiveMerchant::Billing::Base.integration_mode == :test ? TEST_URL : LIVE_URL
- end
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/hi_trust/helper.rb b/lib/active_merchant/billing/integrations/hi_trust/helper.rb
deleted file mode 100644
index a2867296033..00000000000
--- a/lib/active_merchant/billing/integrations/hi_trust/helper.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module HiTrust
- class Helper < ActiveMerchant::Billing::Integrations::Helper
-
- # Transaction types
- # * Auth
- # * AuthRe
- # * Capture
- # * CaptureRe
- # * Refund
- # * RefundRe
- # * Query
- def initialize(order, account, options = {})
- super
- # Perform an authorization by default
- add_field('Type', 'Auth')
-
- # Capture the payment right away
- add_field('depositflag', '1')
-
- # Disable auto query - who knows what it does?
- add_field('queryflag', '1')
-
- add_field('orderdesc', 'Store purchase')
- end
-
- mapping :account, 'storeid'
- mapping :amount, 'amount'
-
- def amount=(money)
- cents = money.respond_to?(:cents) ? money.cents : money
-
- if money.is_a?(String) or cents.to_i < 0
- raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
- end
-
- add_field(mappings[:amount], cents)
- end
- # Supported currencies include:
- # * CNY:Chinese Yuan (Renminbi)
- # * TWD:New Taiwan Dollar
- # * HKD:Hong Kong Dollar
- # * USD:US Dollar
- # * AUD:Austrian Dollar
- mapping :currency, 'currency'
-
- mapping :order, 'ordernumber'
- mapping :description, 'orderdesc'
-
- mapping :notify_url, 'merUpdateURL'
- mapping :return_url, 'returnURL'
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/integrations/hi_trust/notification.rb b/lib/active_merchant/billing/integrations/hi_trust/notification.rb
deleted file mode 100644
index 06a3d8f769d..00000000000
--- a/lib/active_merchant/billing/integrations/hi_trust/notification.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module HiTrust
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- SUCCESS = '00'
-
- self.production_ips = [ '203.75.242.8' ]
-
- def complete?
- status == 'Completed'
- end
-
- def transaction_id
- params['authRRN']
- end
-
- def item_id
- params['ordernumber']
- end
-
- def received_at
- Time.parse(params['orderdate']) rescue nil
- end
-
- def currency
- params['currency']
- end
-
- def gross
- sprintf("%.2f", gross_cents.to_f / 100)
- end
-
- def gross_cents
- params['approveamount'].to_i
- end
-
- def account
- params['storeid']
- end
-
- def status
- params['retcode'] == SUCCESS ? 'Completed' : 'Failed'
- end
-
- def test?
- ActiveMerchant::Billing::Base.integration_mode == :test
- end
-
- def acknowledge
- true
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/hi_trust/return.rb b/lib/active_merchant/billing/integrations/hi_trust/return.rb
deleted file mode 100644
index 846013da6d8..00000000000
--- a/lib/active_merchant/billing/integrations/hi_trust/return.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module HiTrust
- class Return < ActiveMerchant::Billing::Integrations::Return
- SUCCESS = "00"
- CODES = { "00" => "Operation completed successfully",
- "-1" => "Unable to initialize winsock dll.",
- "-2" => "Can't create stream socket.",
- "-3" => "No Request Message.",
- "-4" => "Can't connect to server.",
- "-5" => "Send socket error.",
- "-6" => "Couldn't receive data.",
- "-7" => "Receive Broken message.",
- "-8" => "Unable to initialize Envirnment.",
- "-9" => "Can't Read Server RSA File.",
- "-10" => "Can't Read Client RSA File.",
- "-11" => "Web Server error.",
- "-12" => "Receive Message type error.",
- "-13" => "No Request Message.",
- "-14" => "No Response Content.",
- "-18" => "Merchant Update URL not found.",
- "-19" => "Server URL not find Domain or IP.",
- "-20" => "Server URL only can fill http or https.",
- "-21" => "Server Config File open error.",
- "-22" => "Server RSA Key File open error.",
- "-23" => "Server RSA Key File read error.",
- "-24" => "Server Config File have some errors, Please to check it.",
- "-25" => "Merchant Config File open error.",
- "-26" => "Merchant RSA Key File open error.",
- "-27" => "Merchant RSA Key File read error.",
- "-28" => "Merchant Config File has some errors, Please to check it.",
- "-29" => "Server Type is unknown.",
- "-30" => "Comm Type is unknown.",
- "-31" => "Input Parameter [ORDERNO] is null or empty.",
- "-32" => "Input Parameter [STOREID] is null or empty.",
- "-33" => "Input Parameter [ORDERDESC] is null or empty.",
- "-34" => "Input Parameter [CURRENCY] is null or empty.",
- "-35" => "Input Parameter [AMOUNT] is null or empty.",
- "-36" => "Input Parameter [ORDERURL] is null or empty.",
- "-37" => "Input Parameter [RETURNURL] is null or empty.",
- "-38" => "Input Parameter [DEPOSIT] is null or empty.",
- "-39" => "Input Parameter [QUERYFLAG] is null or empty.",
- "-40" => "Input Parameter [UPDATEURL] is null or empty.",
- "-41" => "Input Parameter [MERUPDATEURL] is null or empty.",
- "-42" => "Input Parameter [KEY] is null or empty.",
- "-43" => "Input Parameter [MAC] is null or empty.",
- "-44" => "Input Parameter [CIPHER] is null or empty.",
- "-45" => "Input Parameter [TrxType] is wrong.",
- "-100" => "TrustLink Server is closed. Or Merchant Server IP is not consistent with TrustLink Server setting.",
- "-101" => "TrustLink Server receives NULL.",
- "-308" => "Order Number already exists.",
- "positive" => "Response from Bank. Please contact with Acquirer Bank Service or HiTRUST Call Center."
- }
-
- def success?
- params['retcode'] == SUCCESS
- end
-
- def message
- return CODES["positive"] if params['retcode'].to_i > 0
- CODES[ params['retcode'] ]
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/ipay88.rb b/lib/active_merchant/billing/integrations/ipay88.rb
deleted file mode 100644
index cc6b77f2c01..00000000000
--- a/lib/active_merchant/billing/integrations/ipay88.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Ipay88
- autoload :Return, "active_merchant/billing/integrations/ipay88/return.rb"
- autoload :Helper, "active_merchant/billing/integrations/ipay88/helper.rb"
- autoload :Notification, "active_merchant/billing/integrations/ipay88/notification.rb"
-
- def self.service_url
- "https://www.mobile88.com/epayment/entry.asp"
- end
-
- def self.return(query_string, options={})
- Return.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/ipay88/helper.rb b/lib/active_merchant/billing/integrations/ipay88/helper.rb
deleted file mode 100644
index 123f660358b..00000000000
--- a/lib/active_merchant/billing/integrations/ipay88/helper.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-require "digest/sha1"
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Ipay88
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include RequiresParameters
-
- # Currencies supported
- # MYR (Malaysian Ringgit - for all payment methods except China Union Pay and PayPal)
- # USD (US Dollar - only for PayPal)
- # CNY (Yuan Renminbi - only for China Union Pay)
- SUPPORTED_CURRENCIES = %w[MYR USD CNY]
-
- # Languages supported
- # ISO-8859-1 (English)
- # UTF-8 (Unicode)
- # GB2312 (Chinese Simplified)
- # GD18030 (Chinese Simplified)
- # BIG5 (Chinese Traditional)
- SUPPORTED_LANGS = %w[ISO-8859-1 UTF-8 GB2312 GD18030 BIG5]
-
- # Payment methods supported
- # 8 (Alliance Online Transfer)
- # 10 (AmBank)
- # 21 (China Union Pay)
- # 20 (CIMB Click)
- # 2 (Credit Card MYR)
- # 16 (FPX)
- # 15 (Hong Leong Bank Transfer)
- # 6 (Maybank2u.com)
- # 23 (MEPS Cash)
- # 17 (Mobile Money)
- # 33 (PayPal)
- # 14 (RHB)
- PAYMENT_METHODS = %w[8 10 21 20 2 16 15 6 23 17 33 14]
-
- attr_reader :amount_in_cents, :merchant_key
-
- def initialize(order, account, options = {})
- requires!(options, :amount, :currency, :credential2)
- @merchant_key = options[:credential2]
- @amount_in_cents = options[:amount]
- super
- add_field mappings[:signature], signature
- end
-
- def amount=(money)
- @amount_in_cents = money.respond_to?(:cents) ? money.cents : money
- if money.is_a?(String) or @amount_in_cents.to_i < 0
- raise ArgumentError, "money amount must be either a Money object or a positive integer in cents."
- end
- add_field mappings[:amount], sprintf("%.2f", @amount_in_cents.to_f/100)
- end
-
- def currency(symbol)
- raise ArgumentError, "unsupported currency" unless SUPPORTED_CURRENCIES.include?(symbol)
- add_field mappings[:currency], symbol
- end
-
- def language(lang)
- raise ArgumentError, "unsupported language" unless SUPPORTED_LANGS.include?(lang)
- add_field mappings[:language], lang
- end
-
- def payment(pay_method)
- raise ArgumentError, "unsupported payment method" unless PAYMENT_METHODS.include?(pay_method.to_s)
- add_field mappings[:payment], pay_method
- end
-
- def customer(params = {})
- add_field(mappings[:customer][:name], "#{params[:first_name]} #{params[:last_name]}")
- add_field(mappings[:customer][:email], params[:email])
- add_field(mappings[:customer][:phone], params[:phone])
- end
-
- def self.sign(str)
- [Digest::SHA1.digest(str)].pack("m").chomp
- end
-
- def signature
- self.class.sign(self.sig_components)
- end
-
- mapping :account, "MerchantCode"
- mapping :amount, "Amount"
- mapping :currency, "Currency"
- mapping :order, "RefNo"
- mapping :description, "ProdDesc"
- mapping :customer, :name => "UserName",
- :email => "UserEmail",
- :phone => "UserContact"
- mapping :remark, "Remark"
- mapping :language, "Lang"
- mapping :payment, "PaymentId"
- mapping :return_url, "ResponseURL"
- mapping :signature, "Signature"
-
- protected
-
- def sig_components
- components = [merchant_key]
- components << fields[mappings[:account]]
- components << fields[mappings[:order]]
- components << amount_in_cents.to_s.gsub(/[.,]/, '')
- components << fields[mappings[:currency]]
- components.join
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/ipay88/return.rb b/lib/active_merchant/billing/integrations/ipay88/return.rb
deleted file mode 100644
index 14e1bc8334b..00000000000
--- a/lib/active_merchant/billing/integrations/ipay88/return.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-require "digest/sha1"
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Ipay88
- class Return < ActiveMerchant::Billing::Integrations::Return
- include ActiveMerchant::PostsData
-
- def account
- params["MerchantCode"]
- end
-
- def payment
- params["PaymentId"].to_i
- end
-
- def order
- params["RefNo"]
- end
-
- def amount
- params["Amount"]
- end
-
- def currency
- params["Currency"]
- end
-
- def remark
- params["Remark"]
- end
-
- def transaction
- params["TransId"]
- end
-
- def auth_code
- params["AuthCode"]
- end
-
- def status
- params["Status"]
- end
-
- def error
- params["ErrDesc"]
- end
-
- def signature
- params["Signature"]
- end
-
- def secure?
- self.generated_signature == self.signature
- end
-
- def success?
- self.secure? && self.requery == "00" && self.status == "1"
- end
-
- protected
-
- def generated_signature
- Helper.sign(self.sig_components)
- end
-
- def sig_components
- components = [@options[:credential2]]
- [:account, :payment, :order, :amount_in_cents, :currency, :status].each do |i|
- components << self.send(i)
- end
- components.join
- end
-
- def requery
- data = { "MerchantCode" => self.account, "RefNo" => self.order, "Amount" => self.amount }
- params = parameterize(data)
- ssl_post Ipay88.service_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" }
- end
-
- private
-
- def parameterize(params)
- params.reject { |k, v| v.blank? }.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&")
- end
-
- def amount_in_cents
- @amount_in_cents ||= (self.amount || "").gsub(/[.,]/, "")
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/liqpay.rb b/lib/active_merchant/billing/integrations/liqpay.rb
deleted file mode 100644
index f43e3220ebb..00000000000
--- a/lib/active_merchant/billing/integrations/liqpay.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- # Documentation: https://www.liqpay.com/?do=pages&p=cnb10
- module Liqpay
- autoload :Helper, File.dirname(__FILE__) + '/liqpay/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/liqpay/notification.rb'
- autoload :Return, File.dirname(__FILE__) + '/liqpay/return.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://liqpay.com/?do=clickNbuy'
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'signature'
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
-
- def self.return(query_string)
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/liqpay/helper.rb b/lib/active_merchant/billing/integrations/liqpay/helper.rb
deleted file mode 100644
index 89d158c68ab..00000000000
--- a/lib/active_merchant/billing/integrations/liqpay/helper.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Liqpay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- def initialize(order, account, options = {})
- @secret = options.delete(:secret)
- super
-
- add_field 'version', '1.2'
- end
-
- def form_fields
- xml = "
- 1.2
- #{@fields["result_url"]}
- #{@fields["server_url"]}
- #{@fields["merchant_id"]}
- #{@fields["order_id"]}
- #{@fields["amount"]}
- #{@fields["currency"]}
- #{@fields["description"]}
- #{@fields["default_phone"]}
- card
- ".strip
- sign = Base64.encode64(Digest::SHA1.digest("#{@secret}#{xml}#{@secret}")).strip
- {"operation_xml" => Base64.encode64(xml), "signature" => sign}
- end
-
- mapping :account, 'merchant_id'
- mapping :amount, 'amount'
- mapping :currency, 'currency'
- mapping :order, 'order_id'
- mapping :description, 'description'
- mapping :phone, 'default_phone'
-
- mapping :notify_url, 'server_url'
- mapping :return_url, 'result_url'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/liqpay/notification.rb b/lib/active_merchant/billing/integrations/liqpay/notification.rb
deleted file mode 100644
index 2cfb7a26192..00000000000
--- a/lib/active_merchant/billing/integrations/liqpay/notification.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Liqpay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- def self.recognizes?(params)
- params.has_key?('amount') && params.has_key?('order_id')
- end
-
- def initialize(post, options = {})
- raise ArgumentError if post.blank?
- super
- @params.merge!(Hash.from_xml(Base64.decode64(xml))["response"])
- end
-
- def xml
- @params["operation_xml"]
- end
-
- def complete?
- status == 'success'
- end
-
- def account
- params['merchant_id']
- end
-
- def amount
- BigDecimal.new(gross)
- end
-
- def item_id
- params['order_id']
- end
-
- def transaction_id
- params['transaction_id']
- end
-
- def action_name
- params['action_name'] # either 'result_url' or 'server_url'
- end
-
- def version
- params['version']
- end
-
- def sender_phone
- params['sender_phone']
- end
-
- def security_key
- params[ActiveMerchant::Billing::Integrations::Liqpay.signature_parameter_name]
- end
-
- def gross
- params['amount']
- end
-
- def currency
- params['currency']
- end
-
- def status
- params['status'] # 'success', 'failure' or 'wait_secure'
- end
-
- def code
- params['code']
- end
-
- def generate_signature_string
- "#{@options[:secret]}#{Base64.decode64(xml)}#{@options[:secret]}"
- end
-
- def generate_signature
- Base64.encode64(Digest::SHA1.digest(generate_signature_string)).strip
- end
-
- def acknowledge
- security_key == generate_signature
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/liqpay/return.rb b/lib/active_merchant/billing/integrations/liqpay/return.rb
deleted file mode 100644
index 200862d2572..00000000000
--- a/lib/active_merchant/billing/integrations/liqpay/return.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Liqpay
- class Return < ActiveMerchant::Billing::Integrations::Return
- def self.recognizes?(params)
- params.has_key?('amount') && params.has_key?('order_id')
- end
-
- def initialize(post)
- super
- xml = Base64.decode64(@params["operation_xml"])
- @params.merge!(Hash.from_xml(xml)["response"])
- end
-
- def complete?
- status == 'success'
- end
-
- def account
- params['merchant_id']
- end
-
- def amount
- BigDecimal.new(gross)
- end
-
- def item_id
- params['order_id']
- end
-
- def transaction_id
- params['transaction_id']
- end
-
- def action_name
- params['action_name'] # either 'result_url' or 'server_url'
- end
-
- def version
- params['version']
- end
-
- def sender_phone
- params['sender_phone']
- end
-
- def security_key
- params[ActiveMerchant::Billing::Integrations::Liqpay.signature_parameter_name]
- end
-
- def gross
- params['amount']
- end
-
- def currency
- params['currency']
- end
-
- def status
- params['status'] # 'success', 'failure' or 'wait_secure'
- end
-
- def code
- params['code']
- end
-
- def generate_signature_string
- ['', version, @options[:secret], action_name, sender_phone, account, gross, currency, item_id, transaction_id, status, code, ''].flatten.compact.join('|')
- end
-
- def generate_signature
- Base64.encode64(Digest::SHA1.digest(generate_signature_string)).gsub(/\n/, '')
- end
-
- def acknowledge
- security_key == generate_signature
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/maksuturva.rb b/lib/active_merchant/billing/integrations/maksuturva.rb
deleted file mode 100644
index 1bb12bf6480..00000000000
--- a/lib/active_merchant/billing/integrations/maksuturva.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require File.dirname(__FILE__) + '/maksuturva/helper.rb'
-require File.dirname(__FILE__) + '/maksuturva/notification.rb'
-
-# USAGE:
-#
-# First define Maksuturva seller id and authcode in an initializer:
-#
-# MAKSUTURVA_SELLERID = "testikauppias"
-# MAKSUTURVA_AUTHCODE = "11223344556677889900"
-#
-# Then in view do something like this (use dynamic values for your app)
-#
-# <% payment_service_for 2, MAKSUTURVA_SELLERID,
-# :amount => "200,00", :currency => 'EUR', :credential2 => MAKSUTURVA_AUTHCODE,
-# :service => :maksuturva do |service|
-# service.pmt_reference = "134662"
-# service.pmt_duedate = "24.06.2012"
-# service.customer :phone => "0405051909",
-# :email => "antti@example.com"
-# service.billing_address :city => "Helsinki",
-# :address1 => "Lorem street",
-# :state => "-",
-# :country => 'Finland',
-# :zip => "00530"
-# service.pmt_orderid = "2"
-# service.pmt_buyername = "Antti Akonniemi"
-# service.pmt_deliveryname = "Antti Akonniemi"
-# service.pmt_deliveryaddress = "Köydenpunojankatu 13"
-# service.pmt_deliverypostalcode = "00180"
-# service.pmt_deliverycity = "Helsinki"
-# service.pmt_deliverycountry = "FI"
-# service.pmt_rows = 1
-# service.pmt_row_name1 = "testi"
-# service.pmt_row_desc1 = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
-# service.pmt_row_articlenr1 = "1"
-# service.pmt_row_quantity1 = "1"
-# service.pmt_row_deliverydate1 = "26.6.2012"
-# service.pmt_row_price_gross1 = "200,00"
-# service.pmt_row_vat1= "23,00"
-# service.pmt_row_discountpercentage1 = "0,00"
-# service.pmt_row_type1 = "1"
-# service.pmt_charset = "UTF-8"
-# service.pmt_charsethttp = "UTF-8"
-#
-# service.return_url "http://localhost:3000/process"
-# service.cancel_return_url "http://example.com"
-# service.pmt_errorreturn "http://example.com"
-#
-# service.pmt_delayedpayreturn "http://example.com"
-# service.pmt_escrow "N"
-# service.pmt_escrowchangeallowed "N"
-# service.pmt_sellercosts "0,00"
-# service.pmt_keygeneration "001"
-# %>
-#
-# Then in the controller handle the return with something like this
-#
-# def ipn
-# notify = ActiveMerchant::Billing::Integrations::Maksuturva::Notification.new(params)
-#
-# if notify.acknowledge(MAKSUTURVA_AUTHCODE)
-# # Process order
-# else
-# # Show error
-# end
-# end
-#
-# For full list of available parameters etc check the integration documents
-# here:
-#
-# https://www.maksuturva.fi/services/vendor_services/integration_guidelines.html
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Maksuturva
- mattr_accessor :service_url
- self.service_url = 'https://www.maksuturva.fi/NewPaymentExtended.pmt'
-
- def self.notification(post)
- Notification.new(post)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/maksuturva/helper.rb b/lib/active_merchant/billing/integrations/maksuturva/helper.rb
deleted file mode 100644
index 4d6128cff21..00000000000
--- a/lib/active_merchant/billing/integrations/maksuturva/helper.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Maksuturva
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- def initialize(order, account, options = {})
- md5secret options.delete(:credential2)
- super
- add_field("pmt_action", "NEW_PAYMENT_EXTENDED")
- add_field("pmt_version", "0004")
- add_field("pmt_sellerid", account)
- add_field("pmt_hashversion", "MD5")
- end
-
- def md5secret(value)
- @md5secret = value
- end
-
- def form_fields
- @fields.merge("pmt_hash" => generate_md5string)
- end
-
- def generate_md5string
- fields = [@fields["pmt_action"], @fields["pmt_version"]]
- fields += [@fields["pmt_selleriban"]] unless @fields["pmt_selleriban"].nil?
- fields += [@fields["pmt_id"], @fields["pmt_orderid"], @fields["pmt_reference"], @fields["pmt_duedate"],
- @fields["pmt_amount"], @fields["pmt_currency"], @fields["pmt_okreturn"], @fields["pmt_errorreturn"], @fields["pmt_cancelreturn"],
- @fields["pmt_delayedpayreturn"], @fields["pmt_escrow"], @fields["pmt_escrowchangeallowed"]]
-
- fields += [@fields["pmt_invoicefromseller"]] unless @fields["pmt_invoicefromseller"].nil?
- fields += [@fields["pmt_paymentmethod"]] unless @fields["pmt_paymentmethod"].nil?
- fields += [@fields["pmt_buyeridentificationcode"]] unless @fields["pmt_buyeridentificationcode"].nil?
-
-
- fields += [@fields["pmt_buyername"], @fields["pmt_buyeraddress"], @fields["pmt_buyerpostalcode"], @fields["pmt_buyercity"],
- @fields["pmt_buyercountry"], @fields["pmt_deliveryname"], @fields["pmt_deliveryaddress"], @fields["pmt_deliverypostalcode"], @fields["pmt_deliverycity"],
- @fields["pmt_deliverycountry"], @fields["pmt_sellercosts"]]
-
- (1..@fields["pmt_rows"].to_i).each do |i|
- fields += [@fields["pmt_row_name#{i}"], @fields["pmt_row_desc#{i}"], @fields["pmt_row_quantity#{i}"]]
- fields += [@fields["pmt_row_articlenr#{i}"]] unless @fields["pmt_row_articlenr#{i}"].nil?
- fields += [@fields["pmt_row_unit#{i}"]] unless @fields["pmt_row_unit#{i}"].nil?
- fields += [@fields["pmt_row_deliverydate#{i}"]]
- fields += [@fields["pmt_row_price_gross#{i}"]] unless @fields["pmt_row_price_gross#{i}"].nil?
- fields += [@fields["pmt_row_price_net#{i}"]] unless @fields["pmt_row_price_net#{i}"].nil?
- fields += [@fields["pmt_row_vat#{i}"], @fields["pmt_row_discountpercentage#{i}"], @fields["pmt_row_type#{i}"]]
- end
- fields += [@md5secret]
- fields = fields.join("&") + "&"
- Digest::MD5.hexdigest(fields).upcase
- end
-
- mapping :pmt_selleriban, "pmt_selleriban"
- mapping :pmt_reference, "pmt_reference"
- mapping :pmt_duedate, "pmt_duedate"
- mapping :pmt_userlocale, "pmt_userlocale"
- mapping :pmt_escrow, "pmt_escrow"
- mapping :pmt_escrowchangeallowed, "pmt_escrowchangeallowed"
- mapping :pmt_invoicefromseller, "pmt_invoicefromseller"
- mapping :pmt_paymentmethod, "pmt_paymentmethod"
- mapping :pmt_buyeridentificationcode, "pmt_buyeridentificationcode"
- mapping :pmt_buyername, "pmt_buyername"
-
- mapping :account, ''
- mapping :currency, 'pmt_currency'
- mapping :amount, 'pmt_amount'
-
- mapping :order, 'pmt_id'
- mapping :pmt_orderid, 'pmt_orderid'
- mapping :pmt_deliveryname, "pmt_deliveryname"
- mapping :pmt_deliveryaddress, "pmt_deliveryaddress"
- mapping :pmt_deliverypostalcode, "pmt_deliverypostalcode"
- mapping :pmt_deliverycity, "pmt_deliverycity"
- mapping :pmt_deliverycountry, "pmt_deliverycountry"
- mapping :pmt_sellercosts, "pmt_sellercosts"
- mapping :pmt_rows, "pmt_rows"
-
- (1..499.to_i).each do |i|
- mapping "pmt_row_name#{i}".to_sym, "pmt_row_name#{i}"
- mapping "pmt_row_desc#{i}".to_sym, "pmt_row_desc#{i}"
- mapping "pmt_row_quantity#{i}".to_sym, "pmt_row_quantity#{i}"
- mapping "pmt_row_articlenr#{i}".to_sym, "pmt_row_articlenr#{i}"
- mapping "pmt_row_unit#{i}".to_sym, "pmt_row_unit#{i}"
- mapping "pmt_row_deliverydate#{i}".to_sym, "pmt_row_deliverydate#{i}"
- mapping "pmt_row_price_gross#{i}".to_sym, "pmt_row_price_gross#{i}"
- mapping "pmt_row_price_net#{i}".to_sym, "pmt_row_price_net#{i}"
- mapping "pmt_row_vat#{i}".to_sym, "pmt_row_vat#{i}"
- mapping "pmt_row_discountpercentage#{i}".to_sym, "pmt_row_discountpercentage#{i}"
- mapping "pmt_row_type#{i}".to_sym, "pmt_row_type#{i}"
- end
-
- mapping :pmt_charset, "pmt_charset"
- mapping :pmt_charsethttp, "pmt_charsethttp"
- mapping :pmt_hashversion, "pmt_hashversion"
- mapping :pmt_keygeneration, "pmt_keygeneration"
- mapping :customer, :email => 'pmt_buyeremail',
- :phone => 'pmt_buyerphone'
-
- mapping :billing_address, :city => 'pmt_buyercity',
- :address1 => "pmt_buyeraddress",
- :address2 => '',
- :state => '',
- :zip => "pmt_buyerpostalcode",
- :country => 'pmt_buyercountry'
-
- mapping :notify_url, ''
- mapping :return_url, 'pmt_okreturn'
- mapping :pmt_errorreturn, 'pmt_errorreturn'
- mapping :pmt_delayedpayreturn, 'pmt_delayedpayreturn'
- mapping :cancel_return_url, 'pmt_cancelreturn'
-
- mapping :description, ''
- mapping :tax, ''
- mapping :shipping, ''
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/maksuturva/notification.rb b/lib/active_merchant/billing/integrations/maksuturva/notification.rb
deleted file mode 100644
index ed7cdd2971d..00000000000
--- a/lib/active_merchant/billing/integrations/maksuturva/notification.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require "net/http"
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Maksuturva
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- def complete?
- true
- end
-
- def transaction_id
- params["pmt_id"]
- end
-
- def security_key
- params["pmt_hash"]
- end
-
- def gross
- params["pmt_amount"]
- end
-
- def currency
- params["pmt_currency"]
- end
-
- def status
- "PAID"
- end
-
- def acknowledge(authcode)
- return_authcode = [params["pmt_action"], params["pmt_version"], params["pmt_id"], params["pmt_reference"], params["pmt_amount"], params["pmt_currency"], params["pmt_sellercosts"], params["pmt_paymentmethod"], params["pmt_escrow"], authcode].join("&")
- (Digest::MD5.hexdigest(return_authcode + "&").upcase == params["pmt_hash"])
- end
-
- private
-
- def parse(post)
- post.each do |key, value|
- params[key] = value
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/moneybookers.rb b/lib/active_merchant/billing/integrations/moneybookers.rb
deleted file mode 100644
index 44e034e10f0..00000000000
--- a/lib/active_merchant/billing/integrations/moneybookers.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Moneybookers
-
- autoload :Notification, File.dirname(__FILE__) + '/moneybookers/notification.rb'
- autoload :Helper, File.dirname(__FILE__) + '/moneybookers/helper.rb'
-
- mattr_accessor :production_url
- self.production_url = 'https://www.moneybookers.com/app/payment.pl'
-
- def self.service_url
- self.production_url
- end
-
- def self.notification(post, options)
- Notification.new(post, options)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/moneybookers/helper.rb b/lib/active_merchant/billing/integrations/moneybookers/helper.rb
deleted file mode 100644
index 47ae9464835..00000000000
--- a/lib/active_merchant/billing/integrations/moneybookers/helper.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Moneybookers
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :account, 'pay_to_email'
- mapping :order, 'transaction_id'
- mapping :amount, 'amount'
- mapping :currency, 'currency'
-
- mapping :customer,
- :first_name => 'firstname',
- :last_name => 'lastname',
- :email => 'pay_from_email',
- :phone => 'phone_number'
-
- mapping :billing_address,
- :city => 'city',
- :address1 => 'address',
- :address2 => 'address2',
- :state => 'state',
- :zip => 'postal_code',
- :country => 'country'
-
- mapping :notify_url, 'status_url'
- mapping :return_url, 'return_url'
- mapping :cancel_return_url, 'cancel_url'
- mapping :description, 'detail1_text'
-
- MAPPED_COUNTRY_CODES = {
- 'SE' => 'SV',
- 'DK' => 'DA'
- }
-
- SUPPORTED_COUNTRY_CODES = [
- 'FI', 'DE', 'ES', 'FR',
- 'IT','PL', 'GR', 'RO',
- 'RU', 'TR', 'CN', 'CZ', 'NL'
- ]
-
- def initialize(order, account, options = {})
- super
- add_tracking_token
- add_default_parameters
- add_seller_details(options)
- end
-
- private
-
- def add_tracking_token
- return if application_id.blank? || application_id == 'ActiveMerchant'
-
- add_field('merchant_fields', 'platform')
- add_field('platform', application_id)
- end
-
- def add_default_parameters
- add_field('hide_login', 1)
- end
-
- def add_seller_details(options)
- add_field('recipient_description', options[:account_name]) if options[:account_name]
- add_field('country', lookup_country_code(options[:country], :alpha3)) if options[:country]
- add_field('language', locale_code(options[:country])) if options[:country]
- end
-
- def locale_code(country_code)
- return country_code if SUPPORTED_COUNTRY_CODES.include?(country_code)
- MAPPED_COUNTRY_CODES[country_code] || 'EN'
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/moneybookers/notification.rb b/lib/active_merchant/billing/integrations/moneybookers/notification.rb
deleted file mode 100644
index 5d25b4cba89..00000000000
--- a/lib/active_merchant/billing/integrations/moneybookers/notification.rb
+++ /dev/null
@@ -1,129 +0,0 @@
-require 'net/http'
-require 'digest/md5'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Moneybookers
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- def initialize(data, options)
- if options[:credential2].nil?
- raise ArgumentError, "You need to provide the md5 secret as the option :credential2 to verify that the notification originated from Moneybookers"
- end
- super
- end
-
- def complete?
- status == 'Completed'
- end
-
- # ‘2’ Processed – This status is sent when the transaction is processed and the funds have been received on your Moneybookers account.
- # ‘0’ Pending – This status is sent when the customers pays via the pending bank transfer option. Such transactions will auto-process IF the bank transfer is received by Moneybookers. We strongly recommend that you do NOT process the order/transaction in your system upon receipt of a pending status from Moneybookers.
- # ‘-1’ Cancelled – Pending transactions can either be cancelled manually by the sender in their online account history or they will auto-cancel after 14 days if still pending.
- # ‘-2’ Failed – This status is sent when the customer tries to pay via Credit Card or Direct Debit but our provider declines the transaction. If you do not accept Credit Card or Direct Debit payments via Moneybookers (see page 17) then you will never receive the failed status.
- # ‘-3’ Chargeback – This status could be received only if your account is configured to receive chargebacks. If this is the case, whenever a chargeback is received by Moneybookers, a -3 status will be posted on the status_url for the reversed transaction.
- def status
- case status_code
- when '2'
- 'Completed'
- when '0'
- 'Pending'
- when '-1'
- 'Cancelled'
- when '-2'
- 'Failed'
- when '-3'
- 'Reversed'
- else
- 'Error'
- end
- end
-
- def status_code
- params['status']
- end
-
- def item_id
- params['transaction_id']
- end
-
- def transaction_id
- params['mb_transaction_id']
- end
-
- # When was this payment received by the client.
- def received_at
- nil
- end
-
- def payer_email
- params['pay_from_email']
- end
-
- def receiver_email
- params['pay_to_email']
- end
-
- def md5sig
- params['md5sig']
- end
-
- #Unique ID from the merchant's Moneybookers.com account, needed for calculatinon of md5 sig
- def merchant_id
- params['merchant_id']
- end
-
- # currency of the payment as posted by the merchant on the entry form
- def currency
- params['currency']
- end
-
- # amount of the payment as posted by the merchant on the entry form (ex. 39.60/39.6/39)
- def gross
- params['amount']
- end
-
- # currency of mb_amount, will always be the same as the currency of the beneficiary's account at Moneybookers.com
- def merchant_currency
- params['mb_currency']
- end
-
- # total amount of the payment in Merchants currency (ex 25.46/25.4/25)
- def merchant_amount
- params['mb_amount']
- end
-
- # Was this a test transaction?
- def test?
- false
- end
-
- def secret
- @options[:credential2]
- end
-
- # Acknowledge the transaction to MoneyBooker. This method has to be called after a new
- # apc arrives. It will verify that all the information we received is correct and will return a
- # ok or a fail. The secret (second credential) has to be provided in the parameter :credential2
- # when instantiating the Notification object.
- #
- # Example:
- #
- # def ipn
- # notify = Moneybookers.notification(request.raw_post, :credential2 => 'secret')
- #
- # if notify.acknowledge
- # ... process order ... if notify.complete?
- # else
- # ... log possible hacking attempt ...
- # end
- def acknowledge
- fields = [merchant_id, item_id, Digest::MD5.hexdigest(secret).upcase, merchant_amount, merchant_currency, status_code].join
- md5sig == Digest::MD5.hexdigest(fields).upcase
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/nochex.rb b/lib/active_merchant/billing/integrations/nochex.rb
deleted file mode 100644
index ff3dce6d931..00000000000
--- a/lib/active_merchant/billing/integrations/nochex.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- # To start with Nochex, follow the instructions for installing
- # ActiveMerchant as a plugin, as described on
- # http://www.activemerchant.org/.
- #
- # The plugin will automatically add the ActionView helper for
- # ActiveMerchant, which will allow you to make the Nochex payments.
- # The idea behind the helper is that it generates an invisible
- # forwarding screen that will automatically redirect the user.
- # So you would collect all the information about the order and then
- # simply render the hidden form, which redirects the user to Nochex.
- #
- # The syntax of the helper is as follows:
- #
- # <% payment_service_for 'order id', 'nochex_user_id',
- # :amount => 50.00,
- # :service => :nochex,
- # :html => { :id => 'nochex-form' } do |service| %>
- #
- # <% service.customer :first_name => 'Cody',
- # :last_name => 'Fauser',
- # :phone => '(555)555-5555',
- # :email => 'cody@example.com' %>
- #
- # <% service.billing_address :city => 'Ottawa',
- # :address1 => '21 Snowy Brook Lane',
- # :address2 => 'Apt. 36',
- # :state => 'ON',
- # :country => 'CA',
- # :zip => 'K1J1E5' %>
- #
- # <% service.invoice '#1000' %>
- # <% service.shipping '0.00' %>
- # <% service.tax '0.00' %>
- #
- # <% service.notify_url url_for(:action => 'notify', :only_path => false) %>
- # <% service.return_url url_for(:action => 'done', :only_path => false) %>
- # <% service.cancel_return_url 'http://mystore.com' %>
- # <% end %>
- #
- # The notify_url is the URL that the Nochex IPN will be sent. You can
- # handle the notification in your controller action as follows:
- #
- # class NotificationController < ApplicationController
- # include ActiveMerchant::Billing::Integrations
- #
- # def notify
- # notification = Nochex::Notification.new(request.raw_post)
- #
- # begin
- # # Acknowledge notification with Nochex
- # raise StandardError, 'Illegal Notification' unless notification.acknowledge
- # # Process the payment
- # rescue => e
- # logger.warn("Illegal notification received: #{e.message}")
- # ensure
- # head(:ok)
- # end
- # end
- # end
- module Nochex
- autoload :Return, File.dirname(__FILE__) + '/nochex/return.rb'
- autoload :Helper, File.dirname(__FILE__) + '/nochex/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/nochex/notification.rb'
-
-
- mattr_accessor :service_url
- self.service_url = 'https://secure.nochex.com'
-
- mattr_accessor :notification_confirmation_url
- self.notification_confirmation_url = 'https://www.nochex.com/nochex.dll/apc/apc'
-
- # Simply a convenience method that returns a new
- # ActiveMerchant::Billing::Integrations::Nochex::Notification
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/integrations/nochex/helper.rb b/lib/active_merchant/billing/integrations/nochex/helper.rb
deleted file mode 100644
index e61948021f4..00000000000
--- a/lib/active_merchant/billing/integrations/nochex/helper.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Nochex
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- # Required Parameters
- # email
- # amount
- mapping :account, 'email'
- mapping :amount, 'amount'
-
- # Set the field status = test for testing with accounts:
- # Account Password
- # test1@nochex.com 123456
- # test2@nochex.com 123456
- # def initialize(order, account, options = {})
- # super
- # add_field('status', 'test')
- # end
-
- # Need to format the amount to have 2 decimal places
- def amount=(money)
- cents = money.respond_to?(:cents) ? money.cents : money
- if money.is_a?(String) or cents.to_i <= 0
- raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
- end
- add_field mappings[:amount], sprintf("%.2f", cents.to_f/100)
- end
-
- # Optional Parameters
- # ordernumber
- mapping :order, 'ordernumber'
-
- # firstname
- # lastname
- # email_address_sender
- mapping :customer, :first_name => 'firstname',
- :last_name => 'lastname',
- :email => 'email_address_sender'
-
- # town
- # firstline
- # county
- # postcode
- mapping :billing_address, :city => 'town',
- :address1 => 'firstline',
- :state => 'county',
- :zip => 'postcode'
-
- # responderurl
- mapping :notify_url, 'responderurl'
-
- # returnurl
- mapping :return_url, 'returnurl'
-
- # cancelurl
- mapping :cancel_return_url, 'cancelurl'
-
- # description
- mapping :description, 'description'
-
- # Currently unmapped
- # logo
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/nochex/notification.rb b/lib/active_merchant/billing/integrations/nochex/notification.rb
deleted file mode 100644
index cf9334b3de4..00000000000
--- a/lib/active_merchant/billing/integrations/nochex/notification.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-require 'net/http'
-require 'date'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Nochex
- # Parser and handler for incoming Automatic Payment Confirmations from Nochex.
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include ActiveMerchant::PostsData
-
- def complete?
- status == 'Completed'
- end
-
- # Id of the order we passed to Nochex
- def item_id
- params['order_id']
- end
-
- def transaction_id
- params['transaction_id']
- end
-
- def currency
- 'GBP'
- end
-
- # When was this payment received by the client.
- def received_at
- # U.K. Format: 27/09/2006 22:30:54
- return if params['transaction_date'].blank?
- time = params['transaction_date'].scan(/\d+/)
- Time.utc(time[2], time[1], time[0], time[3], time[4], time[5])
- end
-
- def payer_email
- params['from_email']
- end
-
- def receiver_email
- params['to_email']
- end
-
- def security_key
- params['security_key']
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- sprintf("%.2f", params['amount'].to_f)
- end
-
- # Was this a test transaction?
- def test?
- params['status'] == 'test'
- end
-
- def status
- 'Completed'
- end
-
- # Acknowledge the transaction to Nochex. This method has to be called after a new
- # apc arrives. Nochex will verify that all the information we received are correct and will return a
- # ok or a fail. This is very similar to the PayPal IPN scheme.
- #
- # Example:
- #
- # def nochex_ipn
- # notify = NochexNotification.new(request.raw_post)
- #
- # if notify.acknowledge
- # ... process order ... if notify.complete?
- # else
- # ... log possible hacking attempt ...
- # end
- def acknowledge
- payload = raw
-
- response = ssl_post(Nochex.notification_confirmation_url, payload,
- 'Content-Length' => "#{payload.size}",
- 'User-Agent' => "Active Merchant -- http://activemerchant.org",
- 'Content-Type' => "application/x-www-form-urlencoded"
- )
-
- raise StandardError.new("Faulty Nochex result: #{response}") unless ["AUTHORISED", "DECLINED"].include?(response)
-
- response == "AUTHORISED"
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/nochex/return.rb b/lib/active_merchant/billing/integrations/nochex/return.rb
deleted file mode 100644
index c8fd59e1a90..00000000000
--- a/lib/active_merchant/billing/integrations/nochex/return.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Nochex
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/notification.rb b/lib/active_merchant/billing/integrations/notification.rb
deleted file mode 100644
index 56df8ee8aa1..00000000000
--- a/lib/active_merchant/billing/integrations/notification.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- class Notification
- attr_accessor :params
- attr_accessor :raw
-
- # set this to an array in the subclass, to specify which IPs are allowed
- # to send requests
- class_attribute :production_ips
-
- # * *Args* :
- # - +doc+ -> raw post string
- # - +options+ -> custom options which individual implementations can
- # utilize
- def initialize(post, options = {})
- @options = options
- empty!
- parse(post)
- end
-
- def status
- raise NotImplementedError, "Must implement this method in the subclass"
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- raise NotImplementedError, "Must implement this method in the subclass"
- end
-
- def gross_cents
- (gross.to_f * 100.0).round
- end
-
- # This combines the gross and currency and returns a proper Money object.
- # this requires the money library located at http://dist.leetsoft.com/api/money
- def amount
- return Money.new(gross_cents, currency) rescue ArgumentError
- return Money.new(gross_cents) # maybe you have an own money object which doesn't take a currency?
- end
-
- # reset the notification.
- def empty!
- @params = Hash.new
- @raw = ""
- end
-
- # Check if the request comes from an official IP
- def valid_sender?(ip)
- return true if ActiveMerchant::Billing::Base.integration_mode == :test || production_ips.blank?
- production_ips.include?(ip)
- end
-
- def test?
- false
- end
-
- private
-
- # Take the posted data and move the relevant data into a hash
- def parse(post)
- @raw = post.to_s
- for line in @raw.split('&')
- key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten
- params[key] = CGI.unescape(value)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paxum.rb b/lib/active_merchant/billing/integrations/paxum.rb
deleted file mode 100644
index 78ac7483d1f..00000000000
--- a/lib/active_merchant/billing/integrations/paxum.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- # Documentation:
- # https://www.paxum.com/payment_docs/page.php?name=apiIntroduction
- module Paxum
- autoload :Helper, File.dirname(__FILE__) + '/paxum/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/paxum/notification.rb'
- autoload :Return, File.dirname(__FILE__) + '/paxum/return.rb'
- autoload :Common, File.dirname(__FILE__) + '/paxum/common.rb'
-
- mattr_accessor :test_url
- self.test_url = 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin'
-
- mattr_accessor :production_url
- self.production_url = 'https://paxum.com/payment/phrame.php?action=displayProcessPaymentLogin'
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'key'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paxum/common.rb b/lib/active_merchant/billing/integrations/paxum/common.rb
deleted file mode 100644
index e158142afaf..00000000000
--- a/lib/active_merchant/billing/integrations/paxum/common.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paxum
- module Common
- def generate_signature_string
- @raw_post.slice!(0) if @raw_post.starts_with?("&")
- @raw_post = CGI.unescape(@raw_post)
- @raw_post = "{@raw_post}" unless @raw_post.starts_with?("&")
- arr = @raw_post.split('&')
- arr.delete(arr.last)
- data = arr.join('&')
-
- (data + secret)
- end
-
- def generate_signature
- Digest::MD5.hexdigest(generate_signature_string)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paxum/helper.rb b/lib/active_merchant/billing/integrations/paxum/helper.rb
deleted file mode 100644
index f69fb96d662..00000000000
--- a/lib/active_merchant/billing/integrations/paxum/helper.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paxum
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Common
-
- def initialize(order, account, options = {})
- @paxum_options = options.dup
- options.delete(:description)
- options.delete(:fail_url)
- options.delete(:success_url)
- options.delete(:result_url)
- super
- add_field "button_type_id", "1"
- add_field "variables", "notify_url=#{@paxum_options[:result_url]}"
- @paxum_options.each do |key, value|
- add_field mappings[key], value
- end
- end
-
- def form_fields
- @fields
- end
-
- def params
- @fields
- end
-
- mapping :account, 'business_email'
- mapping :amount, 'amount'
- mapping :currency, 'currency'
- mapping :order, 'item_id'
- mapping :description, 'item_name'
- mapping :fail_url, 'cancel_url'
- mapping :success_url, 'finish_url'
- mapping :result_url, 'notify_url'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paxum/notification.rb b/lib/active_merchant/billing/integrations/paxum/notification.rb
deleted file mode 100644
index 06c5c64b25e..00000000000
--- a/lib/active_merchant/billing/integrations/paxum/notification.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paxum
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include Common
-
- def initialize(post, options = {})
- @raw_post = post.dup
- post.slice!(0)
- super
- end
-
- def self.recognizes?(params)
- (params.has_key?('transaction_item_id') && params.has_key?('transaction_amount'))
- end
-
- def security_key
- params["key"]
- end
-
- def secret
- @options[:secret]
- end
-
- def acknowledge
- (security_key == generate_signature)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pay_fast.rb b/lib/active_merchant/billing/integrations/pay_fast.rb
deleted file mode 100644
index 6e44ef55f38..00000000000
--- a/lib/active_merchant/billing/integrations/pay_fast.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- # Documentation:
- # https://www.payfast.co.za/s/std/integration-guide
- module PayFast
- autoload :Return, File.dirname(__FILE__) + '/pay_fast/return.rb'
- autoload :Helper, File.dirname(__FILE__) + '/pay_fast/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/pay_fast/notification.rb'
- autoload :Common, File.dirname(__FILE__) + '/pay_fast/common.rb'
-
- # Overwrite this if you want to change the PayFast sandbox url
- mattr_accessor :process_test_url
- self.process_test_url = 'https://sandbox.payfast.co.za/eng/process'
-
- # Overwrite this if you want to change the PayFast production url
- mattr_accessor :process_production_url
- self.process_production_url = 'https://www.payfast.co.za/eng/process'
-
- # Overwrite this if you want to change the PayFast sandbox url
- mattr_accessor :validate_test_url
- self.validate_test_url = 'https://sandbox.payfast.co.za/eng/query/validate'
-
- # Overwrite this if you want to change the PayFast production url
- mattr_accessor :validate_production_url
- self.validate_production_url = 'https://www.payfast.co.za/eng/query/validate'
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'signature'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.process_production_url
- when :test
- self.process_test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.validate_service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.validate_production_url
- when :test
- self.validate_test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pay_fast/common.rb b/lib/active_merchant/billing/integrations/pay_fast/common.rb
deleted file mode 100644
index d5fb5dc5b28..00000000000
--- a/lib/active_merchant/billing/integrations/pay_fast/common.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayFast
- module Common
- def generate_signature(type)
- string = case type
- when :request
- request_signature_string
- when :notify
- notify_signature_string
- end
-
- Digest::MD5.hexdigest(string)
- end
-
- def request_attributes
- [:merchant_id, :merchant_key, :return_url, :cancel_url,
- :notify_url, :name_first, :name_last, :email_address,
- :payment_id, :amount, :item_name, :item_description,
- :custom_str1, :custom_str2, :custom_str3, :custom_str4,
- :custom_str5, :custom_int1, :custom_int2, :custom_int3,
- :custom_int4, :custom_int5, :email_confirmation,
- :confirmation_address]
- end
-
- def request_signature_string
- request_attributes.map do |attr|
- "#{mappings[attr]}=#{CGI.escape(@fields[mappings[attr]])}" if @fields[mappings[attr]].present?
- end.compact.join('&')
- end
-
- def notify_signature_string
- params.map do |key, value|
- "#{key}=#{CGI.escape(value)}" unless key == PayFast.signature_parameter_name
- end.compact.join('&')
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pay_fast/helper.rb b/lib/active_merchant/billing/integrations/pay_fast/helper.rb
deleted file mode 100644
index 6d1a43c8649..00000000000
--- a/lib/active_merchant/billing/integrations/pay_fast/helper.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayFast
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Common
-
- def initialize(order, account, options = {})
- super
- add_field('merchant_id', account)
- add_field('merchant_key', options.delete(:credential2))
- add_field('m_payment_id', order)
- end
-
- def form_fields
- @fields
- end
-
- def params
- @fields
- end
-
- mapping :merchant_id, 'merchant_id'
- mapping :merchant_key, 'merchant_key'
- mapping :return_url, 'return_url'
- mapping :cancel_return_url, 'cancel_url'
- mapping :notify_url, 'notify_url'
- mapping :name_first, 'name_first'
- mapping :name_last, 'name_last'
- mapping :email_address, 'email_address'
- mapping :payment_id, 'm_payment_id'
- mapping :amount, 'amount'
- mapping :item_name, 'item_name'
- mapping :description, 'item_name'
-
- mapping :customer, :first_name => 'name_first',
- :last_name => 'name_last',
- :email => 'email_address',
- :phone => 'phone'
-
- 5.times { |i| mapping :"custom_str#{i}", "custom_str#{i}" }
- 5.times { |i| mapping :"custom_int#{i}", "custom_int#{i}" }
-
- mapping :email_confirmation, 'email_confirmation'
- mapping :confirmation_address, 'confirmation_address'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pay_fast/notification.rb b/lib/active_merchant/billing/integrations/pay_fast/notification.rb
deleted file mode 100644
index b0df3332193..00000000000
--- a/lib/active_merchant/billing/integrations/pay_fast/notification.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayFast
- # Parser and handler for incoming ITN from PayFast.
- # The Example shows a typical handler in a rails application.
- #
- # Example
- #
- # class BackendController < ApplicationController
- # include ActiveMerchant::Billing::Integrations
- #
- # def pay_fast_itn
- # notify = PayFast::Notification.new(request.raw_post)
- #
- # order = Order.find(notify.item_id)
- #
- # if notify.acknowledge
- # begin
- #
- # if notify.complete? and order.total == notify.amount
- # order.status = 'success'
- #
- # shop.ship(order)
- # else
- # logger.error("Failed to verify Paypal's notification, please investigate")
- # end
- #
- # rescue => e
- # order.status = 'failed'
- # raise
- # ensure
- # order.save
- # end
- # end
- #
- # render :nothing
- # end
- # end
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include PostsData
- include Common
-
- # Was the transaction complete?
- def complete?
- status == "Completed"
- end
-
- # Status of transaction. List of possible values:
- # COMPLETE::
- def status
- if params['payment_status'] == "COMPLETE"
- "Completed"
- else
- "Failed"
- end
- end
-
- # Id of this transaction (uniq PayFast transaction id)
- def transaction_id
- params['pf_payment_id']
- end
-
- # Id of this transaction (uniq Shopify transaction id)
- def item_id
- params['m_payment_id']
- end
-
- # The total amount which the payer paid.
- def gross
- params['amount_gross']
- end
-
- # The total in fees which was deducated from the amount.
- def fee
- params['amount_fee']
- end
-
- # The net amount credited to the receiver's account.
- def amount
- params['amount_net']
- end
-
- # The name of the item being charged for.
- def item_name
- params['item_name']
- end
-
- # The Merchant ID as given by the PayFast system. Used to uniquely identify the receiver's account.
- def merchant_id
- params['merchant_id']
- end
-
- def currency
- nil
- end
- # Generated hash depends on params order so use OrderedHash instead of Hash
- def empty!
- super
- @params = ActiveSupport::OrderedHash.new
- end
-
- # Acknowledge the transaction to PayFast. This method has to be called after a new
- # ITN arrives. PayFast will verify that all the information we received are correct and will return a
- # VERIFIED or INVALID status.
- #
- # Example:
- #
- # def pay_fast_itn
- # notify = PayFastNotification.new(request.raw_post)
- #
- # if notify.acknowledge
- # ... process order ... if notify.complete?
- # else
- # ... log possible hacking attempt ...
- # end
- def acknowledge
- if params[PayFast.signature_parameter_name] == generate_signature(:notify)
- response = ssl_post(PayFast.validate_service_url, notify_signature_string,
- 'Content-Type' => "application/x-www-form-urlencoded",
- 'Content-Length' => "#{notify_signature_string.size}"
- )
- raise StandardError.new("Faulty PayFast result: #{response}") unless ['VALID', 'INVALID'].include?(response)
-
- response == "VALID"
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pay_fast/return.rb b/lib/active_merchant/billing/integrations/pay_fast/return.rb
deleted file mode 100644
index 505dc14fa21..00000000000
--- a/lib/active_merchant/billing/integrations/pay_fast/return.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayFast
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/payflow_link.rb b/lib/active_merchant/billing/integrations/payflow_link.rb
deleted file mode 100644
index 875eac688fb..00000000000
--- a/lib/active_merchant/billing/integrations/payflow_link.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayflowLink
- autoload :Helper, 'active_merchant/billing/integrations/payflow_link/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/payflow_link/notification.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://payflowlink.paypal.com'
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- ActiveMerchant::Billing::Integrations::Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/payflow_link/helper.rb b/lib/active_merchant/billing/integrations/payflow_link/helper.rb
deleted file mode 100644
index 29326dcf721..00000000000
--- a/lib/active_merchant/billing/integrations/payflow_link/helper.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayflowLink
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include PostsData
-
- def initialize(order, account, options = {})
- super
- add_field('login', account)
- add_field('echodata', 'True')
- add_field('user2', self.test?)
- add_field('invoice', order)
- add_field('vendor', account)
- add_field('user', options[:credential4].presence || account)
- add_field('trxtype', options[:transaction_type] || 'S')
- end
-
- mapping :account, 'login'
- mapping :credential2, 'pwd'
- mapping :credential3, 'partner'
- mapping :order, 'user1'
-
- mapping :amount, 'amt'
-
-
- mapping :billing_address, :city => 'city',
- :address => 'address',
- :state => 'state',
- :zip => 'zip',
- :country => 'country',
- :phone => 'phone',
- :name => 'name'
-
- mapping :customer, { :first_name => 'first_name', :last_name => 'last_name' }
-
- def description(value)
- add_field('description', normalize("#{value}").delete("#"))
- end
-
- def customer(params = {})
- add_field(mappings[:customer][:first_name], params[:first_name])
- add_field(mappings[:customer][:last_name], params[:last_name])
- end
-
- def billing_address(params = {})
- # Get the country code in the correct format
- # Use what we were given if we can't find anything
- country_code = lookup_country_code(params.delete(:country))
- add_field(mappings[:billing_address][:country], country_code)
-
- add_field(mappings[:billing_address][:address], [params.delete(:address1), params.delete(:address2)].compact.join(' '))
-
- province_code = params.delete(:state)
- add_field(mappings[:billing_address][:state], province_code.blank? ? 'N/A' : province_code.upcase)
-
- # Everything else
- params.each do |k, v|
- field = mappings[:billing_address][k]
- add_field(field, v) unless field.nil?
- end
- end
-
- def form_fields
- token, token_id = request_secure_token
-
- {"securetoken" => token, "securetokenid" => token_id, "mode" => test? ? "test" : "live"}
- end
-
- private
-
- def secure_token_id
- @secure_token_id ||= Utils.generate_unique_id
- end
-
- def secure_token_url
- test? ? "https://pilot-payflowpro.paypal.com" : "https://payflowpro.paypal.com"
- end
-
- def request_secure_token
- @fields["securetokenid"] = secure_token_id
- @fields["createsecuretoken"] = "Y"
-
- fields = @fields.collect {|key, value| "#{key}[#{value.length}]=#{value}" }.join("&")
-
- response = ssl_post(secure_token_url, fields)
-
- parse_response(response)
- end
-
- def parse_response(response)
- response = response.split("&").inject({}) do |hash, param|
- key, value = param.split("=")
- hash[key] = value
- hash
- end
-
- [response['SECURETOKEN'], response['SECURETOKENID']] if response['RESPMSG'] && response['RESPMSG'].downcase == "approved"
- end
-
- def normalize(text)
- return unless text
-
- if ActiveSupport::Inflector.method(:transliterate).arity == -2
- ActiveSupport::Inflector.transliterate(text,'')
- elsif RUBY_VERSION >= '1.9'
- text.gsub(/[^\x00-\x7F]+/, '')
- else
- ActiveSupport::Inflector.transliterate(text).to_s
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/payflow_link/notification.rb b/lib/active_merchant/billing/integrations/payflow_link/notification.rb
deleted file mode 100644
index f2879869413..00000000000
--- a/lib/active_merchant/billing/integrations/payflow_link/notification.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayflowLink
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- # Was the transaction complete?
- def complete?
- status == "Completed"
- end
-
- # When was this payment received by the client.
- # sometimes it can happen that we get the notification much later.
- # One possible scenario is that our web application was down. In this case paypal tries several
- # times an hour to inform us about the notification
- def received_at
- DateTime.parse(params['TRANSTIME']) if params['TRANSTIME']
- rescue ArgumentError
- nil
- end
-
- def status
- params['RESPMSG']
- end
-
- # Id of this transaction (paypal number)
- def transaction_id
- params['PNREF']
- end
-
- # What type of transaction are we dealing with?
- def type
- params['TYPE']
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- params['AMT']
- end
-
- # What currency have we been dealing with
- def currency
- nil
- end
-
- def status
- params['RESULT'] == '0' ? 'Completed' : 'Failed'
- end
-
- # This is the item number which we submitted to paypal
- def item_id
- params['USER1']
- end
-
- # This is the invoice which you passed to paypal
- def invoice
- params['INVNUM']
- end
-
- # Was this a test transaction?
- def test?
- params['USER2'] == 'true'
- end
-
- def account
- params["ACCT"]
- end
-
- def acknowledge
- true
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paypal.rb b/lib/active_merchant/billing/integrations/paypal.rb
deleted file mode 100644
index 13a1d277bcb..00000000000
--- a/lib/active_merchant/billing/integrations/paypal.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paypal
- autoload :Return, 'active_merchant/billing/integrations/paypal/return.rb'
- autoload :Helper, 'active_merchant/billing/integrations/paypal/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/paypal/notification.rb'
-
- # Overwrite this if you want to change the Paypal test url
- mattr_accessor :test_url
- self.test_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
-
- # Overwrite this if you want to change the Paypal production url
- mattr_accessor :production_url
- self.production_url = 'https://www.paypal.com/cgi-bin/webscr'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paypal/helper.rb b/lib/active_merchant/billing/integrations/paypal/helper.rb
deleted file mode 100644
index c905ae75901..00000000000
--- a/lib/active_merchant/billing/integrations/paypal/helper.rb
+++ /dev/null
@@ -1,119 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paypal
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- CANADIAN_PROVINCES = { 'AB' => 'Alberta',
- 'BC' => 'British Columbia',
- 'MB' => 'Manitoba',
- 'NB' => 'New Brunswick',
- 'NL' => 'Newfoundland',
- 'NS' => 'Nova Scotia',
- 'NU' => 'Nunavut',
- 'NT' => 'Northwest Territories',
- 'ON' => 'Ontario',
- 'PE' => 'Prince Edward Island',
- 'QC' => 'Quebec',
- 'SK' => 'Saskatchewan',
- 'YT' => 'Yukon'
- }
- # See https://www.paypal.com/IntegrationCenter/ic_std-variable-reference.html for details on the following options.
- mapping :order, [ 'item_number', 'custom' ]
-
- def initialize(order, account, options = {})
- super
- add_field('cmd', '_ext-enter')
- add_field('redirect_cmd', '_xclick')
- add_field('quantity', 1)
- add_field('item_name', 'Store purchase')
- add_field('no_shipping', '1')
- add_field('no_note', '1')
- add_field('charset', 'utf-8')
- add_field('address_override', '0')
- add_field('bn', application_id.to_s.slice(0,32)) unless application_id.blank?
- end
-
- mapping :amount, 'amount'
- mapping :account, 'business'
- mapping :currency, 'currency_code'
- mapping :notify_url, 'notify_url'
- mapping :return_url, 'return'
- mapping :cancel_return_url, 'cancel_return'
- mapping :invoice, 'invoice'
- mapping :item_name, 'item_name'
- mapping :quantity, 'quantity'
- mapping :no_shipping, 'no_shipping'
- mapping :no_note, 'no_note'
- mapping :address_override, 'address_override'
-
- mapping :application_id, 'bn'
-
- mapping :customer, :first_name => 'first_name',
- :last_name => 'last_name',
- :email => 'email'
-
- mapping :shipping_address, :city => 'city',
- :address1 => 'address1',
- :address2 => 'address2',
- :state => 'state',
- :zip => 'zip',
- :country => 'country'
-
- def shipping_address(params = {})
-
- # Get the country code in the correct format
- # Use what we were given if we can't find anything
- country_code = lookup_country_code(params.delete(:country))
- add_field(mappings[:shipping_address][:country], country_code)
-
- if params.has_key?(:phone)
- phone = params.delete(:phone).to_s
-
- # Whipe all non digits
- phone.gsub!(/\D+/, '')
-
- if ['US', 'CA'].include?(country_code) && phone =~ /(\d{3})(\d{3})(\d{4})$/
- add_field('night_phone_a', $1)
- add_field('night_phone_b', $2)
- add_field('night_phone_c', $3)
- else
- add_field('night_phone_b', phone)
- end
- end
-
- province_code = params.delete(:state)
-
- case country_code
- when 'CA'
- add_field(mappings[:shipping_address][:state], CANADIAN_PROVINCES[province_code.upcase]) unless province_code.nil?
- when 'US'
- add_field(mappings[:shipping_address][:state], province_code)
- else
- add_field(mappings[:shipping_address][:state], province_code.blank? ? 'N/A' : province_code)
- end
-
- # Everything else
- params.each do |k, v|
- field = mappings[:shipping_address][k]
- add_field(field, v) unless field.nil?
- end
- end
-
- mapping :tax, 'tax'
- mapping :shipping, 'shipping'
- mapping :cmd, 'cmd'
- mapping :custom, 'custom'
- mapping :src, 'src'
- mapping :sra, 'sra'
- %w(a p t).each do |l|
- (1..3).each do |i|
- mapping "#{l}#{i}".to_sym, "#{l}#{i}"
- end
- end
- end
- end
- end
- end
-end
-
-
diff --git a/lib/active_merchant/billing/integrations/paypal/notification.rb b/lib/active_merchant/billing/integrations/paypal/notification.rb
deleted file mode 100644
index 7fdb217da21..00000000000
--- a/lib/active_merchant/billing/integrations/paypal/notification.rb
+++ /dev/null
@@ -1,227 +0,0 @@
-require 'net/http'
-require 'time'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paypal
- # Parser and handler for incoming Instant payment notifications from paypal.
- # The Example shows a typical handler in a rails application. Note that this
- # is an example, please read the Paypal API documentation for all the details
- # on creating a safe payment controller.
- #
- # Example
- #
- # class BackendController < ApplicationController
- # include ActiveMerchant::Billing::Integrations
- #
- # def paypal_ipn
- # notify = Paypal::Notification.new(request.raw_post)
- #
- # if notify.masspay?
- # masspay_items = notify.items
- # end
- #
- # order = Order.find(notify.item_id)
- #
- # if notify.acknowledge
- # begin
- #
- # if notify.complete? and order.total == notify.amount
- # order.status = 'success'
- #
- # shop.ship(order)
- # else
- # logger.error("Failed to verify Paypal's notification, please investigate")
- # end
- #
- # rescue => e
- # order.status = 'failed'
- # raise
- # ensure
- # order.save
- # end
- # end
- #
- # render :nothing
- # end
- # end
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include PostsData
-
- def initialize(post, options = {})
- super
- extend MassPayNotification if masspay?
- end
-
- # Was the transaction complete?
- def complete?
- status == "Completed"
- end
-
- # Is it a masspay notification?
- def masspay?
- type == "masspay"
- end
-
- # When was this payment received by the client.
- # sometimes it can happen that we get the notification much later.
- # One possible scenario is that our web application was down. In this case paypal tries several
- # times an hour to inform us about the notification
- def received_at
- parsed_time_fields = DateTime._strptime(params['payment_date'], "%H:%M:%S %b %d, %Y %Z")
- Time.gm(
- parsed_time_fields[:year],
- parsed_time_fields[:mon],
- parsed_time_fields[:mday],
- parsed_time_fields[:hour],
- parsed_time_fields[:min],
- parsed_time_fields[:sec]
- ) + Time.zone_offset(parsed_time_fields[:zone])
- end
-
- # Status of transaction. List of possible values:
- # Canceled-Reversal::
- # Completed::
- # Denied::
- # Expired::
- # Failed::
- # In-Progress::
- # Partially-Refunded::
- # Pending::
- # Processed::
- # Refunded::
- # Reversed::
- # Voided::
- def status
- params['payment_status']
- end
-
- # Id of this transaction (paypal number)
- def transaction_id
- params['txn_id']
- end
-
- # What type of transaction are we dealing with?
- # "cart" "send_money" "web_accept" are possible here.
- def type
- params['txn_type']
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- params['mc_gross']
- end
-
- # the markup paypal charges for the transaction
- def fee
- params['mc_fee']
- end
-
- # What currency have we been dealing with
- def currency
- params['mc_currency']
- end
-
- # This is the item number which we submitted to paypal
- # The custom field is also mapped to item_id because PayPal
- # doesn't return item_number in dispute notifications
- def item_id
- params['item_number'] || params['custom']
- end
-
- # This is the invoice which you passed to paypal
- def invoice
- params['invoice']
- end
-
- # Was this a test transaction?
- def test?
- params['test_ipn'] == '1'
- end
-
- def account
- params['business'] || params['receiver_email']
- end
-
- # Acknowledge the transaction to paypal. This method has to be called after a new
- # ipn arrives. Paypal will verify that all the information we received are correct and will return a
- # ok or a fail.
- #
- # Example:
- #
- # def paypal_ipn
- # notify = PaypalNotification.new(request.raw_post)
- #
- # if notify.acknowledge
- # ... process order ... if notify.complete?
- # else
- # ... log possible hacking attempt ...
- # end
- def acknowledge
- payload = raw
-
- response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload,
- 'Content-Length' => "#{payload.size}",
- 'User-Agent' => "Active Merchant -- http://activemerchant.org"
- )
-
- raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response)
-
- response == "VERIFIED"
- end
- end
-
- module MassPayNotification
- # Mass pay returns a collection of MassPay Items, so inspect items to get the values
- def transaction_id
- end
-
- # Mass pay returns a collection of MassPay Items, so inspect items to get the values
- def gross
- end
-
- # Mass pay returns a collection of MassPay Items, so inspect items to get the values
- def fee
- end
-
- # Mass pay returns a collection of MassPay Items, so inspect items to get the values
- def currency
- end
-
- # Mass pay returns a collection of MassPay Items, so inspect items to get the values
- def item_id
- end
-
- # Mass pay returns a collection of MassPay Items, so inspect items to get the values
- def account
- end
-
- # Collection of notification items returned for MassPay transactions
- def items
- @items ||= (1..number_of_mass_pay_items).map do |item_number|
- MassPayItem.new(
- params["masspay_txn_id_#{item_number}"],
- params["mc_gross_#{item_number}"],
- params["mc_fee_#{item_number}"],
- params["mc_currency_#{item_number}"],
- params["unique_id_#{item_number}"],
- params["receiver_email_#{item_number}"],
- params["status_#{item_number}"]
- )
- end
- end
-
- private
-
- def number_of_mass_pay_items
- @number_of_mass_pay_items ||= params.keys.select { |k| k.start_with? 'masspay_txn_id' }.size
- end
- end
-
- class MassPayItem < Struct.new(:transaction_id, :gross, :fee, :currency, :item_id, :account, :status)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paypal/return.rb b/lib/active_merchant/billing/integrations/paypal/return.rb
deleted file mode 100644
index 2b22a06a610..00000000000
--- a/lib/active_merchant/billing/integrations/paypal/return.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paypal
- class Return < ActiveMerchant::Billing::Integrations::Return
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb b/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb
deleted file mode 100644
index f1741421e47..00000000000
--- a/lib/active_merchant/billing/integrations/paypal_payments_advanced.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module ActiveMerchant
- module Billing
- module Integrations
- module PaypalPaymentsAdvanced
- autoload :Helper, 'active_merchant/billing/integrations/paypal_payments_advanced/helper.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://payflowlink.paypal.com'
-
- def self.notification(post, options = {})
- PayflowLink::Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb b/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb
deleted file mode 100644
index cd1889999b3..00000000000
--- a/lib/active_merchant/billing/integrations/paypal_payments_advanced/helper.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveMerchant
- module Billing
- module Integrations
- module PaypalPaymentsAdvanced
- class Helper < PayflowLink::Helper
-
- def initialize(order, account, options)
- super
- add_field('partner', 'PayPal')
- end
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/integrations/paysbuy.rb b/lib/active_merchant/billing/integrations/paysbuy.rb
deleted file mode 100644
index d74db8c4bcc..00000000000
--- a/lib/active_merchant/billing/integrations/paysbuy.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paysbuy
- autoload :Helper, File.dirname(__FILE__) + '/paysbuy/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/paysbuy/notification.rb'
-
- mattr_accessor :test_url
- self.test_url = 'https://demo.paysbuy.com/paynow.aspx'
-
- mattr_accessor :production_url
- self.production_url = 'https://www.paysbuy.com/paynow.aspx'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paysbuy/helper.rb b/lib/active_merchant/billing/integrations/paysbuy/helper.rb
deleted file mode 100644
index 3cd3430b199..00000000000
--- a/lib/active_merchant/billing/integrations/paysbuy/helper.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paysbuy
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :account, 'biz'
- mapping :amount, 'amt'
- mapping :order, 'inv'
- mapping :description, 'itm'
- mapping :notify_url, 'postURL'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/paysbuy/notification.rb b/lib/active_merchant/billing/integrations/paysbuy/notification.rb
deleted file mode 100644
index c7121a96389..00000000000
--- a/lib/active_merchant/billing/integrations/paysbuy/notification.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Paysbuy
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- SUCCESS = '00'
- FAIL = '99'
-
- def complete?
- status == 'Completed'
- end
-
- def item_id
- params['result'][2..-1]
- end
-
- def status
- params['result'][0..1] == SUCCESS ? 'Completed' : 'Failed'
- end
-
- def acknowledge
- true
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/payu_in.rb b/lib/active_merchant/billing/integrations/payu_in.rb
deleted file mode 100755
index 74933f1c7c6..00000000000
--- a/lib/active_merchant/billing/integrations/payu_in.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'digest/sha2'
-require 'bigdecimal'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayuIn
- autoload :Return, 'active_merchant/billing/integrations/payu_in/return.rb'
- autoload :Helper, 'active_merchant/billing/integrations/payu_in/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/payu_in/notification.rb'
-
- mattr_accessor :test_url
- mattr_accessor :production_url
-
- self.test_url = 'https://test.payu.in/_payment.php'
- self.production_url = 'https://secure.payu.in/_payment.php'
-
- def self.service_url
- ActiveMerchant::Billing::Base.integration_mode == :production ? self.production_url : self.test_url
- end
-
- def self.notification(post, options = {})
- Notification.new(post, options)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
-
- def self.checksum(merchant_id, secret_key, *payload_items )
- options = payload_items.pop if Hash === payload_items.last
- options ||= {}
- payload = if options[:reverse] then
- payload_items.dup.push( merchant_id || "" ).unshift( secret_key || "" ).collect{ |x| x.to_s }.join("|")
- else
- payload_items.dup.unshift( merchant_id || "" ).push( secret_key || "" ).collect{ |x| x.to_s }.join("|")
- end
- Digest::SHA512.hexdigest( payload )
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/payu_in/helper.rb b/lib/active_merchant/billing/integrations/payu_in/helper.rb
deleted file mode 100755
index 32048d309bc..00000000000
--- a/lib/active_merchant/billing/integrations/payu_in/helper.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayuIn
- class Helper < ActiveMerchant::Billing::Integrations::Helper
-
- mapping :amount, 'amount'
- mapping :account, 'key'
- mapping :order, 'txnid'
- mapping :credential2, 'productinfo'
-
- mapping :customer, :first_name => 'firstname',
- :last_name => 'lastname',
- :email => 'email',
- :phone => 'phone'
-
- mapping :billing_address, :city => 'city',
- :address1 => 'address1',
- :address2 => 'address2',
- :state => 'state',
- :zip => 'zip',
- :country => 'country'
-
- # Which tab you want to be open default on PayU
- # CC (CreditCard) or NB (NetBanking)
- mapping :mode, 'pg'
-
- mapping :notify_url, 'notify_url'
- mapping :return_url, ['surl', 'furl']
- mapping :cancel_return_url, 'curl'
- mapping :checksum, 'hash'
-
- mapping :user_defined, { :var1 => 'udf1',
- :var2 => 'udf2',
- :var3 => 'udf3',
- :var4 => 'udf4',
- :var5 => 'udf5',
- :var6 => 'udf6',
- :var7 => 'udf7',
- :var8 => 'udf8',
- :var9 => 'udf9',
- :var10 => 'udf10'
- }
-
- def initialize(order, account, options = {})
- super
- self.pg = 'CC'
- end
-
- def form_fields
- @fields.merge(mappings[:checksum] => generate_checksum)
- end
-
- def generate_checksum( options = {} )
- checksum_fields = [ :order, :amount, :credential2, { :customer => [ :first_name, :email ] },
- { :user_defined => [ :var1, :var2, :var3, :var4, :var5, :var6, :var7, :var8, :var9, :var10 ] } ]
- checksum_payload_items = checksum_fields.inject( [] ) do | items, field |
- if Hash === field then
- key = field.keys.first
- field[key].inject( items ){ |s,x| items.push( @fields[ mappings[key][x] ] ) }
- else
- items.push( @fields[ mappings[field] ] )
- end
- end
- checksum_payload_items.push( options )
- PayuIn.checksum(@fields["key"], @fields["productinfo"], *checksum_payload_items )
- end
-
- end
-
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/payu_in/notification.rb b/lib/active_merchant/billing/integrations/payu_in/notification.rb
deleted file mode 100755
index 2c4b3466dd6..00000000000
--- a/lib/active_merchant/billing/integrations/payu_in/notification.rb
+++ /dev/null
@@ -1,165 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayuIn
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- def initialize(post, options = {})
- super(post, options)
- @merchant_id = options[:credential1]
- @secret_key = options[:credential2]
- end
-
- def complete?
- status == "Completed"
- end
-
- def status
- @status ||= if checksum_ok?
- if transaction_id.blank?
- 'Invalid'
- else
- case transaction_status.downcase
- when 'success' then 'Completed'
- when 'failure' then 'Failed'
- when 'pending' then 'Pending'
- end
- end
- else
- 'Tampered'
- end
- end
-
- def invoice_ok?( order_id )
- order_id.to_s == invoice.to_s
- end
-
- # Order amount should be equal to gross - discount
- def amount_ok?( order_amount, order_discount = BigDecimal.new( '0.0' ) )
- BigDecimal.new( gross ) == order_amount && BigDecimal.new( discount ) == order_discount
- end
-
- # Status of transaction return from the PayU. List of possible values:
- # SUCCESS::
- # PENDING::
- # FAILURE::
- def transaction_status
- params['status']
- end
-
- # ID of this transaction (PayU.in number)
- def transaction_id
- params['mihpayid']
- end
-
- # Mode of Payment
- #
- # 'CC' for credit-card
- # 'NB' for net-banking
- # 'CD' for cheque or DD
- # 'CO' for Cash Pickup
- def type
- params['mode']
- end
-
- # What currency have we been dealing with
- def currency
- 'INR'
- end
-
- def item_id
- params['txnid']
- end
-
- # This is the invoice which you passed to PayU.in
- def invoice
- params['txnid']
- end
-
- # Merchant Id provided by the PayU.in
- def account
- params['key']
- end
-
- # original amount send by merchant
- def gross
- params['amount']
- end
-
- # This is discount given to user - based on promotion set by merchants.
- def discount
- params['discount']
- end
-
- # Description offer for what PayU given the offer to user - based on promotion set by merchants.
- def offer_description
- params['offer']
- end
-
- # Information about the product as send by merchant
- def product_info
- params['productinfo']
- end
-
- # Email of the customer
- def customer_email
- params['email']
- end
-
- # Phone of the customer
- def customer_phone
- params['phone']
- end
-
- # Firstname of the customer
- def customer_first_name
- params['firstname']
- end
-
- # Lastname of the customer
- def customer_last_name
- params['lastname']
- end
-
- # Full address of the customer
- def customer_address
- { :address1 => params['address1'], :address2 => params['address2'],
- :city => params['city'], :state => params['state'],
- :country => params['country'], :zipcode => params['zipcode'] }
- end
-
- def user_defined
- return @user_defined if @user_defined
- @user_defined = []
- 10.times{ |i| @user_defined.push( params[ "udf#{i+1}" ] ) }
- @user_defined
- end
-
- def checksum
- params['hash']
- end
-
- def message
- @message || params['error']
- end
-
- def acknowledge
- checksum_ok?
- end
-
- def checksum_ok?
- fields = user_defined.dup.push( customer_email, customer_first_name, product_info, gross, invoice, :reverse => true )
- fields.unshift( transaction_status )
- unless PayuIn.checksum(@merchant_id, @secret_key, *fields ) == checksum
- @message = 'Return checksum not matching the data provided'
- return false
- end
- true
- end
-
- end
- end
- end
- end
-end
-
diff --git a/lib/active_merchant/billing/integrations/payu_in/return.rb b/lib/active_merchant/billing/integrations/payu_in/return.rb
deleted file mode 100755
index e2456f7317f..00000000000
--- a/lib/active_merchant/billing/integrations/payu_in/return.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module PayuIn
- class Return < ActiveMerchant::Billing::Integrations::Return
-
- def initialize(query_string, options = {})
- super
- @notification = Notification.new(query_string, options)
- end
-
- def transaction_id
- @notification.transaction_id
- end
-
- def status( order_id, order_amount )
- if @notification.invoice_ok?( order_id ) && @notification.amount_ok?( BigDecimal.new(order_amount) )
- @notification.status
- else
- 'Mismatch'
- end
- end
-
- def success?
- status( @params['txnid'], @params['amount'] ) == 'Completed'
- end
-
- def message
- @notification.message
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/platron.rb b/lib/active_merchant/billing/integrations/platron.rb
deleted file mode 100644
index 087a6d76f97..00000000000
--- a/lib/active_merchant/billing/integrations/platron.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- # Platron API: www.platron.ru/PlatronAPI.pdf
- module Platron
- autoload :Helper, File.dirname(__FILE__) + '/platron/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/platron/notification.rb'
- autoload :Common, File.dirname(__FILE__) + '/platron/common.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://www.platron.ru/payment.php'
-
- def self.notification(raw_post)
- Notification.new(raw_post)
- end
-
- def self.generate_signature_string(params, path, secret)
- sorted_params = params.sort_by{|k,v| k.to_s}.collect{|k,v| v}
- [path, sorted_params, secret].flatten.compact.join(';')
- end
-
- def self.generate_signature(params, path, secret)
- Digest::MD5.hexdigest(generate_signature_string(params, path, secret))
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/platron/helper.rb b/lib/active_merchant/billing/integrations/platron/helper.rb
deleted file mode 100644
index 42cf7d1e085..00000000000
--- a/lib/active_merchant/billing/integrations/platron/helper.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Platron
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- def initialize(order, account, options = {})
- @secret_key = options.delete(:secret)
- @path = options.delete(:path)
- description = options.delete(:description)
- super
- self.add_field('pg_salt', rand(36**15).to_s(36))
- self.add_field('pg_description', description)
- end
-
- def form_fields
- @fields.merge('pg_sig' => Common.generate_signature(@fields, @path, @secret_key))
- end
-
- def params
- @fields
- end
-
- mapping :account, 'pg_merchant_id'
- mapping :amount, 'pg_amount'
- mapping :order, 'pg_order_id'
- mapping :description, 'pg_description'
- mapping :currency, 'pg_currency'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/platron/notification.rb b/lib/active_merchant/billing/integrations/platron/notification.rb
deleted file mode 100644
index 7c417a8b5b3..00000000000
--- a/lib/active_merchant/billing/integrations/platron/notification.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-require 'builder'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Platron
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- def initialize(*args)
- super
- @signature = params.delete('pg_sig')
- end
-
- def complete?
- params['pg_result']
- end
-
- def order_id
- params['pg_order_id']
- end
-
- def platron_payment_id
- params['pg_payment_id']
- end
-
- def currency
- params['pg_ps_currency']
- end
-
- def payment_system
- params['pg_payment_system']
- end
-
- def user_phone
- params['pg_user_phone']
- end
-
- def card_brand
- params['pg_card_brand']
- end
-
- def captured
- params['pg_captured']
- end
-
- def overpayment
- params['pg_overpayment']
- end
-
- def failure_code
- params['pg_failure_code']
- end
-
- def failure_description
- params['pg_failure_description']
- end
-
- def payment_date
- params['pg_payment_date']
- end
-
- def salt
- params['pg_salt']
- end
-
- def signature
- @signature
- end
-
- def net_amount
- params['pg_net_amount']
- end
-
- def ps_amount
- params['pg_ps_amount']
- end
-
- def ps_full_amount
- params['pg_ps_full_amount']
- end
-
- def amount
- params['pg_amount']
- end
-
- def secret
- @options[:secret]
- end
-
- def path
- @options[:path]
- end
-
- def acknowledge
- signature == Platron.generate_signature(params, path, secret)
- end
-
- def success_response(path,secret)
- salt = rand(36**15).to_s(36)
- xml = ""
- doc = Builder::XmlMarkup.new(:target => xml)
- sign = Platron.generate_signature({:pg_status => 'ok', :pg_salt => salt}, path, secret)
- doc.response do
- doc.pg_status 'ok'
- doc.pg_salt salt
- doc.pg_sig sign
- end
- xml
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pxpay.rb b/lib/active_merchant/billing/integrations/pxpay.rb
deleted file mode 100644
index 4973a9c967e..00000000000
--- a/lib/active_merchant/billing/integrations/pxpay.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Pxpay
- autoload :Helper, 'active_merchant/billing/integrations/pxpay/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/pxpay/notification.rb'
- autoload :Return, 'active_merchant/billing/integrations/pxpay/return.rb'
-
- TOKEN_URL = 'https://sec.paymentexpress.com/pxpay/pxaccess.aspx'
-
- LIVE_URL = 'https://sec.paymentexpress.com/pxpay/pxpay.aspx'
-
- def self.token_url
- TOKEN_URL
- end
-
- def self.service_url
- LIVE_URL
- end
-
- def self.notification(post, options={})
- Notification.new(post, options)
- end
-
- def self.return(query_string, options={})
- Return.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pxpay/helper.rb b/lib/active_merchant/billing/integrations/pxpay/helper.rb
deleted file mode 100644
index 71ca2932437..00000000000
--- a/lib/active_merchant/billing/integrations/pxpay/helper.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-require 'active_support/version' # for ActiveSupport2.3
-require 'active_support/core_ext/float/rounding.rb' unless ActiveSupport::VERSION::MAJOR > 3 # Float#round(precision)
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Pxpay
- # An example. Note the username as a parameter and transaction key you
- # will want to use later. The amount that you pass in will be *rounded*,
- # so preferably pass in X.2 decimal so that no rounding occurs. You need
- # to set :credential2 to your PxPay secret key.
- #
- # PxPay accounts have Failproof Notification enabled by default which means
- # in addition to the user being redirected to your return_url, the return_url will
- # be accessed by the PxPay servers directly, immediately after transaction success.
- #
- # payment_service_for('order_id', 'pxpay_user_ID', :service => :pxpay,
- # :amount => 157.0, :currency => 'USD', :credential2 => 'pxpay_key') do |service|
- #
- # service.customer :email => 'customer@email.com'
- #
- # service.description 'Order 123 for MyStore'
- #
- # # Must specify both a return_url or PxPay will show an error instead of
- # # capturing credit card details.
- #
- # service.return_url "http://t/pxpay/payment_received_notification_sub_step"
- #
- # # These fields will be copied verbatim to the Notification
- # service.custom1 'custom text 1'
- # service.custom2 ''
- # service.custom3 ''
- # # See the helper.rb file for various custom fields
- # end
-
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include PostsData
- mapping :account, 'PxPayUserId'
- mapping :credential2, 'PxPayKey'
- mapping :currency, 'CurrencyInput'
- mapping :description, 'MerchantReference'
- mapping :order, 'TxnId'
- mapping :customer, :email => 'EmailAddress'
-
- mapping :custom1, 'TxnData1'
- mapping :custom2, 'TxnData2'
- mapping :custom3, 'TxnData3'
-
- def initialize(order, account, options = {})
- super
- add_field 'AmountInput', "%.2f" % options[:amount].to_f.round(2)
- add_field 'EnableAddBillCard', '0'
- add_field 'TxnType', 'Purchase'
- end
-
- def return_url(url)
- add_field 'UrlSuccess', url
- add_field 'UrlFail', url
- end
-
- def form_fields
- # if either return URLs are blank PxPay will generate a token but redirect user to error page.
- raise "error - must specify return_url" if @fields['UrlSuccess'].blank?
- raise "error - must specify cancel_return_url" if @fields['UrlFail'].blank?
-
- result = request_secure_redirect
- raise "error - failed to get token - message was #{result[:redirect]}" unless result[:valid] == "1"
-
- url = URI.parse(result[:redirect])
-
- CGI.parse(url.query)
- end
-
- def form_method
- "GET"
- end
-
- private
- def generate_request
- xml = REXML::Document.new
- root = xml.add_element('GenerateRequest')
-
- @fields.each do | k, v |
- v = v.slice(0, 50) if k == "MerchantReference"
- root.add_element(k).text = v
- end
-
- xml.to_s
- end
-
- def request_secure_redirect
- request = generate_request
-
- response = ssl_post(Pxpay.token_url, request)
- xml = REXML::Document.new(response)
- root = REXML::XPath.first(xml, "//Request")
- valid = root.attributes["valid"]
- redirect = root.elements["URI"].text
-
- # example positive response:
- # https://sec.paymentexpress.com/pxpay/pxpay.aspx?userid=PxpayUser&request=REQUEST_TOKEN
-
- # example negative response:
- # Invalid TxnType
-
- {:valid => valid, :redirect => redirect}
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pxpay/notification.rb b/lib/active_merchant/billing/integrations/pxpay/notification.rb
deleted file mode 100644
index 14c075f2886..00000000000
--- a/lib/active_merchant/billing/integrations/pxpay/notification.rb
+++ /dev/null
@@ -1,157 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- module Pxpay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include PostsData
- include RequiresParameters
-
- def initialize(query_string, options={})
- # PxPay appends ?result=...&userid=... to whatever return_url was specified, even if that URL ended with a ?query.
- # So switch the first ? if present to a &
- query_string[/\?/] = '&' if query_string[/\?/]
- super
-
- @encrypted_params = @params
- @params = {}
-
- requires! @encrypted_params, "result"
- requires! @options, :credential1, :credential2
-
- decrypt_transaction_result(@encrypted_params["result"])
- end
-
- # was the notification a validly formed request?
- def acknowledge
- @valid == '1'
- end
-
- def status
- return 'Failed' unless success?
- return 'Completed' if complete?
- 'Error'
- end
-
- def complete?
- @params['TxnType'] == 'Purchase' && success?
- end
-
- def cancelled?
- !success?
- end
-
- # for field definitions see
- # http://www.paymentexpress.com/Technical_Resources/Ecommerce_Hosted/PxPay
-
- def success?
- @params['Success'] == '1'
- end
-
- def gross
- @params['AmountSettlement']
- end
-
- def currency
- @params['CurrencySettlement']
- end
-
- def account
- @params['userid']
- end
-
- def item_id
- @params['TxnId']
- end
-
- def currency_input
- @params['CurrencyInput']
- end
-
- def auth_code
- @params['AuthCode']
- end
-
- def card_type
- @params['CardName']
- end
-
- def card_holder_name
- @params['CardHolderName']
- end
-
- def card_number
- @params['CardNumber']
- end
-
- def expiry_date
- @params['DateExpiry']
- end
-
- def client_ip
- @params['ClientInfo']
- end
-
- def order_id
- item_id
- end
-
- def payer_email
- @params['EmailAddress']
- end
-
- def transaction_id
- @params['DpsTxnRef']
- end
-
- def settlement_date
- @params['DateSettlement']
- end
-
- # Indication of the uniqueness of a card number
- def txn_mac
- @params['TxnMac']
- end
-
- def message
- @params['ResponseText']
- end
-
- def optional_data
- [@params['TxnData1'],@fields['TxnData2'],@fields['TxnData3']]
- end
-
- # When was this payment was received by the client.
- def received_at
- settlement_date
- end
-
- # Was this a test transaction?
- def test?
- nil
- end
-
- private
-
- def decrypt_transaction_result(encrypted_result)
- request_xml = REXML::Document.new
- root = request_xml.add_element('ProcessResponse')
-
- root.add_element('PxPayUserId').text = @options[:credential1]
- root.add_element('PxPayKey').text = @options[:credential2]
- root.add_element('Response').text = encrypted_result
-
- @raw = ssl_post(Pxpay.token_url, request_xml.to_s)
-
- response_xml = REXML::Document.new(@raw)
- root = REXML::XPath.first(response_xml)
- @valid = root.attributes["valid"]
- @params = {}
- root.elements.each { |e| @params[e.name] = e.text }
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/pxpay/return.rb b/lib/active_merchant/billing/integrations/pxpay/return.rb
deleted file mode 100644
index 39a546cf382..00000000000
--- a/lib/active_merchant/billing/integrations/pxpay/return.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Pxpay
- class Return < ActiveMerchant::Billing::Integrations::Return
- def initialize(query_string, options={})
- @notification = Notification.new(query_string, options)
- end
-
- def success?
- @notification && @notification.complete?
- end
-
- def cancelled?
- @notification && @notification.cancelled?
- end
-
- def message
- @notification.message
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/quickpay.rb b/lib/active_merchant/billing/integrations/quickpay.rb
deleted file mode 100644
index 74884f8e808..00000000000
--- a/lib/active_merchant/billing/integrations/quickpay.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Quickpay
- autoload :Helper, File.dirname(__FILE__) + '/quickpay/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/quickpay/notification.rb'
-
- mattr_accessor :service_url
- self.service_url = 'https://secure.quickpay.dk/form/'
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/quickpay/helper.rb b/lib/active_merchant/billing/integrations/quickpay/helper.rb
deleted file mode 100644
index 8e3f61eb153..00000000000
--- a/lib/active_merchant/billing/integrations/quickpay/helper.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Quickpay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
-
- def initialize(order, account, options = {})
- md5secret options.delete(:credential2)
- super
- add_field('protocol', '6')
- add_field('msgtype', 'authorize')
- add_field('language', 'da')
- add_field('autocapture', 0)
- add_field('testmode', test? ? 1 : 0)
- add_field('ordernumber', format_order_number(order))
- end
-
- def md5secret(value)
- @md5secret = value
- end
-
- def form_fields
- @fields.merge('md5check' => generate_md5check)
- end
-
- def generate_md5string
- MD5_CHECK_FIELDS.map {|key| @fields[key.to_s]} * "" + @md5secret
- end
-
- def generate_md5check
- Digest::MD5.hexdigest(generate_md5string)
- end
-
- # Limited to 20 digits max
- def format_order_number(number)
- number.to_s.gsub(/[^\w]/, '').rjust(4, "0")[0...20]
- end
-
- MD5_CHECK_FIELDS = [
- :protocol, :msgtype, :merchant, :language, :ordernumber,
- :amount, :currency, :continueurl, :cancelurl, :callbackurl,
- :autocapture, :cardtypelock, :description, :ipaddress, :testmode,
- :deadline, :cardhash
- ]
-
- mapping :protocol, 'protocol'
- mapping :msgtype, 'msgtype'
- mapping :account, 'merchant'
- mapping :language, 'language'
- mapping :amount, 'amount'
- mapping :currency, 'currency'
-
- mapping :return_url, 'continueurl'
- mapping :cancel_return_url, 'cancelurl'
- mapping :notify_url, 'callbackurl'
-
- mapping :autocapture, 'autocapture'
- mapping :cardtypelock, 'cardtypelock'
-
- mapping :description, 'description'
- mapping :ipaddress, 'ipaddress'
- mapping :testmode, 'testmode'
- mapping :deadline, 'deadline'
- mapping :cardhash, 'cardhash'
-
- mapping :customer, ''
- mapping :billing_address, {}
- mapping :tax, ''
- mapping :shipping, ''
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/quickpay/notification.rb b/lib/active_merchant/billing/integrations/quickpay/notification.rb
deleted file mode 100644
index 0d5da68a987..00000000000
--- a/lib/active_merchant/billing/integrations/quickpay/notification.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Quickpay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- def complete?
- status == '000'
- end
-
- def item_id
- params['ordernumber']
- end
-
- def transaction_id
- params['transaction']
- end
-
- def received_at
- time = params['time']
- # If time only contains 12 integers then it's pre v5 format
- time = "20#{params['time']}" if /[0-9]{12}/.match(params['time'])
- Time.parse(time)
- end
-
- def gross
- "%.2f" % (gross_cents / 100.0)
- end
-
- def gross_cents
- params['amount'].to_i
- end
-
- def status
- params['qpstat']
- end
-
- def currency
- params['currency']
- end
-
- # Provide access to raw fields from quickpay
- %w(
- msgtype
- ordernumber
- state
- chstat
- chstatmsg
- qpstat
- qpstatmsg
- merchant
- merchantemail
- cardtype
- cardnumber
- cardhash
- cardexpire
- splitpayment
- fraudprobability
- fraudremarks
- fraudreport
- fee
- ).each do |attr|
- define_method(attr) do
- params[attr]
- end
- end
-
- MD5_CHECK_FIELDS = [
- :msgtype,
- :ordernumber,
- :amount,
- :currency,
- :time,
- :state,
- :qpstat,
- :qpstatmsg,
- :chstat,
- :chstatmsg,
- :merchant,
- :merchantemail,
- :transaction,
- :cardtype,
- :cardnumber,
- :cardhash,
- :cardexpire,
- :splitpayment,
- :fraudprobability,
- :fraudremarks,
- :fraudreport,
- :fee
- ]
-
- def generate_md5string
- MD5_CHECK_FIELDS.map { |key| params[key.to_s] } * "" + @options[:credential2].to_s
- end
-
- def generate_md5check
- Digest::MD5.hexdigest(generate_md5string)
- end
-
- # Quickpay doesn't do acknowledgements of callback notifications
- # Instead it uses and MD5 hash of all parameters
- def acknowledge
- generate_md5check == params['md5check']
- end
-
- # Take the posted data and move the relevant data into a hash
- def parse(post)
- # 30 + 12
- #------------------------------8a827a0e6829
- #Content-Disposition: form-data; name="msgtype"
- #
- #subscribe
- #------------------------------8a827a0e6829
- #Content-Disposition: form-data; name="ordernumber"
- #
- #BILP94406
-
- if post =~ /-{20,40}\w{6,24}/
- @raw = post.to_s
- post.split(/-{20,40}\w{6,24}[\n\r]*/m).each do |part|
- part.scan(/([^\n\r]+)[\n\r]+([^\n\r]*)/m) do |header, value|
- if header.match(/name=["'](.*)["']/)
- params[$1] = value.strip
- end
- end
- end
- else
- super
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/rbkmoney.rb b/lib/active_merchant/billing/integrations/rbkmoney.rb
deleted file mode 100644
index ff7c73a91ed..00000000000
--- a/lib/active_merchant/billing/integrations/rbkmoney.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require File.dirname(__FILE__) + '/rbkmoney/helper.rb'
-require File.dirname(__FILE__) + '/rbkmoney/notification.rb'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Rbkmoney
- mattr_accessor :service_url
- self.service_url = 'https://rbkmoney.ru/acceptpurchase.aspx'
-
- def self.notification(*args)
- Notification.new(*args)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/rbkmoney/helper.rb b/lib/active_merchant/billing/integrations/rbkmoney/helper.rb
deleted file mode 100644
index 4d79584b220..00000000000
--- a/lib/active_merchant/billing/integrations/rbkmoney/helper.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Rbkmoney
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :account, 'eshopId'
- mapping :amount, 'recipientAmount'
-
- # NOTE: rbkmoney uses outdated currency code 'RUR'
- mapping :currency, 'recipientCurrency'
-
- mapping :order, 'orderId'
-
- mapping :customer, :email => 'user_email'
-
- mapping :credential2, 'serviceName'
- mapping :credential3, 'successUrl'
- mapping :credential4, 'failUrl'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/rbkmoney/notification.rb b/lib/active_merchant/billing/integrations/rbkmoney/notification.rb
deleted file mode 100644
index a4ce79d79f0..00000000000
--- a/lib/active_merchant/billing/integrations/rbkmoney/notification.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Rbkmoney
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- %w(
- eshopId
- paymentId
- orderId
- eshopAccount
- serviceName
- recipientAmount
- recipientCurrency
- paymentStatus
- userName
- userEmail
- paymentData
- secretKey
- hash
- ).each do |param_name|
- define_method(param_name.underscore){ params[param_name] }
- end
-
- def complete?
- (payment_status == '5')
- end
-
- def test?
- false
- end
-
- def status
- case payment_status
- when '3'
- 'pending'
- when '5'
- 'completed'
- else 'unknown'
- end
- end
-
- def user_fields
- params.inject({}) do |fields, (k,v)|
- if /\AuserField_[\d+]\z/.match(k)
- fields[k] = v
- end
- fields
- end
- end
-
- alias_method :client_id, :eshop_id
- alias_method :item_id, :order_id
- alias_method :transaction_id, :payment_id
- alias_method :received_at, :payment_data
- alias_method :payer_email, :user_email
- alias_method :gross, :recipient_amount
- alias_method :currency, :recipient_currency
-
- def acknowledge
- string = [
- eshop_id,
- order_id,
- service_name,
- eshop_account,
- recipient_amount,
- recipient_currency,
- payment_status,
- user_name,
- user_email,
- payment_data,
- @options[:secret]
- ].join '::'
-
- signature = case hash.to_s.length
- when 32
- Digest::MD5.hexdigest(string)
- when 128
- Digest::SHA512.hexdigest(string)
- else
- return false
- end
-
- signature == hash
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/return.rb b/lib/active_merchant/billing/integrations/return.rb
deleted file mode 100644
index 579f5d10c9d..00000000000
--- a/lib/active_merchant/billing/integrations/return.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- class Return
- attr_accessor :params
- attr_reader :notification
-
- def initialize(query_string, options = {})
- @params = parse(query_string)
- @options = options
- end
-
- # Successful by default. Overridden in the child class
- def success?
- true
- end
-
- # Not cancelled by default. Overridden in the child class.
- def cancelled?
- false
- end
-
- def message
-
- end
-
- def parse(query_string)
- return {} if query_string.blank?
-
- query_string.split('&').inject({}) do |memo, chunk|
- next if chunk.empty?
- key, value = chunk.split('=', 2)
- next if key.empty?
- value = value.nil? ? nil : CGI.unescape(value)
- memo[CGI.unescape(key)] = value
- memo
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/robokassa.rb b/lib/active_merchant/billing/integrations/robokassa.rb
deleted file mode 100644
index 1b27ffed2e4..00000000000
--- a/lib/active_merchant/billing/integrations/robokassa.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- # Documentation: http://robokassa.ru/Doc/En/Interface.aspx
- module Robokassa
- autoload :Helper, File.dirname(__FILE__) + '/robokassa/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/robokassa/notification.rb'
- autoload :Return, File.dirname(__FILE__) + '/robokassa/return.rb'
- autoload :Common, File.dirname(__FILE__) + '/robokassa/common.rb'
-
- # Overwrite this if you want to change the Robokassa test url
- mattr_accessor :test_url
- self.test_url = 'http://test.robokassa.ru/Index.aspx'
-
- # Overwrite this if you want to change the Robokassa production url
- mattr_accessor :production_url
- self.production_url = 'https://merchant.roboxchange.com/Index.aspx'
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'SignatureValue'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
-
- def self.return(query_string)
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/robokassa/common.rb b/lib/active_merchant/billing/integrations/robokassa/common.rb
deleted file mode 100644
index 6c8a79146cc..00000000000
--- a/lib/active_merchant/billing/integrations/robokassa/common.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Robokassa
- module Common
- def generate_signature_string
- custom_param_keys = params.keys.select {|key| key =~ /^shp/}.sort
- custom_params = custom_param_keys.map {|key| "#{key}=#{params[key]}"}
- [main_params, secret, custom_params.compact].flatten.join(':')
- end
-
- def generate_signature
- Digest::MD5.hexdigest(generate_signature_string)
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/robokassa/helper.rb b/lib/active_merchant/billing/integrations/robokassa/helper.rb
deleted file mode 100644
index 58102958ceb..00000000000
--- a/lib/active_merchant/billing/integrations/robokassa/helper.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Robokassa
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Common
-
- def initialize(order, account, options = {})
- @md5secret = options.delete(:secret)
- super
- end
-
- def form_fields
- @fields.merge(ActiveMerchant::Billing::Integrations::Robokassa.signature_parameter_name => generate_signature)
- end
-
- def main_params
- [:account, :amount, :order].map {|key| @fields[mappings[key]]}
- end
-
- def params
- @fields
- end
-
- def secret
- @md5secret
- end
-
- def method_missing(method_id, *args)
- method_id = method_id.to_s.gsub(/=$/, '')
-
- # support for robokassa custom parameters
- if method_id =~ /^shp/
- add_field method_id, args.last
- end
-
- super
- end
-
- mapping :account, 'MrchLogin'
- mapping :amount, 'OutSum'
- mapping :currency, 'IncCurrLabel'
- mapping :order, 'InvId'
- mapping :description, 'Desc'
- mapping :email, 'Email'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/robokassa/notification.rb b/lib/active_merchant/billing/integrations/robokassa/notification.rb
deleted file mode 100644
index bc4de6e7fe8..00000000000
--- a/lib/active_merchant/billing/integrations/robokassa/notification.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Robokassa
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include Common
-
- def self.recognizes?(params)
- params.has_key?('InvId') && params.has_key?('OutSum')
- end
-
- def complete?
- true
- end
-
- def amount
- BigDecimal.new(gross)
- end
-
- def item_id
- params['InvId']
- end
-
- def security_key
- params[ActiveMerchant::Billing::Integrations::Robokassa.signature_parameter_name].to_s.downcase
- end
-
- def gross
- params['OutSum']
- end
-
- def status
- 'success'
- end
-
- def secret
- @options[:secret]
- end
-
- def main_params
- [gross, item_id]
- end
-
- def acknowledge
- security_key == generate_signature
- end
-
- def success_response(*args)
- "OK#{item_id}"
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/robokassa/return.rb b/lib/active_merchant/billing/integrations/robokassa/return.rb
deleted file mode 100644
index c9321433ccd..00000000000
--- a/lib/active_merchant/billing/integrations/robokassa/return.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Robokassa
- class Return < ActiveMerchant::Billing::Integrations::Return
- def item_id
- @params['InvId']
- end
-
- def amount
- @params['OutSum']
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/sage_pay_form.rb b/lib/active_merchant/billing/integrations/sage_pay_form.rb
deleted file mode 100644
index 48c092da72a..00000000000
--- a/lib/active_merchant/billing/integrations/sage_pay_form.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module SagePayForm
- autoload :Helper, File.dirname(__FILE__) + '/sage_pay_form/helper.rb'
- autoload :Return, File.dirname(__FILE__) + '/sage_pay_form/return.rb'
- autoload :Notification, File.dirname(__FILE__) + '/sage_pay_form/notification.rb'
- autoload :Encryption, File.dirname(__FILE__) + '/sage_pay_form/encryption.rb'
-
- mattr_accessor :production_url
- mattr_accessor :test_url
- mattr_accessor :simulate_url
- self.production_url = 'https://live.sagepay.com/gateway/service/vspform-register.vsp'
- self.test_url = 'https://test.sagepay.com/gateway/service/vspform-register.vsp'
- self.simulate_url = 'https://test.sagepay.com/Simulator/VSPFormGateway.asp'
-
- def self.return(query_string, options = {})
- Return.new(query_string, options)
- end
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- when :simulate
- self.simulate_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb b/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb
deleted file mode 100644
index 9c933a427c2..00000000000
--- a/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module SagePayForm
- module Encryption
- def sage_encrypt(plaintext, key)
- Base64.strict_encode64(sage_encrypt_xor(plaintext, key))
- end
-
- def sage_decrypt(ciphertext, key)
- sage_encrypt_xor(Base64.decode64(ciphertext), key)
- end
-
- def sage_encrypt_salt(min, max)
- length = rand(max - min + 1) + min
- SecureRandom.base64(length + 4)[0, length]
- end
-
- private
-
- def sage_encrypt_xor(data, key)
- raise 'No key provided' if key.blank?
-
- key *= (data.bytesize.to_f / key.bytesize.to_f).ceil
- key = key[0, data.bytesize]
-
- data.bytes.zip(key.bytes).map { |b1, b2| (b1 ^ b2).chr }.join
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb b/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb
deleted file mode 100644
index b0295dbfcfe..00000000000
--- a/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-require 'uri'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module SagePayForm
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Encryption
-
- mapping :credential2, 'EncryptKey'
-
- mapping :account, 'Vendor'
- mapping :amount, 'Amount'
- mapping :currency, 'Currency'
-
- mapping :order, 'VendorTxCode'
-
- mapping :customer,
- :first_name => 'BillingFirstnames',
- :last_name => 'BillingSurname',
- :email => 'CustomerEMail',
- :phone => 'BillingPhone',
- :send_email_confirmation => 'SendEmail'
-
- mapping :billing_address,
- :city => 'BillingCity',
- :address1 => 'BillingAddress1',
- :address2 => 'BillingAddress2',
- :state => 'BillingState',
- :zip => 'BillingPostCode',
- :country => 'BillingCountry'
-
- mapping :shipping_address,
- :city => 'DeliveryCity',
- :address1 => 'DeliveryAddress1',
- :address2 => 'DeliveryAddress2',
- :state => 'DeliveryState',
- :zip => 'DeliveryPostCode',
- :country => 'DeliveryCountry'
-
- mapping :return_url, 'SuccessURL'
- mapping :description, 'Description'
-
- class_attribute :referrer_id
-
- def shipping_address(params = {})
- @shipping_address_set = true unless params.empty?
-
- params.each do |k, v|
- field = mappings[:shipping_address][k]
- add_field(field, v) unless field.nil?
- end
- end
-
- def map_billing_address_to_shipping_address
- %w(City Address1 Address2 State PostCode Country).each do |field|
- fields["Delivery#{field}"] = fields["Billing#{field}"]
- end
- end
-
- def form_fields
- map_billing_address_to_shipping_address unless @shipping_address_set
-
- fields['DeliveryFirstnames'] ||= fields['BillingFirstnames']
- fields['DeliverySurname'] ||= fields['BillingSurname']
-
- fields['FailureURL'] ||= fields['SuccessURL']
-
- fields['BillingPostCode'] ||= "0000"
- fields['DeliveryPostCode'] ||= "0000"
-
- crypt_skip = ['Vendor', 'EncryptKey', 'SendEmail']
- crypt_skip << 'BillingState' unless fields['BillingCountry'] == 'US'
- crypt_skip << 'DeliveryState' unless fields['DeliveryCountry'] == 'US'
- crypt_skip << 'CustomerEMail' unless fields['SendEmail']
- key = fields['EncryptKey']
- @crypt ||= create_crypt_field(fields.except(*crypt_skip), key)
-
- result = {
- 'VPSProtocol' => '2.23',
- 'TxType' => 'PAYMENT',
- 'Vendor' => @fields['Vendor'],
- 'Crypt' => @crypt
- }
- result['ReferrerID'] = referrer_id if referrer_id
- result
- end
-
- private
-
- def create_crypt_field(fields, key)
- parts = fields.map { |k, v| "#{k}=#{sanitize(k, v)}" unless v.nil? }.compact.shuffle
- parts.unshift(sage_encrypt_salt(key.length, key.length * 2))
- sage_encrypt(parts.join('&'), key)
- end
-
- def sanitize(key, value)
- reject = exact = nil
-
- case key
- when /URL$/
- # allow all
- when 'VendorTxCode'
- reject = /[^A-Za-z0-9{}._-]+/
- when /[Nn]ames?$/
- reject = %r{[^[:alpha:] /\\.'-]+}
- when /(?:Address[12]|City)$/
- reject = %r{[^[:alnum:] +'/\\:,.\n()-]+}
- when /PostCode$/
- reject = /[^A-Za-z0-9 -]+/
- when /Phone$/
- reject = /[^0-9A-Za-z+ ()-]+/
- when 'Currency'
- exact = /^[A-Z]{3}$/
- when /State$/
- exact = /^[A-Z]{2}$/
- when 'Description'
- value = value[0...100]
- else
- reject = /&+/
- end
-
- if exact
- raise ArgumentError, "Invalid value for #{key}: #{value.inspect}" unless value =~ exact
- value
- elsif reject
- value.gsub(reject, ' ')
- else
- value
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb b/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb
deleted file mode 100644
index f9dc672d78c..00000000000
--- a/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module SagePayForm
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- class CryptError < StandardError; end
-
- include Encryption
-
- def initialize(post_data, options)
- super
- load_crypt_params(params['crypt'], options[:credential2])
- end
-
- # Was the transaction complete?
- def complete?
- status_code == 'OK'
- end
-
- # Was the transaction cancelled?
- # Unfortunately, we can't distinguish "user abort" from "idle too long".
- def cancelled?
- status_code == 'ABORT'
- end
-
- # Text version of #complete?, since we don't support Pending.
- def status
- complete? ? 'Completed' : 'Failed'
- end
-
- # Status of transaction. List of possible values:
- # OK:: Transaction completed successfully.
- # NOTAUTHED:: Incorrect card details / insufficient funds.
- # MALFORMED:: Invalid input data.
- # INVALID:: Valid input data, but some fields are incorrect.
- # ABORT:: User hit cancel button or went idle for 15+ minutes.
- # REJECTED:: Rejected by account fraud screening rules.
- # AUTHENTICATED:: Authenticated card details secured at SagePay.
- # REGISTERED:: Non-authenticated card details secured at SagePay.
- # ERROR:: Problem internal to SagePay.
- def status_code
- params['Status']
- end
-
- # Check this if #completed? is false.
- def message
- params['StatusDetail']
- end
-
- # Vendor-supplied code (:order mapping).
- def item_id
- params['VendorTxCode']
- end
-
- # Internal SagePay code, typically "{LONG-UUID}".
- def transaction_id
- params['VPSTxId']
- end
-
- # Authorization number (only if #completed?).
- def auth_id
- params['TxAuthNo']
- end
-
- # Total amount (no fees).
- def gross
- params['Amount']
- end
-
- # AVS and CV2 check results. Possible values:
- # ALL MATCH::
- # SECURITY CODE MATCH ONLY::
- # ADDRESS MATCH ONLY::
- # NO DATA MATCHES::
- # DATA NOT CHECKED::
- def avs_cv2_result
- params['AVSCV2']
- end
-
- # Numeric address check. Possible values:
- # NOTPROVIDED::
- # NOTCHECKED::
- # MATCHED::
- # NOTMATCHED::
- def address_result
- params['AddressResult']
- end
-
- # Post code check. Possible values:
- # NOTPROVIDED::
- # NOTCHECKED::
- # MATCHED::
- # NOTMATCHED::
- def post_code_result
- params['PostCodeResult']
- end
-
- # CV2 code check. Possible values:
- # NOTPROVIDED::
- # NOTCHECKED::
- # MATCHED::
- # NOTMATCHED::
- def cv2_result
- params['CV2Result']
- end
-
- # Was the Gift Aid box checked?
- def gift_aid?
- params['GiftAid'] == '1'
- end
-
- # Result of 3D Secure checks. Possible values:
- # OK:: Authenticated correctly.
- # NOTCHECKED:: Authentication not performed.
- # NOTAVAILABLE:: Card not auth-capable, or auth is otherwise impossible.
- # NOTAUTHED:: User failed authentication.
- # INCOMPLETE:: Authentication unable to complete.
- # ERROR:: Unable to attempt authentication due to data / service errors.
- def buyer_auth_result
- params['3DSecureStatus']
- end
-
- # Encoded 3D Secure result code.
- def buyer_auth_result_code
- params['CAVV']
- end
-
- # Address confirmation status. PayPal only. Possible values:
- # NONE::
- # CONFIRMED::
- # UNCONFIRMED::
- def address_status
- params['AddressStatus']
- end
-
- # Payer verification. Undocumented.
- def payer_verified?
- params['PayerStatus'] == 'VERIFIED'
- end
-
- # Credit card type. Possible values:
- # VISA:: Visa
- # MC:: MasterCard
- # DELTA:: Delta
- # SOLO:: Solo
- # MAESTRO:: Maestro (UK and International)
- # UKE:: Visa Electron
- # AMEX:: American Express
- # DC:: Diners Club
- # JCB:: JCB
- # LASER:: Laser
- # PAYPAL:: PayPal
- def credit_card_type
- params['CardType']
- end
-
- # Last four digits of credit card.
- def credit_card_last_4_digits
- params['Last4Digits']
- end
-
- # Used by composition methods, but not supplied by SagePay.
- def currency
- nil
- end
-
- def test?
- false
- end
-
- def acknowledge
- true
- end
-
- private
-
- def load_crypt_params(crypt, key)
- raise MissingCryptData if crypt.blank?
- raise MissingCryptKey if key.blank?
-
- crypt_data = sage_decrypt(crypt.gsub(' ', '+'), key)
- raise InvalidCryptData unless crypt_data =~ /(^|&)Status=/
-
- params.clear
- parse(crypt_data)
- end
-
- class MissingCryptKey < CryptError
- def message
- 'No merchant decryption key supplied'
- end
- end
- class MissingCryptData < CryptError
- def message
- 'No data received from SagePay'
- end
- end
- class InvalidCryptData < CryptError
- def message
- 'Invalid data received from SagePay'
- end
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/sage_pay_form/return.rb b/lib/active_merchant/billing/integrations/sage_pay_form/return.rb
deleted file mode 100644
index b2a2432ea9c..00000000000
--- a/lib/active_merchant/billing/integrations/sage_pay_form/return.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module SagePayForm
- class Return < ActiveMerchant::Billing::Integrations::Return
-
- def initialize(query_string, options)
- begin
- @notification = Notification.new(query_string, options)
- rescue Notification::CryptError => e
- @message = e.message
- end
- end
-
- def success?
- @notification && @notification.complete?
- end
-
- def cancelled?
- @notification && @notification.cancelled?
- end
-
- def message
- @message || @notification.message
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/two_checkout.rb b/lib/active_merchant/billing/integrations/two_checkout.rb
deleted file mode 100644
index e5733aad3a9..00000000000
--- a/lib/active_merchant/billing/integrations/two_checkout.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module TwoCheckout
- autoload 'Helper', File.dirname(__FILE__) + '/two_checkout/helper'
- autoload 'Return', File.dirname(__FILE__) + '/two_checkout/return'
- autoload 'Notification', File.dirname(__FILE__) + '/two_checkout/notification'
-
- mattr_accessor :payment_routine
- self.payment_routine = :single_page
-
- def self.service_url
- case self.payment_routine
- when :multi_page
- 'https://www.2checkout.com/checkout/purchase'
- when :single_page
- 'https://www.2checkout.com/checkout/spurchase'
- else
- raise StandardError, "Integration payment routine set to an invalid value: #{self.payment_routine}"
- end
- end
-
- def self.service_url=(service_url)
- # Note: do not use this method, it is here for backward compatibility
- # Use the payment_routine method to change service_url
- if service_url =~ /spurchase/
- self.payment_routine = :single_page
- else
- self.payment_routine = :multi_page
- end
- end
-
-
- def self.notification(post, options = {})
- Notification.new(post)
- end
-
- def self.return(query_string, options = {})
- Return.new(query_string)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/two_checkout/helper.rb b/lib/active_merchant/billing/integrations/two_checkout/helper.rb
deleted file mode 100644
index 077b9ae424d..00000000000
--- a/lib/active_merchant/billing/integrations/two_checkout/helper.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module TwoCheckout
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- def initialize(order, account, options = {})
- super
- add_field('fixed', 'Y')
-
- if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test]
- add_field('demo', 'Y')
- end
- end
-
- # The 2checkout vendor account number
- mapping :account, 'sid'
-
- # The total amount to be billed, in decimal form, without a currency symbol. (8 characters, decimal, 2 characters: Example: 99999999.99)
- mapping :amount, 'total'
-
- # Pass your order id if you are using Third Part Cart Parameters. (128 characters max)
- mapping :order, 'cart_order_id'
-
- # Pass your order id if you are using the Pass Through Products Parameters. (50 characters max)
- mapping :invoice, 'merchant_order_id'
-
- # Left here for backward compatibility, do not use. The line_item method will add automatically.
- mapping :mode, 'mode'
-
- mapping :customer, :email => 'email',
- :phone => 'phone'
-
- mapping :billing_address, :city => 'city',
- :address1 => 'street_address',
- :address2 => 'street_address2',
- :state => 'state',
- :zip => 'zip',
- :country => 'country'
-
- mapping :shipping_address, :city => 'ship_city',
- :address1 => 'ship_street_address',
- :state => 'ship_state',
- :zip => 'ship_zip',
- :country => 'ship_country'
-
- # Does nothing, since we've disabled the Continue Shopping button by using the fixed = Y field
- mapping :return_url, 'return_url'
-
- # Approved URL path
- mapping :notification_url, 'x_receipt_link_url'
-
- def customer(params = {})
- add_field(mappings[:customer][:email], params[:email])
- add_field(mappings[:customer][:phone], params[:phone])
- add_field('card_holder_name', "#{params[:first_name]} #{params[:last_name]}")
- end
-
- # Uses Pass Through Product Parameters to pass in lineitems.
- # (must mark tanigble sales as shipped to settle the transaction)
- def line_item(params = {})
- add_field('mode', '2CO')
- (max_existing_line_item_id = form_fields.keys.map do |key|
- i = key.to_s[/^li_(\d+)_/, 1]
- (i && i.to_i)
- end.compact.max || 0)
-
- line_item_id = max_existing_line_item_id + 1
- params.each do |key, value|
- add_field("li_#{line_item_id}_#{key}", value)
- end
- end
-
- # Uses Third Party Cart parameter set to pass in lineitem details.
- # (sales settle automatically)
- def auto_settle(params = {})
- add_field('id_type', '1')
- (max_existing_line_item_id = form_fields.keys.map do |key|
- i = key.to_s[/^c_prod_(\d+)/, 1]
- (i && i.to_i)
- end.compact.max || 0)
-
- line_item_id = max_existing_line_item_id + 1
- params.each do |key, value|
- add_field("c_#{key}_#{line_item_id}", value)
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/two_checkout/notification.rb b/lib/active_merchant/billing/integrations/two_checkout/notification.rb
deleted file mode 100644
index 8ba211b3459..00000000000
--- a/lib/active_merchant/billing/integrations/two_checkout/notification.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-require 'net/http'
-require 'base64'
-require 'digest/md5'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module TwoCheckout
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- # card_holder_name - Provides the customer’s name.
- # city - Provides the customer’s city.
- # country - Provides the customer’s country.
- # credit_card_processed - This parameter will always be passed back as Y.
- # demo - Defines if an order was live, or if the order was a demo order. If the order was a demo, the MD5 hash will fail.
- # email - Provides the email address the customer provided when placing the order.
- # fixed - This parameter will only be passed back if it was passed into the purchase routine.
- # ip_country - Provides the customer’s IP location.
- # key - An MD5 hash used to confirm the validity of a sale.
- # lang - Customer language
- # merchant_order_id - The order ID you had assigned to the order.
- # order_number - The 2Checkout order number associated with the order.
- # invoice_id - The 2Checkout invoice number.
- # pay_method - Provides seller with the customer’s payment method. CC for Credit Card, PPI for PayPal.
- # phone - Provides the phone number the customer provided when placing the order.
- # ship_name - Provides the ship to name for the order.
- # ship_street_address - Provides ship to address.
- # ship_street_address2 - Provides more detailed shipping address if this information was provided by the customer.
- # ship_city - Provides ship to city.
- # ship_state - Provides ship to state.
- # ship_zip - Ship Zip
-
- # Pass Through Products Only
- # li_#_name - Name of the corresponding lineitem.
- # li_#_quantity - Quantity of the corresponding lineitem.
- # li_#_price - Price of the corresponding lineitem.
- # li_#_tangible - Specifies if the corresponding li_#_type is a tangible or intangible. ‘Y’ OR ‘N’
- # li_#_product_id - ID of the corresponding lineitem.
- # li_#_product_description - Description of the corresponding lineitem.
- # li_#_recurrence - # WEEK | MONTH | YEAR – always singular.
- # li_#_duration - Forever or # WEEK | MONTH | YEAR – always singular, defaults to Forever.
- # li_#_startup_fee - Amount in account pricing currency.
- # li_#_option_#_name - Name of option. 64 characters max – cannot include '<' or '>'.
- # li_#_option_#_value - Name of option. 64 characters max – cannot include '<' or '>'.
- # li_#_option_#_surcharge - Amount in account pricing currency.
-
- #Third Party Cart Only
- # cart_order_id - The order ID you had assigned to the order.
-
- # Allow seller to define default currency (should match 2Checkout account pricing currency)
- def currency
- 'USD'
- end
-
- def complete?
- status == 'Completed'
- end
-
- # Third Party Cart parameters will return 'card_order_id'
- # Pass Through Product parameters will only return 'merchant_order_id'
- def item_id
- if (params['cart_order_id'].nil?)
- params['merchant_order_id']
- else
- params['cart_order_id']
- end
- end
-
- # 2Checkout Sale ID
- def transaction_id
- params['order_number']
- end
-
- def received_at
- params['']
- end
-
- #Customer Email
- def payer_email
- params['email']
- end
-
- def receiver_email
- params['']
- end
-
- # The MD5 Hash
- def security_key
- params['key']
- end
-
- # The money amount we received in X.2 decimal.
- def gross
- params['total']
- end
-
- # Was this a test transaction? # Use the hash
- # Please note 2Checkout forces the order number computed in the hash to '1' on demo sales.
- def test?
- params['demo'] == 'Y'
- end
-
- # 2Checkout only returns 'Y' for this parameter. If the sale is not authorized, no passback occurs.
- def status
- case params['credit_card_processed']
- when 'Y'
- 'Completed'
- else
- 'Failed'
- end
- end
-
- # Secret Word defined in 2Checkout account
- def secret
- @options[:credential2]
- end
-
- # Checks against MD5 Hash
- def acknowledge
- return false if security_key.blank?
-
- Digest::MD5.hexdigest("#{secret}#{params['sid']}#{transaction_id}#{gross}").upcase == security_key.upcase
- end
-
- private
-
- # Parses Header Redirect Query String
- def parse(post)
- @raw = post.to_s
- for line in @raw.split('&')
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
- params[key] = CGI.unescape(value || '')
- end
- end
-
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/two_checkout/return.rb b/lib/active_merchant/billing/integrations/two_checkout/return.rb
deleted file mode 100644
index 327d1a319e1..00000000000
--- a/lib/active_merchant/billing/integrations/two_checkout/return.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module TwoCheckout
- class Return < ActiveMerchant::Billing::Integrations::Return
- def success?
- params['credit_card_processed'] == 'Y'
- end
-
- def message
-
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/valitor.rb b/lib/active_merchant/billing/integrations/valitor.rb
deleted file mode 100644
index 20a5a8ca3cf..00000000000
--- a/lib/active_merchant/billing/integrations/valitor.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Valitor
- autoload :Return, 'active_merchant/billing/integrations/valitor/return.rb'
- autoload :Helper, 'active_merchant/billing/integrations/valitor/helper.rb'
- autoload :Notification, 'active_merchant/billing/integrations/valitor/notification.rb'
-
- mattr_accessor :test_url
- self.test_url = 'https://testvefverslun.valitor.is/1_1/'
-
- mattr_accessor :production_url
- self.production_url = 'https://vefverslun.valitor.is/1_1/'
-
- def self.test?
- (ActiveMerchant::Billing::Base.integration_mode == :test)
- end
-
- def self.service_url
- (test? ? test_url : production_url)
- end
-
- def self.notification(params, options={})
- Notification.new(params, options.merge(:test => test?))
- end
-
- def self.return(query_string, options={})
- Return.new(query_string, options.merge(:test => test?))
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/valitor/helper.rb b/lib/active_merchant/billing/integrations/valitor/helper.rb
deleted file mode 100644
index 15630f659de..00000000000
--- a/lib/active_merchant/billing/integrations/valitor/helper.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'digest/md5'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Valitor
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include RequiresParameters
-
- DEFAULT_SUCCESS_TEXT = "The transaction has been completed."
-
- def initialize(order, account, options={})
- options[:currency] ||= 'ISK'
- super
- add_field 'Adeinsheimild', '0'
- add_field 'KaupandaUpplysingar', '0'
- add_field 'SlokkvaHaus', '0'
- @security_number = options[:credential2]
- @amount = options[:amount]
- @order = order
- end
-
- mapping :account, 'VefverslunID'
- mapping :currency, 'Gjaldmidill'
-
- mapping :order, 'Tilvisunarnumer'
-
- mapping :notify_url, 'SlodTokstAdGjaldfaeraServerSide'
- mapping :return_url, 'SlodTokstAdGjaldfaera'
- mapping :cancel_return_url, 'SlodNotandiHaettirVid'
-
- mapping :success_text, 'SlodTokstAdGjaldfaeraTexti'
-
- mapping :language, 'Lang'
-
- def authorize_only
- add_field 'Adeinsheimild', '1'
- end
-
- def collect_customer_info
- add_field 'KaupandaUpplysingar', '1'
- end
-
- def hide_header
- add_field 'SlokkvaHaus', '1'
- end
-
- def product(id, options={})
- raise ArgumentError, "Product id #{id} is not an integer between 1 and 500" unless id.to_i > 0 && id.to_i <= 500
- requires!(options, :amount, :description)
- options.assert_valid_keys([:description, :quantity, :amount, :discount])
-
- add_field("Vara_#{id}_Verd", format_amount(options[:amount]))
- add_field("Vara_#{id}_Fjoldi", options[:quantity] || "1")
-
- add_field("Vara_#{id}_Lysing", options[:description]) if options[:description]
- add_field("Vara_#{id}_Afslattur", options[:discount] || '0')
-
- @products ||= []
- @products << id.to_i
- end
-
- def signature
- raise ArgumentError, "Security number not set" unless @security_number
- parts = [@security_number, @fields['Adeinsheimild']]
- @products.sort.uniq.each do |id|
- parts.concat(["Vara_#{id}_Fjoldi", "Vara_#{id}_Verd", "Vara_#{id}_Afslattur"].collect{|e| @fields[e]})
- end if @products
- parts.concat(%w(VefverslunID Tilvisunarnumer SlodTokstAdGjaldfaera SlodTokstAdGjaldfaeraServerSide Gjaldmidill).collect{|e| @fields[e]})
- Digest::MD5.hexdigest(parts.compact.join(''))
- end
-
- def form_fields
- product(1, :amount => @amount, :description => @order) if Array(@products).empty?
- @fields[mappings[:success_text]] ||= DEFAULT_SUCCESS_TEXT
- @fields.merge('RafraenUndirskrift' => signature)
- end
-
- def format_amount(amount)
- amount.to_f.round
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/valitor/notification.rb b/lib/active_merchant/billing/integrations/valitor/notification.rb
deleted file mode 100644
index 6aa643941f5..00000000000
--- a/lib/active_merchant/billing/integrations/valitor/notification.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'active_merchant/billing/integrations/valitor/response_fields'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Valitor
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include ResponseFields
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/valitor/response_fields.rb b/lib/active_merchant/billing/integrations/valitor/response_fields.rb
deleted file mode 100644
index 9894644f552..00000000000
--- a/lib/active_merchant/billing/integrations/valitor/response_fields.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'digest/md5'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Valitor
- module ResponseFields
- def success?
- status == 'Completed'
- end
- alias :complete? :success?
-
- def test?
- @options[:test]
- end
-
- def item_id
- params['Tilvisunarnumer']
- end
- alias :order :item_id
-
- def transaction_id
- params['VefverslunSalaID']
- end
-
- def currency
- nil
- end
-
- def status
- "Completed" if acknowledge
- end
-
- def received_at
- Time.parse(params['Dagsetning'].to_s)
- end
-
- def gross
- "%0.2f" % params['Upphaed'].to_s
- end
-
- def card_type
- params['Kortategund']
- end
-
- def card_last_four
- params['KortnumerSidustu']
- end
-
- def authorization_number
- params['Heimildarnumer']
- end
-
- def transaction_number
- params['Faerslunumer']
- end
-
- def customer_name
- params['Nafn']
- end
-
- def customer_address
- params['Heimilisfang']
- end
-
- def customer_zip
- params['Postnumer']
- end
-
- def customer_city
- params['Stadur']
- end
-
- def customer_country
- params['Land']
- end
-
- def customer_email
- params['Tolvupostfang']
- end
-
- def customer_comment
- params['Athugasemdir']
- end
-
- def password
- @options[:credential2]
- end
-
- def acknowledge
- password ? Digest::MD5.hexdigest("#{password}#{order}") == params['RafraenUndirskriftSvar'] : true
- end
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/integrations/valitor/return.rb b/lib/active_merchant/billing/integrations/valitor/return.rb
deleted file mode 100644
index b81cdfae288..00000000000
--- a/lib/active_merchant/billing/integrations/valitor/return.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'active_merchant/billing/integrations/valitor/response_fields'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Valitor
- class Return < ActiveMerchant::Billing::Integrations::Return
- include ResponseFields
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/verkkomaksut.rb b/lib/active_merchant/billing/integrations/verkkomaksut.rb
deleted file mode 100644
index 504148e175f..00000000000
--- a/lib/active_merchant/billing/integrations/verkkomaksut.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require File.dirname(__FILE__) + '/verkkomaksut/helper.rb'
-require File.dirname(__FILE__) + '/verkkomaksut/notification.rb'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- # Usage, see the blog post here: http://blog.kiskolabs.com/post/22374612968/understanding-active-merchant-integrations and E1 API documentation here: http://docs.verkkomaksut.fi/
- module Verkkomaksut
-
- mattr_accessor :service_url
- self.service_url = 'https://payment.verkkomaksut.fi/'
-
- def self.notification(post)
- Notification.new(post)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb b/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb
deleted file mode 100644
index ca88579e8f3..00000000000
--- a/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Verkkomaksut
- class Helper < ActiveMerchant::Billing::Integrations::Helper
-
- # Fetches the md5secret and adds MERCHANT_ID and API TYPE to the form
- def initialize(order, account, options = {})
- md5secret options.delete(:credential2)
- super
- add_field("MERCHANT_ID", account)
- add_field("TYPE", "E1")
- end
-
- def md5secret(value)
- @md5secret = value
- end
-
- # Adds the AUTHCODE to the form
- def form_fields
- @fields.merge("AUTHCODE" => generate_md5string)
- end
-
- # Calculates the AUTHCODE
- def generate_md5string
- fields = [@md5secret, @fields["MERCHANT_ID"], @fields["ORDER_NUMBER"], @fields["REFERENCE_NUMBER"], @fields["ORDER_DESCRIPTION"], @fields["CURRENCY"], @fields["RETURN_ADDRESS"], @fields["CANCEL_ADDRESS"], @fields["PENDING_ADDRESS"],
- @fields["NOTIFY_ADDRESS"], @fields["TYPE"], @fields["CULTURE"], @fields["PRESELECTED_METHOD"], @fields["MODE"], @fields["VISIBLE_METHODS"], @fields["GROUP"], @fields["CONTACT_TELNO"], @fields["CONTACT_CELLNO"],
- @fields["CONTACT_EMAIL"], @fields["CONTACT_FIRSTNAME"], @fields["CONTACT_LASTNAME"], @fields["CONTACT_COMPANY"], @fields["CONTACT_ADDR_STREET"], @fields["CONTACT_ADDR_ZIP"], @fields["CONTACT_ADDR_CITY"], @fields["CONTACT_ADDR_COUNTRY"], @fields["INCLUDE_VAT"],
- @fields["ITEMS"]]
-
- (0..@fields["ITEMS"].to_i-1).each do |i|
- fields += [@fields["ITEM_TITLE[#{i}]"], @fields["ITEM_NO[#{i}]"], @fields["ITEM_AMOUNT[#{i}]"], @fields["ITEM_PRICE[#{i}]"], @fields["ITEM_TAX[#{i}]"], @fields["ITEM_DISCOUNT[#{i}]"], @fields["ITEM_TYPE[#{i}]"]]
- end
-
- fields = fields.join("|")
-
- return Digest::MD5.hexdigest(fields).upcase
- end
-
- # Mappings
- mapping :merchant_id, "MERCHANT_ID"
- mapping :order, "ORDER_NUMBER"
- mapping :reference_number, "REFERENCE_NUMBER"
- mapping :customer, :first_name => "CONTACT_FIRSTNAME",
- :last_name => "CONTACT_LASTNAME",
- :email => "CONTACT_EMAIL",
- :phone => "CONTACT_CELLNO",
- :tellno => "CONTACT_TELLNO",
- :company => "CONTACT_COMPANY"
-
-
- mapping :billing_address, :city => "CONTACT_ADDR_CITY",
- :address1 => "CONTACT_ADDR_STREET",
- :address2 => "",
- :state => "",
- :zip => "CONTACT_ADDR_ZIP",
- :country => "CONTACT_ADDR_COUNTRY"
-
- mapping :notify_url, "NOTIFY_ADDRESS"
- mapping :currency, "CURRENCY"
- mapping :return_url, "RETURN_ADDRESS"
- mapping :cancel_return_url, "CANCEL_ADDRESS"
- mapping :description, "ORDER_DESCRIPTION"
- mapping :tax, ""
- mapping :shipping, ""
- mapping :include_vat, "INCLUDE_VAT"
- mapping :pending_address, "PENDING_ADDRESS"
- mapping :culture, "CULTURE"
- mapping :items, "ITEMS"
- mapping :preselected_method, "PRESELECTED_METHOD"
- mapping :mode, "MODE"
- mapping :visible_methods, "VISIBLE_METHODS"
- mapping :group, "GROUP"
-
- (0..499.to_i).each do |i|
- mapping "item_title_#{i}".to_sym, "ITEM_TITLE[#{i}]"
- mapping "item_no_#{i}".to_sym, "ITEM_NO[#{i}]"
- mapping "item_amount_#{i}".to_sym, "ITEM_AMOUNT[#{i}]"
- mapping "item_price_#{i}".to_sym, "ITEM_PRICE[#{i}]"
- mapping "item_tax_#{i}".to_sym, "ITEM_TAX[#{i}]"
- mapping "item_discount_#{i}".to_sym, "ITEM_DISCOUNT[#{i}]"
- mapping "item_type_#{i}".to_sym, "ITEM_TYPE[#{i}]"
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb b/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb
deleted file mode 100644
index 466a282308a..00000000000
--- a/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'net/http'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Verkkomaksut
- class Notification < ActiveMerchant::Billing::Integrations::Notification
-
- # Is the payment complete or not. Verkkomaksut only has two statuses: random string or 0000000000 which means pending
- def complete?
- params['PAID'] != "0000000000"
- end
-
- # Order id
- def order_id
- params['ORDER_NUMBER']
- end
-
- # Payment method used
- def method
- params['METHOD']
- end
-
- # When was this payment received by the client.
- def received_at
- params['TIMESTAMP']
- end
-
- # Security key got from Verkkomaksut
- def security_key
- params['RETURN_AUTHCODE']
- end
-
- # Another way of asking the payment status
- def status
- if complete?
- "PAID"
- else
- "PENDING"
- end
- end
-
- # Acknowldges the payment. If the authcodes match, returns true.
- def acknowledge(authcode)
- return_authcode = [params["ORDER_NUMBER"], params["TIMESTAMP"], params["PAID"], params["METHOD"], authcode].join("|")
- Digest::MD5.hexdigest(return_authcode).upcase == params["RETURN_AUTHCODE"]
- end
- private
-
- def parse(post)
- post.each do |key, value|
- params[key] = value
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/web_pay.rb b/lib/active_merchant/billing/integrations/web_pay.rb
deleted file mode 100644
index 06aca03c324..00000000000
--- a/lib/active_merchant/billing/integrations/web_pay.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
-
- # Documentation: You will get it after registration steps here:
- # http://reg.webpay.by/registration-form.php
- module WebPay
- autoload :Helper, File.dirname(__FILE__) + '/web_pay/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/web_pay/notification.rb'
- autoload :Common, File.dirname(__FILE__) + '/web_pay/common.rb'
-
- # Overwrite this if you want to change the WebPay sandbox url
- mattr_accessor :test_url
- self.test_url = 'https://secure.sandbox.webpay.by:8843'
-
- # Overwrite this if you want to change the WebPay production url
- mattr_accessor :production_url
- self.production_url = 'https://secure.webpay.by'
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'wsb_signature'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/web_pay/common.rb b/lib/active_merchant/billing/integrations/web_pay/common.rb
deleted file mode 100644
index 8a8191f2bf2..00000000000
--- a/lib/active_merchant/billing/integrations/web_pay/common.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module WebPay
- module Common
- def generate_signature(type)
- string = case type
- when :request
- request_signature_string
- when :notify
- notify_signature_string
- end
- if type != :notify && @fields[mappings[:version]] == '2'
- Digest::SHA1.hexdigest(string)
- else
- Digest::MD5.hexdigest(string)
- end
- end
-
- def request_signature_string
- [
- @fields[mappings[:seed]],
- @fields[mappings[:account]],
- @fields[mappings[:order]],
- @fields[mappings[:test]],
- @fields[mappings[:currency]],
- @fields[mappings[:amount]],
- secret
- ].join
- end
-
- def notify_signature_string
- [
- params['batch_timestamp'],
- params['currency_id'],
- params['amount'],
- params['payment_method'],
- params['order_id'],
- params['site_order_id'],
- params['transaction_id'],
- params['payment_type'],
- params['rrn'],
- secret
- ].join
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/web_pay/helper.rb b/lib/active_merchant/billing/integrations/web_pay/helper.rb
deleted file mode 100644
index 1082f4355ce..00000000000
--- a/lib/active_merchant/billing/integrations/web_pay/helper.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module WebPay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Common
-
- def initialize(order, account, options = {})
- @md5secret = options.delete(:secret)
- @line_item_count = 0
- super
- end
-
- def form_fields
- @fields.merge(ActiveMerchant::Billing::Integrations::WebPay.signature_parameter_name => generate_signature(:request))
- end
-
- def params
- @fields
- end
-
- def secret
- @md5secret
- end
-
- def add_line_item(options)
- options.each do |key, value|
- add_field("wsb_invoice_item_#{key}[#{@line_item_count}]", value)
- end
-
- @line_item_count += 1
- end
-
- def calculate_total
- sum = 0
-
- @line_item_count.times do |i|
- sum += @fields["wsb_invoice_item_quantity[#{i}]"].to_i * @fields["wsb_invoice_item_price[#{i}]"].to_i
- end
-
- sum + @fields[mappings[:tax]].to_i + @fields[mappings[:shipping_price]].to_i - @fields[mappings[:discount_price]].to_i
- end
-
- mapping :scart, '*scart'
- mapping :account, 'wsb_storeid'
- mapping :store, 'wsb_store'
- mapping :order, 'wsb_order_num'
- mapping :currency, 'wsb_currency_id'
- mapping :version, 'wsb_version'
- mapping :language, 'wsb_language_id'
- mapping :seed, 'wsb_seed'
- mapping :success_url, 'wsb_return_url'
- mapping :cancel_url, 'wsb_cancel_return_url'
- mapping :notify_url, 'wsb_notify_url'
- mapping :test, 'wsb_test'
- mapping :tax, 'wsb_tax'
- mapping :shipping_name, 'wsb_shipping_name'
- mapping :shipping_price, 'wsb_shipping_price'
- mapping :discount_name, 'wsb_discount_name'
- mapping :discount_price, 'wsb_discount_price'
- mapping :amount, 'wsb_total'
- mapping :email, 'wsb_email'
- mapping :phone, 'wsb_phone'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/web_pay/notification.rb b/lib/active_merchant/billing/integrations/web_pay/notification.rb
deleted file mode 100644
index a0230ed663d..00000000000
--- a/lib/active_merchant/billing/integrations/web_pay/notification.rb
+++ /dev/null
@@ -1,51 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module WebPay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include Common
-
- def self.recognizes?(params)
- params.has_key?('site_order_id') && params.has_key?('amount')
- end
-
- def complete?
- true
- end
-
- def amount
- BigDecimal.new(gross)
- end
-
- def item_id
- params['site_order_id']
- end
-
- def security_key
- params[ActiveMerchant::Billing::Integrations::WebPay.signature_parameter_name]
- end
-
- def gross
- params['amount']
- end
-
- def status
- 'success'
- end
-
- def secret
- @options[:secret]
- end
-
- def acknowledge
- (security_key == generate_signature(:notify))
- end
-
- def success_response(*args)
- {:nothing => true}
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/webmoney.rb b/lib/active_merchant/billing/integrations/webmoney.rb
deleted file mode 100644
index 3b704846d5f..00000000000
--- a/lib/active_merchant/billing/integrations/webmoney.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- # Documentation:
- # http://wiki.webmoney.ru/projects/webmoney/wiki/Web_Merchant_Interface
- module Webmoney
- autoload :Helper, File.dirname(__FILE__) + '/webmoney/helper.rb'
- autoload :Notification, File.dirname(__FILE__) + '/webmoney/notification.rb'
- autoload :Return, File.dirname(__FILE__) + '/webmoney/return.rb'
- autoload :Common, File.dirname(__FILE__) + '/webmoney/common.rb'
-
- mattr_accessor :test_url
- self.test_url = "https://merchant.webmoney.ru/lmi/payment.asp"
-
- mattr_accessor :production_url
- self.production_url = "https://merchant.webmoney.ru/lmi/payment.asp"
-
- mattr_accessor :signature_parameter_name
- self.signature_parameter_name = 'LMI_HASH'
-
- def self.service_url
- mode = ActiveMerchant::Billing::Base.integration_mode
- case mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.helper(order, account, options = {})
- Helper.new(order, account, options)
- end
-
- def self.notification(query_string, options = {})
- Notification.new(query_string, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/webmoney/common.rb b/lib/active_merchant/billing/integrations/webmoney/common.rb
deleted file mode 100644
index e6bb96593ac..00000000000
--- a/lib/active_merchant/billing/integrations/webmoney/common.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Webmoney
- module Common
- def generate_signature_string
- "#{params['LMI_PAYEE_PURSE']}#{params['LMI_PAYMENT_AMOUNT']}#{params['LMI_PAYMENT_NO']}#{params['LMI_MODE']}#{params['LMI_SYS_INVS_NO']}#{params['LMI_SYS_TRANS_NO']}#{params['LMI_SYS_TRANS_DATE']}#{secret}#{params['LMI_PAYER_PURSE']}#{params['LMI_PAYER_WM']}"
- end
-
- def generate_signature
- Digest::MD5.hexdigest(generate_signature_string).upcase
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/webmoney/helper.rb b/lib/active_merchant/billing/integrations/webmoney/helper.rb
deleted file mode 100644
index f8808afce81..00000000000
--- a/lib/active_merchant/billing/integrations/webmoney/helper.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Webmoney
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- include Common
-
- def initialize(order, account, options = {})
- @webmoney_options = options.dup
- options.delete(:description)
- options.delete(:fail_url)
- options.delete(:success_url)
- options.delete(:result_url)
- super
- @webmoney_options.each do |key, value|
- add_field mappings[key], value
- end
- end
-
- def form_fields
- @fields
- end
-
- def params
- @fields
- end
-
- mapping :account, 'LMI_PAYEE_PURSE'
- mapping :amount, 'LMI_PAYMENT_AMOUNT'
- mapping :order, 'LMI_PAYMENT_NO'
- mapping :description, 'LMI_PAYMENT_DESC'
- mapping :fail_url, 'LMI_FAIL_URL'
- mapping :success_url, 'LMI_SUCCESS_URL'
- mapping :result_url, 'LMI_RESULT_URL'
- mapping :debug, 'LMI_SIM_MODE'
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/webmoney/notification.rb b/lib/active_merchant/billing/integrations/webmoney/notification.rb
deleted file mode 100644
index 612f9d1cc0f..00000000000
--- a/lib/active_merchant/billing/integrations/webmoney/notification.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module Webmoney
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- include Common
-
- def recognizes?
- (params.has_key?('LMI_PAYMENT_NO') && params.has_key?('LMI_PAYMENT_AMOUNT'))
- end
-
- def amount
- BigDecimal.new(gross)
- end
-
- def key_present?
- params["LMI_HASH"].present?
- end
-
- def item_id
- params['LMI_PAYMENT_NO']
- end
-
- def gross
- params['LMI_PAYMENT_AMOUNT']
- end
-
- def security_key
- params["LMI_HASH"]
- end
-
- def secret
- @options[:secret]
- end
-
- def acknowledge
- (security_key == generate_signature)
- end
-
- def success_response(*args)
- {:nothing => true}
- end
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/world_pay.rb b/lib/active_merchant/billing/integrations/world_pay.rb
deleted file mode 100644
index 73190d31b69..00000000000
--- a/lib/active_merchant/billing/integrations/world_pay.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require File.dirname(__FILE__) + '/world_pay/helper.rb'
-require File.dirname(__FILE__) + '/world_pay/notification.rb'
-
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module WorldPay
-
- mattr_accessor :production_url, :test_url
- self.production_url = 'https://secure.worldpay.com/wcc/purchase'
- self.test_url = 'https://secure-test.worldpay.com/wcc/purchase'
-
- def self.service_url
- case ActiveMerchant::Billing::Base.integration_mode
- when :production
- self.production_url
- when :test
- self.test_url
- else
- raise StandardError, "Integration mode set to an invalid value: #{mode}"
- end
- end
-
- def self.notification(post, options = {})
- Notification.new(post, options)
- end
-
- def self.return(post, options = {})
- Return.new(post, options)
- end
- end
- end
- end
-end
diff --git a/lib/active_merchant/billing/integrations/world_pay/helper.rb b/lib/active_merchant/billing/integrations/world_pay/helper.rb
deleted file mode 100644
index 68ab9d911dd..00000000000
--- a/lib/active_merchant/billing/integrations/world_pay/helper.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module WorldPay
- class Helper < ActiveMerchant::Billing::Integrations::Helper
- mapping :account, 'instId'
- mapping :amount, 'amount'
- mapping :order, 'cartId'
- mapping :currency, 'currency'
-
- mapping :customer, :email => 'email',
- :phone => 'tel'
-
- mapping :billing_address, :zip => 'postcode',
- :country => 'country'
-
- mapping :description, 'desc'
- mapping :notify_url, 'MC_callback'
- mapping :return_url, 'MC_return'
-
-
- # WorldPay supports two different test modes - :always_succeed and :always_fail
- def initialize(order, account, options = {})
- super
-
- if ActiveMerchant::Billing::Base.integration_mode == :test || options[:test]
- test_mode = case options[:test]
- when :always_fail
- 101
- when false
- 0
- else
- 100
- end
- add_field('testMode', test_mode.to_s)
- elsif ActiveMerchant::Billing::Base.integration_mode == :always_succeed
- add_field('testMode', '100')
- elsif ActiveMerchant::Billing::Base.integration_mode == :always_fail
- add_field('testMode', '101')
- end
- end
-
- # WorldPay only supports a single address field so we
- # have to concat together - lines are separated using
- def billing_address(params={})
- add_field(mappings[:billing_address][:zip], params[:zip])
- add_field(mappings[:billing_address][:country], lookup_country_code(params[:country]))
-
- address = [params[:address1], params[:address2], params[:city], params[:state]].compact
- add_field('address', address.join('
'))
- end
-
- # WorldPay only supports a single name field so we have to concat
- def customer(params={})
- add_field(mappings[:customer][:email], params[:email])
- add_field(mappings[:customer][:phone], params[:phone])
- add_field('name', "#{params[:first_name]} #{params[:last_name]}")
- end
-
- # Support for a MD5 hash of selected fields to prevent tampering
- # For futher information read the tech note at the address below:
- # http://support.worldpay.com/kb/integration_guides/junior/integration/help/tech_notes/sjig_tn_009.html
- def encrypt(secret, fields = [:amount, :currency, :account, :order])
- signature_fields = fields.collect{ |field| mappings[field] }
- add_field('signatureFields', signature_fields.join(':'))
-
- field_values = fields.collect{ |field| form_fields[mappings[field]] }
- signature = "#{secret}:#{field_values.join(':')}"
- add_field('signature', Digest::MD5.hexdigest(signature))
- end
-
- # Add a time window for which the payment can be completed. Read the link below for how they work
- # http://support.worldpay.com/kb/integration_guides/junior/integration/help/appendicies/sjig_10100.html
- def valid_from(from_time)
- add_field('authValidFrom', from_time.to_i.to_s + '000')
- end
-
- def valid_to(to_time)
- add_field('authValidTo', to_time.to_i.to_s + '000')
- end
-
- # WorldPay supports the passing of custom parameters prefixed with the following:
- # C_ : These parameters can be used in the response pages hosted on WorldPay's site
- # M_ : These parameters are passed through to the callback script (if enabled)
- # MC_ or CM_ : These parameters are availble both in the response and callback contexts
- def response_params(params={})
- params.each{|k,v| add_field("C_#{k}",v)}
- end
-
- def callback_params(params={})
- params.each{|k,v| add_field("M_#{k}",v)}
- end
-
- def combined_params(params={})
- params.each{|k,v| add_field("MC_#{k}",v)}
- end
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/integrations/world_pay/notification.rb b/lib/active_merchant/billing/integrations/world_pay/notification.rb
deleted file mode 100644
index afd682e9dee..00000000000
--- a/lib/active_merchant/billing/integrations/world_pay/notification.rb
+++ /dev/null
@@ -1,160 +0,0 @@
-module ActiveMerchant #:nodoc:
- module Billing #:nodoc:
- module Integrations #:nodoc:
- module WorldPay
- class Notification < ActiveMerchant::Billing::Integrations::Notification
- def complete?
- status == 'Completed'
- end
-
- def account
- params['instId']
- end
-
- def item_id
- params['cartId']
- end
-
- def transaction_id
- params['transId']
- end
-
- # Time this payment was received by the client in UTC time.
- def received_at
- Time.at(params['transTime'].to_i / 1000).utc
- end
-
- # Callback password set in the WorldPay CMS
- def security_key
- params['callbackPW']
- end
-
- # the money amount we received in X.2 decimal.
- def gross
- params['authAmount']
- end
-
- def currency
- params['authCurrency']
- end
-
- # Was this a test transaction?
- def test?
- params.key?('testMode') && params['testMode'] != '0'
- end
-
- def status
- params['transStatus'] == 'Y' ? 'Completed' : 'Cancelled'
- end
-
- def name
- params['name']
- end
-
- def address
- params['address']
- end
-
- def postcode
- params['postcode']
- end
-
- def country
- params['country']
- end
-
- def phone_number
- params['tel']
- end
-
- def fax_number
- params['fax']
- end
-
- def email_address
- params['email']
- end
-
- def card_type
- params['cardType']
- end
-
- # WorldPay extended fraud checks returned as a 4 character string
- # 1st char: Credit card CVV check
- # 2nd char: Postcode AVS check
- # 3rd char: Address AVS check
- # 4th char: Country comparison check
- # Possible values are:
- # :not_supported - 0
- # :not_checked - 1
- # :matched - 2
- # :not_matched - 4
- # :partial_match - 8
- def cvv_status
- return avs_value_to_symbol(params['AVS'][0].chr)
- end
-
- def postcode_status
- return avs_value_to_symbol(params['AVS'][1].chr)
- end
-
- def address_status
- return avs_value_to_symbol(params['AVS'][2].chr)
- end
-
- def country_status
- return avs_value_to_symbol(params['AVS'][3].chr)
- end
-
- def acknowledge
- return true
- end
-
- # WorldPay supports the passing of custom parameters through to the callback script
- def custom_params
- return @custom_params ||= read_custom_params
- end
-
-
- private
-
- # Take the posted data and move the relevant data into a hash
- def parse(post)
- @raw = post
- for line in post.split('&')
- key, value = *line.scan( %r{^(\w+)\=(.*)$} ).flatten
- params[key] = value
- end
- end
-
- # Read the custom params into a hash
- def read_custom_params
- custom = {}
- params.each do |key, value|
- if /\A(M_|MC_|CM_)/ === key
- custom[key.gsub(/\A(M_|MC_|CM_)/, '').to_sym] = value
- end
- end
- custom
- end
-
- # Convert a AVS value to a symbol - see above for more about AVS
- def avs_value_to_symbol(value)
- case value.to_s
- when '8'
- :partial_match
- when '4'
- :no_match
- when '2'
- :matched
- when '1'
- :not_checked
- else
- :not_supported
- end
- end
- end
- end
- end
- end
-end
\ No newline at end of file
diff --git a/lib/active_merchant/billing/model.rb b/lib/active_merchant/billing/model.rb
new file mode 100644
index 00000000000..ba280a1211e
--- /dev/null
+++ b/lib/active_merchant/billing/model.rb
@@ -0,0 +1,30 @@
+require 'active_merchant/billing/compatibility'
+require 'active_merchant/empty'
+
+module ActiveMerchant
+ module Billing
+ class Model
+ include Compatibility::Model
+ include Empty
+
+ def initialize(attributes = {})
+ attributes.each do |key, value|
+ send("#{key}=", value)
+ end
+ end
+
+ def validate
+ {}
+ end
+
+ private
+
+ def errors_hash(array)
+ array.inject({}) do |hash, (attribute, error)|
+ (hash[attribute] ||= []) << error
+ hash
+ end
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/network_tokenization_credit_card.rb b/lib/active_merchant/billing/network_tokenization_credit_card.rb
new file mode 100644
index 00000000000..0f358948ab8
--- /dev/null
+++ b/lib/active_merchant/billing/network_tokenization_credit_card.rb
@@ -0,0 +1,39 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ class NetworkTokenizationCreditCard < CreditCard
+ # A +NetworkTokenizationCreditCard+ object represents a tokenized credit card
+ # using the EMV Network Tokenization specification, http://www.emvco.com/specifications.aspx?id=263.
+ #
+ # It includes all fields of the +CreditCard+ class with additional fields for
+ # verification data that must be given to gateways through existing fields (3DS / EMV).
+ #
+ # The only tested usage of this at the moment is with an Apple Pay decrypted PKPaymentToken,
+ # https://developer.apple.com/library/ios/documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.html
+
+ # These are not relevant (verification) or optional (name) for Apple Pay
+ self.require_verification_value = false
+ self.require_name = false
+
+ attr_accessor :payment_cryptogram, :eci, :transaction_id
+ attr_writer :source
+
+ SOURCES = %i(apple_pay android_pay google_pay)
+
+ def source
+ if defined?(@source) && SOURCES.include?(@source)
+ @source
+ else
+ :apple_pay
+ end
+ end
+
+ def credit_card?
+ true
+ end
+
+ def type
+ 'network_tokenization'
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/payment_token.rb b/lib/active_merchant/billing/payment_token.rb
new file mode 100644
index 00000000000..aea70ff6f66
--- /dev/null
+++ b/lib/active_merchant/billing/payment_token.rb
@@ -0,0 +1,21 @@
+module ActiveMerchant #:nodoc:
+ module Billing #:nodoc:
+ # Base class representation of cryptographic payment data tokens that may be used for EMV-style transactions
+ # like Apple Pay. Payment data may be transmitted via any data type, and may also be padded
+ # with metadata specific to the cryptographer. This metadata should be parsed and interpreted in concrete
+ # implementations of your given cryptographer. Like credit cards, you must also return a string representing
+ # the token's type, like 'apple_pay' or 'stripe' should your target payment gateway process these tokens.
+ class PaymentToken
+ attr_reader :payment_data
+
+ def initialize(payment_data, options = {})
+ @payment_data = payment_data
+ @metadata = options.with_indifferent_access
+ end
+
+ def type
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/billing/rails.rb b/lib/active_merchant/billing/rails.rb
new file mode 100644
index 00000000000..afa9eeb973a
--- /dev/null
+++ b/lib/active_merchant/billing/rails.rb
@@ -0,0 +1,3 @@
+require 'active_merchant/billing/compatibility'
+
+ActiveMerchant::Billing::Compatibility.rails_required!
diff --git a/lib/active_merchant/billing/response.rb b/lib/active_merchant/billing/response.rb
index 715855511bc..491bb0cab5b 100644
--- a/lib/active_merchant/billing/response.rb
+++ b/lib/active_merchant/billing/response.rb
@@ -4,7 +4,7 @@ class Error < ActiveMerchantError #:nodoc:
end
class Response
- attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result
+ attr_reader :params, :message, :test, :authorization, :avs_result, :cvv_result, :error_code, :emv_authorization
def success?
@success
@@ -23,57 +23,64 @@ def initialize(success, message, params = {}, options = {})
@test = options[:test] || false
@authorization = options[:authorization]
@fraud_review = options[:fraud_review]
+ @error_code = options[:error_code]
+ @emv_authorization = options[:emv_authorization]
@avs_result = if options[:avs_result].kind_of?(AVSResult)
- options[:avs_result].to_hash
- else
- AVSResult.new(options[:avs_result]).to_hash
+ options[:avs_result].to_hash
+ else
+ AVSResult.new(options[:avs_result]).to_hash
end
@cvv_result = if options[:cvv_result].kind_of?(CVVResult)
- options[:cvv_result].to_hash
- else
- CVVResult.new(options[:cvv_result]).to_hash
+ options[:cvv_result].to_hash
+ else
+ CVVResult.new(options[:cvv_result]).to_hash
end
end
end
class MultiResponse < Response
- def self.run(primary_response = :last, &block)
- response = new.tap(&block)
- response.primary_response = primary_response
- response
+ def self.run(use_first_response = false, &block)
+ new(use_first_response).tap(&block)
end
- attr_reader :responses
- attr_writer :primary_response
+ attr_reader :responses, :primary_response
- def initialize
+ def initialize(use_first_response = false)
@responses = []
- @primary_response = :last
+ @use_first_response = use_first_response
+ @primary_response = nil
end
- def process
- self << yield if(responses.empty? || success?)
+ def process(ignore_result=false)
+ return unless success?
+
+ response = yield
+ self << response
+
+ unless ignore_result
+ if(@use_first_response && response.success?)
+ @primary_response ||= response
+ else
+ @primary_response = response
+ end
+ end
end
def <<(response)
if response.is_a?(MultiResponse)
- response.responses.each{|r| @responses << r}
+ response.responses.each { |r| @responses << r }
else
@responses << response
end
end
def success?
- @responses.all?{|r| r.success?}
- end
-
- def primary_response
- success? && @primary_response == :first ? @responses.first : @responses.last
+ (primary_response ? primary_response.success? : true)
end
- %w(params message test authorization avs_result cvv_result test? fraud_review?).each do |m|
+ %w(params message test authorization avs_result cvv_result error_code emv_authorization test? fraud_review?).each do |m|
class_eval %(
def #{m}
(@responses.empty? ? nil : primary_response.#{m})
diff --git a/lib/active_merchant/connection.rb b/lib/active_merchant/connection.rb
new file mode 100644
index 00000000000..e6731ed8566
--- /dev/null
+++ b/lib/active_merchant/connection.rb
@@ -0,0 +1,195 @@
+require 'uri'
+require 'net/http'
+require 'net/https'
+require 'benchmark'
+
+module ActiveMerchant
+ class Connection
+ using NetHttpSslConnection
+ include NetworkConnectionRetries
+
+ MAX_RETRIES = 3
+ OPEN_TIMEOUT = 60
+ READ_TIMEOUT = 60
+ VERIFY_PEER = true
+ CA_FILE = File.expand_path('../certs/cacert.pem', File.dirname(__FILE__))
+ CA_PATH = nil
+ MIN_VERSION = :TLS1_1
+ RETRY_SAFE = false
+ RUBY_184_POST_HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded' }
+
+ attr_accessor :endpoint
+ attr_accessor :open_timeout
+ attr_accessor :read_timeout
+ attr_accessor :verify_peer
+ attr_accessor :ssl_version
+ if Net::HTTP.instance_methods.include?(:min_version=)
+ attr_accessor :min_version
+ attr_accessor :max_version
+ end
+ attr_reader :ssl_connection
+ attr_accessor :ca_file
+ attr_accessor :ca_path
+ attr_accessor :pem
+ attr_accessor :pem_password
+ attr_reader :wiredump_device
+ attr_accessor :logger
+ attr_accessor :tag
+ attr_accessor :ignore_http_status
+ attr_accessor :max_retries
+ attr_accessor :proxy_address
+ attr_accessor :proxy_port
+
+ def initialize(endpoint)
+ @endpoint = endpoint.is_a?(URI) ? endpoint : URI.parse(endpoint)
+ @open_timeout = OPEN_TIMEOUT
+ @read_timeout = READ_TIMEOUT
+ @retry_safe = RETRY_SAFE
+ @verify_peer = VERIFY_PEER
+ @ca_file = CA_FILE
+ @ca_path = CA_PATH
+ @max_retries = MAX_RETRIES
+ @ignore_http_status = false
+ @ssl_version = nil
+ if Net::HTTP.instance_methods.include?(:min_version=)
+ @min_version = MIN_VERSION
+ @max_version = nil
+ end
+ @ssl_connection = {}
+ @proxy_address = :ENV
+ @proxy_port = nil
+ end
+
+ def wiredump_device=(device)
+ raise ArgumentError, "can't wiredump to frozen #{device.class}" if device&.frozen?
+ @wiredump_device = device
+ end
+
+ def request(method, body, headers = {})
+ request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+
+ headers = headers.dup
+ headers['connection'] ||= 'close'
+
+ retry_exceptions(:max_retries => max_retries, :logger => logger, :tag => tag) do
+ begin
+ info "connection_http_method=#{method.to_s.upcase} connection_uri=#{endpoint}", tag
+
+ result = nil
+
+ realtime = Benchmark.realtime do
+ http.start unless http.started?
+ @ssl_connection = http.ssl_connection
+ info "connection_ssl_version=#{ssl_connection[:version]} connection_ssl_cipher=#{ssl_connection[:cipher]}", tag
+
+ result = case method
+ when :get
+ raise ArgumentError, 'GET requests do not support a request body' if body
+ http.get(endpoint.request_uri, headers)
+ when :post
+ debug body
+ http.post(endpoint.request_uri, body, RUBY_184_POST_HEADERS.merge(headers))
+ when :put
+ debug body
+ http.put(endpoint.request_uri, body, headers)
+ when :patch
+ debug body
+ http.patch(endpoint.request_uri, body, headers)
+ when :delete
+ # It's kind of ambiguous whether the RFC allows bodies
+ # for DELETE requests. But Net::HTTP's delete method
+ # very unambiguously does not.
+ if body
+ debug body
+ req = Net::HTTP::Delete.new(endpoint.request_uri, headers)
+ req.body = body
+ http.request(req)
+ else
+ http.delete(endpoint.request_uri, headers)
+ end
+ else
+ raise ArgumentError, "Unsupported request method #{method.to_s.upcase}"
+ end
+ end
+
+ info '--> %d %s (%d %.4fs)' % [result.code, result.message, result.body ? result.body.length : 0, realtime], tag
+ debug result.body
+ result
+ end
+ end
+ ensure
+ info 'connection_request_total_time=%.4fs' % [Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start], tag
+ http.finish if http.started?
+ end
+
+ private
+
+ def http
+ @http ||= begin
+ http = Net::HTTP.new(endpoint.host, endpoint.port, proxy_address, proxy_port)
+ configure_debugging(http)
+ configure_timeouts(http)
+ configure_ssl(http)
+ configure_cert(http)
+ http
+ end
+ end
+
+ def configure_debugging(http)
+ http.set_debug_output(wiredump_device)
+ end
+
+ def configure_timeouts(http)
+ http.open_timeout = open_timeout
+ http.read_timeout = read_timeout
+ end
+
+ def configure_ssl(http)
+ return unless endpoint.scheme == 'https'
+
+ http.use_ssl = true
+ http.ssl_version = ssl_version if ssl_version
+ if http.respond_to?(:min_version=)
+ http.min_version = min_version if min_version
+ http.max_version = max_version if max_version
+ end
+
+ if verify_peer
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ http.ca_file = ca_file
+ http.ca_path = ca_path
+ else
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ end
+
+ def configure_cert(http)
+ return if pem.blank?
+
+ http.cert = OpenSSL::X509::Certificate.new(pem)
+
+ if pem_password
+ http.key = OpenSSL::PKey::RSA.new(pem, pem_password)
+ else
+ http.key = OpenSSL::PKey::RSA.new(pem)
+ end
+ end
+
+ def debug(message, tag = nil)
+ log(:debug, message, tag)
+ end
+
+ def info(message, tag = nil)
+ log(:info, message, tag)
+ end
+
+ def error(message, tag = nil)
+ log(:error, message, tag)
+ end
+
+ def log(level, message, tag)
+ message = "[#{tag}] #{message}" if tag
+ logger&.send(level, message)
+ end
+ end
+end
diff --git a/lib/active_merchant/country.rb b/lib/active_merchant/country.rb
new file mode 100644
index 00000000000..eb18ba69665
--- /dev/null
+++ b/lib/active_merchant/country.rb
@@ -0,0 +1,336 @@
+# encoding: utf-8
+
+module ActiveMerchant #:nodoc:
+ class InvalidCountryCodeError < StandardError
+ end
+
+ class CountryCodeFormatError < StandardError
+ end
+
+ class CountryCode
+ attr_reader :value, :format
+ def initialize(value)
+ @value = value.to_s.upcase
+ detect_format
+ end
+
+ def to_s
+ value
+ end
+
+ private
+
+ def detect_format
+ case @value
+ when /^[[:alpha:]]{2}$/
+ @format = :alpha2
+ when /^[[:alpha:]]{3}$/
+ @format = :alpha3
+ when /^[[:digit:]]{3}$/
+ @format = :numeric
+ else
+ raise CountryCodeFormatError, "The country code is not formatted correctly #{@value}"
+ end
+ end
+ end
+
+ class Country
+ attr_reader :name
+
+ def initialize(options = {})
+ @name = options.delete(:name)
+ @codes = options.collect { |k, v| CountryCode.new(v) }
+ end
+
+ def code(format)
+ @codes.detect { |c| c.format == format }
+ end
+
+ def ==(other)
+ if other.class == ActiveMerchant::Country
+ (@name == other.name)
+ else
+ super
+ end
+ end
+
+ alias eql? ==
+
+ def hash
+ @name.hash
+ end
+
+ def to_s
+ @name
+ end
+
+ COUNTRIES = [
+ { alpha2: 'AF', name: 'Afghanistan', alpha3: 'AFG', numeric: '004' },
+ { alpha2: 'AL', name: 'Albania', alpha3: 'ALB', numeric: '008' },
+ { alpha2: 'DZ', name: 'Algeria', alpha3: 'DZA', numeric: '012' },
+ { alpha2: 'AS', name: 'American Samoa', alpha3: 'ASM', numeric: '016' },
+ { alpha2: 'AD', name: 'Andorra', alpha3: 'AND', numeric: '020' },
+ { alpha2: 'AO', name: 'Angola', alpha3: 'AGO', numeric: '024' },
+ { alpha2: 'AI', name: 'Anguilla', alpha3: 'AIA', numeric: '660' },
+ { alpha2: 'AQ', name: 'Antarctica', alpha3: 'ATA', numeric: '010' },
+ { alpha2: 'AG', name: 'Antigua and Barbuda', alpha3: 'ATG', numeric: '028' },
+ { alpha2: 'AR', name: 'Argentina', alpha3: 'ARG', numeric: '032' },
+ { alpha2: 'AM', name: 'Armenia', alpha3: 'ARM', numeric: '051' },
+ { alpha2: 'AW', name: 'Aruba', alpha3: 'ABW', numeric: '533' },
+ { alpha2: 'AU', name: 'Australia', alpha3: 'AUS', numeric: '036' },
+ { alpha2: 'AT', name: 'Austria', alpha3: 'AUT', numeric: '040' },
+ { alpha2: 'AZ', name: 'Azerbaijan', alpha3: 'AZE', numeric: '031' },
+ { alpha2: 'BS', name: 'Bahamas', alpha3: 'BHS', numeric: '044' },
+ { alpha2: 'BH', name: 'Bahrain', alpha3: 'BHR', numeric: '048' },
+ { alpha2: 'BD', name: 'Bangladesh', alpha3: 'BGD', numeric: '050' },
+ { alpha2: 'BB', name: 'Barbados', alpha3: 'BRB', numeric: '052' },
+ { alpha2: 'BY', name: 'Belarus', alpha3: 'BLR', numeric: '112' },
+ { alpha2: 'BE', name: 'Belgium', alpha3: 'BEL', numeric: '056' },
+ { alpha2: 'BZ', name: 'Belize', alpha3: 'BLZ', numeric: '084' },
+ { alpha2: 'BJ', name: 'Benin', alpha3: 'BEN', numeric: '204' },
+ { alpha2: 'BM', name: 'Bermuda', alpha3: 'BMU', numeric: '060' },
+ { alpha2: 'BT', name: 'Bhutan', alpha3: 'BTN', numeric: '064' },
+ { alpha2: 'BO', name: 'Bolivia', alpha3: 'BOL', numeric: '068' },
+ { alpha2: 'BQ', name: 'Bonaire, Sint Eustatius and Saba', alpha3: 'BES', numeric: '535' },
+ { alpha2: 'BA', name: 'Bosnia and Herzegovina', alpha3: 'BIH', numeric: '070' },
+ { alpha2: 'BW', name: 'Botswana', alpha3: 'BWA', numeric: '072' },
+ { alpha2: 'BV', name: 'Bouvet Island', alpha3: 'BVD', numeric: '074' },
+ { alpha2: 'BR', name: 'Brazil', alpha3: 'BRA', numeric: '076' },
+ { alpha2: 'IO', name: 'British Indian Ocean Territory', alpha3: 'IOT', numeric: '086' },
+ { alpha2: 'BN', name: 'Brunei Darussalam', alpha3: 'BRN', numeric: '096' },
+ { alpha2: 'BG', name: 'Bulgaria', alpha3: 'BGR', numeric: '100' },
+ { alpha2: 'BF', name: 'Burkina Faso', alpha3: 'BFA', numeric: '854' },
+ { alpha2: 'BI', name: 'Burundi', alpha3: 'BDI', numeric: '108' },
+ { alpha2: 'KH', name: 'Cambodia', alpha3: 'KHM', numeric: '116' },
+ { alpha2: 'CM', name: 'Cameroon', alpha3: 'CMR', numeric: '120' },
+ { alpha2: 'CA', name: 'Canada', alpha3: 'CAN', numeric: '124' },
+ { alpha2: 'CV', name: 'Cape Verde', alpha3: 'CPV', numeric: '132' },
+ { alpha2: 'KY', name: 'Cayman Islands', alpha3: 'CYM', numeric: '136' },
+ { alpha2: 'CF', name: 'Central African Republic', alpha3: 'CAF', numeric: '140' },
+ { alpha2: 'TD', name: 'Chad', alpha3: 'TCD', numeric: '148' },
+ { alpha2: 'CL', name: 'Chile', alpha3: 'CHL', numeric: '152' },
+ { alpha2: 'CN', name: 'China', alpha3: 'CHN', numeric: '156' },
+ { alpha2: 'CX', name: 'Christmas Island', alpha3: 'CXR', numeric: '162' },
+ { alpha2: 'CC', name: 'Cocos (Keeling) Islands', alpha3: 'CCK', numeric: '166' },
+ { alpha2: 'CO', name: 'Colombia', alpha3: 'COL', numeric: '170' },
+ { alpha2: 'KM', name: 'Comoros', alpha3: 'COM', numeric: '174' },
+ { alpha2: 'CG', name: 'Congo', alpha3: 'COG', numeric: '178' },
+ { alpha2: 'CD', name: 'Congo, the Democratic Republic of the', alpha3: 'COD', numeric: '180' },
+ { alpha2: 'CK', name: 'Cook Islands', alpha3: 'COK', numeric: '184' },
+ { alpha2: 'CR', name: 'Costa Rica', alpha3: 'CRI', numeric: '188' },
+ { alpha2: 'CI', name: 'Cote D\'Ivoire', alpha3: 'CIV', numeric: '384' },
+ { alpha2: 'HR', name: 'Croatia', alpha3: 'HRV', numeric: '191' },
+ { alpha2: 'CU', name: 'Cuba', alpha3: 'CUB', numeric: '192' },
+ { alpha2: 'CW', name: 'Curaçao', alpha3: 'CUW', numeric: '531' },
+ { alpha2: 'CY', name: 'Cyprus', alpha3: 'CYP', numeric: '196' },
+ { alpha2: 'CZ', name: 'Czech Republic', alpha3: 'CZE', numeric: '203' },
+ { alpha2: 'DK', name: 'Denmark', alpha3: 'DNK', numeric: '208' },
+ { alpha2: 'DJ', name: 'Djibouti', alpha3: 'DJI', numeric: '262' },
+ { alpha2: 'DM', name: 'Dominica', alpha3: 'DMA', numeric: '212' },
+ { alpha2: 'DO', name: 'Dominican Republic', alpha3: 'DOM', numeric: '214' },
+ { alpha2: 'EC', name: 'Ecuador', alpha3: 'ECU', numeric: '218' },
+ { alpha2: 'EG', name: 'Egypt', alpha3: 'EGY', numeric: '818' },
+ { alpha2: 'SV', name: 'El Salvador', alpha3: 'SLV', numeric: '222' },
+ { alpha2: 'GQ', name: 'Equatorial Guinea', alpha3: 'GNQ', numeric: '226' },
+ { alpha2: 'ER', name: 'Eritrea', alpha3: 'ERI', numeric: '232' },
+ { alpha2: 'EE', name: 'Estonia', alpha3: 'EST', numeric: '233' },
+ { alpha2: 'ET', name: 'Ethiopia', alpha3: 'ETH', numeric: '231' },
+ { alpha2: 'FK', name: 'Falkland Islands (Malvinas)', alpha3: 'FLK', numeric: '238' },
+ { alpha2: 'FO', name: 'Faroe Islands', alpha3: 'FRO', numeric: '234' },
+ { alpha2: 'FJ', name: 'Fiji', alpha3: 'FJI', numeric: '242' },
+ { alpha2: 'FI', name: 'Finland', alpha3: 'FIN', numeric: '246' },
+ { alpha2: 'FR', name: 'France', alpha3: 'FRA', numeric: '250' },
+ { alpha2: 'GF', name: 'French Guiana', alpha3: 'GUF', numeric: '254' },
+ { alpha2: 'PF', name: 'French Polynesia', alpha3: 'PYF', numeric: '258' },
+ { alpha2: 'TF', name: 'French Southern Territories', alpha3: 'ATF', numeric: '260' },
+ { alpha2: 'GA', name: 'Gabon', alpha3: 'GAB', numeric: '266' },
+ { alpha2: 'GM', name: 'Gambia', alpha3: 'GMB', numeric: '270' },
+ { alpha2: 'GE', name: 'Georgia', alpha3: 'GEO', numeric: '268' },
+ { alpha2: 'DE', name: 'Germany', alpha3: 'DEU', numeric: '276' },
+ { alpha2: 'GH', name: 'Ghana', alpha3: 'GHA', numeric: '288' },
+ { alpha2: 'GI', name: 'Gibraltar', alpha3: 'GIB', numeric: '292' },
+ { alpha2: 'GR', name: 'Greece', alpha3: 'GRC', numeric: '300' },
+ { alpha2: 'GL', name: 'Greenland', alpha3: 'GRL', numeric: '304' },
+ { alpha2: 'GD', name: 'Grenada', alpha3: 'GRD', numeric: '308' },
+ { alpha2: 'GP', name: 'Guadeloupe', alpha3: 'GLP', numeric: '312' },
+ { alpha2: 'GU', name: 'Guam', alpha3: 'GUM', numeric: '316' },
+ { alpha2: 'GT', name: 'Guatemala', alpha3: 'GTM', numeric: '320' },
+ { alpha2: 'GG', name: 'Guernsey', alpha3: 'GGY', numeric: '831' },
+ { alpha2: 'GN', name: 'Guinea', alpha3: 'GIN', numeric: '324' },
+ { alpha2: 'GW', name: 'Guinea-Bissau', alpha3: 'GNB', numeric: '624' },
+ { alpha2: 'GY', name: 'Guyana', alpha3: 'GUY', numeric: '328' },
+ { alpha2: 'HT', name: 'Haiti', alpha3: 'HTI', numeric: '332' },
+ { alpha2: 'HM', name: 'Heard Island And Mcdonald Islands', alpha3: 'HMD', numeric: '334' },
+ { alpha2: 'VA', name: 'Holy See (Vatican City State)', alpha3: 'VAT', numeric: '336' },
+ { alpha2: 'HN', name: 'Honduras', alpha3: 'HND', numeric: '340' },
+ { alpha2: 'HK', name: 'Hong Kong', alpha3: 'HKG', numeric: '344' },
+ { alpha2: 'HU', name: 'Hungary', alpha3: 'HUN', numeric: '348' },
+ { alpha2: 'IS', name: 'Iceland', alpha3: 'ISL', numeric: '352' },
+ { alpha2: 'IN', name: 'India', alpha3: 'IND', numeric: '356' },
+ { alpha2: 'ID', name: 'Indonesia', alpha3: 'IDN', numeric: '360' },
+ { alpha2: 'IR', name: 'Iran, Islamic Republic of', alpha3: 'IRN', numeric: '364' },
+ { alpha2: 'IQ', name: 'Iraq', alpha3: 'IRQ', numeric: '368' },
+ { alpha2: 'IE', name: 'Ireland', alpha3: 'IRL', numeric: '372' },
+ { alpha2: 'IM', name: 'Isle Of Man', alpha3: 'IMN', numeric: '833' },
+ { alpha2: 'IL', name: 'Israel', alpha3: 'ISR', numeric: '376' },
+ { alpha2: 'IT', name: 'Italy', alpha3: 'ITA', numeric: '380' },
+ { alpha2: 'JM', name: 'Jamaica', alpha3: 'JAM', numeric: '388' },
+ { alpha2: 'JP', name: 'Japan', alpha3: 'JPN', numeric: '392' },
+ { alpha2: 'JE', name: 'Jersey', alpha3: 'JEY', numeric: '832' },
+ { alpha2: 'JO', name: 'Jordan', alpha3: 'JOR', numeric: '400' },
+ { alpha2: 'KZ', name: 'Kazakhstan', alpha3: 'KAZ', numeric: '398' },
+ { alpha2: 'KE', name: 'Kenya', alpha3: 'KEN', numeric: '404' },
+ { alpha2: 'KI', name: 'Kiribati', alpha3: 'KIR', numeric: '296' },
+ { alpha2: 'KP', name: 'Korea, Democratic People\'s Republic of', alpha3: 'PRK', numeric: '408' },
+ { alpha2: 'KR', name: 'Korea, Republic of', alpha3: 'KOR', numeric: '410' },
+ { alpha2: 'XK', name: 'Kosovo', alpha3: 'XKX', numeric: '900' },
+ { alpha2: 'KW', name: 'Kuwait', alpha3: 'KWT', numeric: '414' },
+ { alpha2: 'KG', name: 'Kyrgyzstan', alpha3: 'KGZ', numeric: '417' },
+ { alpha2: 'LA', name: 'Lao People\'s Democratic Republic', alpha3: 'LAO', numeric: '418' },
+ { alpha2: 'LV', name: 'Latvia', alpha3: 'LVA', numeric: '428' },
+ { alpha2: 'LB', name: 'Lebanon', alpha3: 'LBN', numeric: '422' },
+ { alpha2: 'LS', name: 'Lesotho', alpha3: 'LSO', numeric: '426' },
+ { alpha2: 'LR', name: 'Liberia', alpha3: 'LBR', numeric: '430' },
+ { alpha2: 'LY', name: 'Libyan Arab Jamahiriya', alpha3: 'LBY', numeric: '434' },
+ { alpha2: 'LI', name: 'Liechtenstein', alpha3: 'LIE', numeric: '438' },
+ { alpha2: 'LT', name: 'Lithuania', alpha3: 'LTU', numeric: '440' },
+ { alpha2: 'LU', name: 'Luxembourg', alpha3: 'LUX', numeric: '442' },
+ { alpha2: 'MO', name: 'Macao', alpha3: 'MAC', numeric: '446' },
+ { alpha2: 'MK', name: 'Macedonia, the Former Yugoslav Republic of', alpha3: 'MKD', numeric: '807' },
+ { alpha2: 'MG', name: 'Madagascar', alpha3: 'MDG', numeric: '450' },
+ { alpha2: 'MW', name: 'Malawi', alpha3: 'MWI', numeric: '454' },
+ { alpha2: 'MY', name: 'Malaysia', alpha3: 'MYS', numeric: '458' },
+ { alpha2: 'MV', name: 'Maldives', alpha3: 'MDV', numeric: '462' },
+ { alpha2: 'ML', name: 'Mali', alpha3: 'MLI', numeric: '466' },
+ { alpha2: 'MT', name: 'Malta', alpha3: 'MLT', numeric: '470' },
+ { alpha2: 'MH', name: 'Marshall Islands', alpha3: 'MHL', numeric: '584' },
+ { alpha2: 'MQ', name: 'Martinique', alpha3: 'MTQ', numeric: '474' },
+ { alpha2: 'MR', name: 'Mauritania', alpha3: 'MRT', numeric: '478' },
+ { alpha2: 'MU', name: 'Mauritius', alpha3: 'MUS', numeric: '480' },
+ { alpha2: 'YT', name: 'Mayotte', alpha3: 'MYT', numeric: '175' },
+ { alpha2: 'MX', name: 'Mexico', alpha3: 'MEX', numeric: '484' },
+ { alpha2: 'FM', name: 'Micronesia, Federated States of', alpha3: 'FSM', numeric: '583' },
+ { alpha2: 'MD', name: 'Moldova, Republic of', alpha3: 'MDA', numeric: '498' },
+ { alpha2: 'MC', name: 'Monaco', alpha3: 'MCO', numeric: '492' },
+ { alpha2: 'MN', name: 'Mongolia', alpha3: 'MNG', numeric: '496' },
+ { alpha2: 'ME', name: 'Montenegro', alpha3: 'MNE', numeric: '499' },
+ { alpha2: 'MS', name: 'Montserrat', alpha3: 'MSR', numeric: '500' },
+ { alpha2: 'MA', name: 'Morocco', alpha3: 'MAR', numeric: '504' },
+ { alpha2: 'MZ', name: 'Mozambique', alpha3: 'MOZ', numeric: '508' },
+ { alpha2: 'MM', name: 'Myanmar', alpha3: 'MMR', numeric: '104' },
+ { alpha2: 'NA', name: 'Namibia', alpha3: 'NAM', numeric: '516' },
+ { alpha2: 'NR', name: 'Nauru', alpha3: 'NRU', numeric: '520' },
+ { alpha2: 'NP', name: 'Nepal', alpha3: 'NPL', numeric: '524' },
+ { alpha2: 'NL', name: 'Netherlands', alpha3: 'NLD', numeric: '528' },
+ { alpha2: 'NC', name: 'New Caledonia', alpha3: 'NCL', numeric: '540' },
+ { alpha2: 'NZ', name: 'New Zealand', alpha3: 'NZL', numeric: '554' },
+ { alpha2: 'NI', name: 'Nicaragua', alpha3: 'NIC', numeric: '558' },
+ { alpha2: 'NE', name: 'Niger', alpha3: 'NER', numeric: '562' },
+ { alpha2: 'NG', name: 'Nigeria', alpha3: 'NGA', numeric: '566' },
+ { alpha2: 'NU', name: 'Niue', alpha3: 'NIU', numeric: '570' },
+ { alpha2: 'NF', name: 'Norfolk Island', alpha3: 'NFK', numeric: '574' },
+ { alpha2: 'MP', name: 'Northern Mariana Islands', alpha3: 'MNP', numeric: '580' },
+ { alpha2: 'NO', name: 'Norway', alpha3: 'NOR', numeric: '578' },
+ { alpha2: 'OM', name: 'Oman', alpha3: 'OMN', numeric: '512' },
+ { alpha2: 'PK', name: 'Pakistan', alpha3: 'PAK', numeric: '586' },
+ { alpha2: 'PW', name: 'Palau', alpha3: 'PLW', numeric: '585' },
+ { alpha2: 'PS', name: 'Palestinian Territory, Occupied', alpha3: 'PSE', numeric: '275' },
+ { alpha2: 'PA', name: 'Panama', alpha3: 'PAN', numeric: '591' },
+ { alpha2: 'PG', name: 'Papua New Guinea', alpha3: 'PNG', numeric: '598' },
+ { alpha2: 'PY', name: 'Paraguay', alpha3: 'PRY', numeric: '600' },
+ { alpha2: 'PE', name: 'Peru', alpha3: 'PER', numeric: '604' },
+ { alpha2: 'PH', name: 'Philippines', alpha3: 'PHL', numeric: '608' },
+ { alpha2: 'PN', name: 'Pitcairn', alpha3: 'PCN', numeric: '612' },
+ { alpha2: 'PL', name: 'Poland', alpha3: 'POL', numeric: '616' },
+ { alpha2: 'PT', name: 'Portugal', alpha3: 'PRT', numeric: '620' },
+ { alpha2: 'PR', name: 'Puerto Rico', alpha3: 'PRI', numeric: '630' },
+ { alpha2: 'QA', name: 'Qatar', alpha3: 'QAT', numeric: '634' },
+ { alpha2: 'RE', name: 'Reunion', alpha3: 'REU', numeric: '638' },
+ { alpha2: 'RO', name: 'Romania', alpha3: 'ROU', numeric: '642' },
+ { alpha2: 'RO', name: 'Romania', alpha3: 'ROM', numeric: '642' },
+ { alpha2: 'RU', name: 'Russian Federation', alpha3: 'RUS', numeric: '643' },
+ { alpha2: 'RW', name: 'Rwanda', alpha3: 'RWA', numeric: '646' },
+ { alpha2: 'BL', name: 'Saint Barthélemy', alpha3: 'BLM', numeric: '652' },
+ { alpha2: 'SH', name: 'Saint Helena', alpha3: 'SHN', numeric: '654' },
+ { alpha2: 'KN', name: 'Saint Kitts and Nevis', alpha3: 'KNA', numeric: '659' },
+ { alpha2: 'LC', name: 'Saint Lucia', alpha3: 'LCA', numeric: '662' },
+ { alpha2: 'MF', name: 'Saint Martin (French part)', alpha3: 'MAF', numeric: '663' },
+ { alpha2: 'PM', name: 'Saint Pierre and Miquelon', alpha3: 'SPM', numeric: '666' },
+ { alpha2: 'VC', name: 'Saint Vincent and the Grenadines', alpha3: 'VCT', numeric: '670' },
+ { alpha2: 'WS', name: 'Samoa', alpha3: 'WSM', numeric: '882' },
+ { alpha2: 'SM', name: 'San Marino', alpha3: 'SMR', numeric: '674' },
+ { alpha2: 'ST', name: 'Sao Tome and Principe', alpha3: 'STP', numeric: '678' },
+ { alpha2: 'SA', name: 'Saudi Arabia', alpha3: 'SAU', numeric: '682' },
+ { alpha2: 'SN', name: 'Senegal', alpha3: 'SEN', numeric: '686' },
+ { alpha2: 'RS', name: 'Serbia', alpha3: 'SRB', numeric: '688' },
+ { alpha2: 'SC', name: 'Seychelles', alpha3: 'SYC', numeric: '690' },
+ { alpha2: 'SL', name: 'Sierra Leone', alpha3: 'SLE', numeric: '694' },
+ { alpha2: 'SG', name: 'Singapore', alpha3: 'SGP', numeric: '702' },
+ { alpha2: 'SX', name: 'Sint Maarten', alpha3: 'SXM', numeric: '534' },
+ { alpha2: 'SK', name: 'Slovakia', alpha3: 'SVK', numeric: '703' },
+ { alpha2: 'SI', name: 'Slovenia', alpha3: 'SVN', numeric: '705' },
+ { alpha2: 'SB', name: 'Solomon Islands', alpha3: 'SLB', numeric: '090' },
+ { alpha2: 'SO', name: 'Somalia', alpha3: 'SOM', numeric: '706' },
+ { alpha2: 'ZA', name: 'South Africa', alpha3: 'ZAF', numeric: '710' },
+ { alpha2: 'GS', name: 'South Georgia and the South Sandwich Islands', alpha3: 'SGS', numeric: '239' },
+ { alpha2: 'SS', name: 'South Sudan', alpha3: 'SSD', numeric: '728' },
+ { alpha2: 'ES', name: 'Spain', alpha3: 'ESP', numeric: '724' },
+ { alpha2: 'LK', name: 'Sri Lanka', alpha3: 'LKA', numeric: '144' },
+ { alpha2: 'SD', name: 'Sudan', alpha3: 'SDN', numeric: '729' },
+ { alpha2: 'SR', name: 'Suriname', alpha3: 'SUR', numeric: '740' },
+ { alpha2: 'SJ', name: 'Svalbard and Jan Mayen', alpha3: 'SJM', numeric: '744' },
+ { alpha2: 'SZ', name: 'Swaziland', alpha3: 'SWZ', numeric: '748' },
+ { alpha2: 'SE', name: 'Sweden', alpha3: 'SWE', numeric: '752' },
+ { alpha2: 'CH', name: 'Switzerland', alpha3: 'CHE', numeric: '756' },
+ { alpha2: 'SY', name: 'Syrian Arab Republic', alpha3: 'SYR', numeric: '760' },
+ { alpha2: 'TW', name: 'Taiwan, Province of China', alpha3: 'TWN', numeric: '158' },
+ { alpha2: 'TJ', name: 'Tajikistan', alpha3: 'TJK', numeric: '762' },
+ { alpha2: 'TZ', name: 'Tanzania, United Republic of', alpha3: 'TZA', numeric: '834' },
+ { alpha2: 'TH', name: 'Thailand', alpha3: 'THA', numeric: '764' },
+ { alpha2: 'TL', name: 'Timor Leste', alpha3: 'TLS', numeric: '626' },
+ { alpha2: 'TG', name: 'Togo', alpha3: 'TGO', numeric: '768' },
+ { alpha2: 'TK', name: 'Tokelau', alpha3: 'TKL', numeric: '772' },
+ { alpha2: 'TO', name: 'Tonga', alpha3: 'TON', numeric: '776' },
+ { alpha2: 'TT', name: 'Trinidad and Tobago', alpha3: 'TTO', numeric: '780' },
+ { alpha2: 'TN', name: 'Tunisia', alpha3: 'TUN', numeric: '788' },
+ { alpha2: 'TR', name: 'Turkey', alpha3: 'TUR', numeric: '792' },
+ { alpha2: 'TM', name: 'Turkmenistan', alpha3: 'TKM', numeric: '795' },
+ { alpha2: 'TC', name: 'Turks and Caicos Islands', alpha3: 'TCA', numeric: '796' },
+ { alpha2: 'TV', name: 'Tuvalu', alpha3: 'TUV', numeric: '798' },
+ { alpha2: 'UG', name: 'Uganda', alpha3: 'UGA', numeric: '800' },
+ { alpha2: 'UA', name: 'Ukraine', alpha3: 'UKR', numeric: '804' },
+ { alpha2: 'AE', name: 'United Arab Emirates', alpha3: 'ARE', numeric: '784' },
+ { alpha2: 'GB', name: 'United Kingdom', alpha3: 'GBR', numeric: '826' },
+ { alpha2: 'US', name: 'United States', alpha3: 'USA', numeric: '840' },
+ { alpha2: 'UM', name: 'United States Minor Outlying Islands', alpha3: 'UMI', numeric: '581' },
+ { alpha2: 'UY', name: 'Uruguay', alpha3: 'URY', numeric: '858' },
+ { alpha2: 'UZ', name: 'Uzbekistan', alpha3: 'UZB', numeric: '860' },
+ { alpha2: 'VU', name: 'Vanuatu', alpha3: 'VUT', numeric: '548' },
+ { alpha2: 'VE', name: 'Venezuela', alpha3: 'VEN', numeric: '862' },
+ { alpha2: 'VN', name: 'Viet Nam', alpha3: 'VNM', numeric: '704' },
+ { alpha2: 'VG', name: 'Virgin Islands, British', alpha3: 'VGB', numeric: '092' },
+ { alpha2: 'VI', name: 'Virgin Islands, U.S.', alpha3: 'VIR', numeric: '850' },
+ { alpha2: 'WF', name: 'Wallis and Futuna', alpha3: 'WLF', numeric: '876' },
+ { alpha2: 'EH', name: 'Western Sahara', alpha3: 'ESH', numeric: '732' },
+ { alpha2: 'YE', name: 'Yemen', alpha3: 'YEM', numeric: '887' },
+ { alpha2: 'ZM', name: 'Zambia', alpha3: 'ZMB', numeric: '894' },
+ { alpha2: 'ZW', name: 'Zimbabwe', alpha3: 'ZWE', numeric: '716' },
+ { alpha2: 'AX', name: 'Åland Islands', alpha3: 'ALA', numeric: '248' }
+ ]
+
+ def self.find(name)
+ raise InvalidCountryCodeError, 'Cannot lookup country for an empty name' if name.blank?
+
+ case name.length
+ when 2, 3
+ upcase_name = name.upcase
+ country_code = CountryCode.new(name)
+ country = COUNTRIES.detect { |c| c[country_code.format] == upcase_name }
+ else
+ country = COUNTRIES.detect { |c| c[:name].casecmp(name).zero? }
+ end
+ raise InvalidCountryCodeError, "No country could be found for the country #{name}" if country.nil?
+ Country.new(country.dup)
+ end
+ end
+end
diff --git a/lib/active_merchant/empty.rb b/lib/active_merchant/empty.rb
new file mode 100644
index 00000000000..6c5f50b273c
--- /dev/null
+++ b/lib/active_merchant/empty.rb
@@ -0,0 +1,20 @@
+module ActiveMerchant
+ module Empty
+ private
+
+ def empty?(value)
+ case value
+ when nil
+ true
+ when Array, Hash
+ value.empty?
+ when String
+ value.strip.empty?
+ when Numeric
+ (value == 0)
+ else
+ false
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/errors.rb b/lib/active_merchant/errors.rb
new file mode 100644
index 00000000000..af4bcb8b1be
--- /dev/null
+++ b/lib/active_merchant/errors.rb
@@ -0,0 +1,35 @@
+module ActiveMerchant #:nodoc:
+ class ActiveMerchantError < StandardError #:nodoc:
+ end
+
+ class ConnectionError < ActiveMerchantError # :nodoc:
+ attr_reader :triggering_exception
+
+ def initialize(message, triggering_exception)
+ super(message)
+ @triggering_exception = triggering_exception
+ end
+ end
+
+ class RetriableConnectionError < ConnectionError # :nodoc:
+ end
+
+ class ResponseError < ActiveMerchantError # :nodoc:
+ attr_reader :response
+
+ def initialize(response, message = nil)
+ @response = response
+ @message = message
+ end
+
+ def to_s
+ "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
+ end
+ end
+
+ class ClientCertificateError < ActiveMerchantError # :nodoc
+ end
+
+ class InvalidResponseError < ActiveMerchantError # :nodoc
+ end
+end
diff --git a/lib/active_merchant/net_http_ssl_connection.rb b/lib/active_merchant/net_http_ssl_connection.rb
new file mode 100644
index 00000000000..c0ae5ce1080
--- /dev/null
+++ b/lib/active_merchant/net_http_ssl_connection.rb
@@ -0,0 +1,10 @@
+require 'net/http'
+
+module NetHttpSslConnection
+ refine Net::HTTP do
+ def ssl_connection
+ return {} unless use_ssl? && @socket.present?
+ { version: @socket.io.ssl_version, cipher: @socket.io.cipher[0] }
+ end
+ end
+end
diff --git a/lib/active_merchant/network_connection_retries.rb b/lib/active_merchant/network_connection_retries.rb
new file mode 100644
index 00000000000..09e1b146f30
--- /dev/null
+++ b/lib/active_merchant/network_connection_retries.rb
@@ -0,0 +1,80 @@
+require 'openssl'
+
+module ActiveMerchant
+ module NetworkConnectionRetries
+ DEFAULT_RETRIES = 3
+ DEFAULT_CONNECTION_ERRORS = {
+ EOFError => 'The remote server dropped the connection',
+ Errno::ECONNRESET => 'The remote server reset the connection',
+ Timeout::Error => 'The connection to the remote server timed out',
+ Errno::ETIMEDOUT => 'The connection to the remote server timed out',
+ SocketError => 'The connection to the remote server could not be established',
+ Errno::EHOSTUNREACH => 'The connection to the remote server could not be established',
+ OpenSSL::SSL::SSLError => 'The SSL connection to the remote server could not be established'
+ }
+
+ def self.included(base)
+ base.send(:attr_accessor, :retry_safe)
+ end
+
+ def retry_exceptions(options={})
+ connection_errors = DEFAULT_CONNECTION_ERRORS.merge(options[:connection_exceptions] || {})
+
+ retry_network_exceptions(options) do
+ begin
+ yield
+ rescue Errno::ECONNREFUSED => e
+ raise ActiveMerchant::RetriableConnectionError.new('The remote server refused the connection', e)
+ rescue OpenSSL::X509::CertificateError => e
+ NetworkConnectionRetries.log(options[:logger], :error, e.message, options[:tag])
+ raise ActiveMerchant::ClientCertificateError, 'The remote server did not accept the provided SSL certificate'
+ rescue Zlib::BufError
+ raise ActiveMerchant::InvalidResponseError, 'The remote server replied with an invalid response'
+ rescue *connection_errors.keys => e
+ raise ActiveMerchant::ConnectionError.new(derived_error_message(connection_errors, e.class), e)
+ end
+ end
+ end
+
+ def self.log(logger, level, message, tag=nil)
+ tag ||= self.class.to_s
+ message = "[#{tag}] #{message}"
+ logger&.send(level, message)
+ end
+
+ private
+
+ def retry_network_exceptions(options = {})
+ initial_retries = options[:max_retries] || DEFAULT_RETRIES
+ retries = initial_retries
+ request_start = nil
+
+ begin
+ request_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ result = yield
+ log_with_retry_details(options[:logger], initial_retries-retries + 1, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, 'success', options[:tag])
+ result
+ rescue ActiveMerchant::RetriableConnectionError => e
+ retries -= 1
+
+ log_with_retry_details(options[:logger], initial_retries-retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag])
+ retry unless retries.zero?
+ raise ActiveMerchant::ConnectionError.new(e.message, e)
+ rescue ActiveMerchant::ConnectionError, ActiveMerchant::InvalidResponseError => e
+ retries -= 1
+ log_with_retry_details(options[:logger], initial_retries-retries, Process.clock_gettime(Process::CLOCK_MONOTONIC) - request_start, e.message, options[:tag])
+ retry if (options[:retry_safe] || retry_safe) && !retries.zero?
+ raise
+ end
+ end
+
+ def log_with_retry_details(logger, attempts, time, message, tag)
+ NetworkConnectionRetries.log(logger, :info, 'connection_attempt=%d connection_request_time=%.4fs connection_msg="%s"' % [attempts, time, message], tag)
+ end
+
+ def derived_error_message(errors, klass)
+ key = (errors.keys & klass.ancestors).first
+ key ? errors[key] : nil
+ end
+ end
+end
diff --git a/lib/active_merchant/post_data.rb b/lib/active_merchant/post_data.rb
new file mode 100644
index 00000000000..c95b85244d2
--- /dev/null
+++ b/lib/active_merchant/post_data.rb
@@ -0,0 +1,25 @@
+require 'cgi'
+
+module ActiveMerchant
+ class PostData < Hash
+ class_attribute :required_fields, :instance_writer => false
+ self.required_fields = []
+
+ def []=(key, value)
+ return if value.blank? && !required?(key)
+ super
+ end
+
+ def to_post_data
+ collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
+ end
+
+ alias_method :to_s, :to_post_data
+
+ private
+
+ def required?(key)
+ required_fields.include?(key)
+ end
+ end
+end
diff --git a/lib/active_merchant/posts_data.rb b/lib/active_merchant/posts_data.rb
new file mode 100644
index 00000000000..15446c09fd8
--- /dev/null
+++ b/lib/active_merchant/posts_data.rb
@@ -0,0 +1,92 @@
+module ActiveMerchant #:nodoc:
+ module PostsData #:nodoc:
+ def self.included(base)
+ base.class_attribute :ssl_strict
+ base.ssl_strict = true
+
+ base.class_attribute :ssl_version
+ base.ssl_version = nil
+
+ base.class_attribute :min_version
+ base.min_version = Connection::MIN_VERSION
+
+ base.class_attribute :max_version
+ base.max_version = nil
+
+ base.class_attribute :retry_safe
+ base.retry_safe = false
+
+ base.class_attribute :open_timeout
+ base.open_timeout = Connection::OPEN_TIMEOUT
+
+ base.class_attribute :read_timeout
+ base.read_timeout = Connection::READ_TIMEOUT
+
+ base.class_attribute :max_retries
+ base.max_retries = Connection::MAX_RETRIES
+
+ base.class_attribute :logger
+ base.class_attribute :wiredump_device
+
+ base.class_attribute :proxy_address
+ base.class_attribute :proxy_port
+ end
+
+ def ssl_get(endpoint, headers={})
+ ssl_request(:get, endpoint, nil, headers)
+ end
+
+ def ssl_post(endpoint, data, headers = {})
+ ssl_request(:post, endpoint, data, headers)
+ end
+
+ def ssl_request(method, endpoint, data, headers)
+ handle_response(raw_ssl_request(method, endpoint, data, headers))
+ end
+
+ def raw_ssl_request(method, endpoint, data, headers = {})
+ logger&.warn "#{self.class} using ssl_strict=false, which is insecure" unless ssl_strict
+ logger&.warn "#{self.class} posting to plaintext endpoint, which is insecure" unless endpoint.to_s =~ /^https:/
+
+ connection = new_connection(endpoint)
+ connection.open_timeout = open_timeout
+ connection.read_timeout = read_timeout
+ connection.retry_safe = retry_safe
+ connection.verify_peer = ssl_strict
+ connection.ssl_version = ssl_version
+ connection.logger = logger
+ connection.max_retries = max_retries
+ connection.tag = self.class.name
+ connection.wiredump_device = wiredump_device
+ if connection.respond_to?(:min_version=)
+ connection.min_version = min_version
+ connection.max_version = max_version
+ end
+
+ connection.pem = @options[:pem] if @options
+ connection.pem_password = @options[:pem_password] if @options
+
+ connection.ignore_http_status = @options[:ignore_http_status] if @options
+
+ connection.proxy_address = proxy_address
+ connection.proxy_port = proxy_port
+
+ connection.request(method, data, headers)
+ end
+
+ private
+
+ def new_connection(endpoint)
+ Connection.new(endpoint)
+ end
+
+ def handle_response(response)
+ case response.code.to_i
+ when 200...300
+ response.body
+ else
+ raise ResponseError.new(response)
+ end
+ end
+ end
+end
diff --git a/lib/active_merchant/version.rb b/lib/active_merchant/version.rb
index c6646f5133b..f01a6db39d8 100644
--- a/lib/active_merchant/version.rb
+++ b/lib/active_merchant/version.rb
@@ -1,3 +1,3 @@
module ActiveMerchant
- VERSION = "1.38.1"
+ VERSION = '1.97.0'
end
diff --git a/lib/activemerchant.rb b/lib/activemerchant.rb
index 0a3f08fee3f..118568f06b3 100644
--- a/lib/activemerchant.rb
+++ b/lib/activemerchant.rb
@@ -1 +1 @@
-require 'active_merchant'
\ No newline at end of file
+require 'active_merchant'
diff --git a/lib/certs/cacert.pem b/lib/certs/cacert.pem
new file mode 100644
index 00000000000..72bbb947fe5
--- /dev/null
+++ b/lib/certs/cacert.pem
@@ -0,0 +1,3988 @@
+##
+## ca-bundle.crt -- Bundle of CA Root Certificates
+##
+## Certificate data from Mozilla as of: Tue Apr 22 08:29:31 2014
+##
+## This is a bundle of X.509 certificates of public Certificate Authorities
+## (CA). These were automatically extracted from Mozilla's root certificates
+## file (certdata.txt). This file can be found in the mozilla source tree:
+## http://mxr.mozilla.org/mozilla-release/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1
+##
+## It contains the certificates in PEM format and therefore
+## can be directly used with curl / libcurl / php_curl, or with
+## an Apache+mod_ssl webserver for SSL client authentication.
+## Just configure this file as the SSLCACertificateFile.
+##
+
+
+GTE CyberTrust Global Root
+==========================
+-----BEGIN CERTIFICATE-----
+MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg
+Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG
+A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz
+MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL
+Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0
+IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u
+sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql
+HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID
+AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW
+M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF
+NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
+-----END CERTIFICATE-----
+
+Thawte Server CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE
+AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j
+b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV
+BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u
+c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG
+A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0
+ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl
+/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7
+1CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR
+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J
+GubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ
+GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP/iiMucEc=
+-----END CERTIFICATE-----
+
+Thawte Premium Server CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT
+DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
+dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE
+AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl
+ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT
+AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU
+VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2
+aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ
+cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
+aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIh
+Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMRuHM/
+qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm
+SCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf
+8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7t
+UCemDaYj+bvLpgcUQg==
+-----END CERTIFICATE-----
+
+Equifax Secure CA
+=================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE
+ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT
+B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR
+fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW
+8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG
+A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE
+CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG
+A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS
+spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB
+Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961
+zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB
+BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95
+70+sB3c4
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA
+TxQT3ab7/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah
+WM1pF+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf
+Tqj/ZA1k
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G2
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT
+MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
+biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
+dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO
+FoUgRm1HP9SFIIThbbP4pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71
+lSk8UOg013gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB
+MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT
+1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD
+Oaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9
+-----END CERTIFICATE-----
+
+GlobalSign Root CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
+GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
+b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
+BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
+VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
+DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
+THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
+Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
+c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
+gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
+AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
+Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
+j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
+hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
+X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
+
+GlobalSign Root CA - R2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6
+ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
+s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
+S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
+TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
+ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
+YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
+BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp
+9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu
+01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
+9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
+TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
+-----END CERTIFICATE-----
+
+ValiCert Class 1 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy
+MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi
+GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm
+DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG
+lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX
+icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP
+Orf1LXLI
+-----END CERTIFICATE-----
+
+ValiCert Class 2 VA
+===================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC
+CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf
+ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ
+SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV
+UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8
+W9ViH0Pd
+-----END CERTIFICATE-----
+
+RSA Root Certificate 1
+======================
+-----BEGIN CERTIFICATE-----
+MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
+b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
+YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
+bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
+MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
+d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg
+UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
+LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td
+3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H
+BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs
+3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF
+V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r
+on+jjBXu
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1
+EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc
+cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw
+EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj
+055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f
+j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
+/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0
+xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa
+t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
+-----END CERTIFICATE-----
+
+Verisign Class 4 Public Primary Certification Authority - G3
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
+dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS
+tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM
+8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW
+Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX
+Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
+j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt
+mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
+fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd
+RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG
+UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
+-----END CERTIFICATE-----
+
+Entrust.net Secure Server CA
+============================
+-----BEGIN CERTIFICATE-----
+MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV
+BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg
+cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl
+ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv
+cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG
+A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi
+eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p
+dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ
+aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5
+gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw
+ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw
+CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l
+dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
+bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
+dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw
+NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow
+HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA
+BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN
+Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9
+n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
+-----END CERTIFICATE-----
+
+Entrust.net Premium 2048 Secure Server CA
+=========================================
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
+ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
+bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
+BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
+NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
+d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
+MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
+ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
+Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
+hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
+nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
+VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
+KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPy
+T/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
+J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+bYQLCIt+jerXmCHG8+c8eS9e
+nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/ErfF6adulZkMV8gzURZVE=
+-----END CERTIFICATE-----
+
+Baltimore CyberTrust Root
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
+ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
+ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
+SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
+dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
+uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
+UnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
+G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
+XbIGevOF6uvUA65ehD5f/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
+l3RjM71oGDHweI12v/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
+VDaGezq1BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
+BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT929hkTI7gQCvlYpNRh
+cL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
+hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
+Y71k5h+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
+RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+
+Equifax Secure Global eBusiness CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp
+bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx
+HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds
+b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV
+PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN
+qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn
+hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
+BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs
+MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN
+I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY
+NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
+-----END CERTIFICATE-----
+
+Equifax Secure eBusiness CA 1
+=============================
+-----BEGIN CERTIFICATE-----
+MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB
+LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE
+ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz
+IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ
+1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a
+IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk
+MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW
+Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF
+AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5
+lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+
+KpYrtWKmpj29f5JZzVoqgrI3eQ==
+-----END CERTIFICATE-----
+
+AddTrust Low-Value Services Root
+================================
+-----BEGIN CERTIFICATE-----
+MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU
+cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw
+CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO
+ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6
+54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr
+oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1
+Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui
+GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w
+HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD
+AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT
+RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw
+HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt
+ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph
+iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
+eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr
+mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj
+ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
+-----END CERTIFICATE-----
+
+AddTrust External Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD
+VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw
+NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU
+cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg
+Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821
++iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw
+Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo
+aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy
+2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7
+7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL
+VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk
+VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB
+IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl
+j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355
+e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u
+G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
+
+AddTrust Public Services Root
+=============================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU
+cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ
+BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l
+dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu
+nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i
+d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG
+Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw
+HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G
+A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G
+A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4
+JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL
++YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
+GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9
+Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H
+EufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
+-----END CERTIFICATE-----
+
+AddTrust Qualified Certificates Root
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
+QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU
+cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx
+CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ
+IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx
+64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3
+KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o
+L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR
+wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU
+MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE
+BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y
+azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG
+GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
+dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze
+RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB
+iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE=
+-----END CERTIFICATE-----
+
+Entrust Root Certification Authority
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
+BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
+b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
+A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
+MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
+MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
+Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
+dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
+A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
+Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
+j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
+rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
+MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
+hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
+Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
+v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
+W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
+tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+
+Entrust G2 Root Certificate Authority
+=====================================
+-----BEGIN CERTIFICATE-----
+MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
+cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
+IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
+dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
+NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
+dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
+dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
+aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
+RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
+cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
+wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
+U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
+jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
+BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
+jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
+Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
+1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
+nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
+VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
+-----END CERTIFICATE-----
+
+Entrust L1M Chain Root Certificate
+==================================
+-----BEGIN CERTIFICATE-----
+MIIE/zCCA+egAwIBAgIEUdNARDANBgkqhkiG9w0BAQsFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE0MDkyMjE3MTQ1N1oXDTI0MDkyMzAx
+MzE1M1owgb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgw
+JgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQL
+EzAoYykgMjAwOSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9u
+bHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eSAtIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuoS2ctueDGvi
+mekwAad26jK4lUEaydphTlhyz/72gnm/c2EGCqUn2LNf00VOHHLWTjLycooP94MZ
+0GqAgABFHrDH55q/ElcnHKNoLwqHvWprDl5l8xx31dSFjXAhtLMy54ui1YY5ArG4
+0kfO5MlJxDun3vtUfVe+8OhuwnmyOgtV4lCYFjITXC94VsHClLPyWuQnmp8k18bs
+0JslguPMwsRFxYyXegZrKhGfqQpuSDtv29QRGUL3jwe/9VNfnD70FyzmaaxOMkxi
+d+q36OW7NLwZi66cUee3frVTsTMi5W3PcDwa+uKbZ7aD9I2lr2JMTeBYrGQ0EgP4
+to2UYySkcQIDAQABo4IBDzCCAQswDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQI
+MAYBAf8CAQEwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdodHRwOi8vb2Nz
+cC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmVudHJ1
+c3QubmV0L3Jvb3RjYTEuY3JsMDsGA1UdIAQ0MDIwMAYEVR0gADAoMCYGCCsGAQUF
+BwIBFhpodHRwOi8vd3d3LmVudHJ1c3QubmV0L0NQUzAdBgNVHQ4EFgQUanImetAe
+733nO2lR1GyNn5ASZqswHwYDVR0jBBgwFoAUaJDkZ6SmU4DHhmak8fdLQ/uEvW0w
+DQYJKoZIhvcNAQELBQADggEBAGkzg/woem99751V68U+ep11s8zDODbZNKIoaBjq
+HmnTvefQd9q4AINOSs9v0fHBIj905PeYSZ6btp7h25h3LVY0sag82f3Azce/BQPU
+AsXx5cbaCKUTx2IjEdFhMB1ghEXveajGJpOkt800uGnFE/aRs8lFc3a2kvZ2Clvh
+A0e36SlMkTIjN0qcNdh4/R0f5IOJJICtt/nP5F2l1HHEhVtwH9s/HAHrGkUmMRTM
+Zb9n3srMM2XlQZHXN75BGpad5oqXnafOrE6aPb0BoGrZTyIAi0TVaWJ7LuvMuueS
+fWlnPfy4fN5Bh9Bp6roKGHoalUOzeXEodm2h+1dK7E3IDhA=
+-----END CERTIFICATE-----
+
+RSA Security 2048 v3
+====================
+-----BEGIN CERTIFICATE-----
+MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK
+ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy
+MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb
+BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7
+Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb
+WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH
+KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP
++Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/
+MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E
+FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY
+v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj
+0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj
+VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395
+nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA
+pKnXwiJPZ9d37CAFYd4=
+-----END CERTIFICATE-----
+
+GeoTrust Global CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
+Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw
+MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo
+BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet
+8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc
+T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU
+vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk
+DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q
+zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4
+d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2
+mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p
+XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm
+Mw==
+-----END CERTIFICATE-----
+
+GeoTrust Global CA 2
+====================
+-----BEGIN CERTIFICATE-----
+MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw
+MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
+LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/
+NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k
+LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA
+Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b
+HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH
+K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7
+srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh
+ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL
+OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC
+x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF
+H4z1Ir+rzoPz4iIprn2DQKi6bA==
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA
+=====================
+-----BEGIN CERTIFICATE-----
+MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1
+MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu
+Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t
+JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e
+RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs
+7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d
+8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V
+qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga
+Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB
+Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu
+KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08
+ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0
+XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB
+hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
+aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2
+qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL
+oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK
+xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF
+KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2
+DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK
+xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU
+p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI
+P/rmMuGNG2+k5o7Y+SlIis5z/iw=
+-----END CERTIFICATE-----
+
+GeoTrust Universal CA 2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
+R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0
+MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg
+SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0
+DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17
+j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q
+JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a
+QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2
+WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP
+20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn
+ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC
+SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG
+8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2
++/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E
+BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
+dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ
+4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+
+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq
+A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg
+Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP
+pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d
+FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp
+gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm
+X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 1
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG
+v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z
+DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh
+sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP
+8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z
+o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf
+GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF
+VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft
+3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g
+Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
+sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
+-----END CERTIFICATE-----
+
+America Online Root Certification Authority 2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
+QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG
+A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
+T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en
+fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8
+f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO
+qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN
+RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0
+gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn
+6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid
+FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6
+Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj
+B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op
+aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY
+T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p
++DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg
+JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy
+zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO
+ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh
+1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf
+GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff
+Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP
+cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk=
+-----END CERTIFICATE-----
+
+Visa eCommerce Root
+===================
+-----BEGIN CERTIFICATE-----
+MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG
+EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug
+QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2
+WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm
+VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
+bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL
+F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b
+RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0
+TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI
+/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs
+GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
+MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc
+CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW
+YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz
+zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu
+YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
+398znM/jra6O1I7mT1GvFpLgXPYHDw==
+-----END CERTIFICATE-----
+
+Certum Root CA
+==============
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK
+ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla
+Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u
+by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x
+wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL
+kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ
+89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K
+Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P
+NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+
+GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg
+GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/
+0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS
+qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==
+-----END CERTIFICATE-----
+
+Comodo AAA Services root
+========================
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
+MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
+c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
+BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
+C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M/vg4aijJRPn2jymJBGhCfHdr/jzDUs
+i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
+Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
+Ypy+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
+Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
+BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
+cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
+LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
+7l3sAg9g1o1QGE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
+8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
+12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+
+Comodo Secure Services root
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw
+MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu
+Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi
+BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP
+9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc
+rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC
+oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V
+p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E
+FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
+gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj
+YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm
+aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm
+4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
+Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL
+DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw
+pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H
+RR3B7Hzs/Sk=
+-----END CERTIFICATE-----
+
+Comodo Trusted Services root
+============================
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
+R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
+TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw
+MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h
+bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw
+IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7
+3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y
+/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6
+juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS
+ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud
+DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp
+ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl
+cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw
+uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
+pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA
+BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l
+R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O
+9y5Xt5hwXsjEeLBi
+-----END CERTIFICATE-----
+
+QuoVadis Root CA
+================
+-----BEGIN CERTIFICATE-----
+MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE
+ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz
+MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp
+cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD
+EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk
+J0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL
+F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL
+YzcS19Dsw3sgQUSj7cugF+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen
+AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w
+PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y
+ZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7
+MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj
+YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
+ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
+Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW
+Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy+/fMIGu
+BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw
+FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6
+tlCLMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo
+fFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul
+LsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x
+gI4JVrmcGmD+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi
+5upZIof4l/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi
+5nrQNiOKSnQ2+Q==
+-----END CERTIFICATE-----
+
+QuoVadis Root CA 2
+==================
+-----BEGIN CERTIFICATE-----
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
+ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
+XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
+lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
+lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
+lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
+66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
+wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
+D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
+BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
+J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
+DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
+a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
+Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
+UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
+VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
++JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
+IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
+WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
+f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
+4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
+VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
+-----END CERTIFICATE-----
+
+QuoVadis Root CA 3
+==================
+-----BEGIN CERTIFICATE-----
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
+EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
+OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
+DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
+KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
+DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
+BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
+p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
+nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
+MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
+Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
+uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
+BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
+YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
+BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
+VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
+ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
+AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
+qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
+hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
+POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
+Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
+8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
+bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
+g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
+vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
+qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
+-----END CERTIFICATE-----
+
+Security Communication Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
+U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
+8yl89f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
+DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9Ms+k2Y7CI9eNqPPYJayX
+5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
+DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
+JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYw
+DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
+0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
+mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
+s58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
+6rBK+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
+FL39vmwLAw==
+-----END CERTIFICATE-----
+
+Sonera Class 2 Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG
+U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw
+NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh
+IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3
+/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT
+dXnt5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG
+f+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P
+tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH
+nfO+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT
+XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt
+0jSv9zilzqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI
+cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph
+Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx
+EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLH
+llpwrN9M
+-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA
+=============================
+-----BEGIN CERTIFICATE-----
+MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE
+ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w
+HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh
+bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt
+vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P
+jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca
+C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth
+vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6
+22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV
+HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v
+dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN
+BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR
+EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw
+MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y
+nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
+iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
+-----END CERTIFICATE-----
+
+TDC Internet Root CA
+====================
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE
+ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx
+NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu
+ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j
+xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL
+znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc
+5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6
+otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI
+AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM
+VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM
+MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC
+AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe
+UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G
+CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m
+gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+
+2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb
+O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU
+Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l
+-----END CERTIFICATE-----
+
+UTN DATACorp SGC Root CA
+========================
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ
+BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa
+MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w
+HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy
+dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys
+raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo
+wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA
+9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv
+33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud
+DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9
+BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD
+LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3
+DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
+Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0
+I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx
+EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP
+DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI
+-----END CERTIFICATE-----
+
+UTN USERFirst Hardware Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
+IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd
+BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx
+OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0
+eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz
+ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI
+wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd
+tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8
+i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf
+Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw
+gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF
+lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF
+UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF
+BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW
+XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2
+lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn
+iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67
+nfhmqA==
+-----END CERTIFICATE-----
+
+Camerfirma Chambers of Commerce Root
+====================================
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx
+NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp
+cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn
+MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC
+AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU
+xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH
+NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW
+DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV
+d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud
+EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v
+cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P
+AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh
+bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD
+VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
+aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi
+fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD
+L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN
+UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n
+ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1
+erfutGWaIZDgqtCYvDi1czyL+Nw=
+-----END CERTIFICATE-----
+
+Camerfirma Global Chambersign Root
+==================================
+-----BEGIN CERTIFICATE-----
+MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
+QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
+ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx
+NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt
+YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg
+MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw
+ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J
+1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O
+by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl
+6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c
+8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/
+BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j
+aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B
+Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj
+aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y
+ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
+bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA
+PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y
+gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ
+PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4
+IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes
+t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
+-----END CERTIFICATE-----
+
+NetLock Notary (Class A) Root
+=============================
+-----BEGIN CERTIFICATE-----
+MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI
+EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j
+ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX
+DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH
+EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD
+VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz
+cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM
+D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ
+z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC
+/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7
+tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6
+4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG
+A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC
+Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv
+bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
+IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn
+LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0
+ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz
+IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh
+IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu
+b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh
+bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg
+Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp
+bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5
+ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP
+ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB
+CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr
+KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM
+8CgHrTwXZoi1/baI
+-----END CERTIFICATE-----
+
+NetLock Business (Class B) Root
+===============================
+-----BEGIN CERTIFICATE-----
+MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg
+VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD
+VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv
+bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg
+VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
+iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S
+o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr
+1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV
+HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ
+RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh
+dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0
+ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv
+c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg
+YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
+c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz
+Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA
+bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl
+IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2
+YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj
+cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM
+43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR
+stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI
+-----END CERTIFICATE-----
+
+NetLock Express (Class C) Root
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT
+CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
+BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD
+KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ
+BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
+dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j
+ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB
+jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z
+W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63
+euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw
+DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN
+RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn
+YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB
+IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i
+aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0
+ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
+ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo
+dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y
+emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k
+IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ
+UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg
+YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2
+xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW
+gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A==
+-----END CERTIFICATE-----
+
+XRamp Global CA Root
+====================
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
+BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
+dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
+HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
+U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
+IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
+foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FE
+zG+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
+AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNViPvry
+xS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
+oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
+AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
+/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
+nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
+8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+
+Go Daddy Class 2 CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
+VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
+A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
+RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
+ggENADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
+2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
+qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
+YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
+vLEHZ6IVDd2gWMZEewo+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
+BBYEFNLEsNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
+atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
+MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
+PQoZ+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
+I3lpjbi2Tc7PTMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
+Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw+vHX/b
+vZ8=
+-----END CERTIFICATE-----
+
+Starfield Class 2 CA
+====================
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
+U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
+Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
+MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
+A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
+SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
+bitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
+JRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
+epsZGD3/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
+F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
+MIHCMB0GA1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
+hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
+bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
+QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
+afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLM
+PUxA2IGvd56Deruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
+KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
+QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
+FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0
+Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj
+YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH
+AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw
+Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg
+U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5
+LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh
+cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT
+dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC
+AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh
+3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm
+vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk
+fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3
+fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ
+EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
+yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl
+1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/
+lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro
+g14=
+-----END CERTIFICATE-----
+
+Taiwan GRCA
+===========
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG
+EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X
+DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv
+dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN
+w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5
+BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O
+1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO
+htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov
+J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7
+Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t
+B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB
+O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8
+lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV
+HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2
+09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
+TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj
+Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2
+Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU
+D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz
+DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk
+Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk
+7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ
+CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy
++fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS
+-----END CERTIFICATE-----
+
+Swisscom Root CA 1
+==================
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG
+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy
+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4
+MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln
+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM
+MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF
+NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe
+AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC
+b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn
+7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN
+cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp
+WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5
+haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY
+MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
+BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9
+MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn
+jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ
+MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H
+VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl
+vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl
+OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3
+1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq
+nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy
+x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW
+NY6E0F/6MBr1mmz0DlP5OlvRHA==
+-----END CERTIFICATE-----
+
+DigiCert Assured ID Root CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
+IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
+MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
+ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
+9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
+UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
+/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
+oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
+GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
+66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
+hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
+EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
+SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
+8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+
+DigiCert Global Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
+HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
+MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
+dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
+hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
+TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
+BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
+4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
+7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
+o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
+8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
+BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
+EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
+tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
+UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+
+DigiCert Global Root G2
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
+5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
+1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
+NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
+Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
+8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
+pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
+MrY=
+-----END CERTIFICATE-----
+
+DigiCert High Assurance EV Root CA
+==================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
+KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
+MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
+MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
+Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
+Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
+OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
+MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
+NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
+h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
+Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
+JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
+V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
+myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
+mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
+-----END CERTIFICATE-----
+
+Certplus Class 2 Primary CA
+===========================
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE
+BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN
+OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy
+dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR
+5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ
+Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO
+YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e
+e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME
+CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ
+YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t
+L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD
+P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R
+TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+
+7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW
+//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
+l7+ijrRU
+-----END CERTIFICATE-----
+
+DST Root CA X3
+==============
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK
+ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
+DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1
+cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT
+rE4Orz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9
+UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy
+xXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d
+utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ
+MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69ikug
+dB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE
+GB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw
+RLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS
+fZGL+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----
+
+DST ACES CA X6
+==============
+-----BEGIN CERTIFICATE-----
+MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT
+MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha
+MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE
+CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI
+DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa
+pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow
+GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy
+MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud
+EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu
+Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy
+dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU
+CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2
+5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t
+Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
+nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs
+vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3
+oKfN5XozNmr6mis=
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 1
+==============================================
+-----BEGIN CERTIFICATE-----
+MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP
+MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0
+acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx
+MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg
+U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB
+TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC
+aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX
+yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i
+Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ
+8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4
+W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME
+BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46
+sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE
+q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
+B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY
+nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 2
+==============================================
+-----BEGIN CERTIFICATE-----
+MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
+MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
+QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN
+MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr
+dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G
+A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls
+acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe
+LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI
+x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g
+QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr
+5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB
+AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt
+Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
+Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+
+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P
+9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5
+UrbnBEI=
+-----END CERTIFICATE-----
+
+SwissSign Gold CA - G2
+======================
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
+EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
+MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
+c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
+t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
+jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
+vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
+ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
+AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
+jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
+peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
+7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
+GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
+OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
+5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
+44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
+Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
+Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
+mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
+vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
+KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
+NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
+viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+
+SwissSign Silver CA - G2
+========================
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
+BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
+DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
+aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
+9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644
+N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
++/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
+6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
+MGoM9ZDulebyzYLs2aFK7PayS+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
+qAaEuSh6XzjZG6k4sIN/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
+FZGkECwJMoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
+ROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
+celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i/4z3GcRm25xBWNOHkDRUjvxF3X
+CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
+tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
+4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
+kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L
+3XWgwF15kIwb4FDm3jH+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
+/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
+DGi8aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
+e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq/gsD/KNVV4n+Ssuu
+WxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
+DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
+DgEj8Z+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx
+CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ
+cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN
+b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9
+nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge
+RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt
+tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI
+hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K
+Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN
+NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa
+Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG
+1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
+-----END CERTIFICATE-----
+
+thawte Primary Root CA
+======================
+-----BEGIN CERTIFICATE-----
+MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3
+MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg
+SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv
+KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT
+FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs
+oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ
+1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc
+q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K
+aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p
+afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF
+AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE
+uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
+xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89
+jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH
+z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G5
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB
+yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
+biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh
+dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz
+j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD
+Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
+Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r
+fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/
+BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
+Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
+aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
+SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+
+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE
+KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC
+Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE
+ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
+-----END CERTIFICATE-----
+
+SecureTrust CA
+==============
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
+dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
+BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
+OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
+DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
+GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
+01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
+ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
+aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
+SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
+mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
+nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+
+Secure Global CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
+EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
+bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
+MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
+Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
+YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
+bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
+8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
+HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
+0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
+oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
+MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
+CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
+3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+
+COMODO Certification Authority
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
+BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
+A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
+dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
+MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
+T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
++7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
+xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
+4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
+1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
+rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
+b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
+AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
+OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
+IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
++8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
+-----END CERTIFICATE-----
+
+Network Solutions Certificate Authority
+=======================================
+-----BEGIN CERTIFICATE-----
+MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG
+EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr
+IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx
+MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
+MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwzc7MEL7xx
+jOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT
+aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXT
+crA/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc
+/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB
+AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv
+bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA
+A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q
+4LqILPxFzBiwmZVRDuwduIj/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/
+GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
+wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD
+ydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
+-----END CERTIFICATE-----
+
+WellsSecure Public Root Certificate Authority
+=============================================
+-----BEGIN CERTIFICATE-----
+MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM
+F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw
+NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
+MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl
+bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD
+VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1
+iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13
+i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8
+bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB
+K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB
+AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu
+cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm
+lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB
+i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww
+GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI
+K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0
+bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj
+qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es
+E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ
+tylv2G0xffX8oRAHh84vWdw+WNs=
+-----END CERTIFICATE-----
+
+COMODO ECC Certification Authority
+==================================
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
+R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
+ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
+GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
+b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
+4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
+wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
+FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
+U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+
+IGC/A
+=====
+-----BEGIN CERTIFICATE-----
+MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD
+VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE
+Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy
+MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI
+EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT
+STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2
+TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW
+So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy
+HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd
+frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ
+tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB
+egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC
+iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK
+q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q
+MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
+Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI
+lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF
+0mBWWg==
+-----END CERTIFICATE-----
+
+Security Communication EV RootCA1
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE
+BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl
+Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO
+/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX
+WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z
+ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4
+bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK
+9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm
+iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG
+Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW
+mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW
+T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
+-----END CERTIFICATE-----
+
+OISTE WISeKey Global Root GA CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE
+BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG
+A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH
+bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD
+VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw
+IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5
+IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9
+Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg
+Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD
+d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ
+/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R
+LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
+KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm
+MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4
++vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
+hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY
+okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE
+BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL
+EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0
+MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz
+dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT
+GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG
+d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N
+oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc
+QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ
+PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb
+MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG
+IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD
+VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3
+LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A
+dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
+AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA
+4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg
+AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA
+egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6
+Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO
+PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv
+c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h
+cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw
+IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT
+WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV
+MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER
+MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp
+Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal
+HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT
+nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE
+aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
+86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK
+yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB
+S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
+-----END CERTIFICATE-----
+
+Certigna
+========
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
+EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
+MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
+Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
+XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
+GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
+ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
+DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
+Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
+tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
+BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
+SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
+hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
+PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
+1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+
+AC Ra\xC3\xADz Certic\xC3\xA1mara S.A.
+======================================
+-----BEGIN CERTIFICATE-----
+MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT
+AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg
+LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w
+HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+
+U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh
+IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN
+yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU
+2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3
+4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP
+2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm
+8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf
+HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa
+Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK
+5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b
+czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g
+ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF
+BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug
+cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf
+AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX
+EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v
+/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3
+MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4
+3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk
+eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f
+/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h
+RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU
+Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 2 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw
+MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw
+IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2
+xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ
+Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u
+SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G
+dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ
+KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj
+TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP
+JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk
+vQ==
+-----END CERTIFICATE-----
+
+TC TrustCenter Class 3 CA II
+============================
+-----BEGIN CERTIFICATE-----
+MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
+IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw
+MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
+c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE
+AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W
+yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo
+6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ
+uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk
+2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB
+7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
+Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
+cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
+SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
+TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE
+O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8
+yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9
+IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal
+092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc
+5A==
+-----END CERTIFICATE-----
+
+TC TrustCenter Universal CA I
+=============================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC
+REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
+IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN
+MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg
+VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw
+JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC
+qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv
+xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw
+ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O
+gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j
+BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG
+1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy
+vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3
+ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
+ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a
+7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
+-----END CERTIFICATE-----
+
+Deutsche Telekom Root CA 2
+==========================
+-----BEGIN CERTIFICATE-----
+MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT
+RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG
+A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5
+MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G
+A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS
+b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5
+bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI
+KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY
+AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK
+Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV
+jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV
+HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr
+E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy
+zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8
+rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G
+dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
+Cm26OWMohpLzGITY+9HPBVZkVw==
+-----END CERTIFICATE-----
+
+ComSign Secured CA
+==================
+-----BEGIN CERTIFICATE-----
+MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE
+AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w
+NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD
+QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs
+49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH
+7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB
+kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1
+9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw
+AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t
+U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA
+j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC
+AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a
+BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp
+FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP
+51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
+OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
+-----END CERTIFICATE-----
+
+Cybertrust Global Root
+======================
+-----BEGIN CERTIFICATE-----
+MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
+ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
+MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
+ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
++Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
+0ozSJ8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
+AfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
+89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
+8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
+MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
+A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
+lZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
+5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
+hO0j9n0Hq0V+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
+X3EJIrduPuocA06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
+WL1WMRJOEcgh4LMRkWXbtKaIOM5V
+-----END CERTIFICATE-----
+
+ePKI Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
+EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
+Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
+MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
+MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
+IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
+lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
+qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
+12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
+WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
+lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
+vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
+Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
+MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
+1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
+KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
+xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
+NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
+GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
+xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
+gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
+sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
+BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+
+T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3
+=============================================================================================================================
+-----BEGIN CERTIFICATE-----
+MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH
+DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q
+aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry
+b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV
+BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg
+S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4
+MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl
+IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF
+n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl
+IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft
+dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl
+cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO
+Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1
+xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR
+6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
+hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd
+BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4
+N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT
+y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh
+LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M
+dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI=
+-----END CERTIFICATE-----
+
+Buypass Class 2 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2
+MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M
+cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83
+0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4
+0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R
+uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV
+1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt
+7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2
+fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w
+wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
+-----END CERTIFICATE-----
+
+Buypass Class 3 CA 1
+====================
+-----BEGIN CERTIFICATE-----
+MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1
+MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
+c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx
+ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0
+n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia
+AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c
+1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P
+AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7
+pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA
+EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5
+htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj
+el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
+-----END CERTIFICATE-----
+
+EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1
+==========================================================================
+-----BEGIN CERTIFICATE-----
+MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg
+QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe
+Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p
+ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt
+IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by
+X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b
+gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr
+eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ
+TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy
+Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn
+uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI
+qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm
+ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0
+Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW
+Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t
+FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm
+zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k
+XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT
+bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU
+RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK
+1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt
+2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ
+Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9
+AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
+-----END CERTIFICATE-----
+
+certSIGN ROOT CA
+================
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
+VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
+Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
+CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
+JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
+rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
+ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
+0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
+AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
+Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
+AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
+SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
+x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
+vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
+TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+
+CNNIC ROOT
+==========
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE
+ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw
+OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD
+o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz
+VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT
+VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or
+czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK
+y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC
+wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S
+lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5
+Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM
+O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8
+BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2
+G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m
+mxE=
+-----END CERTIFICATE-----
+
+ApplicationCA - Japanese Government
+===================================
+-----BEGIN CERTIFICATE-----
+MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT
+SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw
+MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl
+cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4
+fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN
+wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE
+jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu
+nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU
+WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV
+BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD
+vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs
+o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g
+/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD
+io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW
+dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
+rosot4LKGAfmt1t06SAZf7IbiVQ=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G3
+=============================================
+-----BEGIN CERTIFICATE-----
+MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE
+BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0
+IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy
+eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz
+NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo
+YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT
+LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j
+K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE
+c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C
+IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu
+dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr
+2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9
+cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE
+Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
+AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s
+t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G2
+===========================
+-----BEGIN CERTIFICATE-----
+MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC
+VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu
+IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg
+Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV
+MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG
+b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt
+IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS
+LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5
+8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
+mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN
+G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K
+rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
+-----END CERTIFICATE-----
+
+thawte Primary Root CA - G3
+===========================
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE
+BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
+aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
+cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w
+ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
+d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD
+VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG
+A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At
+P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC
++BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY
+7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW
+vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ
+KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK
+A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC
+8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm
+er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
+
+GeoTrust Primary Certification Authority - G2
+=============================================
+-----BEGIN CERTIFICATE-----
+MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu
+Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1
+OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
+MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl
+b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG
+BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc
+KiEIfJo22Av+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+
+EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m
+ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2
+npaqBA+K
+-----END CERTIFICATE-----
+
+VeriSign Universal Root Certification Authority
+===============================================
+-----BEGIN CERTIFICATE-----
+MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE
+BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
+ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
+IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV
+UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
+cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
+IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj
+1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGP
+MiJhgsWHH26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72
+9fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I
+AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV/c4DPxhGD5WycR
+tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G
+CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2O
+a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
+DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3
+Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx
+Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx
+P/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P
+wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4
+mJO37M2CYfE45k+XmCpajQ==
+-----END CERTIFICATE-----
+
+VeriSign Class 3 Public Primary Certification Authority - G4
+============================================================
+-----BEGIN CERTIFICATE-----
+MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC
+VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3
+b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz
+ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL
+MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU
+cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo
+b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8
+Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz
+rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw
+HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u
+Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD
+A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx
+AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
+-----END CERTIFICATE-----
+
+NetLock Arany (Class Gold) Főtanúsítvány
+============================================
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
+A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
+dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
+cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
+MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
+ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
+biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
+c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
+0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
+/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
+H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
+fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
+neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
+qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
+YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
+NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
+dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+
+Staat der Nederlanden Root CA - G2
+==================================
+-----BEGIN CERTIFICATE-----
+MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
+CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
+Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC
+TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
+ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ
+5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn
+vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj
+CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil
+e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR
+OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI
+CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65
+48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi
+trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737
+qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB
+AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC
+ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA
+A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz
++51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj
+f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN
+kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk
+CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF
+URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb
+CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h
+oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV
+IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm
+66+KAQ==
+-----END CERTIFICATE-----
+
+CA Disig
+========
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK
+QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw
+MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz
+bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm
+GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD
+Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo
+hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt
+ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w
+gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P
+AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz
+aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff
+ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa
+BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t
+WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3
+mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
+CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K
+ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA
+4Z7CRneC9VkGjCFMhwnN5ag=
+-----END CERTIFICATE-----
+
+Juur-SK
+=======
+-----BEGIN CERTIFICATE-----
+MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA
+c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw
+DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG
+SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy
+aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf
+TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC
++Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw
+UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa
+Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF
+MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD
+HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh
+AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA
+cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr
+AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw
+cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
+FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G
+A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo
+ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL
+abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678
+IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh
+Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2
+yyqcjg==
+-----END CERTIFICATE-----
+
+Hongkong Post Root CA 1
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT
+DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx
+NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n
+IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1
+ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr
+auh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh
+qZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY
+V18meMjWCnl3v/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV
+HRMBAf8ECDAGAQH/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i
+h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio
+l7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei
+IAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps
+T/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT
+c4afU9hDDl3WY4JxHYB0yvbiAmvZWg==
+-----END CERTIFICATE-----
+
+SecureSign RootCA11
+===================
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
+SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
+b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
+KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
+cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvL
+TJszi1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
+wvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
+g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rP
+O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
+bpSACW22s293bzUIUPsCh8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
+t94wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
+OBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
+bnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
+Oh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
+y8hSyn+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
+lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+
+ACEDICOM Root
+=============
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD
+T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4
+MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG
+A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk
+WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD
+YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew
+MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb
+m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk
+HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT
+xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2
+3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9
+2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq
+TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz
+4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU
+9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
+bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg
+aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP
+eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk
+zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1
+ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI
+KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq
+nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE
+I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp
+MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o
+tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA==
+-----END CERTIFICATE-----
+
+Verisign Class 3 Public Primary Certification Authority
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx
+FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
+IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow
+XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
+IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
+f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
+hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky
+CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX
+bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/
+D/xwzoiQ
+-----END CERTIFICATE-----
+
+Microsec e-Szigno Root CA 2009
+==============================
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
+MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
+c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
+BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
+U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
+fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
+0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
+pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
+1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
+AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
+QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
+FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
+lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
+I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
+yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
+LXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+
+E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi
+===================================================
+-----BEGIN CERTIFICATE-----
+MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz
+ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3
+MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0
+cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u
+aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY
+8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y
+jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI
+JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk
+9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG
+SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d
+F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq
+D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4
+Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
+fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
+-----END CERTIFICATE-----
+
+GlobalSign Root CA - R3
+=======================
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
+YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
+bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
+aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
+bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
+iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
+0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
+rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
+OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
+xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
+FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
+lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
+EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
+bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
+YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
+kpeDMdmztcpHWD9f
+-----END CERTIFICATE-----
+
+Autoridad de Certificacion Firmaprofesional CIF A62634068
+=========================================================
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
+BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
+MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
+QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
+NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
+Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
+B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
+7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
+ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
+plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
+MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
+LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
+bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
+vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1Ud
+EwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
+DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
+bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
+ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx
+51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
+R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
+T481PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
+Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
+osS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
+crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
+saS8I8nkvof/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
+KCOM/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
+6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+
+Izenpe.com
+==========
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
+EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
+MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
+QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
+03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
+ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
++zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
+PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
+OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
+F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
+0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
+leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
+AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
+SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
+NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
+BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
+Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
+kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
+hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
+g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
+aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
+nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
+ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
+Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
+WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+
+Chambers of Commerce Root - 2008
+================================
+-----BEGIN CERTIFICATE-----
+MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy
+Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl
+ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF
+EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl
+cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA
+XuFixrYp4YFs8r/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj
+h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/
+ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk
+NNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g
+D2vlGts/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331
+lubKgdaX8ZSD6e2wsWsSaR6s+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ
+0wlf2eOKNcx5Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
+ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2
+EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI
+G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ
+BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh
+bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh
+bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC
+CQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH
+AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1
+wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH
+3qLPaYRgM+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU
+RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaHFoI6
+M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1
+YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF
+9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK
+zBIKinmwPQN/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG
+nrDQWzilm1DefhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
+OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ
+-----END CERTIFICATE-----
+
+Global Chambersign Root - 2008
+==============================
+-----BEGIN CERTIFICATE-----
+MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD
+MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
+bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
+QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx
+NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg
+Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ
+QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
+aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf
+VtPkOpt2RbQT2//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf
+XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0
+ZJJ0YPP2zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB
+/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA
+TH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M
+H/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe
+Ox2YItaswTXbo6Al/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF
+HTynyQbehP9r6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
+wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB
+AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT
+BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE
+BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm
+aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm
+aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp
+1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0
+dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG
+/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6
+ReAJ3spED8IXDneRRXozX1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s
+dZ7LoR/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg
+9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH
+foUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du
+qqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr
+P3iZ8ntxPjzxmKfFGBI/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq
+c5iJWzouE4gev8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
+09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
+-----END CERTIFICATE-----
+
+Go Daddy Root Certificate Authority - G2
+========================================
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
+MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
+A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
+9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
++qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
+fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
+NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
+BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
+vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
+5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
+N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
+-----END CERTIFICATE-----
+
+Starfield Root Certificate Authority - G2
+=========================================
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
+eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
+DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
+VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
+dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
+W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
+bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
+N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
+ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
+JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
+TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
+4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
+F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
+c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+
+Starfield Services Root Certificate Authority - G2
+==================================================
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
+B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
+b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
+IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
+BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
+dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
+Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
+h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
+hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
+LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
+rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
+SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
+E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
+xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
+YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
+-----END CERTIFICATE-----
+
+AffirmTrust Commercial
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
+MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
+DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
+C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
+BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
+MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
+HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
+hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
+qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
+0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
+sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+
+AffirmTrust Networking
+======================
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
+MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
+bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
+Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
+dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
+/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
+h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
+HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
+UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
+12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
+WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
+/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+
+AffirmTrust Premium
+===================
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
+BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
+OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
+dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
+BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
+5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
++7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
+GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
+p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
+S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
+6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
+/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
++Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
+MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
+6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
+L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
++4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
+BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
+IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
+g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
+zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+
+AffirmTrust Premium ECC
+=======================
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
+BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
+MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
+cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
+N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
+BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
+BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
+57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
+eQ==
+-----END CERTIFICATE-----
+
+Certum Trusted Network CA
+=========================
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
+ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
+MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
+ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
+l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
+J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
+fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
+cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
+Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
+DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
+jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
+mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
+Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+
+Certinomis - Autorité Racine
+=============================
+-----BEGIN CERTIFICATE-----
+MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK
+Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg
+LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG
+A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw
+JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa
+wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly
+Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw
+2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N
+jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q
+c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC
+lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb
+xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g
+530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna
+4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
+KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x
+WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva
+R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40
+nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B
+CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv
+JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE
+qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b
+WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE
+wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/
+vgt2Fl43N+bYdJeimUV5
+-----END CERTIFICATE-----
+
+Root CA Generalitat Valenciana
+==============================
+-----BEGIN CERTIFICATE-----
+MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE
+ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290
+IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3
+WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE
+CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2
+F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B
+ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ
+D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte
+JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB
+AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n
+dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB
+ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl
+AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA
+YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy
+AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
+aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt
+AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA
+YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu
+AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA
+OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0
+dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV
+BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G
+A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S
+b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh
+TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz
+Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63
+NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH
+iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt
++GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
+-----END CERTIFICATE-----
+
+A-Trust-nQual-03
+================
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE
+Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy
+a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R
+dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw
+RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0
+ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1
+c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA
+zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n
+yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE
+SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4
+iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V
+cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV
+eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40
+ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr
+sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd
+JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
+mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6
+ahq97BvIxYSazQ==
+-----END CERTIFICATE-----
+
+TWCA Root Certification Authority
+=================================
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
+VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
+EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
+IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
+QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
+oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
+4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
+y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
+9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
+mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
+QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
+T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
+Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+
+Security Communication RootCA2
+==============================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
+dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
+SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
+aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
+3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
+spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
+EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
+QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
+CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
+u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
+3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
+tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
+mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+
+EC-ACC
+======
+-----BEGIN CERTIFICATE-----
+MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
+BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
+ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
+VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
+CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
+BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
+MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
+SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
+Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
+cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R85iK
+w5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
+ae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
+HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
+E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
+0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
+VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
+Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
+dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJ
+lF7W2u++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
+Al6kSBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
+l+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
+E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
+5EI=
+-----END CERTIFICATE-----
+
+Hellenic Academic and Research Institutions RootCA 2011
+=======================================================
+-----BEGIN CERTIFICATE-----
+MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
+O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
+aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
+AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
+IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
+IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
+1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
+71HFK9+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
+8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
+3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
+MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
+MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
+b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
+XdMiKahsog2p6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
+TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
+/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
+7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
+-----END CERTIFICATE-----
+
+Actalis Authentication Root CA
+==============================
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
+BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
+AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
+MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
+IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
+wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
+by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
+zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
+YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
+oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
+EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
+hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
+EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
+jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
+iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
+WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
+JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
+K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
+Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
+4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
+2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
+lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
+OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
+vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+
+Trustis FPS Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG
+EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290
+IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV
+BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQ
+RUN+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk
+H5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa
+cY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt
+o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA
+AaNTMFEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd
+BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01GX2c
+GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC
+yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy+Q7DsdwyhEQsb8tGD+pmQQ9P
+8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV
+l/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl
+iB6XzCGcKQENZetX2fNXlrtIzYE=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority
+================================
+-----BEGIN CERTIFICATE-----
+MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
+ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
+NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
+LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
+U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
+o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
+Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
+eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
+2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
+6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
+osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
+untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
+UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
+37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ
+Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0
+dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu
+c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv
+bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0
+aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t
+L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG
+cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5
+fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm
+N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN
+Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T
+tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX
+e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA
+2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs
+HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
+JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib
+D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8=
+-----END CERTIFICATE-----
+
+StartCom Certification Authority G2
+===================================
+-----BEGIN CERTIFICATE-----
+MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
+U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
+RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE
+ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp
+dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O
+o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG
+4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi
+Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul
+Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs
+O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H
+vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L
+nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS
+FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa
+z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ
+KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
+2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk
+J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+
+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG
+/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc
+nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld
+blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc
+l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm
+7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm
+obp573PYtlNXLfbQ4ddI
+-----END CERTIFICATE-----
+
+Buypass Class 2 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
+DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
+g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
+9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
+/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
+CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
+awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
+zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
+Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
+Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
+M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
+osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
+aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
+DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
+LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
+oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
+wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
+CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
+rJgWVqA=
+-----END CERTIFICATE-----
+
+Buypass Class 3 Root CA
+=======================
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
+QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
+DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
+eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
+sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
+5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
+7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
+ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
+2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
+/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
+RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
+Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
+j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
+AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
+uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
+Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
+ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
+KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
+6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
+UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
+eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
+Cp/HuZc=
+-----END CERTIFICATE-----
+
+T-TeleSec GlobalRoot Class 3
+============================
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
+MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
+9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
+NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
+iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
+0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
+AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
+fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
+ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
+P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
+-----END CERTIFICATE-----
+
+EE Certification Centre Root CA
+===============================
+-----BEGIN CERTIFICATE-----
+MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
+EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy
+dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw
+MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB
+UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy
+ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM
+TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2
+rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw
+93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN
+P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ
+MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF
+BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj
+xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM
+lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
+uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU
+3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM
+dcGWxZ0=
+-----END CERTIFICATE-----
+
+TURKTRUST Certificate Services Provider Root 2007
+=================================================
+-----BEGIN CERTIFICATE-----
+MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
+bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
+MA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
+QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4X
+DTA3MTIyNTE4MzcxOVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxl
+a3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMCVFIxDzAN
+BgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDEsGxldGnFn2ltIHZlIEJp
+bGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7Fni4gKGMpIEFyYWzEsWsgMjAwNzCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9N
+YvDdE3ePYakqtdTyuTFYKTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQv
+KUmi8wUG+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveGHtya
+KhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6PIzdezKKqdfcYbwnT
+rqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M733WB2+Y8a+xwXrXgTW4qhe04MsC
+AwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHkYb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/s
+Px+EnWVUXKgWAkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
+aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5mxRZNTZPz/OO
+Xl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsaXRik7r4EW5nVcV9VZWRi1aKb
+BFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZqxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAK
+poRq0Tl9
+-----END CERTIFICATE-----
+
+D-TRUST Root Class 3 CA 2 2009
+==============================
+-----BEGIN CERTIFICATE-----
+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK
+DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe
+Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE
+LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD
+ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA
+BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv
+KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z
+p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC
+AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ
+4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y
+eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw
+MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G
+PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw
+OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm
+2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV
+dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph
+X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=
+-----END CERTIFICATE-----
+
+D-TRUST Root Class 3 CA 2 EV 2009
+=================================
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
+DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
+OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
+DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
+OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS
+egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh
+zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T
+7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60
+sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35
+11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv
+cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v
+ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El
+MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp
+b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh
+c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+
+PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX
+ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA
+NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv
+w9y4AyHqnxbxLFS1
+-----END CERTIFICATE-----
+
+PSCProcert
+==========
+-----BEGIN CERTIFICATE-----
+MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1dG9yaWRhZCBk
+ZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9sYW5vMQswCQYDVQQGEwJWRTEQ
+MA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlzdHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lz
+dGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBl
+cmludGVuZGVuY2lhIGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUw
+IwYJKoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEwMFoXDTIw
+MTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHByb2NlcnQubmV0LnZlMQ8w
+DQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGExKjAoBgNVBAsTIVByb3ZlZWRvciBkZSBD
+ZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZp
+Y2FjaW9uIEVsZWN0cm9uaWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIw
+DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo97BVC
+wfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74BCXfgI8Qhd19L3uA
+3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38GieU89RLAu9MLmV+QfI4tL3czkkoh
+RqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmO
+EO8GqQKJ/+MMbpfg353bIdD0PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG2
+0qCZyFSTXai20b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
+0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/6mnbVSKVUyqU
+td+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1mv6JpIzi4mWCZDlZTOpx+FIyw
+Bm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvp
+r2uKGcfLFFb14dq12fy/czja+eevbqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/
+AgEBMDcGA1UdEgQwMC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAz
+Ni0wMB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFDgBStuyId
+xuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0b3JpZGFkIGRlIENlcnRp
+ZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xhbm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQH
+EwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5h
+Y2lvbmFsIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5k
+ZW5jaWEgZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkqhkiG
+9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQDAgEGME0GA1UdEQRG
+MESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0wMDAwMDKgGwYFYIZeAgKgEgwQUklG
+LUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEagRKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52
+ZS9sY3IvQ0VSVElGSUNBRE8tUkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNy
+YWl6LnN1c2NlcnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
+Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsGAQUFBwIBFh5o
+dHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcNAQELBQADggIBACtZ6yKZu4Sq
+T96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmN
+g7+mvTV+LFwxNG9s2/NkAZiqlCxB3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4q
+uxtxj7mkoP3YldmvWb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1
+n8GhHVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHmpHmJWhSn
+FFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXzsOfIt+FTvZLm8wyWuevo
+5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bEqCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq
+3TNWOByyrYDT13K9mmyZY+gAu0F2BbdbmRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5
+poLWccret9W6aAjtmcz9opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3Y
+eMLEYC/HYvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
+-----END CERTIFICATE-----
+
+China Internet Network Information Center EV Certificates Root
+==============================================================
+-----BEGIN CERTIFICATE-----
+MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMCQ04xMjAwBgNV
+BAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyMUcwRQYDVQQDDD5D
+aGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMg
+Um9vdDAeFw0xMDA4MzEwNzExMjVaFw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAG
+A1UECgwpQ2hpbmEgSW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMM
+PkNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRpZmljYXRl
+cyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z7r07eKpkQ0H1UN+U8i6y
+jUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV
+98YPjUesWgbdYavi7NifFy2cyjw1l1VxzUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2H
+klY0bBoQCxfVWhyXWIQ8hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23
+KzhmBsUs4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54ugQEC
+7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oYNJKiyoOCWTAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUfHJLOcfA22KlT5uqGDSSosqD
+glkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd5
+0XPFtQO3WKwMVC/GVhMPMdoG52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM
+7+czV0I664zBechNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
+ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrIzo9uoV1/A3U0
+5K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATywy39FCqQmbkHzJ8=
+-----END CERTIFICATE-----
+
+Swisscom Root CA 2
+==================
+-----BEGIN CERTIFICATE-----
+MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQG
+EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy
+dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2
+MjUwNzM4MTRaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln
+aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIIC
+IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvErjw0DzpPM
+LgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r0rk0X2s682Q2zsKwzxNo
+ysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJ
+wDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVPACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpH
+Wrumnf2U5NGKpV+GY3aFy6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1a
+SgJA/MTAtukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL6yxS
+NLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0uPoTXGiTOmekl9Ab
+mbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrALacywlKinh/LTSlDcX3KwFnUey7QY
+Ypqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velhk6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3
+qPyZ7iVNTA6z00yPhOgpD/0QVAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw
+HQYDVR0hBBYwFDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
+BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqhb97iEoHF8Twu
+MA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4RfbgZPnm3qKhyN2abGu2sEzsO
+v2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ
+82YqZh6NM4OKb3xuqFp1mrjX2lhIREeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLz
+o9v/tdhZsnPdTSpxsrpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcs
+a0vvaGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciATwoCqISxx
+OQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99nBjx8Oto0QuFmtEYE3saW
+mA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5Wt6NlUe07qxS/TFED6F+KBZvuim6c779o
++sjaC+NCydAXFJy3SuCvkychVSa1ZC+N8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TC
+rvJcwhbtkj6EPnNgiLx29CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX
+5OfNeOI5wSsSnqaeG8XmDtkx2Q==
+-----END CERTIFICATE-----
+
+Swisscom Root EV CA 2
+=====================
+-----BEGIN CERTIFICATE-----
+MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAwZzELMAkGA1UE
+BhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdpdGFsIENlcnRpZmljYXRlIFNl
+cnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcN
+MzEwNjI1MDg0NTA4WjBnMQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsT
+HERpZ2l0YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYg
+Q0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7BxUglgRCgz
+o3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD1ycfMQ4jFrclyxy0uYAy
+Xhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPHoCE2G3pXKSinLr9xJZDzRINpUKTk4Rti
+GZQJo/PDvO/0vezbE53PnUgJUmfANykRHvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8Li
+qG12W0OfvrSdsyaGOx9/5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaH
+Za0zKcQvidm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHLOdAG
+alNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaCNYGu+HuB5ur+rPQa
+m3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f46Fq9mDU5zXNysRojddxyNMkM3Ox
+bPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCBUWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDi
+xzgHcgplwLa7JSnaFp6LNYth7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
+MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWBbj2ITY1x0kbB
+bkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6xXCX5145v9Ydkn+0UjrgEjihL
+j6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98TPLr+flaYC/NUn81ETm484T4VvwYmneTwkLbU
+wp4wLh/vx3rEUMfqe9pQy3omywC0Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7
+XwgiG/W9mR4U9s70WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH
+59yLGn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm7JFe3VE/
+23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4Snr8PyQUQ3nqjsTzyP6Wq
+J3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VNvBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyA
+HmBR3NdUIR7KYndP+tiPsys6DXhyyWhBWkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/gi
+uMod89a2GQ+fYWVq6nTIfI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuW
+l8PVP3wbI+2ksx0WckNLIOFZfsLorSa/ovc=
+-----END CERTIFICATE-----
+
+CA Disig Root R1
+================
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNVBAYTAlNLMRMw
+EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
+ZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQyMDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sx
+EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
+c2lnIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy
+3QRkD2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/oOI7bm+V8
+u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3AfQ+lekLZWnDZv6fXARz2
+m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJeIgpFy4QxTaz+29FHuvlglzmxZcfe+5nk
+CiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8noc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTa
+YVKvJrT1cU/J19IG32PK/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6
+vpmumwKjrckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD3AjL
+LhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE7cderVC6xkGbrPAX
+ZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkCyC2fg69naQanMVXVz0tv/wQFx1is
+XxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLdqvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ
+04IwDQYJKoZIhvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
+xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaASfX8MPWbTx9B
+LxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXoHqJPYNcHKfyyo6SdbhWSVhlM
+CrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpBemOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5Gfb
+VSUZP/3oNn6z4eGBrxEWi1CXYBmCAMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85
+YmLLW1AL14FABZyb7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKS
+ds+xDzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvkF7mGnjix
+lAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqFa3qdnom2piiZk4hA9z7N
+UaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsTQ6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJ
+a7+h89n07eLw4+1knj0vllJPgFOL
+-----END CERTIFICATE-----
+
+CA Disig Root R2
+================
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw
+EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
+ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx
+EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
+c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC
+w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia
+xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7
+A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S
+GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV
+g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa
+5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE
+koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A
+Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i
+Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u
+Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV
+sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je
+dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8
+1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx
+mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01
+utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0
+sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg
+UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV
+7+ZtsH8tZ/3zbBt1RqPlShfppNcL
+-----END CERTIFICATE-----
+
+ACCVRAIZ1
+=========
+-----BEGIN CERTIFICATE-----
+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB
+SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1
+MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH
+UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM
+jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0
+RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD
+aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ
+0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG
+WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7
+8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR
+5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J
+9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK
+Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw
+Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu
+Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM
+Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA
+QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh
+AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA
+YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj
+AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA
+IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk
+aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0
+dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2
+MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI
+hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E
+R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN
+YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49
+nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ
+TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3
+sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg
+Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd
+3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p
+EfbRD0tVNEYqi4Y7
+-----END CERTIFICATE-----
+
+TWCA Global Root CA
+===================
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT
+CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD
+QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK
+EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg
+Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C
+nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV
+r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR
+Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV
+tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W
+KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99
+sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p
+yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn
+kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI
+zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC
+AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g
+cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M
+8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg
+/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg
+lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP
+A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m
+i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8
+EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3
+zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=
+-----END CERTIFICATE-----
+
+TeliaSonera Root CA v1
+======================
+-----BEGIN CERTIFICATE-----
+MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE
+CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4
+MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW
+VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+
+6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA
+3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k
+B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn
+Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH
+oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3
+F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ
+oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7
+gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc
+TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB
+AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW
+DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm
+zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
+0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW
+pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV
+G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc
+c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT
+JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2
+qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6
+Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems
+WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
+-----END CERTIFICATE-----
+
+E-Tugra Certification Authority
+===============================
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w
+DQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls
+ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
+ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw
+NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx
+QDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl
+cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD
+DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEA4vU/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd
+hQd2h8y/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K
+CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1+g
+ElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ
+BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW/hkd5s3kc91r0
+E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz
+rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAq
+jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
+rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5
+dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr+zE7S6E5UMA8GA1UdEwEB
+/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG
+MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK
+kEh47U6YA5n+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO
+XKqYGwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807
+VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo
+a2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc
+dlqMQ1DujjByTd//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV
+KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT
+Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0
+8ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G
+C7TbO6Orb1wdtn7os4I07QZcJA==
+-----END CERTIFICATE-----
+
+T-TeleSec GlobalRoot Class 2
+============================
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
+IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
+cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx
+MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
+dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
+ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ
+SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F
+vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970
+2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV
+WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy
+YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4
+r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf
+vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR
+3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==
+-----END CERTIFICATE-----
+
+Atos TrustedRoot 2011
+=====================
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU
+cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4
+MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG
+A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV
+hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr
+54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+
+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320
+HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR
+z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R
+l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ
+bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h
+k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh
+TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
+61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
+3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
+-----END CERTIFICATE-----
+
+COMODO RSA Certification Authority
+==================================
+-----BEGIN CERTIFICATE-----
+MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
+6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
+pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
+9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
+/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
+Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
+qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
+SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
+u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
+Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
+crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
+FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
+/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
+wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
+4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
+2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
+FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
+CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
+boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
+jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
+S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
+QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
+0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
+NVOFBkpdn627G190
+-----END CERTIFICATE-----
diff --git a/lib/support/gateway_support.rb b/lib/support/gateway_support.rb
index ef649e59adf..b3bb28e54cf 100644
--- a/lib/support/gateway_support.rb
+++ b/lib/support/gateway_support.rb
@@ -2,7 +2,6 @@
require 'active_support'
require 'active_merchant'
-
class GatewaySupport #:nodoc:
ACTIONS = [:purchase, :authorize, :capture, :void, :credit, :recurring]
@@ -15,9 +14,9 @@ def initialize
filename = File.basename(f, '.rb')
gateway_name = filename + '_gateway'
begin
- gateway_class = ('ActiveMerchant::Billing::' + gateway_name.camelize).constantize
+ ('ActiveMerchant::Billing::' + gateway_name.camelize).constantize
rescue NameError
- puts "Could not load gateway " + gateway_name.camelize + " from " + f + "."
+ puts 'Could not load gateway ' + gateway_name.camelize + ' from ' + f + '.'
end
end
@gateways = Gateway.implementations.sort_by(&:name)
@@ -25,20 +24,20 @@ def initialize
end
def each_gateway
- @gateways.each{|g| yield g }
+ @gateways.each { |g| yield g }
end
def features
width = 15
- print "Name".center(width + 20)
- ACTIONS.each{|f| print "#{f.to_s.capitalize.center(width)}" }
+ print 'Name'.center(width + 20)
+ ACTIONS.each { |f| print f.to_s.capitalize.center(width) }
puts
each_gateway do |g|
- print "#{g.display_name.ljust(width + 20)}"
+ print g.display_name.ljust(width + 20)
ACTIONS.each do |f|
- print "#{(g.instance_methods.include?(f.to_s) ? "Y" : "N").center(width)}"
+ print((g.instance_methods.include?(f.to_s) ? 'Y' : 'N').center(width))
end
puts
end
@@ -68,4 +67,3 @@ def to_s
end
end
end
-
diff --git a/lib/support/outbound_hosts.rb b/lib/support/outbound_hosts.rb
index bac790662b6..844df856bb3 100644
--- a/lib/support/outbound_hosts.rb
+++ b/lib/support/outbound_hosts.rb
@@ -2,24 +2,27 @@
require 'set'
class OutboundHosts
- def self.list
- uris = Set.new
+ def self.list
+ hosts = Set.new
+ invalid_lines = Set.new
Dir['lib/**/*.rb'].each do |file|
content = File.read(file)
content.each_line do |line|
next if line =~ /homepage_url/
-
- if line =~ /("|')(https:\/\/.*)("|')/
- uri = URI.parse($2)
- uris << [uri.host, uri.port]
+
+ if line =~ /("|')(https:\/\/[^'"]*)("|')/
+ begin
+ uri = URI.parse($2)
+ hosts << "#{uri.host}:#{uri.port}"
+ rescue URI::InvalidURIError
+ invalid_lines << line
+ end
end
end
end
- uris.each do |uri|
- puts "#{uri.first} #{uri.last}"
- end
+ [hosts, invalid_lines]
end
-end
\ No newline at end of file
+end
diff --git a/lib/support/ssl_verify.rb b/lib/support/ssl_verify.rb
index 1ba28878a72..5570e7fde47 100644
--- a/lib/support/ssl_verify.rb
+++ b/lib/support/ssl_verify.rb
@@ -23,16 +23,16 @@ def test_gateways
end
uri = URI.parse(g.live_url)
- result,message = ssl_verify_peer?(uri)
+ result, message = ssl_verify_peer?(uri)
case result
when :success
- print "."
+ print '.'
success << g
when :fail
- print "F"
+ print 'F'
failed << {:gateway => g, :message => message}
when :error
- print "E"
+ print 'E'
errored << {:gateway => g, :message => message}
end
end
@@ -60,13 +60,12 @@ def test_gateways
puts d.name
end
end
-
end
def try_host(http, path)
http.get(path)
rescue Net::HTTPBadResponse, EOFError, SocketError
- http.post(path, "")
+ http.post(path, '')
end
def ssl_verify_peer?(uri)
@@ -78,7 +77,7 @@ def ssl_verify_peer?(uri)
http.read_timeout = 60
if uri.path.blank?
- try_host(http, "/")
+ try_host(http, '/')
else
try_host(http, uri.path)
end
diff --git a/lib/support/ssl_version.rb b/lib/support/ssl_version.rb
new file mode 100644
index 00000000000..ed7c716c9c0
--- /dev/null
+++ b/lib/support/ssl_version.rb
@@ -0,0 +1,87 @@
+require 'active_merchant'
+require 'support/gateway_support'
+
+class SSLVersion
+ attr_accessor :success, :failed, :missing, :errored
+
+ def initialize
+ @gateways = GatewaySupport.new.gateways
+ @success, @failed, @missing, @errored = [], [], [], []
+ end
+
+ def test_gateways(min_version = :TLS1_1)
+ raise 'Requires Ruby 2.5 or better' unless Net::HTTP.instance_methods.include?(:min_version=)
+
+ puts "Verifying #{@gateways.count} gateways for SSL min_version=#{min_version}\n\n"
+
+ @gateways.each do |g|
+ unless g.live_url
+ missing << g unless g.abstract_class
+ next
+ end
+
+ uri = URI.parse(g.live_url)
+ result, message = test_min_version(uri, min_version)
+
+ case result
+ when :success
+ print '.'
+ success << g
+ when :fail
+ print 'F'
+ failed << {:gateway => g, :message => message}
+ when :error
+ print 'E'
+ errored << {:gateway => g, :message => message}
+ end
+ end
+
+ print_summary
+ end
+
+ def print_summary
+ puts "\n\nSucceeded gateways (#{success.length})"
+
+ puts "\n\nFailed Gateways (#{failed.length}):"
+ failed.each do |f|
+ puts "#{f[:gateway].name} - #{f[:message]}"
+ end
+
+ puts "\n\nError Gateways (#{errored.length}):"
+ errored.each do |e|
+ puts "#{e[:gateway].name} - #{e[:message]}"
+ end
+
+ if missing.length > 0
+ puts "\n\nGateways missing live_url (#{missing.length}):"
+ missing.each do |m|
+ puts m.name
+ end
+ end
+ end
+
+ private
+
+ def test_min_version(uri, min_version)
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # don't care about certificate validity, just protocol version
+ http.min_version = min_version
+ http.open_timeout = 10
+ http.read_timeout = 10
+
+ http.post('/', '')
+
+ return :success
+ rescue Net::HTTPBadResponse
+ return :success # version negotiation succeeded
+ rescue OpenSSL::SSL::SSLError => ex
+ return :fail, ex.inspect
+ rescue Interrupt => ex
+ print_summary
+ raise ex
+ rescue StandardError => ex
+ return :error, ex.inspect
+ end
+
+end
diff --git a/rails/init.rb b/rails/init.rb
deleted file mode 100644
index f03a3eb9431..00000000000
--- a/rails/init.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# If Active Merchant is included into a Rails project as a gem, then
-# this file gets loaded instead of the top-level init.rb
-require File.dirname(__FILE__) + '/../init'
diff --git a/script/generate b/script/generate
index e5cfc875a99..6312f82aa0c 100755
--- a/script/generate
+++ b/script/generate
@@ -1,16 +1,15 @@
#!/usr/bin/env ruby
-require "rubygems"
-require "thor"
+require 'rubygems'
+require 'thor'
-require File.expand_path("../../generators/active_merchant_generator", __FILE__)
+require File.expand_path('../../generators/active_merchant_generator', __FILE__)
-Dir[File.expand_path("../..", __FILE__) + "/generators/*/*.rb"].each do |generator|
+Dir[File.expand_path('../..', __FILE__) + '/generators/*/*.rb'].each do |generator|
require generator
end
class Generate < Thor
- register(GatewayGenerator, "gateway", "gateway NAME", "Generates a new gateway.")
- register(IntegrationGenerator, "integration", "integration NAME", "Generates a new integration.")
+ register(GatewayGenerator, 'gateway', 'gateway NAME', 'Generates a new gateway.')
end
Generate.start
diff --git a/shipit.rubygems.yml b/shipit.rubygems.yml
new file mode 100644
index 00000000000..c3f42d3795a
--- /dev/null
+++ b/shipit.rubygems.yml
@@ -0,0 +1 @@
+# using the default shipit config
diff --git a/test/comm_stub.rb b/test/comm_stub.rb
index f96c4c8f951..9293ef77955 100644
--- a/test/comm_stub.rb
+++ b/test/comm_stub.rb
@@ -5,6 +5,7 @@ def initialize(gateway, method_to_stub, action)
@action = action
@complete = false
@method_to_stub = method_to_stub
+ @check = nil
end
def check_request(&block)
@@ -15,8 +16,10 @@ def check_request(&block)
def respond_with(*responses)
@complete = true
check = @check
- (class << @gateway; self; end).send(:define_method, @method_to_stub) do |*args|
- check.call(*args) if check
+ singleton_class = (class << @gateway; self; end)
+ singleton_class.send(:undef_method, @method_to_stub)
+ singleton_class.send(:define_method, @method_to_stub) do |*args|
+ check&.call(*args)
(responses.size == 1 ? responses.last : responses.shift)
end
@action.call
@@ -25,16 +28,24 @@ def respond_with(*responses)
def complete?
@complete
end
- end
- def stub_comms(method_to_stub=:ssl_post, &action)
- if @last_comm_stub
- assert @last_comm_stub.complete?, "Tried to stub communications when there's a stub already in progress."
+ class Complete
+ def complete?
+ true
+ end
end
- @last_comm_stub = Stub.new(@gateway, method_to_stub, action)
+ end
+
+ def last_comm_stub
+ @last_comm_stub ||= Stub::Complete.new
+ end
+
+ def stub_comms(gateway=@gateway, method_to_stub=:ssl_post, &action)
+ assert last_comm_stub.complete?, "Tried to stub communications when there's a stub already in progress."
+ @last_comm_stub = Stub.new(gateway, method_to_stub, action)
end
def teardown
- assert(@last_comm_stub.complete?) if @last_comm_stub
+ assert(last_comm_stub.complete?)
end
-end
\ No newline at end of file
+end
diff --git a/test/fixtures.yml b/test/fixtures.yml
index 814be2c77f9..7f8439a76eb 100644
--- a/test/fixtures.yml
+++ b/test/fixtures.yml
@@ -10,91 +10,301 @@
#
# Paste any required PEM certificates after the pem key.
#
+
+adyen:
+ username: ''
+ password: ''
+ merchant_account: ''
+
+allied_wallet:
+ site_id: site_id
+ merchant_id: merchant_id
+ token: token
+
+# Working credentials, no need to replace as of Oct 28 2014
authorize_net:
- login: X
- password: Y
+ login: 7Tt72zseSzH
+ password: 7gTh55rdy92ZkP4z
+
+axcessms:
+ channel: channel
+ sender: sender
+ login: login
+ password: password
# Working credentials, no need to replace
balanced:
login: 'e1c5ad38d1c711e1b36c026ba7e239a9'
+bambora_apac:
+ username: nmi.api
+ password: qwerty123
+
+# Bank Frick doesn't provide public testing data
+bank_frick:
+ sender: sender-uuid
+ channel: channel-uuid
+ userid: user-uuid
+ userpwd: password
+
banwire:
login: "desarrollo"
-barclays_epdq:
- login: test
- password: test
- client_id: 1234
+# Non-alphanumeric characters may cause an authentication error
+barclaycard_smartpay:
+ company: company
+ merchant: merchant
+ password: password
barclays_epdq_extra_plus:
login: merchant number
user: username
password: password
+be2bill:
+ login: your_test_login
+ password: your_test_password
+
beanstream:
login: merchant id
user: username
password: password
secure_profile_api_key: API Access Passcode
recurring_api_key: API Access Passcode
+ api_key: API KEY
beanstream_interac:
login: merchant id
user: username
password: password
+bit_pay:
+ api_key: 'rKrahSl7WRrYeKRUhGzbvW3nBzo0jG4FPaL8uPYaoPk'
+
blue_pay:
login: '100096218902'
password: 'MBUVE4G1BACMFM4W3XQHUUL2SMQRP.LW'
-braintree_orange:
- login: demo
- password: password
+# Working credentials, no need to replace
+blue_snap:
+ api_username: 'API_146117371665767340423'
+ api_password: 'nfYwTx8fFAvJqBXcxqwC8'
+
+borgun:
+ processor: 118
+ merchant_id: 118
+ username: spreedly
+ password: Qxi.34k
+
+bpoint:
+ username: 'A'
+ password: 'B'
+ merchant_number: 'C'
+ biller_code: ''
braintree_blue:
merchant_id: X
public_key: Y
private_key: Z
+ merchant_account_id: A
braintree_blue_with_processing_rules:
merchant_id: X
public_key: Y
private_key: Z
+braintree_orange:
+ login: demo
+ password: password
+
+# Working credentials, no need to replace
+bridge_pay:
+ user_name: Spre3676
+ password: H3392nc5
+
+# Working credentials, no need to replace
+cams:
+ username: testintegrationc
+ password: password9
+
+# Working credentials, no need to replace
+card_connect:
+ merchant_id: "496160873888"
+ username: testing
+ password: testing123
+
#Username/Password given in sign up email. Not MMS credentials
card_save:
login: merchant_id
password: password
card_stream:
- login: X
- password: Y
+ login: 103191
+ shared_secret: Dear0Coming15Edge
-card_stream_modern:
- login: "0000992"
+# Working credentials, no need to replace
+cardknox:
+ api_key: ActiveMerchant_Test
+
+# Working credentials, no need to replace
+cardprocess:
+ user_id: 8a8294174e735d0c014e78beb6c5154f
+ password: cTZjAm9c87
+ entity_id: 8a8294174e735d0c014e78beb6b9154b
+
+# Cashnet doesn't provide public testing data
+cashnet:
+ merchant: 'X'
+ operator: 'X'
+ password: 'X'
+ merchant_gateway_name: 'X'
+
+cecabank:
+ merchant_id: MERCHANTID
+ acquirer_bin: ACQUIRERBIN
+ terminal_id: TERMINALID
+ key: KEY
+
+cenpos:
+ merchant_id: SOMECREDENTIAL
+ password: ANOTHERCREDENTIAL
+ user_id: ANOTHERCREDENTIAL
certo_direct:
login: 1
password: vP6OwK3
+checkout:
+ merchant_id: SBMTEST
+ password: Password1!
+
+checkout_v2:
+ secret_key: secret_key
+
+citrus_pay:
+ userid: CPF00001
+ password: 7c70414732de7e0ba3a04db5f24fcec8
+
+# Working credentials, no need to replace
+clearhaus: &clearhaus
+ api_key: a2c583e7-23f2-4097-b327-84a87128cfb4
+
+clearhaus_secure:
+ <<: *clearhaus
+ signing_key: 7e51b92e-ca7e-48e3-8a96-7d66cf1f2da2
+ private_key: |
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIBOwIBAAJBALYK0zmwuYkH3YWcFNLLddx5cwDxEY7Gi1xITuQqRrU4yD3uSw+J
+ WYKknb4Tbndb6iEHY+e6gIGD+49TojnNeIUCAwEAAQJARyuYRRe4kcBHdPL+mSL+
+ Y0IAGkAlUyKAXYXPghidKD/v/oLrFaZWALGM2clv6UoYYpPnInSgbcud4sTcfeUm
+ QQIhAN2JZ2qv0WGcbIopBpwpQ5jDxMGVkmkVVUEWWABGF8+pAiEA0lySxTELZm8b
+ Gx9UEDRghN+Qv/OuIKFldu1Ba4f8W30CIQCaQFIBtunTTVdF28r+cLzgYW9eWwbW
+ pEP4TdZ4WlW6AQIhAMDCTUdeUpjxlH/87BXROORozAXocBW8bvJUI486U5ctAiAd
+ InviQqJd1KTGRDmWIGrE5YACVmW2JSszD9t5VKxkAA==
+ -----END RSA PRIVATE KEY-----
+
+
+# Contact Support at it_support@commercegate.com for credentials and offer/site
+commercegate:
+ login: "XXXXXXX"
+ password: "XXXXXXX"
+ site_id: "XXXXXXX"
+ offer_id: "XXXXXXX"
+ card_number: "XXXXXXXXXXXXXXXX"
+
+conekta:
+ key: key_6FTbuwqhYs6zvyyeL3PySg
+
+# Working credentials, no need to replace
+creditcall:
+ terminal_id: '99961426'
+ transaction_key: '9drdRU9wJ65SNRw3'
+
+# NOTE: the IP address you run the remote tests from will need to be
+# whitelisted by Credorax; contact support@credorax.com as necessary to request
+# your IP address be added to the whitelist for your test account.
+credorax:
+ merchant_id: 'merchant_id'
+ cipher_key: 'cipher_key'
+
+ct_payment:
+ api_key: SOMECREDENTIAL
+ company_number: '12345'
+ merchant_number: '12345678'
+
+# Culqi does not provide public testing data
+culqi:
+ merchant_id: MERCHANT
+ terminal_id: TERMINAL
+ partner_id: PARTNER
+ secret_key: SECRET
+
+# To get 100% passing Cybersource remote tests, you must ask
+# Cybersource support to enable the recurring and pinless debit
+# services on your test account.
cyber_source:
login: X
password: Y
+# Working credentials, no need to replace
+d_local:
+ login: aeaf9bbfa1
+ trans_key: 9de3769b7e
+ secret_key: ae132899f56162a669b38dab5927862f3
+
data_cash:
login: X
password: Y
+# Working credentials, no need to replace
+decidir_authorize:
+ api_key: 5a15fbc227224edabdb6f2e8219e8b28
+ preauth_mode: true
+
+decidir_purchase:
+ api_key: 5df6b5764c3f4822aecdc82d56f26b9d
+
+# No working test credentials
+dibs:
+ merchant_id: SOMECREDENTIAL
+ secret_key: NOPUBLICCREDENTIAL
+
+# Working credentials, no need to replace
+digitzs:
+ app_key: tcwtTux8SPZYO44Gf0UHZH74Z1HSutqCxmIV2PFj2jRc9Poroh3Z3R1BBQNRQ98Q
+ api_key: 0HhRdOU2AsWVEu3gRIKi2UpMMmj8Fj48qggBYTo4
+
direc_pay:
login: 200904281000001
+# Working credentials, no need to replace
+ebanx:
+ integration_key: 1231000
+
efsnet:
login: X
password: Y
+# Provided for url update test
+
elavon:
- login: LOGIN
- password: PASSWORD
+ login: "009005"
+ user: "devportal"
+ password: "BDDZY5KOUDCNPV4L3821K7PETO4Z7TPYOJB06TYBI1CW771IDHXBVBP51HZ6ZANJ"
+
+elavon_multi_currency:
+ login: "009006"
+ user: "devportal"
+ password: "XWJS3QTFCH40HW0QGHJKXAYADCTDH0TXXAKXAEZCGCCJ29CFNPCZT4KA9D5KQMDA"
+ multi_currency: true
+
+element:
+ account_id: "1013963"
+ account_token: "683EED8A1A357EB91575A168E74482A74836FD72B1AD11B41B29B473CA9D65B9FE067701"
+ application_id: "5211"
+ acceptor_id: "3928907"
+ application_name: "Spreedly"
+ application_version: "1"
# login: merchant number
# password: referrer url (for authorize authentication)
@@ -115,16 +325,19 @@ eway_managed:
username: 'test@eway.com.au'
password: 'test123'
-# Working credentials, no need to replace
eway_rapid:
- login: "F9802CIVgUgNaD8y/H52MBMnL5OvMoy4cYimpi1L/dCXeNNR6or3vLPoC9GjeLVdA7ymi+"
- password: "sandbox1"
+ login: LOGIN
+ password: PASSWORD
# Working credentials, no need to replace
exact:
login: "A00427-01"
password: testus
+# Working credentials, no need to replace
+ezic:
+ account_id: "120536457270"
+
# Working credentials, no need to replace
fat_zebra:
username: TEST
@@ -135,56 +348,118 @@ federated_canada:
password: password
finansbank:
- login: TEST
- password: TEST
- client_id: TEST
+ login: FINANSAPI
+ password: FINANS06
+ client_id: 600100000
+
+first_giving:
+ application_key: ''
+ security_token: ''
+ charity_id: "1234"
first_pay:
- login:
- password:
+ transaction_center_id: 1264
+ gateway_id: "a91c38c3-7d7f-4d29-acc7-927b4dca0dbe"
firstdata_e4:
- login:
- password:
+ login: SD8821-67
+ password: T6bxSywbcccbJ19eDXNIGaCDOBg1W7T8
+
+firstdata_e4_v27:
+ login: ALOGIN
+ password: APASSWORD
+ key_id: ANINTEGER
+ hmac_key: AMAGICALKEY
+
+flo2cash:
+ username: SOMECREDENTIAL
+ password: ANOTHERCREDENTIAL
+ account_id: ANOTHERCREDENTIAL
+
+flo2cash_simple:
+ username: SOMECREDENTIAL
+ password: ANOTHERCREDENTIAL
+ account_id: ANOTHERCREDENTIAL
+
+forte:
+ location_id: "176008"
+ account_id: "300111"
+ api_key: "f087a90f00f0ae57050c937ed3815c9f"
+ secret: "d793d64064e3113a74fa72035cfc3a1d"
garanti:
login: "PROVAUT"
- terminal_id: 111995
- merchant_id: 600218
+ terminal_id: 30691300
+ merchant_id: 7000679
password: "123qweASD"
-# login: merchant number
-# password: password for the PEM secret key
-# pem: public certificate and PEM secret key
-ideal_rabobank:
+global_collect:
+ merchant_id: 2196
+ api_key_id: c91d6752cbbf9cf1
+ secret_api_key: xHjQr5gL9Wcihkqoj4w/UQugdSCNXM2oUQHG5C82jy4=
+
+global_transport:
+ global_user_name: "USERNAME"
+ global_password: "PASSWORD"
+ term_type: "ABC"
+
+hdfc:
login: LOGIN
password: PASSWORD
- pem: |
- PASTE YOUR PEM FILE HERE
-instapay:
- login: TEST0
- password:
+# Working credentials, no need to replace
+hps:
+ secret_api_key: "skapi_cert_MYl2AQAowiQAbLp5JesGKh7QFkcizOP2jcX9BrEMqQ"
+
+iats_payments:
+ agent_code: TEST88
+ password: TEST88
+ region: na
inspire:
login: demo
password: password
+instapay:
+ login: TEST0
+ password:
+
+# Working credentials, no need to replace
+ipp:
+ username: nmi.api
+ password: qwerty123
+
+iridium:
+ login: LOGIN
+ password: PASSWORD
+
itransact:
login: API_ACCESS_USERNAME
password: API_ACCESS_KEY
gateway_id: GATEWAY_ID
+# Working credentials, no need to replace
+iveri:
+ cert_id: CB69E68D-C7E7-46B9-9B7A-025DCABAD6EF
+ app_id: d10a603d-4ade-405b-93f1-826dfc0181e8
+
jetpay:
login: TESTTERMINAL
-hdfc:
- login: LOGIN
- password: PASSWORD
+jetpay_v2:
+ login: TESTMCC3136X
-iridium:
- login: LOGIN
- password: PASSWORD
+komoju:
+ login: sk_f1dd75ce3d5cad477eac0c827c1cac8eaa51ede3
+
+kushki:
+ public_merchant_id: "Your Public Merchant Id"
+ private_merchant_id: "Your Private Merchant Id"
+
+latitude19:
+ account_number: "03022016"
+ configuration_id: "380835424362"
+ secret: "&2016(march)02"
linkpoint:
login: STOREID
@@ -196,6 +471,15 @@ litle:
password: MERCHANT
merchant_id: 101
+# Working test credentials, no need to replace
+maxipago:
+ login: "100"
+ password: "21g8u6gh6szw1gywfs165vui"
+
+# Working credentials, no need to replace
+mercado_pago:
+ access_token: "TEST-8527269031909288-071213-0fc96cb7cd3633189bfbe29f63722700__LB_LA__-263489584"
+
# Working test credentials, no need to replace
merchant_esolutions:
login: "94100008043900000004"
@@ -205,15 +489,21 @@ merchant_one:
username: 'demo'
password: 'password'
+# Working credentials, no need to replace
+merchant_partners:
+ account_id: TEST0
+ merchant_pin: "1234567890"
+
merchant_ware:
login:
password:
name:
+# Working credentials, no need to replace
merchant_ware_version_four:
- login:
- password:
- name:
+ login: 'BK34Z768'
+ password: 'TCTTS-IDYQV-RDFY1-6DS01-WTPVH'
+ name: 'Test Spreedly PayItSimple'
merchant_warrior:
merchant_uuid: '50c5b9f0b52ea'
@@ -222,8 +512,8 @@ merchant_warrior:
# Working credentials, no need to replace
mercury:
- login: '395347306=TOKEN'
- password: '123TOKEN'
+ login: '089716741701445'
+ password: 'xyz'
mercury_no_tokenization:
login: '595901'
@@ -234,49 +524,74 @@ metrics_global:
login: 'demo'
password: 'password'
-# Working credentials, no need to replace
-migs_purchase: # MiGS gateway for purchase mode
- login: TESTANZTEST3
- password: '6447E199'
- secure_hash: '76AF3392002D202A60D0AB5F9D81653C'
- advanced_login: noack_ama
- advanced_password: test1234
+micropayment:
+ access_key: "0b4832ca37a31e748c4490b58d743986"
# Working credentials, no need to replace
-migs_capture: # MiGS gateway for auth/capture mode
- login: TESTANZTEST2
- password: '8ED23813'
- secure_hash: '5816DF2E29819E43EA7C1F6E0DC2F1B5'
- advanced_login: noack_ama
- advanced_password: test1234
+migs:
+ login: TESTH-STATION
+ password: 'F1CE6F32'
+ secure_hash: 'D8CF972645E69EA15A7D6005059E18DF'
+ advanced_login: activemerchant
+ advanced_password: test12345
modern_payments:
login: login
password: password
+# Working credentials, no need to replace
+monei:
+ sender_id: 8a829417488d34c401489a5cd1350977
+ channel_id: 8a829417488d34c401489a5de5dd097b
+ login: 8a829417488d34c401489a5cd1360979
+ pwd: GyNSEAp2
+
# Working credentials, no need to replace
moneris:
- login: store1
+ login: store3
password: yesguy
moneris_us:
login: monusqa002
password: qatoken
+money_movers:
+ login: demo
+ password: password
+
+mundipagg:
+ api_key: api_key
+ gateway_affiliation_id: gateway_affiliation_id
+ # left for backward-compatibility
+ gateway_id: gateway_id
+
# Working credentials, no need to replace
nab_transact:
login: ABC0001
password: changeit
# Working credentials, no need to replace
-nab_transact_card_acceptor:
+nab_transact_privileged:
login: XYZ0010
password: abcd1234
+# Working credentials, no need to replace
+ncr_secure_pay:
+ username: test_ecomm:public
+ password: publ1ct3st
+
+net_registry:
+ login: X
+ password: Y
+
netaxept:
login: LOGIN
password: PASSWORD
+netbanx:
+ api_key: APIKEY
+ account_number: ACCOUNTNUMBER
+
# Working credentials, no need to replace
# Contact Netbilling for login info to the admin area
netbilling:
@@ -287,9 +602,9 @@ netpay:
login: POS
password: plaza2020
-net_registry:
- login: X
- password: Y
+network_merchants:
+ login: demo
+ password: password
nmi:
login: demo
@@ -301,70 +616,180 @@ ogone:
password: PASSWORD
signature: SIGNATURE
+# Get credentials or api keys from https://dashboard.omise.co
+omise:
+ public_key: pkey_test_4zt0fss8gs0z6b4zlsq
+ secret_key: skey_test_4zt0fss8eklsj88dx9l
+ api_version: '2015-11-17'
+
+# Working credentials, no need to replace
+openpay:
+ key: 'sk_e568c42a6c384b7ab02cd47d2e407cab'
+ merchant_id: 'mzdtln0bmtms6o3kck8f'
+
+opp:
+ user_id: '8a8294174b7ecb28014b9699220015cc'
+ password: 'sy6KJsT8'
+ entity_id: '8a8294174d0a8edd014d242337942575'
+
optimal_payment:
- login: test
+ store_id: test
password: test
- account: 'ACCOUNT_NUMBER'
-
-paybox_direct:
- login: 199988899
- password: 1999888I
+ account_number: ACCOUNTNUMBER
orbital_gateway:
login: LOGIN
password: PASSWORD
merchant_id: MERCHANTID
+# Working credentials, no need to replace
+pagarme:
+ api_key: 'ak_test_e1QGU2gL98MDCHZxHLJ9sofPUFJ7tH'
+
+# Working credentials, no need to replace
+pago_facil:
+ branch_id: 60f961360ca187d533d5adba7d969d6334771370
+ merchant_id: 62ad6f592ecf2faa87ef2437ed85a4d175e73c58
+ service_id: 3
+
+# Working credentials, no need to replace
+pay_conex:
+ account_id: '220614968961'
+ api_accesskey: '69e9c4dd6b8ab9ab47da4e288df78315'
+
# Working credentials, no need to replace
pay_gate_xml:
login: '10011021600'
password: 'test'
-payflow:
+# Working credentials, no need to replace
+pay_hub:
+ orgid: '123456'
+ username: 'abc123DEF'
+ password: 'abc123DEF'
+ tid: '123'
+
+# Working credentials, no need to replace
+pay_junction:
+ login: 'pj-ql-01'
+ password: 'pj-ql-01p'
+
+# Working credentials, no need to replace
+pay_junction_v2:
+ api_login: 'pj-ql-01'
+ api_password: 'pj-ql-01p'
+ api_key: ''
+
+pay_secure:
login: LOGIN
password: PASSWORD
- partner: PayPal
+
+paybox_direct:
+ login: 199988863
+ password: 1999888I
+ rang: 85
+
+# Working credentials, no need to replace
+payeezy:
+ apikey: UyDMTXx6TD9WErF6ynw7xeEfCAn8fcGs
+ apisecret: 2a4974e242c91cab7f38910938f2a5db79e89756b084bbf7cab7849b50a9930f
+ token: fdoa-a480ce8951daa73262734cf102641994c1e55e7cdf4c02b6
+
+payex:
+ account: ACCOUNT
+ # encryption key is generated via the PayEx Admin console
+ encryption_key: ENCRYPTION_KEY
+
+# Working credentials, no need to replace
+payflow:
+ login: 'spreedlyIntegrations'
+ password: 'L9DjqEKjXCkU'
+ partner: 'PayPal'
payflow_uk:
login: LOGIN
password: PASSWORD
partner: PayPalUk
-# Working credentials, no need to replace
-pay_junction:
- login: 'pj-ql-01'
- password: 'pj-ql-01p'
-
payment_express:
login: LOGIN
password: PASSWORD
+paymentez:
+ application_code: APPCODE
+ app_key: APPKEY
+
paymill:
- private_key: PRIVATE_KEY
- public_key: PUBLIC_KEY
+ private_key: a9580be4a7b9d0151a3da88c6c935ce0
+ public_key: 57313835619696ac361dc591bc973626
-# You can use either your API PEM file or API signature with PayPal.
paypal_certificate:
- login: LOGIN
- password: PASSWORD
+ login: activemerchant-cert-test_api1.example.com
+ password: ERDD3JRFU5H5DQXS
subject:
pem: |
- PASTE YOUR PEM FILE HERE
+ -----BEGIN RSA PRIVATE KEY-----
+ MIICXQIBAAKBgQD/fK2V+joi32b3hXwyZdWTrjX0eXF1NTY7iSvBDPipJalFcopa
+ RRQV41WHB7Ard8g4tsw9uP4aDqil/MJqoLm2pttOokKO1CX1R4xVz8kITefTDrMT
+ uGs43Sz6Kd/GLKeYpc+kk7vLgP9CqUNnuBWPLzw98t9XKTcdSf140WciPwIDAQAB
+ AoGAMxWK3+IYncBtpjBalPknq0+6GhfuR7FMFrtmtEMTtT6CihBM+Z+2VGoQP9+Z
+ qhdZQX3LeMv0guFLd2UCuq9IcobEuBL5Oyxvd1MYcSVuMHa8DQBYQfL2Dtq/m8K9
+ vTGdsvL3IyVlWs9xxZRtuEoBpmb3axjohVS62rq7CzGbMYECQQD/054sW/V7MZD7
+ 8Siz1Dhe3CJpGJ3RIkBzuMi5vtJftx6S4GX4jsbE8tyMSwX7TnO1xvHP7s1OCSEC
+ gUwIMQQlAkEA/6kAVG37ErrN5WX0kw+UIrBYQhnyZvjqVIqSSm8ZAn1cDbx84GbX
+ UTbPpJBbpcP5PDxeQnBqpvnLuIZfzSZtkwJAYSfD5ULTOoL7dcMDWzAYbGYbp2AS
+ 506jvY8KpAgFKxaHRO51q2zFrhwxiBIh5mvH49v3D6m4TI+I+sOR1XaQBQJBAIvh
+ 2i5X5rH+x70mJcV5FqJMPl4ceEbjFsOe9iAH3XVBRea2JNVbL6BeDwqJebufGHVe
+ ymwrug8WSeLykuRajEUCQQDlDXignvtCZlXlOCxohPuJ+o+vnF3R/L6MBtQmRjWI
+ mv6epzBqjXcB9Dv3n10k7M9H6YKfAZbuZ23PDTxrv3o+
+ -----END RSA PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIICsDCCAhmgAwIBAgIDEBZBMA0GCSqGSIb3DQEBBQUAMIGfMQswCQYDVQQGEwJV
+ UzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2UxFTATBgNV
+ BAoTDFBheVBhbCwgSW5jLjEWMBQGA1UECxQNc2FuZGJveF9jZXJ0czEbMBkGA1UE
+ AxQSc2FuZGJveF9jYW1lcmNoYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwu
+ Y29tMB4XDTE0MTAyMzE0MzQxM1oXDTI0MTAyMDE0MzQxM1owgYoxMjAwBgNVBAMU
+ KWFjdGl2ZW1lcmNoYW50LWNlcnQtdGVzdF9hcGkxLmV4YW1wbGUuY29tMScwJQYD
+ VQQKEx5OYXRoYW5pZWwgVGFsYm90dCdzIFRlc3QgU3RvcmUxETAPBgNVBAcTCFNh
+ biBKb3NlMQswCQYDVQQIEwJDQTELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEB
+ BQADgY0AMIGJAoGBAP98rZX6OiLfZveFfDJl1ZOuNfR5cXU1NjuJK8EM+KklqUVy
+ ilpFFBXjVYcHsCt3yDi2zD24/hoOqKX8wmqgubam206iQo7UJfVHjFXPyQhN59MO
+ sxO4azjdLPop38Ysp5ilz6STu8uA/0KpQ2e4FY8vPD3y31cpNx1J/XjRZyI/AgMB
+ AAGjDTALMAkGA1UdEwQCMAAwDQYJKoZIhvcNAQEFBQADgYEABR7D4mbOHBHYgMsG
+ sSxxYqqvhFSF7/HBQW4G132w6lxKVqdF3a6ICJYC/gOjOQ609kowDOrCwag9cbiO
+ 3gPzIjyqNemdkTJxiTaq9+fG7G+r6f+pXo0ZVvfFjgtoSUVdMlqgu572EEZOQmSL
+ w00oJt7RcWBpIkpS18Qpmk6KZ7o=
+ -----END CERTIFICATE-----
paypal_signature:
- login: LOGIN
- password: PASSWORD
- signature: SIGNATURE
+ login: activemerchant-test_api1.example.com
+ password: HBC6A84QLRWC923A
+ signature: AFcWxV21C7fd0v3bYYYRCpSSRl31AC-11AKBL8FFO9tjImL311y8a0hx
+
+payscout:
+ username: demo
+ password: password
+
+# There are all kind of special options that must be set on a test account, and
+# PayU is not transparent about what they are. At this point you need to tell
+# them specifically that the account will be used with Spreedly, which should
+# hopefully get you the right options.
+payu_in:
+ key: KEY
+ salt: SALT
+
+# Working credentials, no need to replace
+payu_latam:
+ merchant_id: "508029"
+ account_id: "512322"
+ api_login: "pRRXKOl8ikMmt9u"
+ api_key: "4Vj8eK4rloUd272L48hsrarnUA"
payway:
username:
password:
pem:
-pay_secure:
- login: LOGIN
- password: PASSWORD
-
pin:
api_key: "I_mo9BUUUXIwXF-avcs3LA"
@@ -372,16 +797,9 @@ plugnpay:
login: LOGIN
password: PASSWORD
-pxpay:
- login: LOGIN
- password: PASSWORD
-
-redsys:
- login: MERCHANT CODE
- secret_key: SECRET KEY
-
-sage_pay:
- login: LOGIN
+# Working credentials, no need to replace
+pro_pay:
+ cert_str: "5ab9cddef2e4911b77e0c4ffb70f03"
# Working credentials, no need to replace
psigate:
@@ -448,103 +866,258 @@ psl_visa_debit_address:
state:
zip:
+pxpay:
+ login: LOGIN
+ password: PASSWORD
+
+quantum:
+ login: X
+ password: Y
+
+# You will need to create a developer sandbox at https://developer.intuit.com/ and
+# successfully generate an OAuth 1.0a access token and token secret.
+quickbooks:
+ consumer_key:
+ consumer_secret:
+ access_token:
+ token_secret:
+ realm:
+
# Quickpay offers a test account.
# To log in to the manager, use demo@quickpay.net and test234
quickpay:
login: 89898978
password: "29p61DveBZ79c3144LW61lVz1qrwk2gfAFCxPyi5sn49m3Y3IRK5M6SN5d8a68u7"
+
+quickpay_v10_api_key:
+ api_key: "6e4e4f0a7cf692b0118ffe2c56c45b0cd3171badf65626de2ead023fd99b8bbc"
+
# To get the right apikey, log in to the manager.
# Go to Indstillinger > API adgang and make sure you have the right key in the fixture.
quickpay_with_api_key:
login: 89898978
password: "29p61DveBZ79c3144LW61lVz1qrwk2gfAFCxPyi5sn49m3Y3IRK5M6SN5d8a68u7"
apikey: "fB46983ZwR5dzy46A3r6ngDx7P37N5YTu1F4S9W2JKCs9v4t5L9m2Q8Mlbjpa2I1"
- version: 6
+ version: 7
-quantum:
- login: X
- password: Y
+qvalent:
+ username: "QRSL"
+ password: "QRSLTEST"
+ merchant: 24436057
+ pem_password: "Ceic4s4ig"
+ pem: |
+ Bag Attributes
+ localKeyID: 01 00 00 00
+ friendlyName: CCAPI Client Certificate
+ Key Attributes
+ X509v3 Key Usage: 10
+ -----BEGIN RSA PRIVATE KEY-----
+ MIIEpAIBAAKCAQEAtgPNEzLEFAm2CXCiaJeEFQxzkxXHn+Lyf65FjTDIOMknXnmw
+ gtoPTrFHtDVLJqWHaccLg9/lMKHivW1iGPTeZj4Uke07EcmIaDQD3Gq33gukDbgx
+ d5QDN+mEPRs0TF6FHlKKELKGIZdw4BlM+Jg7hebwZnBC/g9cetmB3ny99g0e5ANk
+ pPESguo+HTFk5vwHkLjMtDB+0zM5y6AyU6z+PaCDr3Q7+vcR50kQLNxpSWH1Il1g
+ BewIHAroN4lJjPvKLnDWvK5SsxW9XB14gLAyQg6qCHDYOdTsdiG6BFG1eijT+3W7
+ iXm8uTDiXSj+PiADEeKvnbEsWqXu+DvUsR/m8wIDAQABAoIBADtC/ZBUpRbJGqX0
+ MEzRmEWqKi8nljlukPoVabvQuEAU7maKRHg2O2mpuujnuTI6Dt7X2d30FhFBhCuc
+ 46WwhIDRkaz5ipP+BBW5adBoRrlbHO0CnciLPokD1PR4WQzMcZcv1JgfKCDjx/KP
+ CkqedjLgwED6KDXEFp5BF1GzV742dfux0Bgq+5kzLp0uAe0+ADxGll1Fx1wWbP/e
+ 4XSTWz4P1q3fmGAJB7fgXcfdp+Yri1x0RNDjBUa6YsR5fFVZBXcFEmPcRGbcsO3J
+ ApJTrskt4H0rQafcx/LmUj3pBVsl/tcyMBHHGHUb0N5pKypf2X+79IYhV+b9K9Ll
+ 5EHVV+ECgYEA8TTci5PsysRvzN5HIgpfsd3ZK/JrTkJ2z3Xx2HFL9XU8EAx9gO5h
+ Qa7CnJY+myoes+x90I7uKqfJdB1JDq2Bek6Jfj8stVd/YTtrOMt7WhHc6OW2S+qY
+ TpvtdeoVcJ2rKGFyo5/mj0aO+Hg1i6wxsIhDPKvSEqSqUrbjmLi5AfkCgYEAwS2V
+ IVzmMZIaRg0FxRccWl/XgpK9JNVjNr4PRiSkjsbFktVNLkbBcFOvrbmPPkBuHW8t
+ XDK6goRcNraxkkzB81mx0glW/zHfEQS4GWuQQEqIWwBfC9QEvC+AlGLaNhQYMleC
+ omLzTDTkBPtQSc72H0ov8TpowgeyM1+U2uozq0sCgYEAm+Szag64KzEcpQdAaDLW
+ OIoO04WBbvor+dfb8C0Bj+ouYJ0B/HOVLjN6GmRMoFJvt4/wnPvT2IPLAx3uWusu
+ 1NKvsIW6KpYbgMc7fGCfH86NvYTB9nzv5VaH+f7JzphIx/d7dV9iT1WmD9b5nIU1
+ NEhNVIgkZOJCJuWHYex5vlkCgYAF4uqxapBFIGuWiN0NJWgixNrfSrNixPHSADac
+ 747oHtx0XfWNHHDWiGZJB+d6gSIZ2YJrVcxjH79jl2uPxrD+RlRpzwkMm6ttbFRj
+ yehKXTsMctVymdJPHa9wVhbKIRCfsBT198fsIYx1LmdC6ICNcYhGdH4us2dVs2ro
+ xMwwQwKBgQDgNA9bGhiajsOI//M9ndH8KZCGdOoYM+6JBurHKX2fF8INRX+sxWix
+ DbUr9rQXkDrX1CY5RNs9kZpNxLJpOSHOpYlIytnGghYTrU1nzXoKLVopkO9Oz1A6
+ WzrOd297XdEoyEbAeCgcLr2WtjMiFHonesjeWIgbqXSKZ+W1oek0vQ==
+ -----END RSA PRIVATE KEY-----
+ Bag Attributes
+ localKeyID: 01 00 00 00
+ friendlyName: CCAPI Client Certificate
+ subject=/CN=Returned & Services League of Australia (Queensland Branch) - Support/OU=IT/O=Returned & Services League of Australia (Queensland Branch)/ST=NSW/C=AU/EMAILADDRESS=pp_support@qvalent.com
+ issuer=/CN=eQvalent/OU=Operations/O=QValent Pty Ltd/L=Wallsend/ST=NSW/C=AU
+ -----BEGIN CERTIFICATE-----
+ MIIE0DCCA7igAwIBAgIEWLX2pzANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJB
+ VTEMMAoGA1UECBMDTlNXMREwDwYDVQQHEwhXYWxsc2VuZDEYMBYGA1UEChMPUVZh
+ bGVudCBQdHkgTHRkMRMwEQYDVQQLEwpPcGVyYXRpb25zMREwDwYDVQQDEwhlUXZh
+ bGVudDAeFw0xNzAyMjgyMjE2MDdaFw0xOTAyMjgyMjE2MDdaMIHtMSUwIwYJKoZI
+ hvcNAQkBFhZwcF9zdXBwb3J0QHF2YWxlbnQuY29tMQswCQYDVQQGEwJBVTEMMAoG
+ A1UECAwDTlNXMUgwRgYDVQQKDD9SZXR1cm5lZCAmIzM4OyBTZXJ2aWNlcyBMZWFn
+ dWUgb2YgQXVzdHJhbGlhIChRdWVlbnNsYW5kIEJyYW5jaCkxCzAJBgNVBAsMAklU
+ MVIwUAYDVQQDDElSZXR1cm5lZCAmIzM4OyBTZXJ2aWNlcyBMZWFndWUgb2YgQXVz
+ dHJhbGlhIChRdWVlbnNsYW5kIEJyYW5jaCkgLSBTdXBwb3J0MIIBIjANBgkqhkiG
+ 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtgPNEzLEFAm2CXCiaJeEFQxzkxXHn+Lyf65F
+ jTDIOMknXnmwgtoPTrFHtDVLJqWHaccLg9/lMKHivW1iGPTeZj4Uke07EcmIaDQD
+ 3Gq33gukDbgxd5QDN+mEPRs0TF6FHlKKELKGIZdw4BlM+Jg7hebwZnBC/g9cetmB
+ 3ny99g0e5ANkpPESguo+HTFk5vwHkLjMtDB+0zM5y6AyU6z+PaCDr3Q7+vcR50kQ
+ LNxpSWH1Il1gBewIHAroN4lJjPvKLnDWvK5SsxW9XB14gLAyQg6qCHDYOdTsdiG6
+ BFG1eijT+3W7iXm8uTDiXSj+PiADEeKvnbEsWqXu+DvUsR/m8wIDAQABo4HzMIHw
+ MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBTujPN/magozRJ8XJxAVYPw
+ v5/HsDCBqQYDVR0jBIGhMIGegBQ2QKUf9O24xKZrWTyUqRmZeq04IqF0pHIwcDEL
+ MAkGA1UEBhMCQVUxDDAKBgNVBAgTA05TVzERMA8GA1UEBxMIV2FsbHNlbmQxGDAW
+ BgNVBAoTD1FWYWxlbnQgUHR5IEx0ZDETMBEGA1UECxMKT3BlcmF0aW9uczERMA8G
+ A1UEAxMIZVF2YWxlbnSCEG49SZXSVViNQE7XcOvVjwYwDgYDVR0PAQH/BAQDAgTw
+ MA0GCSqGSIb3DQEBBQUAA4IBAQCQwdv8D4B5sRnA9/ppDfwlix6omzh/SdA3SI4m
+ BNcImoqzBEq1OokdWKDkRQXlP6822aJn8fzDMV1/YsmomC4fT0wdag0DvBEvRlhy
+ roQRFjQai6CKRUgoH/p0q4EwxKLOa/H0kix+cn6Nszl07wu6YtHzvISXf4MC72au
+ /xTf/qwXI1uXnQghb4sFM9/ubYlsNEFiNHt7CHBK7ivhFGcT7eI9rmSOTMKsZfmV
+ pALJ58Ynz08xLYRMq54FxzwhN3CZ7AWRA+9JpzJacJ6lzHX9Y4FAjzRzoqn5h/IN
+ dgMIW+HoTgCe4+M1aDx2A0SKJCSK9tYZCYSsPMi9JXdLDU+k
+ -----END CERTIFICATE-----
+ Bag Attributes
+ localKeyID: 02 00 00 00
+ subject=/CN=eQvalent/OU=Operations/O=QValent Pty Ltd/L=Wallsend/ST=NSW/C=AU
+ issuer=/CN=eQvalent/OU=Operations/O=QValent Pty Ltd/L=Wallsend/ST=NSW/C=AU
+ -----BEGIN CERTIFICATE-----
+ MIIDuzCCAqOgAwIBAgIQbj1JldJVWI1ATtdw69WPBjANBgkqhkiG9w0BAQUFADBw
+ MQswCQYDVQQGEwJBVTEMMAoGA1UECBMDTlNXMREwDwYDVQQHEwhXYWxsc2VuZDEY
+ MBYGA1UEChMPUVZhbGVudCBQdHkgTHRkMRMwEQYDVQQLEwpPcGVyYXRpb25zMREw
+ DwYDVQQDEwhlUXZhbGVudDAeFw0xMDA0MDcwNTUyMThaFw0zMDA0MDcwNjAyMTda
+ MHAxCzAJBgNVBAYTAkFVMQwwCgYDVQQIEwNOU1cxETAPBgNVBAcTCFdhbGxzZW5k
+ MRgwFgYDVQQKEw9RVmFsZW50IFB0eSBMdGQxEzARBgNVBAsTCk9wZXJhdGlvbnMx
+ ETAPBgNVBAMTCGVRdmFsZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+ AQEAxKuwonFlg53dyb0zFyS0JPUbxKmHwBDZcTT78eV4hs9TveOR34yT9xv+y7R1
+ GbUdtXxidGaXW6lfkWvVnS1mkDbj84OY2FzRTHeKuNmkcPddQDUgE+gaOGV6GKW7
+ v/s5jMt6pz075teLKvNO2Gs2alpPl4NYU5zJfa+AGFzqE1z7Zxbl9N/Sc6Y8ZbWI
+ IGWKJFeaTic8tE05hIGuWw1KfxbhH0QMKnTSzW5fJi4Pce3iIftkCTjIXdTg+El8
+ 9b+Jn5VMk+bQZQiusPbPbHZTvAADX/WCUuzAlFqyyNtutJmUlD/TiEB12YYp9R6U
+ 1MGRvC9+c+sz2bBccW3c+Zm7cQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0T
+ AQH/BAUwAwEB/zAdBgNVHQ4EFgQUNkClH/TtuMSma1k8lKkZmXqtOCIwEAYJKwYB
+ BAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAAJRMGZzxO3lcTXoFdmOpo8P
+ Ptf/agwrlGT8krwKcZt42YZ98VoIaCTI83kkACYlTlkTTNkDdHtAwF7ROteZmbfQ
+ 5ky0nqZTpdQ2IPfGXpxcMIrGqrSJ6GbLTxrm0kXDH6TgYeY1dVTHtJD9wEgWwdI9
+ coRkKQVjK/QucPuVX0uoRppCz0rCiJfCQf2825BrcbaY4HyDHvNxRXKV9ECuYwrk
+ HYDy3bnYWVB2ekk8xOEiocjQI3T//V36ZfiGubKlUZ4xsSed410hkLtB6ttCyZB1
+ RFjxWKtn9pXbM1PLmUXCkKQnSJSeD1K0NjV+g8KFChTEgmhnLogyF/7YYw/amfc=
+ -----END CERTIFICATE-----
+
+raven_pac_net:
+ user: ernest
+ secret: "all good men die young"
+ prn: 987743
realex:
login: X
password: Y
-realex_with_account:
- login: X
- password: Y
- account: testaccount
-
-# Realex doesn't provide public testing data
-# Fill in the card numbers with the Realex test
-# data.
-realex_visa:
- number:
+realex_mastercard:
+ number: '5425230000004415'
month: '6'
year: '2020'
verification_value: '123'
+ brand: 'master'
-realex_visa_declined:
- number:
+realex_mastercard_coms_error:
+ number: '5135020000005871'
month: '6'
year: '2020'
verification_value: '123'
+ brand: 'master'
-realex_visa_referral_a:
- number:
+realex_mastercard_declined:
+ number: '5114610000004778'
month: '6'
year: '2020'
verification_value: '123'
+ brand: 'master'
-realex_visa_referral_b:
- number:
+realex_mastercard_referral_a:
+ number: '5121220000006921'
month: '6'
year: '2020'
verification_value: '123'
+ brand: 'master'
-realex_visa_coms_error:
- number:
+realex_mastercard_referral_b:
+ number: '5114630000009791'
month: '6'
year: '2020'
verification_value: '123'
+ brand: 'master'
-realex_mastercard:
- number:
+# Realex doesn't provide public testing data
+# Fill in the card numbers with the Realex test
+# data.
+realex_visa:
+ number: '4263970000005262'
month: '6'
year: '2020'
verification_value: '123'
-realex_mastercard_declined:
- number:
+realex_visa_3ds_enrolled:
+ number: '4012001037141112'
+ month: '9'
+ year: '2021'
+ verification_value: '123'
+
+realex_visa_coms_error:
+ number: '4009830000001985'
month: '6'
year: '2020'
verification_value: '123'
-realex_mastercard_referral_a:
- number:
+realex_visa_declined:
+ number: '4000120000001154'
month: '6'
year: '2020'
verification_value: '123'
-realex_mastercard_referral_b:
- number:
+realex_visa_referral_a:
+ number: '4000160000004147'
month: '6'
year: '2020'
verification_value: '123'
-realex_mastercard_coms_error:
- number:
+realex_visa_referral_b:
+ number: '4000130000001724'
month: '6'
year: '2020'
verification_value: '123'
-samurai:
- login: 'a1ebafb6da5238fb8a3ac9f6'
- password: 'ae1aa640f6b735c4730fbb56'
- processor_token: '5a0e1ca1e5a11a2997bbf912'
+realex_with_account:
+ login: X
+ password: Y
+ account: testaccount
+
+redsys:
+ login: MERCHANT CODE
+ secret_key: SECRET KEY
+
+redsys_sha256:
+ login: MERCHANT CODE
+ secret_key: SECRET KEY
+ signature_algorithm: 'sha256'
+
+# Working credentials, no need to replace
+s5:
+ mode: CONNECTOR_TEST
+ sender: ff80808142b2c03c0142b7a7339603e0
+ channel: ff80808142b2c03c0142b7a7339803e5
+ login: 8a82941847c4d0780147cea1d1730dcc
+ password: n3yNMBGK
+
+# Working credentials, no need to replace
+safe_charge:
+ client_login_id: 'SpreedlyTestTRX'
+ client_password: '5Jp5xKmgqY'
+
+safe_charge_three_ds:
+ client_login_id: 'SpreedlyManTestTRX'
+ client_password: 'iGx9DQQHQG'
sage:
- login: login
- password: password
+ login: 214282982451
+ password: 'Z5W2S8J7X8T5'
+
+sage_pay:
+ login: spreedly
sallie_mae:
login: TEST0
@@ -557,49 +1130,100 @@ secure_pay:
login: LOGIN
password: PASSWORD
+secure_pay_au:
+ login: ABC0030
+ password: abc123
+
secure_pay_tech:
login: TESTDIGISPL1
password: d557591484cb2cd12bba445aba420d2c69cd6a88
-secure_pay_au:
- login: ABC0030
- password: abc123
+securion_pay:
+ secret_key: pr_test_qZN4VVIKCySfCeXCBoHO9DBe
# Replace with your serial numbers for the skipjack test environment
skipjack:
login: X
password: Y
+so_easy_pay:
+ login: 110159
+ password: b1fe9de37c234ef8fd53668377076687d6314dc8f6146023338d61b5969719e0
+
# Working credentials, no need to replace
spreedly_core:
login: "4Y9bVkOCpYesPQOfDi7TXQyUw50"
password: "Y2i7AjgU03SUjwY4xnOPqzdsv4dMbPDCQzorAk8Bcoy0U8EIVE4innGjuoMQv7MN"
gateway_token: "3gLeg4726V5P0HK7cq7QzHsL0a6"
+# Working credentials, no need to replace
stripe:
- login:
- fee_refund_login:
+ login: sk_test_3OD4TdKSIOhDOL2146JJcC79
+
+# Working credentials, no need to replace
+stripe_destination:
+ stripe_user_id: "acct_17FRNfIPBJTitsen"
+
+# Externally verified bank account for testing
+stripe_verified_bank_account:
+ customer_id: "cus_7s22nNueP2Hjj6"
+ bank_account_id: "ba_17cHxeAWOtgoysogv3NM8CJ1"
+
+# Working credentials, no need to replace
+swipe_checkout:
+ login: 2077103073D8B5
+ api_key: f2fe4efd5033edfaed9e4aad319ef4d34536a10eea07f90f182616d7216ae2b8
+ region: NZ
+
+# Working credentials, no need to replace
+telr:
+ merchant_id: 16715
+ api_key: WZV7W#gbMVw^kSBk
+
+tns:
+ userid: TESTSPREEDLY01
+ password: 3f34fe50334fbe6cbe04c283411a5860
+
+tns_ap:
+ userid: TESTUNISOLMAA01
+ password: b7f8119fda3bd27c17656badb52c95bb
trans_first:
- login: LOGIN
+ login: 45567
+ password: TNYYKYMFZ59HSN7Q
+
+# Working credentials, no need to replace
+trans_first_transaction_express:
+ gateway_id: 7777778764
+ reg_key: M84PKPDMD5BY86HN
+
+# Transact Pro doesn't provide public testing data
+transact_pro:
+ guid: GUID
password: PASSWORD
+ terminal: TERMINALID
+ card_number: "TESTCARDNUMBER"
+ verification_value: "TESTCARDCVV"
+ month: "TESTCARDMONTH"
+ year: "TESTCARDYEAR"
transax:
login: transaxdemo
password: nelix123
-transnational:
- login: demo
- password: password
+# Working credentials, no need to replace
+trexle:
+ api_key: "J5RGMpDlFlTfv9mEFvNWYoqHufyukPP4"
# Working credentials, no need to replace
trust_commerce:
login: 'TestMerchant'
password: 'password'
+ aggregator_id: 'abc123'
# Working credentials, no need to replace
usa_epay:
- login: 'yCaWGYQsSVR0S48B6AKMK07RQhaxHvGu'
+ login: '4EoZ5U2Q55j976W7eplC71i6b7kn4pcV'
# Get credentials here: https://www.usaepay.com/developer/login
usa_epay_advanced:
@@ -611,6 +1235,12 @@ valitor:
login: WebsiteID
password: SecurityNumber
+# Working credentials, no need to replace
+vanco:
+ user_id: SPREEDWS
+ password: v@nco2oo
+ client_id: SPREEDLY
+
# Working credentials, no need to replace
verify:
login: 'demo'
@@ -621,22 +1251,52 @@ viaklix:
password: PASSWORD
user: USER
-vindicia:
- login: LOGIN
- password: PASSWORD
+# test credentails
+visanet_peru:
+ access_key_id: "AKIAJLJTVQYHO4P76YYA"
+ secret_access_key: "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"
+ merchant_id: "543025501"
+ ruc: '20341198217'
webpay:
login: "test_secret_eHn4TTgsGguBcW764a2KA8Yd"
# Working test credentials, no need to replace
+wepay:
+ client_id: "44716"
+ account_id: "2080478981"
+ access_token: "STAGE_c91882b0bed3584b8aed0f7f515f2f05a1d40924ee6f394ce82d91018cb0f2d3"
+ client_secret: "d48fefe743"
+
+# Working test credentials with AVS/CVV support, no need to replace
wirecard:
- login: 56500
+ login: 00000031629CA9FA
password: TestXAPTER
+ signature: 00000031629CAFD5
+
+wirecard_checkout_page:
+ secret: B8AKTPWBRMNBV455FG6M2DANE99WU2
+ shop_id: ''
+ paymenttype: IDL
+
+# Working credentials, no need to replace
+world_net:
+ terminal_id: '6001'
+ secret: 'sandboxEUR'
world_pay_gateway:
- login: LOGIN
- password: PASSWORD
+ login: 'SPREEDLY'
+ password: 'KZ#P2aR+'
-iats_payments:
- login: TEST88
- password: TEST88
+world_pay_gateway_cft:
+ login: 'SPREEDLYCFT'
+ password: 'Xbf+6#pD'
+
+worldpay_online_payments:
+ client_key: "T_C_b9f629e7-cea7-4edb-8206-24bbe351d699"
+ service_key: "T_S_eea7c405-25a9-4428-918f-27a9d63fe64f"
+
+worldpay_us:
+ acctid: MPNAB
+ subid: SPREE
+ merchantpin: "1234567890"
diff --git a/test/remote/gateways/remote_adyen_test.rb b/test/remote/gateways/remote_adyen_test.rb
new file mode 100644
index 00000000000..584d9a1e96a
--- /dev/null
+++ b/test/remote/gateways/remote_adyen_test.rb
@@ -0,0 +1,708 @@
+require 'test_helper'
+
+class RemoteAdyenTest < Test::Unit::TestCase
+ def setup
+ @gateway = AdyenGateway.new(fixtures(:adyen))
+
+ @amount = 100
+
+ @credit_card = credit_card('4111111111111111',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737',
+ :brand => 'visa'
+ )
+
+ @avs_credit_card = credit_card('4400000000000008',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737',
+ :brand => 'visa'
+ )
+
+ @elo_credit_card = credit_card('5066 9911 1111 1118',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737',
+ :brand => 'elo'
+ )
+
+ @three_ds_enrolled_card = credit_card('4917610000000000', brand: :visa)
+
+ @declined_card = credit_card('4000300011112220')
+
+ @improperly_branded_maestro = credit_card(
+ '5500000000000004',
+ month: 8,
+ year: 2018,
+ first_name: 'John',
+ last_name: 'Smith',
+ verification_value: '737',
+ brand: 'mastercard'
+ )
+
+ @apple_pay_card = network_tokenization_credit_card('4111111111111111',
+ :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ :month => '08',
+ :year => '2018',
+ :source => :apple_pay,
+ :verification_value => nil
+ )
+
+ @google_pay_card = network_tokenization_credit_card('4111111111111111',
+ :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ :month => '08',
+ :year => '2018',
+ :source => :google_pay,
+ :verification_value => nil
+ )
+
+ @options = {
+ reference: '345123',
+ shopper_email: 'john.smith@test.com',
+ shopper_ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: address(),
+ order_id: '123',
+ stored_credential: {reason_type: 'unscheduled'},
+ }
+
+ @normalized_3ds_2_options = {
+ reference: '345123',
+ shopper_email: 'john.smith@test.com',
+ shopper_ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: address(),
+ order_id: '123',
+ stored_credential: {reason_type: 'unscheduled'},
+ three_ds_2: {
+ channel: 'browser',
+ notification_url: 'https://example.com/notification',
+ browser_info: {
+ accept_header: 'unknown',
+ depth: 100,
+ java: false,
+ language: 'US',
+ height: 1000,
+ width: 500,
+ timezone: '-120',
+ user_agent: 'unknown'
+ }
+ }
+ }
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_successful_authorize_avs
+ # Account configuration may need to be done: https://docs.adyen.com/developers/api-reference/payments-api#paymentresultadditionaldata
+ options = @options.update({
+ billing_address: {
+ address1: 'Infinite Loop',
+ address2: 1,
+ country: 'US',
+ city: 'Cupertino',
+ state: 'CA',
+ zip: '95014'
+ }
+ })
+ response = @gateway.authorize(@amount, @avs_credit_card, options)
+ assert_success response
+ assert_equal 'Authorised', response.message
+ assert_equal 'D', response.avs_result['code']
+ end
+
+ def test_successful_authorize_with_idempotency_key
+ options = @options.merge(idempotency_key: SecureRandom.hex)
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Authorised', response.message
+ first_auth = response.authorization
+
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_equal response.authorization, first_auth
+ end
+
+ def test_successful_authorize_with_3ds
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true))
+ assert response.test?
+ refute response.authorization.blank?
+ assert_equal response.params['resultCode'], 'RedirectShopper'
+ refute response.params['issuerUrl'].blank?
+ refute response.params['md'].blank?
+ refute response.params['paRequest'].blank?
+ end
+
+ def test_successful_authorize_with_3ds_dynamic
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(threed_dynamic: true))
+ assert response.test?
+ refute response.authorization.blank?
+ assert_equal response.params['resultCode'], 'RedirectShopper'
+ refute response.params['issuerUrl'].blank?
+ refute response.params['md'].blank?
+ refute response.params['paRequest'].blank?
+ end
+
+ def test_successful_authorize_with_3ds2_browser_client_data
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @normalized_3ds_2_options)
+ assert response.test?
+ refute response.authorization.blank?
+
+ assert_equal response.params['resultCode'], 'IdentifyShopper'
+ refute response.params['additionalData']['threeds2.threeDS2Token'].blank?
+ refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank?
+ refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank?
+ end
+
+ def test_successful_authorize_with_3ds2_app_based_request
+ three_ds_app_based_options = {
+ reference: '345123',
+ shopper_email: 'john.smith@test.com',
+ shopper_ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: address(),
+ order_id: '123',
+ stored_credential: {reason_type: 'unscheduled'},
+ three_ds_2: {
+ channel: 'app',
+ }
+ }
+
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, three_ds_app_based_options)
+ assert response.test?
+ refute response.authorization.blank?
+ assert_equal response.params['resultCode'], 'IdentifyShopper'
+ refute response.params['additionalData']['threeds2.threeDS2Token'].blank?
+ refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank?
+ refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.algorithm'].blank?
+ refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.directoryServerId'].blank?
+ refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.publicKey'].blank?
+ end
+
+ # with rule set in merchant account to skip 3DS for cards of this brand
+ def test_successful_authorize_with_3ds_dynamic_rule_broken
+ mastercard_threed = credit_card('5212345678901234',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737',
+ :brand => 'mastercard'
+ )
+ assert response = @gateway.authorize(@amount, mastercard_threed, @options.merge(threed_dynamic: true))
+ assert response.test?
+ refute response.authorization.blank?
+ assert_equal response.params['resultCode'], 'Authorised'
+ end
+
+ def test_successful_purchase_with_auth_data_via_threeds1_standalone
+ eci = '05'
+ cavv = '3q2+78r+ur7erb7vyv66vv\/\/\/\/8='
+ cavv_algorithm = '1'
+ xid = 'ODUzNTYzOTcwODU5NzY3Qw=='
+ directory_response_status = 'Y'
+ authentication_response_status = 'Y'
+ options = @options.merge(
+ three_d_secure: {
+ eci: eci,
+ cavv: cavv,
+ cavv_algorithm: cavv_algorithm,
+ xid: xid,
+ directory_response_status: directory_response_status,
+ authentication_response_status: authentication_response_status
+ }
+ )
+
+ auth = @gateway.authorize(@amount, @credit_card, options)
+ assert_success auth
+ assert_equal 'Authorised', auth.message
+ assert_equal 'true', auth.params['additionalData']['liabilityShift']
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_auth_data_via_threeds2_standalone
+ version = '2.1.0'
+ eci = '02'
+ cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA='
+ ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC'
+ directory_response_status = 'C'
+ authentication_response_status = 'Y'
+
+ options = @options.merge(
+ three_d_secure: {
+ version: version,
+ eci: eci,
+ cavv: cavv,
+ ds_transaction_id: ds_transaction_id,
+ directory_response_status: directory_response_status,
+ authentication_response_status: authentication_response_status
+ }
+ )
+
+ auth = @gateway.authorize(@amount, @credit_card, options)
+ assert_success auth
+ assert_equal 'Authorised', auth.message
+ assert_equal 'true', auth.params['additionalData']['liabilityShift']
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
+ def test_successful_authorize_with_no_address
+ options = {
+ reference: '345123',
+ shopper_email: 'john.smith@test.com',
+ shopper_ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ order_id: '123',
+ recurring_processing_model: 'CardOnFile'
+ }
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'CVC Declined', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_no_cvv
+ credit_card = @credit_card
+ credit_card.verification_value = nil
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = @options.merge!(fraudOffset: '1', installments: 2, shopper_statement: 'statement note', device_fingerprint: 'm7Cmrf++0cW4P6XfF7m/rA')
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_risk_data
+ options = @options.merge(
+ risk_data:
+ {
+ 'operatingSystem' => 'HAL9000',
+ 'destinationLatitude' => '77.641423',
+ 'destinationLongitude' => '12.9503376'
+ }
+ )
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_idempotency_key
+ options = @options.merge(idempotency_key: SecureRandom.hex)
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ first_auth = response.authorization
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal response.authorization, first_auth
+ end
+
+ def test_successful_purchase_with_apple_pay
+ response = @gateway.purchase(@amount, @apple_pay_card, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_succesful_purchase_with_brand_override
+ response = @gateway.purchase(@amount, @improperly_branded_maestro, @options.merge({overwrite_brand: true, selected_brand: 'maestro'}))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_google_pay
+ response = @gateway.purchase(@amount, @google_pay_card, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_elo_card
+ response = @gateway.purchase(@amount, @elo_credit_card, @options.merge(currency: 'BRL'))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'CVC Declined', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal '[capture-received]', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_elo_card
+ auth = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal '[capture-received]', capture.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Original pspReference required for this operation', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal '[refund-received]', refund.message
+ end
+
+ def test_successful_refund_with_elo_card
+ purchase = @gateway.purchase(@amount, @elo_credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal '[refund-received]', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'Original pspReference required for this operation', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal '[cancel-received]', void.message
+ end
+
+ def test_successful_void_with_elo_card
+ auth = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal '[cancel-received]', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Original pspReference required for this operation', response.message
+ end
+
+ def test_successful_asynchronous_adjust
+ authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth'))
+ assert_success authorize
+
+ assert adjust = @gateway.adjust(200, authorize.authorization, @options)
+ assert_success adjust
+ assert_equal '[adjustAuthorisation-received]', adjust.message
+ end
+
+ def test_successful_asynchronous_adjust_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth'))
+ assert_success authorize
+
+ assert adjust = @gateway.adjust(200, authorize.authorization, @options)
+ assert_success adjust
+ assert_equal '[adjustAuthorisation-received]', adjust.message
+
+ assert capture = @gateway.capture(200, authorize.authorization)
+ assert_success capture
+ end
+
+ def test_failed_asynchronous_adjust
+ authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth'))
+ assert_success authorize
+
+ assert response = @gateway.adjust(200, '', @options)
+ assert_failure response
+ assert_equal 'Original pspReference required for this operation', response.message
+ end
+
+ # Requires Adyen to set your test account to Synchronous Adjust mode.
+ def test_successful_synchronous_adjust_using_adjust_data
+ authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth', shopper_statement: 'statement note'))
+ assert_success authorize
+
+ options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData'], update_shopper_statement: 'new statement note', industry_usage: 'DelayedCharge')
+ assert adjust = @gateway.adjust(200, authorize.authorization, options)
+ assert_success adjust
+ assert_equal 'Authorised', adjust.message
+ end
+
+ # Requires Adyen to set your test account to Synchronous Adjust mode.
+ def test_successful_synchronous_adjust_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth'))
+ assert_success authorize
+
+ options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData'])
+ assert adjust = @gateway.adjust(200, authorize.authorization, options)
+ assert_success adjust
+ assert_equal 'Authorised', adjust.message
+
+ assert capture = @gateway.capture(200, authorize.authorization)
+ assert_success capture
+ end
+
+ # Requires Adyen to set your test account to Synchronous Adjust mode.
+ def test_failed_synchronous_adjust_using_adjust_data
+ authorize = @gateway.authorize(@amount, @credit_card, @options.merge(authorisation_type: 'PreAuth'))
+ assert_success authorize
+
+ options = @options.merge(adjust_authorisation_data: authorize.params['additionalData']['adjustAuthorisationData'],
+ requested_test_acquirer_response_code: '2')
+ assert adjust = @gateway.adjust(200, authorize.authorization, options)
+ assert_failure adjust
+ assert_equal 'Refused', adjust.message
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @options)
+
+ assert_success response
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_successful_store_with_elo_card
+ assert response = @gateway.store(@elo_credit_card, @options)
+
+ assert_success response
+ assert !response.authorization.split('#')[2].nil?
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_failed_store
+ assert response = @gateway.store(@declined_card, @options)
+
+ assert_failure response
+ assert_equal 'CVC Declined', response.message
+ end
+
+ def test_successful_purchase_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_using_stored_elo_card
+ assert store_response = @gateway.store(@elo_credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_authorize_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.authorize(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'Authorised', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match 'CVC Declined', response.message
+ end
+
+ def test_verify_with_idempotency_key
+ options = @options.merge(idempotency_key: SecureRandom.hex)
+ response = @gateway.authorize(0, @credit_card, options)
+ assert_success response
+ assert_equal 'Authorised', response.message
+ first_auth = response.authorization
+
+ response = @gateway.verify(@credit_card, options)
+ assert_success response
+ assert_equal response.authorization, first_auth
+
+ response = @gateway.void(first_auth, @options)
+ assert_success response
+ end
+
+ def test_invalid_login
+ gateway = AdyenGateway.new(username: '', password: '', merchant_account: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_transcript_scrubbing_network_tokenization_card
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @apple_pay_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@apple_pay_card.number, transcript)
+ assert_scrubbed(@apple_pay_card.payment_cryptogram, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_incorrect_number_for_purchase
+ card = credit_card('4242424242424241')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code
+ end
+
+ def test_invalid_number_for_purchase
+ card = credit_card('-1')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code
+ end
+
+ def test_invalid_expiry_month_for_purchase
+ card = credit_card('4242424242424242', month: 16)
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_equal 'Expiry Date Invalid: Expiry month should be between 1 and 12 inclusive', response.message
+ end
+
+ def test_invalid_expiry_year_for_purchase
+ card = credit_card('4242424242424242', year: 'xx')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert response.message.include?('Expiry year should be a 4 digit number greater than')
+ end
+
+ def test_invalid_cvc_for_purchase
+ card = credit_card('4242424242424242', verification_value: -1)
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code
+ end
+
+ def test_missing_address_for_purchase
+ @options[:billing_address].delete(:address1)
+ @options[:billing_address].delete(:address2)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_missing_city_for_purchase
+ @options[:billing_address].delete(:city)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_missing_house_number_or_name_for_purchase
+ @options[:billing_address].delete(:address2)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_missing_state_for_purchase
+ @options[:billing_address].delete(:state)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_blank_country_for_purchase
+ @options[:billing_address][:country] = ''
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_address], response.error_code
+ end
+
+ def test_nil_state_for_purchase
+ @options[:billing_address][:state] = nil
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_blank_state_for_purchase
+ @options[:billing_address][:state] = ''
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_missing_phone_for_purchase
+ @options[:billing_address].delete(:phone)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ end
+end
diff --git a/test/remote/gateways/remote_allied_wallet_test.rb b/test/remote/gateways/remote_allied_wallet_test.rb
new file mode 100644
index 00000000000..8ab761e508b
--- /dev/null
+++ b/test/remote/gateways/remote_allied_wallet_test.rb
@@ -0,0 +1,149 @@
+require 'test_helper'
+
+class RemoteAlliedWalletTest < Test::Unit::TestCase
+ def setup
+ @gateway = AlliedWalletGateway.new(fixtures(:allied_wallet))
+
+ @amount = 100
+ @credit_card = credit_card
+ @declined_card = credit_card('4242424242424242', verification_value: '555')
+
+ @options = {
+ billing_address: address,
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'The test operation was declined.', response.message
+ end
+
+ def test_failed_purchase_no_address
+ response = @gateway.purchase(@amount, @declined_card)
+ assert_failure response
+ assert_match(/Address.* should not be empty/, response.message)
+ end
+
+ def test_successful_purchase_with_more_options
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(
+ order_id: generate_unique_id,
+ ip: '127.0.0.1',
+ email: 'jim_smith@example.com'
+ ))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'The test operation was declined.', response.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal "'Authorize Transaction Id' should not be empty.", response.message
+ end
+
+ def test_successful_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal "'Authorize Transaction Id' should not be empty.", response.message
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, 'UnknownAuthorization')
+ assert_failure response
+ assert_match(/An internal exception has occurred/, response.message)
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Succeeded}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'The test operation was declined.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:token], clean_transcript)
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_nil_cvv_transcript_scrubbing
+ @credit_card.verification_value = nil
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_equal transcript.include?('\"cVVCode\":[BLANK]'), true
+ end
+
+ def test_empty_string_cvv_transcript_scrubbing
+ @credit_card.verification_value = ''
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_equal transcript.include?('\"cVVCode\":\"[BLANK]'), true
+ end
+
+ def test_whitespace_string_cvv_transcript_scrubbing
+ @credit_card.verification_value = ' '
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_equal transcript.include?('\"cVVCode\":\"[BLANK]'), true
+ end
+end
diff --git a/test/remote/gateways/remote_authorize_net_apple_pay_test.rb b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
new file mode 100644
index 00000000000..cf430b4a975
--- /dev/null
+++ b/test/remote/gateways/remote_authorize_net_apple_pay_test.rb
@@ -0,0 +1,92 @@
+require 'test_helper'
+
+class RemoteAuthorizeNetApplePayTest < Test::Unit::TestCase
+ def setup
+ @gateway = AuthorizeNetGateway.new(fixtures(:authorize_net))
+
+ @amount = 100
+ @apple_pay_payment_token = apple_pay_payment_token
+
+ @options = {
+ order_id: '1',
+ duplicate_window: 0,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_apple_pay_authorization
+ response = @gateway.authorize(5, @apple_pay_payment_token, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_apple_pay_purchase
+ response = @gateway.purchase(5, @apple_pay_payment_token, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ end
+
+ def test_successful_apple_pay_authorization_and_capture
+ assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options)
+ assert_success authorization
+
+ assert capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+ assert_equal 'This transaction has been approved', capture.message
+ end
+
+ def test_successful_apple_pay_authorization_and_void
+ assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options)
+ assert_success authorization
+
+ assert void = @gateway.void(authorization.authorization)
+ assert_success void
+ assert_equal 'This transaction has been approved', void.message
+ end
+
+ def test_failed_apple_pay_authorization
+ response = @gateway.authorize(@amount, apple_pay_payment_token(payment_data: {data: 'garbage'}), @options)
+ assert_failure response
+ assert_equal 'There was an error processing the payment data', response.message
+ assert_equal 'processing_error', response.error_code
+ end
+
+ def test_failed_apple_pay_purchase
+ response = @gateway.purchase(@amount, apple_pay_payment_token(payment_data: {data: 'garbage'}), @options)
+ assert_failure response
+ assert_equal 'There was an error processing the payment data', response.message
+ assert_equal 'processing_error', response.error_code
+ end
+
+ private
+
+ def apple_pay_payment_token(options = {})
+ # The payment_data field below is sourced from: http://developer.authorize.net/api/reference/#apple-pay-transactions
+ # Other fields are motivated by https://developer.apple.com/library/ios/documentation/PassKit/Reference/PKPaymentToken_Ref/index.html
+ defaults = {
+ payment_data: {
+ 'data' => 'BDPNWStMmGewQUWGg4o7E/j+1cq1T78qyU84b67itjcYI8wPYAOhshjhZPrqdUr4XwPMbj4zcGMdy++1H2VkPOY+BOMF25ub19cX4nCvkXUUOTjDllB1TgSr8JHZxgp9rCgsSUgbBgKf60XKutXf6aj/o8ZIbKnrKQ8Sh0ouLAKloUMn+vPu4+A7WKrqrauz9JvOQp6vhIq+HKjUcUNCITPyFhmOEtq+H+w0vRa1CE6WhFBNnCHqzzWKckB/0nqLZRTYbF0p+vyBiVaWHeghERfHxRtbzpeczRPPuFsfpHVs48oPLC/k/1MNd47kz/pHDcR/Dy6aUM+lNfoily/QJN+tS3m0HfOtISAPqOmXemvr6xJCjCZlCuw0C9mXz/obHpofuIES8r9cqGGsUAPDpw7g642m4PzwKF+HBuYUneWDBNSD2u6jbAG3',
+ 'version' => 'EC_v1',
+ 'header' => {
+ 'applicationData' => '94ee059335e587e501cc4bf90613e0814f00a7b08bc7c648fd865a2af6a22cc2',
+ 'transactionId' => 'c1caf5ae72f0039a82bad92b828363734f85bf2f9cadf193d1bad9ddcb60a795',
+ 'ephemeralPublicKey' => 'MIIBSzCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBA0IABGm+gsl0PZFT/kDdUSkxwyfo8JpwTQQzBm9lJJnmTl4DGUvAD4GseGj/pshBZ0K3TeuqDt/tDLbE+8/m0yCmoxw=',
+ 'publicKeyHash' => '/bb9CNC36uBheHFPbmohB7Oo1OsX2J+kJqv48zOVViQ='
+ },
+ 'signature' => 'MIIDQgYJKoZIhvcNAQcCoIIDMzCCAy8CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCCAiswggInMIIBlKADAgECAhBcl+Pf3+U4pk13nVD9nwQQMAkGBSsOAwIdBQAwJzElMCMGA1UEAx4cAGMAaABtAGEAaQBAAHYAaQBzAGEALgBjAG8AbTAeFw0xNDAxMDEwNjAwMDBaFw0yNDAxMDEwNjAwMDBaMCcxJTAjBgNVBAMeHABjAGgAbQBhAGkAQAB2AGkAcwBhAC4AYwBvAG0wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANC8+kgtgmvWF1OzjgDNrjTEBRuo/5MKvlM146pAf7Gx41blE9w4fIXJAD7FfO7QKjIXYNt39rLyy7xDwb/5IkZM60TZ2iI1pj55Uc8fd4fzOpk3ftZaQGXNLYptG1d9V7IS82Oup9MMo1BPVrXTPHNcsM99EPUnPqdbeGc87m0rAgMBAAGjXDBaMFgGA1UdAQRRME+AEHZWPrWtJd7YZ431hCg7YFShKTAnMSUwIwYDVQQDHhwAYwBoAG0AYQBpAEAAdgBpAHMAYQAuAGMAbwBtghBcl+Pf3+U4pk13nVD9nwQQMAkGBSsOAwIdBQADgYEAbUKYCkuIKS9QQ2mFcMYREIm2l+Xg8/JXv+GBVQJkOKoscY4iNDFA/bQlogf9LLU84THwNRnsvV3Prv7RTY81gq0dtC8zYcAaAkCHII3yqMnJ4AOu6EOW9kJk232gSE7WlCtHbfLSKfuSgQX8KXQYuZLk2Rr63N8ApXsXwBL3cJ0xgeAwgd0CAQEwOzAnMSUwIwYDVQQDHhwAYwBoAG0AYQBpAEAAdgBpAHMAYQAuAGMAbwBtAhBcl+Pf3+U4pk13nVD9nwQQMAkGBSsOAwIaBQAwDQYJKoZIhvcNAQEBBQAEgYBaK3ElOstbH8WooseDABf+Jg/129JcIawm7c6Vxn7ZasNbAq3tAt8Pty+uQCgssXqZkLA7kz2GzMolNtv9wYmu9Ujwar1PHYS+B/oGnoz591wjagXWRz0nMo5y3O1KzX0d8CRHAVa88SrV1a5JIiRev3oStIqwv5xuZldag6Tr8w=='
+ },
+ payment_instrument_name: 'SomeBank Points Card',
+ payment_network: 'MasterCard',
+ transaction_identifier: 'uniqueidentifier123'
+ }.update(options)
+
+ ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data],
+ payment_instrument_name: defaults[:payment_instrument_name],
+ payment_network: defaults[:payment_network],
+ transaction_identifier: defaults[:transaction_identifier]
+ )
+ end
+
+end
diff --git a/test/remote/gateways/remote_authorize_net_arb_test.rb b/test/remote/gateways/remote_authorize_net_arb_test.rb
new file mode 100644
index 00000000000..5a04f9de419
--- /dev/null
+++ b/test/remote/gateways/remote_authorize_net_arb_test.rb
@@ -0,0 +1,60 @@
+require 'test_helper'
+
+class AuthorizeNetArbTest < Test::Unit::TestCase
+ def setup
+ @gateway = AuthorizeNetArbGateway.new(fixtures(:authorize_net))
+ @amount = 100
+ @credit_card = credit_card('4242424242424242')
+ @check = check
+
+ @options = {
+ :amount => 100,
+ :subscription_name => 'Test Subscription 1',
+ :credit_card => @credit_card,
+ :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'),
+ :interval => {
+ :length => 1,
+ :unit => :months
+ },
+ :duration => {
+ :start_date => Date.today,
+ :occurrences => 1
+ }
+ }
+ end
+
+ def test_successful_recurring
+ assert response = @gateway.recurring(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+
+ subscription_id = response.authorization
+
+ assert response = @gateway.update_recurring(:subscription_id => subscription_id, :amount => @amount * 2)
+ assert_success response
+
+ assert response = @gateway.status_recurring(subscription_id)
+ assert_success response
+
+ assert response = @gateway.cancel_recurring(subscription_id)
+ assert_success response
+ end
+
+ def test_recurring_should_fail_expired_credit_card
+ @credit_card.year = 2004
+ assert response = @gateway.recurring(@amount, @credit_card, @options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'E00018', response.params['code']
+ end
+
+ def test_bad_login
+ gateway = AuthorizeNetArbGateway.new(
+ :login => 'X',
+ :password => 'Y'
+ )
+
+ assert response = gateway.recurring(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_authorize_net_cim_test.rb b/test/remote/gateways/remote_authorize_net_cim_test.rb
index 24a173d4dee..124b81c7753 100644
--- a/test/remote/gateways/remote_authorize_net_cim_test.rb
+++ b/test/remote/gateways/remote_authorize_net_cim_test.rb
@@ -100,11 +100,11 @@ def test_successful_create_customer_profile_transaction_auth_only_and_then_captu
assert response.test?
assert_success response
assert_equal response.authorization, response.params['direct_response']['transaction_id']
- assert_equal "This transaction has been approved.", response.params['direct_response']['message']
+ assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message']
assert response.params['direct_response']['approval_code'] =~ /\w{6}/
- assert_equal "auth_only", response.params['direct_response']['transaction_type']
- assert_equal "100.00", response.params['direct_response']['amount']
- assert_match /\d+/, response.params['direct_response']['transaction_id']
+ assert_equal 'auth_only', response.params['direct_response']['transaction_type']
+ assert_equal '100.00', response.params['direct_response']['amount']
+ assert_match %r{\d+}, response.params['direct_response']['transaction_id']
approval_code = response.params['direct_response']['approval_code']
@@ -123,10 +123,10 @@ def test_successful_create_customer_profile_transaction_auth_only_and_then_captu
assert response.test?
assert_success response
assert_equal response.authorization, response.params['direct_response']['transaction_id']
- assert_equal "This transaction has been approved.", response.params['direct_response']['message']
+ assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message']
assert_equal approval_code, response.params['direct_response']['approval_code']
- assert_equal "capture_only", response.params['direct_response']['transaction_type']
- assert_equal "100.00", response.params['direct_response']['amount']
+ assert_equal 'capture_only', response.params['direct_response']['transaction_type']
+ assert_equal '100.00', response.params['direct_response']['amount']
end
def test_successful_create_customer_profile_transaction_auth_capture_request
@@ -146,6 +146,7 @@ def test_successful_create_customer_profile_transaction_auth_capture_request
:description => 'Test Order Description',
:purchase_order_number => '4321'
},
+ :recurring_billing => true,
:card_code => '900', # authorize.net says this is a matching CVV
:amount => @amount
}
@@ -154,10 +155,10 @@ def test_successful_create_customer_profile_transaction_auth_capture_request
assert response.test?
assert_success response
assert_equal response.authorization, response.params['direct_response']['transaction_id']
- assert_equal "This transaction has been approved.", response.params['direct_response']['message']
+ assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message']
assert response.params['direct_response']['approval_code'] =~ /\w{6}/
- assert_equal "auth_capture", response.params['direct_response']['transaction_type']
- assert_equal "100.00", response.params['direct_response']['amount']
+ assert_equal 'auth_capture', response.params['direct_response']['transaction_type']
+ assert_equal '100.00', response.params['direct_response']['amount']
assert_equal response.params['direct_response']['invoice_number'], '1234'
assert_equal response.params['direct_response']['order_description'], 'Test Order Description'
assert_equal response.params['direct_response']['purchase_order_number'], '4321'
@@ -184,7 +185,7 @@ def test_successful_create_customer_payment_profile_request
end
def test_successful_create_customer_payment_profile_request_with_bank_account
- payment_profile = @options[:profile].delete(:payment_profiles)
+ @options[:profile].delete(:payment_profiles)
assert response = @gateway.create_customer_profile(@options)
@customer_profile_id = response.authorization
@@ -253,7 +254,7 @@ def test_successful_get_customer_profile_with_multiple_payment_profiles
assert response = @gateway.create_customer_profile(@options)
@customer_profile_id = response.authorization
- assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
+ assert @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
assert response = @gateway.create_customer_payment_profile(
:customer_profile_id => @customer_profile_id,
@@ -330,6 +331,29 @@ def test_successful_get_customer_payment_profile_request
assert response.params['payment_profile']['customer_payment_profile_id'] =~ /\d+/, 'The customer_payment_profile_id should be a number'
assert_equal "XXXX#{@credit_card.last_digits}", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in #{@credit_card.last_digits}"
assert_equal @profile[:payment_profiles][:customer_type], response.params['payment_profile']['customer_type']
+ assert_equal 'XXXX', response.params['payment_profile']['payment']['credit_card']['expiration_date']
+ end
+
+ def test_successful_get_customer_payment_profile_unmasked_request
+ assert response = @gateway.create_customer_profile(@options)
+ @customer_profile_id = response.authorization
+
+ assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
+ assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id']
+
+ assert response = @gateway.get_customer_payment_profile(
+ :customer_profile_id => @customer_profile_id,
+ :customer_payment_profile_id => customer_payment_profile_id,
+ :unmask_expiration_date => true
+ )
+
+ assert response.test?
+ assert_success response
+ assert_nil response.authorization
+ assert response.params['payment_profile']['customer_payment_profile_id'] =~ /\d+/, 'The customer_payment_profile_id should be a number'
+ assert_equal "XXXX#{@credit_card.last_digits}", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in #{@credit_card.last_digits}"
+ assert_equal @profile[:payment_profiles][:customer_type], response.params['payment_profile']['customer_type']
+ assert_equal formatted_expiration_date(@credit_card), response.params['payment_profile']['payment']['credit_card']['expiration_date']
end
def test_successful_get_customer_shipping_address_request
@@ -367,7 +391,7 @@ def test_successful_update_customer_payment_profile_request
)
# The value before updating
- assert_equal "XXXX4242", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in 4242"
+ assert_equal 'XXXX4242', response.params['payment_profile']['payment']['credit_card']['card_number'], 'The card number should contain the last 4 digits of the card we passed in 4242'
# Update the payment profile
assert response = @gateway.update_customer_payment_profile(
@@ -390,7 +414,7 @@ def test_successful_update_customer_payment_profile_request
)
# Show that the payment profile was updated
- assert_equal "XXXX1234", response.params['payment_profile']['payment']['credit_card']['card_number'], "The card number should contain the last 4 digits of the card we passed in: 1234"
+ assert_equal 'XXXX1234', response.params['payment_profile']['payment']['credit_card']['card_number'], 'The card number should contain the last 4 digits of the card we passed in: 1234'
# Show that fields that were left out of the update were cleared
assert_nil response.params['payment_profile']['customer_type']
@@ -399,7 +423,7 @@ def test_successful_update_customer_payment_profile_request
masked_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => response.params['payment_profile']['payment']['credit_card']['card_number'])
# Update only the billing address with a masked card and expiration date
- assert response = @gateway.update_customer_payment_profile(
+ assert @gateway.update_customer_payment_profile(
:customer_profile_id => @customer_profile_id,
:payment_profile => {
:customer_payment_profile_id => customer_payment_profile_id,
@@ -417,7 +441,53 @@ def test_successful_update_customer_payment_profile_request
)
# Show that the billing address on the payment profile was updated
- assert_equal "Frank", response.params['payment_profile']['bill_to']['first_name'], "The billing address should contain the first name we passed in: Frank"
+ assert_equal 'Frank', response.params['payment_profile']['bill_to']['first_name'], 'The billing address should contain the first name we passed in: Frank'
+ end
+
+ def test_successful_update_customer_payment_profile_request_with_credit_card_last_four
+ # Create a new Customer Profile with Payment Profile
+ assert response = @gateway.create_customer_profile(@options)
+ @customer_profile_id = response.authorization
+
+ # Get the customerPaymentProfileId
+ assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
+ assert customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id']
+
+ # Get the customerPaymentProfile
+ assert response = @gateway.get_customer_payment_profile(
+ :customer_profile_id => @customer_profile_id,
+ :customer_payment_profile_id => customer_payment_profile_id
+ )
+
+ # Card number last 4 digits is 4242
+ assert_equal 'XXXX4242', response.params['payment_profile']['payment']['credit_card']['card_number'], 'The card number should contain the last 4 digits of the card we passed in 4242'
+
+ new_billing_address = response.params['payment_profile']['bill_to']
+ new_billing_address.update(:first_name => 'Frank', :last_name => 'Brown')
+
+ # Initialize credit card with only last 4 digits as the number
+ last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => '4242') # Credit card with only last four digits
+
+ # Update only the billing address with a card with the last 4 digits and expiration date
+ assert @gateway.update_customer_payment_profile(
+ :customer_profile_id => @customer_profile_id,
+ :payment_profile => {
+ :customer_payment_profile_id => customer_payment_profile_id,
+ :bill_to => new_billing_address,
+ :payment => {
+ :credit_card => last_four_credit_card
+ }
+ }
+ )
+
+ # Get the updated payment profile
+ assert response = @gateway.get_customer_payment_profile(
+ :customer_profile_id => @customer_profile_id,
+ :customer_payment_profile_id => customer_payment_profile_id
+ )
+
+ # Show that the billing address on the payment profile was updated
+ assert_equal 'Frank', response.params['payment_profile']['bill_to']['first_name'], 'The billing address should contain the first name we passed in: Frank'
end
def test_successful_update_customer_shipping_address_request
@@ -437,7 +507,7 @@ def test_successful_update_customer_shipping_address_request
assert address = response.params['address']
# The value before updating
- assert_equal "1234 Fake Street", address['address']
+ assert_equal '1234 Fake Street', address['address']
# Update the address and remove the phone_number
new_address = address.symbolize_keys.merge!(
@@ -445,7 +515,7 @@ def test_successful_update_customer_shipping_address_request
)
new_address.delete(:phone_number)
- #Update the shipping address
+ # Update the shipping address
assert response = @gateway.update_customer_shipping_address(
:customer_profile_id => @customer_profile_id,
:address => new_address
@@ -461,7 +531,7 @@ def test_successful_update_customer_shipping_address_request
)
# Show that the shipping address was updated
- assert_equal "5678 Fake Street", response.params['address']['address']
+ assert_equal '5678 Fake Street', response.params['address']['address']
# Show that fields that were left out of the update were cleared
assert_nil response.params['address']['phone_number']
end
@@ -484,7 +554,7 @@ def test_successful_validate_customer_payment_profile_request_live
assert response.test?
assert_success response
assert_equal response.authorization, response.params['direct_response']['transaction_id']
- assert_equal "This transaction has been approved.", response.params['direct_response']['message']
+ assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message']
end
def test_validate_customer_payment_profile_request_live_requires_billing_address
@@ -505,7 +575,7 @@ def test_validate_customer_payment_profile_request_live_requires_billing_address
assert response.test?
assert_failure response
- assert_equal "There is one or more missing or invalid required fields.", response.message
+ assert_equal 'There is one or more missing or invalid required fields.', response.message
end
def test_validate_customer_payment_profile_request_old_does_not_require_billing_address
@@ -526,7 +596,7 @@ def test_validate_customer_payment_profile_request_old_does_not_require_billing_
assert response.test?
assert_success response
- assert_equal "Successful.", response.message
+ assert_equal 'Successful.', response.message
end
def test_should_create_duplicate_customer_profile_transactions_with_duplicate_window_alteration
@@ -545,23 +615,59 @@ def test_should_create_duplicate_customer_profile_transactions_with_duplicate_wi
:type => :auth_capture,
:order => {
:invoice_number => key.to_s,
- :description => "Test Order Description #{key.to_s}",
+ :description => "Test Order Description #{key}",
:purchase_order_number => key.to_s
},
:amount => @amount
},
- :extra_options => { "x_duplicate_window" => 1 }
+ :extra_options => { 'x_duplicate_window' => 1 }
}
assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction)
assert_success response
- assert_equal "Successful.", response.message
+ assert_equal 'Successful.', response.message
sleep(5)
assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction)
assert_success response
- assert_equal "Successful.", response.message
+ assert_equal 'Successful.', response.message
+ assert_nil response.error_code
+ end
+
+ def test_should_not_create_duplicate_customer_profile_transactions_without_duplicate_window_alteration
+ assert response = @gateway.create_customer_profile(@options)
+ @customer_profile_id = response.authorization
+
+ assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
+ assert @customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id']
+
+ key = (Time.now.to_f * 1000000).to_i.to_s
+
+ customer_profile_transaction = {
+ :transaction => {
+ :customer_profile_id => @customer_profile_id,
+ :customer_payment_profile_id => @customer_payment_profile_id,
+ :type => :auth_capture,
+ :order => {
+ :invoice_number => key.to_s,
+ :description => "Test Order Description #{key}",
+ :purchase_order_number => key.to_s
+ },
+ :amount => @amount
+ }
+ }
+
+ assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction)
+ assert_success response
+ assert_equal 'Successful.', response.message
+
+ sleep(5)
+
+ assert response = @gateway.create_customer_profile_transaction(customer_profile_transaction)
+ assert_failure response
+ assert_equal 'A duplicate transaction has been submitted.', response.message
+ assert_equal 'E00027', response.error_code
end
def test_should_create_customer_profile_transaction_auth_capture_and_then_void_request
@@ -660,7 +766,7 @@ def test_should_create_customer_profile_transaction_auth_only_and_then_prior_aut
end
def get_and_validate_customer_payment_profile_request_with_bank_account_response
- payment_profile = @options[:profile].delete(:payment_profiles)
+ @options[:profile].delete(:payment_profiles)
assert response = @gateway.create_customer_profile(@options)
@customer_profile_id = response.authorization
@@ -715,7 +821,7 @@ def get_and_validate_auth_capture_response
:type => :auth_capture,
:order => {
:invoice_number => key.to_s,
- :description => "Test Order Description #{key.to_s}",
+ :description => "Test Order Description #{key}",
:purchase_order_number => key.to_s
},
:amount => @amount
@@ -725,12 +831,12 @@ def get_and_validate_auth_capture_response
assert response.test?
assert_success response
assert_equal response.authorization, response.params['direct_response']['transaction_id']
- assert_equal "This transaction has been approved.", response.params['direct_response']['message']
+ assert_match %r{(?:(TESTMODE) )?This transaction has been approved.}, response.params['direct_response']['message']
assert response.params['direct_response']['approval_code'] =~ /\w{6}/
- assert_equal "auth_capture", response.params['direct_response']['transaction_type']
- assert_equal "100.00", response.params['direct_response']['amount']
+ assert_equal 'auth_capture', response.params['direct_response']['transaction_type']
+ assert_equal '100.00', response.params['direct_response']['amount']
assert_equal response.params['direct_response']['invoice_number'], key.to_s
- assert_equal response.params['direct_response']['order_description'], "Test Order Description #{key.to_s}"
+ assert_equal response.params['direct_response']['order_description'], "Test Order Description #{key}"
assert_equal response.params['direct_response']['purchase_order_number'], key.to_s
return response
end
@@ -744,28 +850,27 @@ def get_and_validate_auth_only_response
assert response = @gateway.get_customer_profile(:customer_profile_id => @customer_profile_id)
@customer_payment_profile_id = response.params['profile']['payment_profiles']['customer_payment_profile_id']
assert response = @gateway.create_customer_profile_transaction(
- :transaction => {
- :customer_profile_id => @customer_profile_id,
- :customer_payment_profile_id => @customer_payment_profile_id,
- :type => :auth_only,
- :order => {
+ :transaction => {
+ :customer_profile_id => @customer_profile_id,
+ :customer_payment_profile_id => @customer_payment_profile_id,
+ :type => :auth_only,
+ :order => {
:invoice_number => key.to_s,
- :description => "Test Order Description #{key.to_s}",
+ :description => "Test Order Description #{key}",
:purchase_order_number => key.to_s
},
- :amount => @amount
- }
+ :amount => @amount
+ }
)
assert response.test?
assert_success response
assert_equal response.authorization, response.params['direct_response']['transaction_id']
assert response.params['direct_response']['approval_code'] =~ /\w{6}/
- assert_equal "auth_only", response.params['direct_response']['transaction_type']
- assert_equal "100.00", response.params['direct_response']['amount']
+ assert_equal 'auth_only', response.params['direct_response']['transaction_type']
+ assert_equal '100.00', response.params['direct_response']['amount']
return response
end
-
end
diff --git a/test/remote/gateways/remote_authorize_net_test.rb b/test/remote/gateways/remote_authorize_net_test.rb
index a18b04e3d36..2b239ad18bb 100644
--- a/test/remote/gateways/remote_authorize_net_test.rb
+++ b/test/remote/gateways/remote_authorize_net_test.rb
@@ -1,79 +1,263 @@
require 'test_helper'
-class AuthorizeNetTest < Test::Unit::TestCase
+class RemoteAuthorizeNetTest < Test::Unit::TestCase
def setup
- Base.mode = :test
-
@gateway = AuthorizeNetGateway.new(fixtures(:authorize_net))
+
@amount = 100
- @credit_card = credit_card('4242424242424242')
+ @credit_card = credit_card('4000100011112224')
@check = check
+ @declined_card = credit_card('400030001111222')
+
@options = {
- :order_id => generate_unique_id,
- :billing_address => address,
- :description => 'Store purchase'
+ order_id: '1',
+ email: 'anet@example.com',
+ duplicate_window: 0,
+ billing_address: address,
+ description: 'Store Purchase'
}
- @recurring_options = {
- :amount => 100,
- :subscription_name => 'Test Subscription 1',
- :credit_card => @credit_card,
- :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'),
- :interval => {
- :length => 1,
- :unit => :months
+ @level_2_options = {
+ tax: {
+ amount: '100',
+ name: 'tax name',
+ description: 'tax description'
+ },
+ duty: {
+ amount: '200',
+ name: 'duty name',
+ description: 'duty description'
+ },
+ shipping: {
+ amount: '300',
+ name: 'shipping name',
+ description: 'shipping description',
},
- :duration => {
- :start_date => Date.today,
- :occurrences => 1
- }
+ tax_exempt: 'false',
+ po_number: '123'
}
+
+ @level_3_options = {
+ ship_from_address: {
+ zip: '27701',
+ country: 'US'
+ },
+ summary_commodity_code: 'CODE'
+ }
+
+ @level_2_and_3_options = @level_2_options.merge(@level_3_options)
end
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_minimal_options
+ response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, email: 'anet@example.com', billing_address: address)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_email_customer
+ response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, email_customer: true, email: 'anet@example.com', billing_address: address)
assert_success response
assert response.test?
assert_equal 'This transaction has been approved', response.message
assert response.authorization
end
+ def test_successful_purchase_with_false_email_customer
+ response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, email_customer: false, email: 'anet@example.com', billing_address: address)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_header_email_receipt
+ response = @gateway.purchase(@amount, @credit_card, duplicate_window: 0, header_email_receipt: 'subject line', email: 'anet@example.com', billing_address: address)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_line_items
+ additional_options = {
+ email: 'anet@example.com',
+ line_items: [
+ {
+ item_id: '1',
+ name: 'mug',
+ description: 'coffee',
+ quantity: '100',
+ unit_price: '10'
+ },
+ {
+ item_id: '2',
+ name: 'vase',
+ description: 'floral',
+ quantity: '200',
+ unit_price: '20'
+ }
+ ]
+ }
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_options))
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_level_3_line_item_data
+ additional_options = {
+ email: 'anet@example.com',
+ line_items: [
+ {
+ item_id: '1',
+ name: 'mug',
+ description: 'coffee',
+ quantity: '100',
+ unit_price: '10',
+ unit_of_measure: 'yards',
+ total_amount: '1000',
+ product_code: 'coupon'
+ }
+ ]
+ }
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(additional_options))
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_level_2_and_3_data
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(@level_2_and_3_options))
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ end
+
+ def test_successful_purchase_with_customer
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(customer: 'abcd_123'))
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'The credit card number is invalid', response.message
+ assert_equal 'incorrect_number', response.error_code
+ end
+
+ def test_successful_purchase_with_utf_character
+ card = credit_card('4000100011112224', last_name: 'Wåhlin')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_success response
+ assert_match %r{This transaction has been approved}, response.message
+ end
def test_successful_echeck_purchase
- assert response = @gateway.purchase(@amount, @check, @options)
+ response = @gateway.purchase(@amount, @check, @options)
assert_success response
assert response.test?
assert_equal 'This transaction has been approved', response.message
assert response.authorization
end
+ def test_card_present_purchase_with_no_data
+ no_data_credit_card = ActiveMerchant::Billing::CreditCard.new
+ response = @gateway.purchase(@amount, no_data_credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid}, response.message
+ end
+
def test_expired_credit_card
@credit_card.year = 2004
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert response.test?
assert_equal 'The credit card has expired', response.message
+ assert_equal 'expired_card', response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'This transaction has been approved', auth.message
+
+ capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_purchase_with_disable_partial_authorize
+ purchase = @gateway.purchase(46225, @credit_card, @options.merge(disable_partial_auth: true))
+ assert_success purchase
end
- def test_forced_test_mode_purchase
- gateway = AuthorizeNetGateway.new(fixtures(:authorize_net).update(:test => true))
- assert response = gateway.purchase(@amount, @credit_card, @options)
+ def test_successful_authorize_with_email_and_ip
+ options = @options.merge({email: 'hello@example.com', ip: '127.0.0.1'})
+ auth = @gateway.authorize(@amount, @credit_card, options)
+ assert_success auth
+
+ assert_equal 'This transaction has been approved', auth.message
+
+ capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'The credit card number is invalid', response.message
+ end
+
+ def test_card_present_authorize_and_capture_with_track_data_only
+ track_credit_card = ActiveMerchant::Billing::CreditCard.new(:track_data => '%B378282246310005^LONGSON/LONGBOB^1705101130504392?')
+ assert authorization = @gateway.authorize(@amount, track_credit_card, @options)
+ assert_success authorization
+
+ capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+
+ assert_equal 'This transaction has been approved', capture.message
+ end
+
+ def test_successful_echeck_authorization
+ response = @gateway.authorize(@amount, @check, @options)
assert_success response
- assert response.test?
- assert_match(/TESTMODE/, response.message)
+ assert_equal 'This transaction has been approved', response.message
assert response.authorization
end
- def test_successful_authorization
- assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_success response
+ def test_failed_echeck_authorization
+ response = @gateway.authorize(@amount, check(routing_number: '121042883'), @options)
+ assert_failure response
+ assert_equal 'The ABA code is invalid', response.message
+ assert response.authorization
+ end
+
+ def test_card_present_purchase_with_track_data_only
+ track_credit_card = ActiveMerchant::Billing::CreditCard.new(:track_data => '%B378282246310005^LONGSON/LONGBOB^1705101130504392?')
+ response = @gateway.purchase(@amount, track_credit_card, @options)
+ assert response.test?
assert_equal 'This transaction has been approved', response.message
assert response.authorization
end
- def test_successfule_echeck_authorization
- assert response = @gateway.authorize(@amount, @check, @options)
+ def test_successful_purchase_with_moto_retail_type
+ @credit_card.manual_entry = true
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
assert_success response
+ assert response.test?
assert_equal 'This transaction has been approved', response.message
assert response.authorization
end
@@ -96,80 +280,339 @@ def test_authorization_and_void
assert_equal 'This transaction has been approved', void.message
end
- def test_bad_login
- gateway = AuthorizeNetGateway.new(
- :login => 'X',
- :password => 'Y'
- )
+ def test_successful_authorization_with_moto_retail_type
+ @credit_card.manual_entry = true
+ response = @gateway.authorize(@amount, @credit_card, @options)
- assert response = gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
- assert_equal Response, response.class
- assert_equal ["action",
- "authorization_code",
- "avs_result_code",
- "card_code",
- "response_code",
- "response_reason_code",
- "response_reason_text",
- "transaction_id"], response.params.keys.sort
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ assert_success response.responses.last, 'The void should succeed'
+ end
- assert_match(/The merchant login ID or password is invalid/, response.message)
+ def test_failed_verify
+ bogus_card = credit_card('4424222222222222')
+ response = @gateway.verify(bogus_card, @options)
+ assert_failure response
+ assert_match %r{The credit card number is invalid}, response.message
+ end
- assert_equal false, response.success?
+ def test_successful_store
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+ assert response.authorization
+ assert_equal 'Successful', response.message
+ assert_equal '1', response.params['message_code']
end
- def test_using_test_request
- gateway = AuthorizeNetGateway.new(
- :login => 'X',
- :password => 'Y'
- )
+ def test_successful_store_new_payment_profile
+ assert store = @gateway.store(@credit_card)
+ assert_success store
+ assert store.authorization
- assert response = gateway.purchase(@amount, @credit_card)
+ new_card = credit_card('4424222222222222')
+ customer_profile_id, _, _ = store.authorization.split('#')
- assert_equal Response, response.class
- assert_equal ["action",
- "authorization_code",
- "avs_result_code",
- "card_code",
- "response_code",
- "response_reason_code",
- "response_reason_text",
- "transaction_id"], response.params.keys.sort
+ assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id)
+ assert_success response
+ assert_equal 'Successful', response.message
+ assert_equal '1', response.params['message_code']
+ end
- assert_match(/The merchant login ID or password is invalid/, response.message)
+ def test_failed_store_new_payment_profile
+ assert store = @gateway.store(@credit_card)
+ assert_success store
+ assert store.authorization
- assert_equal false, response.success?
+ new_card = credit_card('141241')
+ customer_profile_id, _, _ = store.authorization.split('#')
+
+ assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id)
+ assert_failure response
+ assert_equal 'The field length is invalid for Card Number', response.message
end
- def test_successful_recurring
- assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
+ def test_failed_store
+ assert response = @gateway.store(credit_card('141241'))
+ assert_failure response
+ assert_equal 'The field length is invalid for Card Number', response.message
+ assert_equal '15', response.params['message_code']
+ end
+
+ def test_successful_purchase_using_stored_card
+ response = @gateway.store(@credit_card, @options)
assert_success response
- assert response.test?
- subscription_id = response.authorization
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved.', response.message
+ end
- assert response = @gateway.update_recurring(:subscription_id => subscription_id, :amount => @amount * 2)
+ def test_successful_purchase_using_stored_card_with_delimiter
+ response = @gateway.store(@credit_card, @options.merge(delimiter: '|'))
assert_success response
- assert response = @gateway.status_recurring(subscription_id)
+ response = @gateway.purchase(@amount, response.authorization, @options.merge(delimiter: '|', description: 'description, with, commas'))
assert_success response
+ assert_equal 'This transaction has been approved.', response.message
+ assert_equal 'description, with, commas', response.params['order_description']
+ end
- assert response = @gateway.cancel_recurring(subscription_id)
+ def test_failed_purchase_using_stored_card
+ response = @gateway.store(@declined_card)
assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_failure response
+ assert_equal 'The credit card number is invalid.', response.message
+ assert_equal 'incorrect_number', response.error_code
+ assert_equal '27', response.params['message_code']
+ assert_equal '6', response.params['response_reason_code']
+ assert_match %r{Address not verified}, response.avs_result['message']
end
- def test_recurring_should_fail_expired_credit_card
- @credit_card.year = 2004
- assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
+ def test_successful_purchase_using_stored_card_new_payment_profile
+ assert store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert store.authorization
+
+ new_card = credit_card('4007000000027')
+ customer_profile_id, _, _ = store.authorization.split('#')
+
+ assert response = @gateway.store(new_card, customer_profile_id: customer_profile_id, email: 'anet@example.com', billing_address: address)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved.', response.message
+ end
+
+ def test_successful_purchase_with_stored_card_and_level_2_and_3_data
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.purchase(@amount, store_response.authorization, @options.merge(@level_2_and_3_options))
+ assert_success response
+ assert_equal 'This transaction has been approved.', response.message
+ end
+
+ def test_successful_authorize_and_capture_using_stored_card
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ auth = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success auth
+ assert_equal 'This transaction has been approved.', auth.message
+
+ capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'This transaction has been approved.', capture.message
+ end
+
+ def test_successful_authorize_and_capture_using_stored_card_with_level_2_and_3_data
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ auth = @gateway.authorize(@amount, store.authorization, @options.merge(@level_2_and_3_options))
+ assert_success auth
+ assert_equal 'This transaction has been approved.', auth.message
+
+ capture = @gateway.capture(@amount, auth.authorization, @options.merge(@level_2_and_3_options))
+ assert_success capture
+ assert_equal 'This transaction has been approved.', capture.message
+ end
+
+ def test_failed_authorize_using_stored_card
+ response = @gateway.store(@declined_card)
+ assert_success response
+
+ response = @gateway.authorize(@amount, response.authorization, @options)
+ assert_failure response
+
+ assert_equal 'The credit card number is invalid.', response.message
+ assert_equal 'incorrect_number', response.error_code
+ assert_equal '27', response.params['message_code']
+ assert_equal '6', response.params['response_reason_code']
+ assert_match %r{Address not verified}, response.avs_result['message']
+ end
+
+ def test_failed_authorize_using_wrong_token
+ response = @gateway.store(@declined_card)
+ assert_success response
+
+ responseA = @gateway.authorize(@amount, response.authorization, @options.merge(customer_payment_profile_id: 12345))
+ responseB = @gateway.authorize(@amount, response.authorization, @options.merge(customer_profile_id: 12345))
+ assert_failure responseA
+ assert_failure responseB
+
+ assert_equal 'Customer Profile ID or Customer Payment Profile ID not found', responseA.message
+ assert_equal 'Customer Profile ID or Customer Payment Profile ID not found', responseB.message
+ end
+
+ def test_failed_capture_using_stored_card
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ auth = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success auth
+
+ capture = @gateway.capture(@amount + 4000, auth.authorization, @options)
+ assert_failure capture
+ assert_match %r{The amount requested for settlement cannot be greater}, capture.message
+ end
+
+ def test_faux_successful_refund_with_billing_address
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization, @options.merge(first_name: 'Jim', last_name: 'Smith'))
+ assert_failure refund
+ assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
+ end
+
+ def test_faux_successful_refund_without_billing_address
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ @options[:billing_address] = nil
+
+ refund = @gateway.refund(@amount, purchase.authorization, @options.merge(first_name: 'Jim', last_name: 'Smith'))
+ assert_failure refund
+ assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
+ end
+
+ def test_faux_successful_refund_using_stored_card
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_failure refund
+ assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
+ end
+
+ def test_faux_successful_refund_using_stored_card_and_level_2_and_3_data
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ purchase = @gateway.purchase(@amount, store.authorization, @options.merge(@level_2_and_3_options))
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization, @options.merge(@level_2_and_3_options))
+ assert_failure refund
+ assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
+ end
+
+ def test_failed_refund_using_stored_card
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+
+ unknown_authorization = '2235494048#XXXX2224#cim_purchase'
+ refund = @gateway.refund(@amount, unknown_authorization, @options)
+ assert_failure refund
+ assert_equal 'The record cannot be found', refund.message
+ end
+
+ def test_successful_void_using_stored_card
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ auth = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success auth
+
+ void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ assert_equal 'This transaction has been approved.', void.message
+ end
+
+ def test_failed_void_using_stored_card
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ auth = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success auth
+
+ void = @gateway.void(auth.authorization, @options)
+ assert_success void
+
+ another_void = @gateway.void(auth.authorization, @options)
+ assert_failure another_void
+ assert_equal 'This transaction has already been voided.', another_void.message
+ end
+
+ def test_bad_login
+ gateway = AuthorizeNetGateway.new(
+ :login => 'X',
+ :password => 'Y'
+ )
+
+ response = gateway.purchase(@amount, @credit_card)
+ assert_failure response
+
+ assert_equal %w(
+ account_number
+ action
+ authorization_code
+ avs_result_code
+ card_code
+ cardholder_authentication_code
+ full_response_code
+ response_code
+ response_reason_code
+ response_reason_text
+ test_request
+ transaction_id
+ ), response.params.keys.sort
+
+ assert_equal 'User authentication failed due to invalid authentication values', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(20, '23124#1234')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
assert_failure response
- assert response.test?
- assert_equal 'E00018', response.params['code']
end
def test_successful_purchase_with_solution_id
ActiveMerchant::Billing::AuthorizeNetGateway.application_id = 'A1000000'
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert response.test?
assert_equal 'This transaction has been approved', response.message
@@ -178,17 +621,127 @@ def test_successful_purchase_with_solution_id
ActiveMerchant::Billing::AuthorizeNetGateway.application_id = nil
end
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_echeck_credit
+ response = @gateway.credit(@amount, @check, @options)
+ assert_equal 'The transaction is currently under review', response.message
+ assert response.authorization
+ end
+
+ def test_successful_echeck_refund
+ purchase = @gateway.purchase(@amount, @check, @options)
+ assert_success purchase
+
+ @options.update(transaction_id: purchase.params['transaction_id'], test_request: true)
+ refund = @gateway.credit(@amount, @check, @options)
+ assert_failure refund
+ assert_match %r{The transaction cannot be found}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'The credit card number is invalid', response.message
+ assert response.authorization
+ end
+
def test_bad_currency
- @options[:currency] = "XYZ"
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, currency: 'XYZ')
assert_failure response
assert_equal 'The supplied currency code is either invalid, not supported, not allowed for this merchant or doesn\'t have an exchange rate', response.message
end
def test_usd_currency
- @options[:currency] = "USD"
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ @options[:currency] = 'USD'
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert response.authorization
end
+
+ def test_dump_transcript
+ # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options)
+ end
+
+ def test_successful_authorize_and_capture_with_network_tokenization
+ credit_card = network_tokenization_credit_card('4000100011112224',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil
+ )
+ auth = @gateway.authorize(@amount, credit_card, @options)
+ assert_success auth
+ assert_equal 'This transaction has been approved', auth.message
+
+ capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_refund_with_network_tokenization
+ credit_card = network_tokenization_credit_card('4000100011112224',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil
+ )
+
+ purchase = @gateway.purchase(@amount, credit_card, @options)
+ assert_success purchase
+
+ @options[:billing_address] = nil
+
+ refund = @gateway.refund(@amount, purchase.authorization, @options.merge(first_name: 'Jim', last_name: 'Smith'))
+ assert_failure refund
+ assert_match %r{does not meet the criteria for issuing a credit}, refund.message, 'Only allowed to refund transactions that have settled. This is the best we can do for now testing wise.'
+ end
+
+ def test_successful_credit_with_network_tokenization
+ credit_card = network_tokenization_credit_card('4000100011112224',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil
+ )
+
+ response = @gateway.credit(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_network_tokenization_transcript_scrubbing
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ :brand => 'visa',
+ :eci => '05',
+ :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.authorize(@amount, credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(credit_card.payment_cryptogram, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_purchase_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = AuthorizeNetGateway.new(login: 'unknown_login', password: 'not_right')
+ assert !gateway.verify_credentials
+ end
+
end
diff --git a/test/remote/gateways/remote_axcessms_test.rb b/test/remote/gateways/remote_axcessms_test.rb
new file mode 100644
index 00000000000..54b1c82213a
--- /dev/null
+++ b/test/remote/gateways/remote_axcessms_test.rb
@@ -0,0 +1,177 @@
+require 'test_helper'
+
+class RemoteAxcessmsTest < Test::Unit::TestCase
+ def setup
+ @gateway = AxcessmsGateway.new(fixtures(:axcessms))
+
+ @amount = 1500
+ @credit_card = credit_card('4200000000000000', month: 05, year: 2022)
+ @declined_card = credit_card('4444444444444444', month: 05, year: 2022)
+ @mode = 'CONNECTOR_TEST'
+
+ @options = {
+ order_id: generate_unique_id,
+ email: 'customer@example.com',
+ description: "Order Number #{Time.now.to_f.divmod(2473)[1]}",
+ ip: '0.0.0.0',
+ mode: @mode,
+ billing_address: address
+ }
+ end
+
+ def test_successful_authorization
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth, 'Authorize failed'
+ assert_match %r{Successful Processing - Request successfully processed}, auth.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth, 'Authorize failed'
+ assert_match %r{Successful Processing - Request successfully processed}, auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization, {mode: @mode})
+ assert_success capture, 'Capture failed'
+ assert_match %r{Successful Processing - Request successfully processed}, capture.message
+ end
+
+ def test_successful_authorize_and_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth, 'Authorize failed'
+ assert_match %r{Successful Processing - Request successfully processed}, auth.message
+
+ assert capture = @gateway.capture(@amount-30, auth.authorization, {mode: @mode})
+ assert_success capture, 'Capture failed'
+ assert_match %r{Successful Processing - Request successfully processed}, capture.message
+ end
+
+ def test_successful_authorize_and_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth, 'Authorize failed'
+ assert_match %r{Successful Processing - Request successfully processed}, auth.message
+
+ assert void = @gateway.void(auth.authorization, {mode: @mode})
+ assert_success void, 'Void failed'
+ assert_match %r{Successful Processing - Request successfully processed}, void.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_match %r{Successful Processing - Request successfully processed}, response.message
+ end
+
+ def test_successful_purchase_with_minimal_options
+ response = @gateway.purchase(@amount, @credit_card, billing_address: address)
+ assert_success response
+ assert_match %r{Successful Processing - Request successfully processed}, response.message
+ end
+
+ def test_successful_reference_purchase
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert_match %r{Successful Processing - Request successfully processed}, purchase.message
+
+ repeat_purchase = @gateway.purchase(@amount, purchase.authorization, @options)
+ assert_success repeat_purchase
+ assert_match %r{Successful Processing - Request successfully processed}, repeat_purchase.message
+ end
+
+ def test_successful_purchase_and_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase, 'Purchase failed'
+ assert_match %r{Successful Processing - Request successfully processed}, purchase.message
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, {mode: @mode})
+ assert_success refund, 'Refund failed'
+ assert_match %r{Successful Processing - Request successfully processed}, refund.message
+ end
+
+ def test_successful_purchase_and_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase, 'Purchase failed'
+ assert_match %r{Successful Processing - Request successfully processed}, purchase.message
+
+ assert refund = @gateway.refund(@amount-50, purchase.authorization, {mode: @mode})
+ assert_success refund, 'Refund failed'
+ assert_match %r{Successful Processing - Request successfully processed}, refund.message
+ end
+
+ # Failure tested
+
+ def test_utf8_description_does_not_blow_up
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(description: 'Habitación'))
+ assert_success response
+ assert_match %r{Successful Processing - Request successfully processed}, response.message
+ end
+
+ def test_failed_capture
+ assert capture = @gateway.capture(@amount, 'invalid authorization')
+ assert_failure capture
+ assert_match %r{Reference Error - capture}, capture.message
+ end
+
+ def test_failed_bigger_capture_then_authorised
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth, 'Authorize failed'
+
+ assert capture = @gateway.capture(@amount+30, auth.authorization, {mode: @mode})
+ assert_failure capture, 'Capture failed'
+ assert_match %r{PA value exceeded}, capture.message
+ end
+
+ def test_failed_authorize
+ authorize = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure authorize
+ assert_match %r{invalid creditcard}, authorize.message
+ end
+
+ def test_failed_refund
+ assert refund = @gateway.refund(@amount, 'invalid authorization', {mode: @mode})
+ assert_failure refund
+ assert_match %r{Configuration Validation - Invalid payment data}, refund.message
+ end
+
+ def test_failed_void
+ void = @gateway.void('invalid authorization', {mode: @mode})
+ assert_failure void
+ assert_match %r{Reference Error - reversal}, void.message
+ end
+
+ def test_unauthorized_capture
+ assert response = @gateway.capture(@amount, '1234567890123456789012')
+ assert_failure response
+ assert_equal 'Reference Error - capture needs at least one successful transaction of type (PA)', response.message
+ end
+
+ def test_unauthorized_purchase_by_reference
+ assert response = @gateway.purchase(@amount, '1234567890123456789012')
+ assert_failure response
+ assert_equal 'Reference Error - reference id not existing', response.message
+ end
+
+ def test_failed_purchase_by_card
+ purchase = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure purchase
+ assert_match %r{Account Validation - invalid creditcard}, purchase.message
+ end
+
+ def test_invalid_login
+ credentials = fixtures(:axcessms).merge(password: 'invalid')
+ response = AxcessmsGateway.new(credentials).purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{success}i, response.message
+ assert_success response.responses.last, 'The void should succeed'
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@bad_credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid}i, response.message
+ end
+end
diff --git a/test/remote/gateways/remote_balanced_test.rb b/test/remote/gateways/remote_balanced_test.rb
index 1ea8135dcf3..d5930116419 100644
--- a/test/remote/gateways/remote_balanced_test.rb
+++ b/test/remote/gateways/remote_balanced_test.rb
@@ -1,7 +1,6 @@
require 'test_helper'
class RemoteBalancedTest < Test::Unit::TestCase
-
def setup
@gateway = BalancedGateway.new(fixtures(:balanced))
@@ -11,110 +10,171 @@ def setup
@declined_card = credit_card('4444444444444448')
@options = {
- :email => 'john.buyer@example.org',
- :billing_address => address,
- :description => 'Shopify Purchase'
+ email: 'john.buyer@example.org',
+ billing_address: address,
+ description: 'Shopify Purchase'
}
end
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal 'Transaction approved', response.message
- assert_equal @amount, response.params['amount']
+ assert_equal 'Success', response.message
+ assert_equal @amount, response.params['debits'][0]['amount']
end
- def test_invalid_card
- assert response = @gateway.purchase(@amount, @invalid_card, @options)
- assert_failure response
- assert_match /Customer call bank/, response.message
+ def test_successful_purchase_with_outside_token
+ outside_token = @gateway.store(@credit_card).params['cards'][0]['href']
+ response = @gateway.purchase(@amount, outside_token, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ assert_equal @amount, response.params['debits'][0]['amount']
end
- def test_invalid_email
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:email => 'invalid_email'))
+ def test_purchase_with_invalid_card
+ response = @gateway.purchase(@amount, @invalid_card, @options)
assert_failure response
- assert_match /Invalid field.*email_address/, response.message
+ assert_match %r{call bank}i, response.message
end
def test_unsuccessful_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
+ response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_match /Account Frozen/, response.message
+ assert_match %r{Account Frozen}, response.message
+ end
+
+ def test_passing_appears_on_statement
+ options = @options.merge(appears_on_statement_as: 'Homer Electric')
+ response = @gateway.purchase(@amount, @credit_card, options)
+
+ assert_success response
+ assert_equal 'BAL*Homer Electric', response.params['debits'][0]['appears_on_statement_as']
+ end
+
+ def test_passing_meta
+ options = @options.merge(meta: { 'order_number' => '12345' })
+ response = @gateway.purchase(@amount, @credit_card, options)
+
+ assert_success response
+ assert_equal options[:meta], response.params['debits'][0]['meta']
end
def test_authorize_and_capture
- amount = @amount
- assert auth = @gateway.authorize(amount, @credit_card, @options)
- assert_success auth
- assert_equal 'Transaction approved', auth.message
- assert auth.authorization
- assert capture = @gateway.capture(amount, auth.authorization)
+ assert authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Success', authorize.message
+
+ assert capture = @gateway.capture(@amount, authorize.authorization)
assert_success capture
- assert_equal amount, capture.params['amount']
- assert_equal auth.authorization, capture.params['hold']['uri']
+ assert_equal @amount, capture.params['debits'][0]['amount']
end
def test_authorize_and_capture_partial
- amount = @amount
- assert auth = @gateway.authorize(amount, @credit_card, @options)
- assert_success auth
- assert_equal 'Transaction approved', auth.message
- assert auth.authorization
- assert capture = @gateway.capture(amount / 2, auth.authorization)
+ assert authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Success', authorize.message
+
+ assert capture = @gateway.capture(@amount / 2, authorize.authorization)
assert_success capture
- assert_equal amount / 2, capture.params['amount']
- assert_equal auth.authorization, capture.params['hold']['uri']
+ assert_equal @amount / 2, capture.params['debits'][0]['amount']
end
def test_failed_capture
- assert response = @gateway.capture(@amount, '')
+ response = @gateway.capture(@amount, '')
assert_failure response
- assert response.message.index('Missing required field') != nil
end
def test_void_authorization
- amount = @amount
- assert auth = @gateway.authorize(amount, @credit_card, @options)
- assert_success auth
- assert void = @gateway.void(auth.authorization)
+ assert authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ assert void = @gateway.void(authorize.authorization)
assert_success void
- assert void.params['is_void']
+ assert void.params['card_holds'][0]['voided_at'], void.inspect
+ end
+
+ def test_voiding_a_capture_not_allowed
+ assert authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert authorize.authorization
+
+ assert capture = @gateway.capture(@amount, authorize.authorization)
+ assert_success capture
+ assert capture.authorization
+
+ void = @gateway.void(capture.authorization)
+ assert_failure void
+ assert_match %r{not found}i, void.message
+ end
+
+ def test_authorize_authorization
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
end
def test_refund_purchase
- assert debit = @gateway.purchase(@amount, @credit_card, @options)
- assert_success debit
- assert refund = @gateway.refund(nil, debit.authorization)
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal @amount, refund.params['refunds'][0]['amount']
+ end
+
+ def test_refund_authorization
+ assert auth = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success auth
+ assert auth.authorization
+ assert refund = @gateway.refund(@amount, auth.authorization)
assert_success refund
- assert_equal @amount, refund.params['amount']
end
def test_refund_partial_purchase
- assert debit = @gateway.purchase(@amount, @credit_card, @options)
- assert_success debit
- assert refund = @gateway.refund(@amount / 2, debit.authorization)
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount / 2, purchase.authorization)
assert_success refund
- assert_equal @amount / 2, refund.params['amount']
+ assert_equal @amount / 2, refund.params['refunds'][0]['amount']
end
def test_store
new_email_address = '%d@example.org' % Time.now
- assert card_uri = @gateway.store(@credit_card, {
- :email => new_email_address
+ store = @gateway.store(@credit_card, {
+ email: new_email_address
})
- assert_instance_of String, card_uri
+ assert_instance_of String, store.authorization
+ end
+
+ def test_store_and_purchase
+ store = @gateway.store(@credit_card)
+ assert_success store
+
+ purchase = @gateway.purchase(@amount, store.authorization)
+ assert_success purchase
+ end
+
+ def test_store_and_authorize
+ store = @gateway.store(@credit_card)
+ assert_success store
+
+ authorize = @gateway.authorize(@amount, store.authorization)
+ assert_success authorize
+ end
+
+ def test_passing_address_with_no_zip
+ response = @gateway.purchase(@amount, @credit_card, address(zip: nil))
+ assert_success response
end
def test_invalid_login
- begin
- BalancedGateway.new(
- :login => ''
- )
- rescue BalancedGateway::Error => ex
- msg = ex.message
- else
- msg = nil
- end
- assert_equal 'Invalid login credentials supplied', msg
+ gateway = BalancedGateway.new(
+ login: ''
+ )
+ response = gateway.store(@credit_card)
+ assert_match %r{credentials}i, response.message
end
end
diff --git a/test/remote/gateways/remote_bambora_apac_test.rb b/test/remote/gateways/remote_bambora_apac_test.rb
new file mode 100644
index 00000000000..896f0d17865
--- /dev/null
+++ b/test/remote/gateways/remote_bambora_apac_test.rb
@@ -0,0 +1,122 @@
+require 'test_helper'
+
+class RemoteBamboraApacTest < Test::Unit::TestCase
+ def setup
+ @gateway = BamboraApacGateway.new(fixtures(:bambora_apac))
+
+ @credit_card = credit_card('4005550000000001')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase',
+ }
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(200, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(200, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(105, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(200, @credit_card, @options)
+ assert_success response
+ response = @gateway.capture(200, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(105, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(200, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(200, @credit_card, @options)
+ response = @gateway.refund(200, response.authorization, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_refund
+ response = @gateway.purchase(200, @credit_card, @options)
+ response = @gateway.refund(105, response.authorization, @options)
+ assert_failure response
+ assert_equal 'Do Not Honour', response.message
+ end
+
+ def test_successful_void
+ response = @gateway.purchase(200, @credit_card, @options)
+ assert_success response
+ response = @gateway.void(200, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_void
+ response = @gateway.purchase(200, @credit_card, @options)
+ assert_success response
+ response = @gateway.void(200, 123)
+ assert_failure response
+ assert_equal 'Cannot find matching transaction to VOID', response.message
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_failed_store
+ bad_credit_card = credit_card(nil)
+
+ response = @gateway.store(bad_credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.purchase(500, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_authorize_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.authorize(500, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_invalid_login
+ gateway = BamboraApacGateway.new(
+ username: '',
+ password: ''
+ )
+ response = gateway.purchase(200, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_bank_frick_test.rb b/test/remote/gateways/remote_bank_frick_test.rb
new file mode 100644
index 00000000000..d1bb6447d4e
--- /dev/null
+++ b/test/remote/gateways/remote_bank_frick_test.rb
@@ -0,0 +1,131 @@
+require 'test_helper'
+
+class RemoteBankFrickTest < Test::Unit::TestCase
+ def setup
+ @gateway = BankFrickGateway.new(fixtures(:bank_frick))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('4222222222222')
+
+ @options = {
+ order_id: Time.now.to_i, # avoid duplicates
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+ assert_match %r{Transaction succeeded}, response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_minimal_options
+ assert response = @gateway.purchase(@amount, @credit_card, {address: address})
+ assert_success response
+ assert response.test?
+ assert_match %r{Transaction succeeded}, response.message
+ assert response.authorization
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match %r{account or user is blacklisted}, response.message
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_match %r{Transaction succeeded}, response.message
+ assert response.authorization
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_match %r{Transaction succeeded}, capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'account or user is blacklisted', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Transaction succeeded}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{account or user is blacklisted}, response.message
+ end
+
+ def test_invalid_login
+ gateway = BankFrickGateway.new(
+ sender: '',
+ channel: '',
+ userid: '',
+ userpwd: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_banwire_test.rb b/test/remote/gateways/remote_banwire_test.rb
index 398c68502a4..0af462e9a7b 100644
--- a/test/remote/gateways/remote_banwire_test.rb
+++ b/test/remote/gateways/remote_banwire_test.rb
@@ -1,4 +1,5 @@
# encoding: utf-8
+
require 'test_helper'
class RemoteBanwireTest < Test::Unit::TestCase
@@ -6,46 +7,12 @@ def setup
@gateway = BanwireGateway.new(fixtures(:banwire))
@amount = 100
- @credit_card = credit_card('5204164299999999',
- :month => 11,
- :year => 2012,
- :verification_value => '999',
- :brand => 'mastercard')
-
- @visa_credit_card = credit_card('4485814063899108',
- :month => 12,
- :year => 2016,
- :verification_value => '434')
+ @credit_card = credit_card('5204164299999999', :verification_value => '999', :brand => 'mastercard')
+ @visa_credit_card = credit_card('4485814063899108', :verification_value => '434')
@declined_card = credit_card('4000300011112220')
-
@options = {
- :order_id => '1',
- :email => "test@email.com",
- :billing_address => address,
- :description => 'Store Purchase'
- }
-
- @amex_credit_card = credit_card('375932134599999',
- :month => 10,
- :year => 2014,
- :first_name => "Banwire",
- :last_name => "Test Card",
- :verification_value => '9999',
- :brand => 'american_express')
-
- @amex_successful_options = {
- :order_id => '3',
- :email => 'test@email.com',
- :billing_address => address(:address1 => 'Horacio', :zip => '11560'),
- :description => 'Store purchase amex'
- }
-
- @amex_options = {
- :order_id => '2',
- :email => 'test@email.com',
- :billing_address => address,
- :description => 'Store purchase amex'
+ billing_address: address,
}
end
@@ -59,15 +26,21 @@ def test_successful_visa_purchase
assert_success response
end
- def test_successful_amex_purchase
- assert response = @gateway.purchase(@amount, @amex_credit_card, @amex_successful_options)
+ def test_successful_purchase_with_extra_options
+ options = {
+ order_id: '1',
+ email: 'test@email.com',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, options)
assert_success response
end
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'denied', response.message
+ assert_equal 'Pago Denegado.', response.message
end
def test_invalid_login
@@ -80,8 +53,14 @@ def test_invalid_login
assert_equal 'ID de cuenta invalido', response.message
end
- def test_invalid_amex_address
- assert response = @gateway.purchase(@amount, @amex_credit_card, @amex_options)
- assert_equal 'Error en los datos de facturación de la tarjeta, por favor inserte su dirección y código postal tal y como viene en su estado de cuenta de American Express. En caso de que persista el error, por favor comuníquese con American Express.', response.message
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
end
+
end
diff --git a/test/remote/gateways/remote_barclaycard_smartpay_test.rb b/test/remote/gateways/remote_barclaycard_smartpay_test.rb
new file mode 100644
index 00000000000..0cd7a6ab001
--- /dev/null
+++ b/test/remote/gateways/remote_barclaycard_smartpay_test.rb
@@ -0,0 +1,418 @@
+require 'test_helper'
+
+class RemoteBarclaycardSmartpayTest < Test::Unit::TestCase
+ def setup
+ @gateway = BarclaycardSmartpayGateway.new(fixtures(:barclaycard_smartpay))
+ BarclaycardSmartpayGateway.ssl_strict = false
+
+ @amount = 100
+ @error_amount = 1_000_000_000_000_000_000_000
+ @credit_card = credit_card('4111111111111111', :month => 10, :year => 2020, :verification_value => 737)
+ @declined_card = credit_card('4000300011112220', :month => 3, :year => 2030, :verification_value => 737)
+ @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa)
+ @three_ds_2_enrolled_card = credit_card('4917610000000000', brand: :visa)
+
+ @options = {
+ order_id: '1',
+ billing_address: {
+ name: 'Jim Smith',
+ address1: '100 Street',
+ company: 'Widgets Inc',
+ city: 'Ottawa',
+ state: 'ON',
+ zip: 'K1C2N6',
+ country: 'CA',
+ phone: '(555)555-5555',
+ fax: '(555)555-6666'},
+ email: 'long@bob.com',
+ customer: 'Longbob Longsen',
+ description: 'Store Purchase'
+ }
+
+ @options_with_alternate_address = {
+ order_id: '1',
+ billing_address: {
+ name: 'PU JOI SO',
+ address1: '新北市店溪路3579號139樓',
+ company: 'Widgets Inc',
+ city: '新北市',
+ zip: '231509',
+ country: 'TW',
+ phone: '(555)555-5555',
+ fax: '(555)555-6666'
+ },
+ email: 'pujoi@so.com',
+ customer: 'PU JOI SO',
+ description: 'Store Purchase'
+ }
+
+ @options_with_house_number_and_street = {
+ order_id: '1',
+ house_number: '100',
+ street: 'Top Level Drive',
+ billing_address: {
+ name: 'Jim Smith',
+ address1: '100 Top Level Dr',
+ company: 'Widgets Inc',
+ city: 'Ottawa',
+ state: 'ON',
+ zip: 'K1C2N6',
+ country: 'CA',
+ phone: '(555)555-5555',
+ fax: '(555)555-6666'
+ },
+ email: 'long@deb.com',
+ customer: 'Longdeb Longsen',
+ description: 'Store Purchase'
+ }
+
+ @options_with_no_address = {
+ order_id: '1',
+ email: 'long@bob.com',
+ customer: 'Longbob Longsen',
+ description: 'Store Purchase'
+ }
+
+ @options_with_credit_fields = {
+ order_id: '1',
+ billing_address: {
+ name: 'Jim Smith',
+ address1: '100 Street',
+ company: 'Widgets Inc',
+ city: 'Ottawa',
+ state: 'ON',
+ zip: 'K1C2N6',
+ country: 'CA',
+ phone: '(555)555-5555',
+ fax: '(555)555-6666'},
+ email: 'long@bob.com',
+ customer: 'Longbob Longsen',
+ description: 'Store Purchase',
+ date_of_birth: '1990-10-11',
+ entity_type: 'NaturalPerson',
+ nationality: 'US',
+ shopper_name: {
+ firstName: 'Longbob',
+ lastName: 'Longsen',
+ gender: 'MALE'
+ }
+ }
+
+ @avs_credit_card = credit_card('4400000000000008',
+ :month => 8,
+ :year => 2018,
+ :verification_value => 737)
+
+ @avs_address = @options.clone
+ @avs_address.update(billing_address: {
+ name: 'Jim Smith',
+ street: 'Test AVS result',
+ houseNumberOrName: '2',
+ city: 'Cupertino',
+ state: 'CA',
+ zip: '95014',
+ country: 'US'
+ })
+
+ @normalized_3ds_2_options = {
+ reference: '345123',
+ shopper_email: 'john.smith@test.com',
+ shopper_ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: address(),
+ order_id: '123',
+ stored_credential: {reason_type: 'unscheduled'},
+ three_ds_2: {
+ channel: 'browser',
+ browser_info: {
+ accept_header: 'unknown',
+ depth: 100,
+ java: false,
+ language: 'US',
+ height: 1000,
+ width: 500,
+ timezone: '-120',
+ user_agent: 'unknown'
+ },
+ notification_url: 'https://example.com/notification'
+ }
+ }
+ end
+
+ def teardown
+ BarclaycardSmartpayGateway.ssl_strict = true
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Refused', response.message
+ end
+
+ def test_successful_purchase_with_unusual_address
+ response = @gateway.purchase(@amount,
+ @credit_card,
+ @options_with_alternate_address)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_house_number_and_street
+ response = @gateway.purchase(@amount,
+ @credit_card,
+ @options.merge(street: 'Top Level Drive', house_number: '100'))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_no_address
+ response = @gateway.purchase(@amount,
+ @credit_card,
+ @options_with_no_address)
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_shopper_interaction
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(shopper_interaction: 'ContAuth'))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_device_fingerprint
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(device_fingerprint: 'abcde1123'))
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_purchase_with_shopper_statement
+ response = @gateway.purchase(
+ @amount,
+ @credit_card,
+ @options.merge(shopper_statement: 'One-year premium subscription')
+ )
+
+ assert_success response
+ assert_equal '[capture-received]', response.message
+ end
+
+ def test_successful_authorize_with_3ds
+ assert response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true))
+ assert_equal 'RedirectShopper', response.message
+ assert response.test?
+ refute response.authorization.blank?
+ refute response.params['issuerUrl'].blank?
+ refute response.params['md'].blank?
+ refute response.params['paRequest'].blank?
+ end
+
+ def test_successful_authorize_with_3ds2_browser_client_data
+ assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, @normalized_3ds_2_options)
+ assert response.test?
+ refute response.authorization.blank?
+ assert_equal response.params['resultCode'], 'IdentifyShopper'
+ refute response.params['additionalData']['threeds2.threeDS2Token'].blank?
+ refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank?
+ refute response.params['additionalData']['threeds2.threeDSMethodURL'].blank?
+ end
+
+ def test_successful_authorize_with_3ds2_app_based_request
+ three_ds_app_based_options = {
+ reference: '345123',
+ shopper_email: 'john.smith@test.com',
+ shopper_ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: address(),
+ order_id: '123',
+ stored_credential: {reason_type: 'unscheduled'},
+ three_ds_2: {
+ channel: 'app',
+ }
+ }
+
+ assert response = @gateway.authorize(@amount, @three_ds_2_enrolled_card, three_ds_app_based_options)
+ assert response.test?
+ refute response.authorization.blank?
+ assert_equal response.params['resultCode'], 'IdentifyShopper'
+ refute response.params['additionalData']['threeds2.threeDS2Token'].blank?
+ refute response.params['additionalData']['threeds2.threeDSServerTransID'].blank?
+ refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.algorithm'].blank?
+ refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.directoryServerId'].blank?
+ refute response.params['additionalData']['threeds2.threeDS2DirectoryServerInformation.publicKey'].blank?
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount - 1, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture_with_bad_auth
+ response = @gateway.capture(100, '0000000000000000', @options)
+ assert_failure response
+ assert_equal('167: Original pspReference required for this operation', response.message)
+ end
+
+ def test_failed_capture_with_bad_amount
+ response = @gateway.capture(nil, '', @options)
+ assert_failure response
+ assert_equal('137: Invalid amount specified', response.message)
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount - 1, purchase.authorization, @options)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, nil, @options)
+ assert_failure response
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options_with_credit_fields)
+ assert_success response
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(nil, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_credit_insufficient_validation
+ # This test will fail currently (the credit will succeed), but it should succeed after October 29th
+ # response = @gateway.credit(@amount, @credit_card, @options)
+ # assert_failure response
+ end
+
+ def test_successful_third_party_payout
+ response = @gateway.credit(@amount, @credit_card, @options_with_credit_fields.merge({third_party_payout: true}))
+ assert_success response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void(nil, @options)
+ assert_failure response
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Authorised', response.message
+ assert response.authorization
+ end
+
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Refused', response.message
+ end
+
+ def test_invalid_login
+ gateway = BarclaycardSmartpayGateway.new(
+ company: '',
+ merchant: '',
+ password: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_store
+ response = @gateway.store(credit_card('4111111111111111', :month => '', :year => '', :verification_value => ''), @options)
+ assert_failure response
+ assert_equal '129: Expiry Date Invalid', response.message
+ end
+
+ # AVS must be enabled on the gateway's end for the test account used
+ def test_avs_result
+ response = @gateway.authorize(@amount, @avs_credit_card, @avs_address)
+ assert_equal 'N', response.avs_result['code']
+ end
+
+ def test_avs_no_with_house_number
+ avs_nohousenumber = @avs_address
+ avs_nohousenumber[:billing_address].delete(:houseNumberOrName)
+ response = @gateway.authorize(@amount, @avs_credit_card, avs_nohousenumber)
+ assert_equal 'Z', response.avs_result['code']
+ end
+
+ def test_nonfractional_currency
+ response = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'JPY'))
+ assert_success response
+ response = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'JPY'))
+ assert_success response
+ end
+
+ def test_three_decimal_currency
+ response = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'OMR'))
+ assert_success response
+
+ response = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'OMR'))
+ assert_success response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:password], clean_transcript)
+ end
+
+ def test_proper_error_response_handling
+ response = @gateway.purchase(@error_amount, @credit_card, @options)
+ assert_equal('702: Internal error', response.message)
+ assert_not_equal(response.message, 'Unable to communicate with the payment system.')
+ end
+end
diff --git a/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb b/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb
index bc81ac22fff..dd06e16d724 100644
--- a/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb
+++ b/test/remote/gateways/remote_barclays_epdq_extra_plus_test.rb
@@ -1,11 +1,13 @@
# coding: utf-8
+
require 'test_helper'
class RemoteBarclaysEpdqExtraPlusTest < Test::Unit::TestCase
def setup
@gateway = BarclaysEpdqExtraPlusGateway.new(fixtures(:barclays_epdq_extra_plus))
@amount = 100
- @credit_card = credit_card('4000100011112224')
+ @credit_card = credit_card('4000100011112224', :verification_value => '987')
+ @mastercard = credit_card('5399999999999999', :brand => 'mastercard')
@declined_card = credit_card('1111111111111111')
@credit_card_d3d = credit_card('4000000000000002', :verification_value => '111')
@options = {
@@ -20,7 +22,7 @@ def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message
- assert_equal @options[:currency], response.params["currency"]
+ assert_equal @options[:currency], response.params['currency']
assert_equal @options[:order_id], response.order_id
end
@@ -30,18 +32,18 @@ def test_successful_purchase_with_minimal_info
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message
- assert_equal @options[:currency], response.params["currency"]
+ assert_equal @options[:currency], response.params['currency']
assert_equal @options[:order_id], response.order_id
end
def test_successful_purchase_with_utf8_encoding_1
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "Rémy", :last_name => "Fröåïør"), @options)
+ assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'Rémy', :last_name => 'Fröåïør'), @options)
assert_success response
assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message
end
def test_successful_purchase_with_utf8_encoding_2
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "ワタシ", :last_name => "ёжзийклмнопрсуфхцч"), @options)
+ assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'ワタシ', :last_name => 'ёжзийклмнопрсуфхцч'), @options)
assert_success response
assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, response.message
end
@@ -93,6 +95,12 @@ def test_unsuccessful_purchase
assert_equal 'No brand', response.message
end
+ def test_successful_authorize_with_mastercard
+ assert auth = @gateway.authorize(@amount, @mastercard, @options)
+ assert_success auth
+ assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, auth.message
+ end
+
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -211,10 +219,20 @@ def test_invalid_login
:login => '',
:user => '',
:password => '',
- :signature_encryptor => "none"
+ :signature_encryptor => 'none'
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Some of the data entered is incorrect. please retry.', response.message
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
end
diff --git a/test/remote/gateways/remote_barclays_epdq_test.rb b/test/remote/gateways/remote_barclays_epdq_test.rb
deleted file mode 100644
index 346390280c8..00000000000
--- a/test/remote/gateways/remote_barclays_epdq_test.rb
+++ /dev/null
@@ -1,212 +0,0 @@
-require 'test_helper'
-
-class RemoteBarclaysEpdqTest < Test::Unit::TestCase
- def setup
- @gateway = BarclaysEpdqGateway.new(fixtures(:barclays_epdq).merge(:test => true))
-
- @approved_amount = 3900
- @declined_amount = 4205
- @approved_card = credit_card('4715320629000001')
- @declined_card = credit_card('4715320629000027')
-
- @options = {
- :order_id => generate_unique_id,
- :billing_address => address,
- :description => 'Store Purchase'
- }
-
- @periodic_options = @options.merge(
- :payment_number => 1,
- :total_payments => 3,
- :group_id => 'MyTestPaymentGroup'
- )
- end
-
- def test_successful_purchase
- assert response = @gateway.purchase(@approved_amount, @approved_card, @options)
- assert_success response
- assert_equal 'Approved.', response.message
- assert_equal @options[:order_id], response.authorization
- assert_no_match(/PaymentNoFraud/, response.params["raw_response"])
- end
-
- def test_successful_purchase_with_mastercard
- assert response = @gateway.purchase(@approved_amount, credit_card('5301250070000050', :brand => :master), @options)
- assert_success response
- end
-
- def test_successful_purchase_with_maestro
- assert response = @gateway.purchase(@approved_amount, credit_card('675938410597000022', :brand => :maestro, :issue_number => '5'), @options)
- assert_success response
- end
-
- def test_successful_purchase_with_switch
- assert response = @gateway.purchase(@approved_amount, credit_card('6759560045005727054', :brand => :switch, :issue_number => '1'), @options)
- assert_success response
- end
-
- def test_successful_purchase_with_minimal_options
- delete_address_details!
-
- assert response = @gateway.purchase(@approved_amount, @approved_card, @options)
- assert_success response
- assert_equal 'Approved.', response.message
- assert_equal @options[:order_id], response.authorization
- assert_no_match(/PaymentNoFraud/, response.params["raw_response"])
- end
-
- def test_successful_purchase_with_no_fraud
- @options[:no_fraud] = true
- assert response = @gateway.purchase(@approved_amount, @approved_card, @options)
- assert_success response
- assert_equal 'Approved.', response.message
- assert_equal @options[:order_id], response.authorization
- assert_match(/PaymentNoFraud/, response.params["raw_response"])
- end
-
- def test_successful_purchase_with_no_fraud_and_minimal_options
- delete_address_details!
-
- @options[:no_fraud] = true
- assert response = @gateway.purchase(@approved_amount, @approved_card, @options)
- assert_success response
- assert_equal 'Approved.', response.message
- assert_equal @options[:order_id], response.authorization
- assert_match(/PaymentNoFraud/, response.params["raw_response"])
- end
-
- def test_successful_purchase_with_no_address_or_order_id_or_description
- assert response = @gateway.purchase(@approved_amount, @approved_card, {})
- assert_success response
- assert_equal 'Approved.', response.message
- end
-
- def test_unsuccessful_purchase
- assert response = @gateway.purchase(@declined_amount, @declined_card, @options)
- assert_failure response
- assert_match(/^Declined/, response.message)
- end
-
- def test_credit_new_order
- assert response = @gateway.credit(@approved_amount, @approved_card, @options)
- assert_success response
- assert_equal 'Approved.', response.message
- end
-
- def test_refund_existing_order
- assert response = @gateway.purchase(@approved_amount, @approved_card, @options)
- assert_success response
-
- assert refund = @gateway.refund(@approved_amount, response.authorization)
- assert_success refund
- assert_equal 'Approved.', refund.message
- end
-
- def test_refund_nonexisting_order_fails
- assert refund = @gateway.refund(@approved_amount, "DOESNOTEXIST", @options)
- assert_failure refund
- assert_match(/^Payment Mechanism CreditCard information not found/, refund.message)
- end
-
- def test_authorize_and_capture
- amount = @approved_amount
- assert auth = @gateway.authorize(amount, @approved_card, @options)
- assert_success auth
- assert_equal 'Approved.', auth.message
- assert auth.authorization
- assert_equal @options[:order_id], auth.authorization
-
- assert capture = @gateway.capture(amount, auth.authorization)
- assert_success capture
- assert_equal 'Approved.', capture.message
- end
-
- def test_authorize_and_capture_without_order_id
- @options.delete(:order_id)
- amount = @approved_amount
- assert auth = @gateway.authorize(amount, @approved_card, @options)
- assert_success auth
- assert_equal 'Approved.', auth.message
- assert auth.authorization
- assert_match(/[0-9a-f\-]{36}/, auth.authorization)
-
- assert capture = @gateway.capture(amount, auth.authorization)
- assert_success capture
- assert_equal 'Approved.', capture.message
- end
-
- def test_authorize_void_and_failed_capture
- amount = @approved_amount
- assert auth = @gateway.authorize(amount, @approved_card, @options)
- assert_success auth
-
- assert void = @gateway.void(auth.authorization)
- assert_success void
- assert_equal 'Approved.', void.message
-
- assert capture = @gateway.capture(amount, auth.authorization)
- assert_failure capture
- assert_match(/^Did not find a unique, qualifying transaction for Order/, capture.message)
- end
-
- def test_failed_authorize
- assert auth = @gateway.authorize(@declined_amount, @approved_card, @options)
- assert_failure auth
- assert_match(/^Declined/, auth.message)
- end
-
- def test_failed_capture
- amount = @approved_amount
- assert auth = @gateway.authorize(amount, @approved_card, @options)
- assert_success auth
-
- @too_much = amount * 10
- assert capture = @gateway.capture(@too_much, auth.authorization)
- assert_success capture
- assert_match(/^The PostAuth is not valid because the amount/, capture.message)
- end
-
- def test_three_successful_periodic_orders
- amount = @approved_amount
- assert auth1 = @gateway.purchase(amount, @approved_card, @periodic_options)
- assert auth1.success?
- assert_equal 'Approved.', auth1.message
-
- @periodic_options[:payment_number] = 2
- @periodic_options[:order_id] = generate_unique_id
- assert auth2 = @gateway.purchase(amount, @approved_card, @periodic_options)
- assert auth2.success?
- assert_equal 'Approved.', auth2.message
-
- @periodic_options[:payment_number] = 3
- @periodic_options[:order_id] = generate_unique_id
- assert auth3 = @gateway.purchase(amount, @approved_card, @periodic_options)
- assert auth3.success?
- assert_equal 'Approved.', auth3.message
- end
-
- def test_invalid_login
- gateway = BarclaysEpdqGateway.new(
- :login => 'NOBODY',
- :password => 'HOME',
- :client_id => '1234'
- )
- assert response = gateway.purchase(@approved_amount, @approved_card, @options)
- assert_failure response
- assert_equal 'Insufficient permissions to perform requested operation.', response.message
- end
-
- protected
- def delete_address_details!
- @options[:billing_address].delete :city
- @options[:billing_address].delete :state
- @options[:billing_address].delete :country
- @options[:billing_address].delete :address1
- @options[:billing_address].delete :phone
- @options[:billing_address].delete :address1
- @options[:billing_address].delete :address2
- @options[:billing_address].delete :name
- @options[:billing_address].delete :fax
- @options[:billing_address].delete :company
- end
-end
diff --git a/test/remote/gateways/remote_be2bill_test.rb b/test/remote/gateways/remote_be2bill_test.rb
new file mode 100644
index 00000000000..6e48bdcba5e
--- /dev/null
+++ b/test/remote/gateways/remote_be2bill_test.rb
@@ -0,0 +1,59 @@
+require 'test_helper'
+
+class RemoteBe2billTest < Test::Unit::TestCase
+ def setup
+ @gateway = Be2billGateway.new(fixtures(:be2bill))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('5555557376384001')
+
+ @options = {
+ :order_id => '1',
+ :description => 'Store Purchase',
+ :client_id => '1',
+ :referrer => 'google.com',
+ :user_agent => 'Firefox 25',
+ :ip => '127.0.0.1',
+ :email => 'customer@yopmail.com'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved : The transaction has been accepted.', response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Declined (4001 - The bank refused the transaction.', response.message
+ end
+
+ def test_authorize_and_capture
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved : The transaction has been accepted.', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(amount, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Declined (1001 - The parameter "TRANSACTIONID" is missing.', response.message
+ end
+
+ def test_invalid_login
+ gateway = Be2billGateway.new(
+ :login => '',
+ :password => ''
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined (1001 - The parameter "IDENTIFIER" is missing.', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_beanstream_interac_test.rb b/test/remote/gateways/remote_beanstream_interac_test.rb
index a4a053d6b70..4f4d1b9b79e 100644
--- a/test/remote/gateways/remote_beanstream_interac_test.rb
+++ b/test/remote/gateways/remote_beanstream_interac_test.rb
@@ -1,13 +1,13 @@
require 'test_helper'
class RemoteBeanstreamInteracTest < Test::Unit::TestCase
-
+
def setup
@gateway = BeanstreamInteracGateway.new(fixtures(:beanstream_interac))
-
+
@amount = 100
-
- @options = {
+
+ @options = {
:order_id => generate_unique_id,
:billing_address => {
:name => 'xiaobo zzz',
@@ -27,19 +27,19 @@ def setup
:custom => 'reference one'
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @options)
assert_success response
- assert_equal "R", response.params["responseType"]
+ assert_equal 'R', response.params['responseType']
assert_false response.redirect.blank?
end
-
+
def test_failed_confirmation
- assert response = @gateway.confirm("")
+ assert response = @gateway.confirm('')
assert_failure response
end
-
+
def test_invalid_login
gateway = BeanstreamInteracGateway.new(
:merchant_id => '',
diff --git a/test/remote/gateways/remote_beanstream_test.rb b/test/remote/gateways/remote_beanstream_test.rb
index 2210c7f2e8a..eed3b72531f 100644
--- a/test/remote/gateways/remote_beanstream_test.rb
+++ b/test/remote/gateways/remote_beanstream_test.rb
@@ -6,40 +6,51 @@
# only work the first time you run them since the profile, if created again, becomes a duplicate. There is a setting in order settings which, when unchecked will allow the tests to be run any number
# of times without needing the manual deletion step between test runs. The setting is: Do not allow profile to be created with card data duplicated from an existing profile.
class RemoteBeanstreamTest < Test::Unit::TestCase
-
+
def setup
@gateway = BeanstreamGateway.new(fixtures(:beanstream))
-
+
# Beanstream test cards. Cards require a CVV of 123, which is the default of the credit card helper
@visa = credit_card('4030000010001234')
@declined_visa = credit_card('4003050500040005')
-
+ @visa_no_cvv = credit_card('4030000010001234', verification_value: nil)
+
@mastercard = credit_card('5100000010001004')
@declined_mastercard = credit_card('5100000020002000')
-
+
@amex = credit_card('371100001000131', {:verification_value => 1234})
- @declined_amex = credit_card('342400001000180')
-
+ @declined_amex = credit_card('342400001000180', {:verification_value => 1234})
+
# Canadian EFT
@check = check(
:institution_number => '001',
:transit_number => '26729'
)
-
+
@amount = 1500
-
- @options = {
+
+ @options = {
:order_id => generate_unique_id,
:billing_address => {
:name => 'xiaobo zzz',
:phone => '555-555-5555',
- :address1 => '1234 Levesque St.',
+ :address1 => '4444 Levesque St.',
:address2 => 'Apt B',
:city => 'Montreal',
- :state => 'QC',
+ :state => 'Quebec',
:country => 'CA',
:zip => 'H2C1X8'
},
+ :shipping_address => {
+ :name => 'shippy',
+ :phone => '888-888-8888',
+ :address1 => '777 Foster Street',
+ :address2 => 'Ste #100',
+ :city => 'Durham',
+ :state => 'North Carolina',
+ :country => 'US',
+ :zip => '27701'
+ },
:email => 'xiaobozzz@example.com',
:subtotal => 800,
:shipping => 100,
@@ -52,12 +63,32 @@ def setup
:interval => { :unit => :months, :length => 1 },
:occurences => 5)
end
-
+
def test_successful_visa_purchase
assert response = @gateway.purchase(@amount, @visa, @options)
assert_success response
assert_false response.authorization.blank?
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_visa_purchase_with_recurring
+ assert response = @gateway.purchase(@amount, @visa, @options.merge(recurring: true))
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_visa_purchase_no_cvv
+ assert response = @gateway.purchase(@amount, @visa_no_cvv, @options.merge(recurring: true))
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal 'Approved', response.message
+ end
+
+ def test_unsuccessful_visa_purchase_with_no_cvv
+ assert response = @gateway.purchase(@amount, @visa_no_cvv, @options)
+ assert_failure response
+ assert_equal 'Card CVD is invalid.', response.message
end
def test_unsuccessful_visa_purchase
@@ -65,12 +96,19 @@ def test_unsuccessful_visa_purchase
assert_failure response
assert_equal 'DECLINE', response.message
end
-
+
def test_successful_mastercard_purchase
assert response = @gateway.purchase(@amount, @mastercard, @options)
assert_success response
assert_false response.authorization.blank?
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_mastercard_purchase_with_recurring
+ assert response = @gateway.purchase(@amount, @mastercard, @options.merge(recurring: true))
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal 'Approved', response.message
end
def test_unsuccessful_mastercard_purchase
@@ -78,12 +116,19 @@ def test_unsuccessful_mastercard_purchase
assert_failure response
assert_equal 'DECLINE', response.message
end
-
+
def test_successful_amex_purchase
assert response = @gateway.purchase(@amount, @amex, @options)
assert_success response
assert_false response.authorization.blank?
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_amex_purchase_with_recurring
+ assert response = @gateway.purchase(@amount, @amex, @options.merge(recurring: true))
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal 'Approved', response.message
end
def test_unsuccessful_amex_purchase
@@ -92,82 +137,163 @@ def test_unsuccessful_amex_purchase
assert_equal 'DECLINE', response.message
end
+ def test_successful_purchase_with_state_in_iso_format
+ assert response = @gateway.purchase(@amount, @visa, @options.merge(billing_address: address, shipping_address: address))
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_only_email
+ options = {
+ :order_id => generate_unique_id,
+ :email => 'xiaobozzz@example.com',
+ :shipping_email => 'ship@mail.com'
+ }
+
+ assert response = @gateway.purchase(@amount, @visa, options)
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_no_addresses
+ @options[:billing_address] = {}
+ @options[:shipping_address] = {}
+ assert response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase_due_to_invalid_billing_state
+ @options[:billing_address][:state] = 'Invalid'
+ assert response = @gateway.purchase(@amount, @visa, @options)
+ assert_failure response
+ assert_match %r{province does not match country}, response.message
+ end
+
+ def test_failed_purchase_due_to_invalid_shipping_state
+ @options[:shipping_address][:state] = 'North'
+ assert response = @gateway.purchase(@amount, @visa, @options)
+ assert_failure response
+ assert_match %r{Invalid shipping province}, response.message
+ end
+
+ def test_failed_purchase_due_to_missing_country_with_state
+ @options[:shipping_address][:country] = nil
+ assert response = @gateway.purchase(@amount, @visa, @options)
+ assert_failure response
+ assert_match %r{Invalid shipping country id}, response.message
+ end
+
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @visa, @options)
assert_success auth
- assert_equal "Approved", auth.message
+ assert_equal 'Approved', auth.message
assert_false auth.authorization.blank?
-
+
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
assert_false capture.authorization.blank?
end
-
+
+ def test_authorize_and_capture_with_recurring
+ assert auth = @gateway.authorize(@amount, @visa, @options.merge(recurring: true))
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert_false auth.authorization.blank?
+
+ assert capture = @gateway.capture(@amount, auth.authorization, recurring: true)
+ assert_success capture
+ assert_false capture.authorization.blank?
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@visa, @options)
+ assert_success response
+ assert_match 'Approved', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_amex, @options)
+ assert_failure response
+ assert_match 'DECLINE', response.message
+ end
+
def test_failed_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
- assert_no_match %r{You are not authorized}, response.message, "You need to enable username/password validation"
+ assert_no_match %r{You are not authorized}, response.message, 'You need to enable username/password validation'
assert_match %r{Missing or invalid adjustment id.}, response.message
end
-
+
def test_successful_purchase_and_void
assert purchase = @gateway.purchase(@amount, @visa, @options)
assert_success purchase
-
+
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ end
+
+ def test_successful_purchase_and_void_with_recurring
+ assert purchase = @gateway.purchase(@amount, @visa, @options.merge(recurring: true))
+ assert_success purchase
+
assert void = @gateway.void(purchase.authorization)
assert_success void
end
-
+
def test_successful_purchase_and_refund_and_void_refund
assert purchase = @gateway.purchase(@amount, @visa, @options)
assert_success purchase
-
+
assert refund = @gateway.refund(@amount, purchase.authorization)
assert_success purchase
-
+
assert void = @gateway.void(refund.authorization)
assert_success void
end
-
+
def test_successful_check_purchase
assert response = @gateway.purchase(@amount, @check, @options)
assert_success response
assert response.test?
assert_false response.authorization.blank?
end
-
+
def test_successful_check_purchase_and_refund
assert purchase = @gateway.purchase(@amount, @check, @options)
assert_success purchase
-
+
assert refund = @gateway.refund(@amount, purchase.authorization)
- assert_success credit
+ assert_success refund
end
-
+
def test_successful_recurring
assert response = @gateway.recurring(@amount, @visa, @recurring_options)
assert_success response
assert response.test?
assert_false response.authorization.blank?
end
-
+
def test_successful_update_recurring
assert response = @gateway.recurring(@amount, @visa, @recurring_options)
assert_success response
assert response.test?
assert_false response.authorization.blank?
-
- assert response = @gateway.update_recurring(@amount + 500, @visa, @recurring_options.merge(:account_id => response.params["rbAccountId"]))
+
+ assert response = @gateway.update_recurring(@amount + 500, @visa, @recurring_options.merge(:account_id => response.params['rbAccountId']))
assert_success response
end
-
+
def test_successful_cancel_recurring
assert response = @gateway.recurring(@amount, @visa, @recurring_options)
assert_success response
assert response.test?
assert_false response.authorization.blank?
-
- assert response = @gateway.cancel_recurring(:account_id => response.params["rbAccountId"])
+
+ assert response = @gateway.cancel_recurring(:account_id => response.params['rbAccountId'])
assert_success response
end
@@ -179,22 +305,29 @@ def test_invalid_login
)
assert response = gateway.purchase(@amount, @visa, @options)
assert_failure response
- assert_equal 'Invalid merchant id (merchant_id = 0)', response.message
+ assert_equal 'merchantid=Invalid merchant id (merchant_id = )', response.message
end
-
+
def test_successful_add_to_vault_with_store_method
- assert response = @gateway.store(@visa,@options)
+ assert response = @gateway.store(@visa, @options)
assert_equal 'Operation Successful', response.message
assert_success response
- assert_not_nil response.params["customer_vault_id"]
+ assert_not_nil response.params['customer_vault_id']
end
-
+
def test_add_to_vault_with_custom_vault_id_with_store_method
- @options[:vault_id] = rand(100000)+10001
+ @options[:vault_id] = rand(10001..110000)
assert response = @gateway.store(@visa, @options.dup)
assert_equal 'Operation Successful', response.message
assert_success response
- assert_equal @options[:vault_id], response.params["customer_vault_id"].to_i
+ assert_equal @options[:vault_id], response.params['customer_vault_id'].to_i
+ end
+
+ def test_successful_add_to_vault_with_single_use_token
+ assert response = @gateway.store(generate_single_use_token(@visa))
+ assert_equal 'Operation Successful', response.message, response.inspect
+ assert_success response
+ assert_not_nil response.params['customer_vault_id']
end
def test_update_vault
@@ -203,41 +336,81 @@ def test_update_vault
assert_success response
assert_equal 'Operation Successful', response.message
end
-
+
+ def test_update_vault_with_single_use_token
+ test_add_to_vault_with_custom_vault_id_with_store_method
+ assert response = @gateway.update(@options[:vault_id], generate_single_use_token(@mastercard))
+ assert_success response
+ assert_equal 'Operation Successful', response.message
+ end
+
def test_delete_from_vault
test_add_to_vault_with_custom_vault_id_with_store_method
assert response = @gateway.delete(@options[:vault_id])
assert_success response
assert_equal 'Operation Successful', response.message
end
-
+
def test_delete_from_vault_with_unstore_method
test_add_to_vault_with_custom_vault_id_with_store_method
assert response = @gateway.unstore(@options[:vault_id])
assert_success response
assert_equal 'Operation Successful', response.message
end
-
+
def test_successful_add_to_vault_and_use
test_add_to_vault_with_custom_vault_id_with_store_method
assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options)
assert_equal 'Approved', second_response.message
- assert second_response.success?
+ assert second_response.success?
end
-
+
def test_unsuccessful_visa_with_vault
test_add_to_vault_with_custom_vault_id_with_store_method
assert response = @gateway.update(@options[:vault_id], @declined_visa)
assert_success response
-
+
assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options)
assert_equal 'DECLINE', second_response.message
end
-
+
def test_unsuccessful_closed_profile_charge
test_delete_from_vault
assert second_response = @gateway.purchase(@amount*2, @options[:vault_id], @options)
assert_failure second_response
- assert_equal "Invalid customer code.", second_response.message
+ assert_match %r{Invalid customer code\.}, second_response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @visa, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@visa.number, clean_transcript)
+ assert_scrubbed(@visa.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:password], clean_transcript)
+ assert_scrubbed(@gateway.options[:api_key], clean_transcript)
+ end
+
+ private
+
+ def generate_single_use_token(credit_card)
+ uri = URI.parse('https://www.beanstream.com/scripts/tokenization/tokens')
+ http = Net::HTTP.new(uri.host, uri.port)
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+
+ request = Net::HTTP::Post.new(uri.path)
+ request.content_type = 'application/json'
+ request.body = {
+ 'number' => credit_card.number,
+ 'expiry_month' => '01',
+ 'expiry_year' => (Time.now.year + 1) % 100,
+ 'cvd' => credit_card.verification_value,
+ }.to_json
+
+ response = http.request(request)
+ JSON.parse(response.body)['token']
end
end
diff --git a/test/remote/gateways/remote_blue_pay_test.rb b/test/remote/gateways/remote_blue_pay_test.rb
index ab0ced7e927..4c0fc7b5477 100644
--- a/test/remote/gateways/remote_blue_pay_test.rb
+++ b/test/remote/gateways/remote_blue_pay_test.rb
@@ -1,172 +1,183 @@
-require 'test_helper'
-
-class BluePayTest < Test::Unit::TestCase
- def setup
- Base.mode = :test
-
- @gateway = BluePayGateway.new(fixtures(:blue_pay))
- @amount = 100
- @credit_card = credit_card('4242424242424242')
- @options = {
- :order_id => generate_unique_id,
- :billing_address => address,
- :description => 'Store purchase'
- }
-
- @recurring_options = {
- :rebill_amount => 100,
- :rebill_start_date => Date.today,
- :rebill_expression => '1 DAY',
- :rebill_cycles => '4',
- :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'),
- :duplicate_override => 1
- }
- end
-
- def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert response.test?
- assert_equal 'This transaction has been approved', response.message
- assert response.authorization
- end
-
- # The included test account credentials do not support ACH processor.
- def test_successful_purchase_with_check
- assert response = @gateway.purchase(@amount, check, @options.merge(:email=>'foo@example.com'))
- assert_success response
- assert response.test?
- assert_equal 'This transaction has been approved', response.message
- assert response.authorization
- end
-
- def test_expired_credit_card
- @credit_card.year = 2004
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert response.test?
- assert_equal 'The credit card has expired', response.message
- end
-
- def test_forced_test_mode_purchase
- gateway = BluePayGateway.new(fixtures(:blue_pay).update(:test => true))
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert response.test?
- assert_equal(true, response.test)
- assert response.authorization
- end
-
- def test_successful_authorization
- assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_success response
- assert_equal 'This transaction has been approved', response.message
- assert response.authorization
- end
-
- def test_that_we_understand_and_parse_all_keys_in_standard_response
- assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_success response
-
- response_keys = response.params.keys.map(&:to_sym)
- unknown_response_keys = response_keys - BluePayGateway::FIELD_MAP.values
- missing_response_keys = BluePayGateway::FIELD_MAP.values - response_keys
-
- assert_empty unknown_response_keys, "unknown_response_keys"
- assert_empty missing_response_keys, "missing response_keys"
- end
-
- def test_that_we_understand_and_parse_all_keys_in_rebilling_response
- assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
- assert_success response
- rebill_id = response.params['rebid']
- assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2)
- assert_success response
-
- response_keys = response.params.keys.map(&:to_sym)
- unknown_response_keys = response_keys - BluePayGateway::REBILL_FIELD_MAP.values
- missing_response_keys = BluePayGateway::REBILL_FIELD_MAP.values - response_keys
-
- assert_empty unknown_response_keys, "unknown_response_keys"
- assert_empty missing_response_keys, "missing response_keys"
- end
-
- def test_authorization_and_capture
- assert authorization = @gateway.authorize(@amount, @credit_card, @options)
- assert_success authorization
- assert capture = @gateway.capture(@amount, authorization.authorization)
- assert_success capture
- assert_equal 'This transaction has been approved', capture.message
- end
-
- def test_authorization_and_void
- assert authorization = @gateway.authorize(@amount, @credit_card, @options)
- assert_success authorization
-
- assert void = @gateway.void(authorization.authorization)
- assert_success void
- assert_equal 'This transaction has been approved', void.message
- end
-
- def test_bad_login
- gateway = BluePayGateway.new(
- :login => 'X',
- :password => 'Y'
- )
- assert response = gateway.purchase(@amount, @credit_card)
-
- assert_equal Response, response.class
- assert_match(/The merchant login ID or password is invalid/, response.message)
- assert_failure response
- end
-
- def test_using_test_request
- gateway = BluePayGateway.new(
- :login => 'X',
- :password => 'Y'
- )
- assert response = gateway.purchase(@amount, @credit_card)
- assert_equal Response, response.class
-
- assert_match(/The merchant login ID or password is invalid/, response.message)
- assert_failure response
- end
-
- def test_successful_recurring
- assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
- assert_success response
- assert response.test?
-
- rebill_id = response.params['rebid']
-
- assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2)
- assert_success response
-
- assert response = @gateway.status_recurring(rebill_id)
- assert_success response
- assert_equal response.params['status'], 'active'
-
- assert response = @gateway.cancel_recurring(rebill_id)
- assert_success response
- assert_equal response.params['status'], 'stopped'
- end
-
- def test_recurring_should_fail_expired_credit_card
- @credit_card.year = 2004
- assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
- assert_failure response
- assert response.test?
- assert_equal 'The credit card has expired', response.message
- end
-
- def test_successful_purchase_with_solution_id
- ActiveMerchant::Billing::BluePayGateway.application_id = 'A1000000'
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert response.test?
- assert_equal 'This transaction has been approved', response.message
- assert response.authorization
- ensure
- ActiveMerchant::Billing::BluePayGateway.application_id = nil
- end
-end
+require 'test_helper'
+
+class BluePayTest < Test::Unit::TestCase
+ def setup
+ Base.mode = :test
+
+ @gateway = BluePayGateway.new(fixtures(:blue_pay))
+ @amount = 100
+ @credit_card = credit_card('4242424242424242')
+ @options = {
+ :order_id => generate_unique_id,
+ :billing_address => address,
+ :description => 'Store purchase',
+ :ip => '192.168.0.1'
+ }
+
+ @recurring_options = {
+ :rebill_amount => 100,
+ :rebill_start_date => Date.today,
+ :rebill_expression => '1 DAY',
+ :rebill_cycles => '4',
+ :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'),
+ :duplicate_override => 1
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ # The included test account credentials do not support ACH processor.
+ def test_successful_purchase_with_check
+ assert response = @gateway.purchase(@amount, check, @options.merge(:email=>'foo@example.com'))
+ assert_success response
+ assert response.test?
+ assert_equal 'App ACH Sale', response.message
+ assert response.authorization
+ end
+
+ def test_expired_credit_card
+ @credit_card.year = 2004
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'The credit card has expired', response.message
+ end
+
+ def test_forced_test_mode_purchase
+ gateway = BluePayGateway.new(fixtures(:blue_pay).update(:test => true))
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal(true, response.test)
+ assert response.authorization
+ end
+
+ def test_successful_authorization
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ end
+
+ def test_that_we_understand_and_parse_all_keys_in_standard_response
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ response_keys = response.params.keys.map(&:to_sym)
+ unknown_response_keys = response_keys - BluePayGateway::FIELD_MAP.values
+ missing_response_keys = BluePayGateway::FIELD_MAP.values - response_keys
+
+ assert_empty unknown_response_keys, 'unknown_response_keys'
+ assert_empty missing_response_keys, 'missing response_keys'
+ end
+
+ def test_that_we_understand_and_parse_all_keys_in_rebilling_response
+ assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
+ assert_success response
+ rebill_id = response.params['rebid']
+ assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2)
+ assert_success response
+
+ response_keys = response.params.keys.map(&:to_sym)
+ unknown_response_keys = response_keys - BluePayGateway::REBILL_FIELD_MAP.values
+ missing_response_keys = BluePayGateway::REBILL_FIELD_MAP.values - response_keys
+
+ assert_empty unknown_response_keys, 'unknown_response_keys'
+ assert_empty missing_response_keys, 'missing response_keys'
+ end
+
+ def test_authorization_and_capture
+ assert authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+ assert_equal 'This transaction has been approved', capture.message
+ end
+
+ def test_authorization_and_void
+ assert authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+
+ assert void = @gateway.void(authorization.authorization)
+ assert_success void
+ assert_equal 'This transaction has been approved', void.message
+ end
+
+ def test_bad_login
+ gateway = BluePayGateway.new(
+ :login => 'X',
+ :password => 'Y'
+ )
+ assert response = gateway.purchase(@amount, @credit_card)
+
+ assert_equal Response, response.class
+ assert_match(/The merchant login ID or password is invalid/, response.message)
+ assert_failure response
+ end
+
+ def test_using_test_request
+ gateway = BluePayGateway.new(
+ :login => 'X',
+ :password => 'Y'
+ )
+ assert response = gateway.purchase(@amount, @credit_card)
+ assert_equal Response, response.class
+
+ assert_match(/The merchant login ID or password is invalid/, response.message)
+ assert_failure response
+ end
+
+ def test_successful_recurring
+ assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
+ assert_success response
+ assert response.test?
+
+ rebill_id = response.params['rebid']
+
+ assert response = @gateway.update_recurring(:rebill_id => rebill_id, :rebill_amount => @amount * 2)
+ assert_success response
+
+ assert response = @gateway.status_recurring(rebill_id)
+ assert_success response
+ assert_equal response.params['status'], 'active'
+
+ assert response = @gateway.cancel_recurring(rebill_id)
+ assert_success response
+ assert_equal response.params['status'], 'stopped'
+ end
+
+ def test_recurring_should_fail_expired_credit_card
+ @credit_card.year = 2004
+ assert response = @gateway.recurring(@amount, @credit_card, @recurring_options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'The credit card has expired', response.message
+ end
+
+ def test_successful_purchase_with_solution_id
+ ActiveMerchant::Billing::BluePayGateway.application_id = 'A1000000'
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'This transaction has been approved', response.message
+ assert response.authorization
+ ensure
+ ActiveMerchant::Billing::BluePayGateway.application_id = nil
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_blue_snap_test.rb b/test/remote/gateways/remote_blue_snap_test.rb
new file mode 100644
index 00000000000..da9bf2c2ce1
--- /dev/null
+++ b/test/remote/gateways/remote_blue_snap_test.rb
@@ -0,0 +1,395 @@
+require 'test_helper'
+
+class RemoteBlueSnapTest < Test::Unit::TestCase
+ def setup
+ @gateway = BlueSnapGateway.new(fixtures(:blue_snap))
+
+ @amount = 100
+ @credit_card = credit_card('4263982640269299')
+ @cabal_credit_card = credit_card('6271701225979642')
+ @declined_card = credit_card('4917484589897107', month: 1, year: 2023)
+ @invalid_card = credit_card('4917484589897106', month: 1, year: 2023)
+ @three_ds_visa_card = credit_card('4000000000001091', month: 1)
+ @three_ds_master_card = credit_card('5200000000001096', month: 1)
+ @invalid_cabal_card = credit_card('5896 5700 0000 0000', month: 1, year: 2023)
+
+ @options = { billing_address: address }
+ @options_3ds2 = @options.merge(
+ three_d_secure: {
+ eci: '05',
+ cavv: 'AAABAWFlmQAAAABjRWWZEEFgFz+A',
+ xid: 'MGpHWm5ZWVpKclo0aUk0VmltVDA=',
+ ds_transaction_id: 'jhg34-sdgds87-sdg87-sdfg7',
+ version: '2.2.0'
+ }
+ )
+
+ @check = check
+ @invalid_check = check(:routing_number => '123456', :account_number => '123456789')
+ @valid_check_options = {
+ billing_address: {
+ address1: '123 Street',
+ address2: 'Apt 1',
+ city: 'Happy City',
+ state: 'CA',
+ zip: '94901'
+ },
+ authorized_by_shopper: true
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_sans_options
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ more_options = @options.merge({
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ description: 'Product Description',
+ soft_descriptor: 'OnCardStatement',
+ personal_identification_number: 'CNPJ'
+ })
+
+ response = @gateway.purchase(@amount, @credit_card, more_options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_3ds2_auth
+ response = @gateway.purchase(@amount, @three_ds_visa_card, @options_3ds2)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_currency
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CAD'))
+ assert_success response
+
+ assert_equal 'Success', response.message
+ assert_equal 'CAD', response.params['currency']
+ end
+
+ def test_successful_purchase_with_level3_data
+ l_three_visa = credit_card('4111111111111111', month: 2, year: 2023)
+ options = @options.merge({
+ customer_reference_number: '1234A',
+ sales_tax_amount: 0.6,
+ freight_amount: 0,
+ duty_amount: 0,
+ destination_zip_code: 12345,
+ destination_country_code: 'us',
+ ship_from_zip_code: 12345,
+ discount_amount: 0,
+ tax_amount: 0.6,
+ tax_rate: 6.0,
+ level_3_data_items: [
+ {
+ line_item_total: 9.00,
+ description: 'test_desc',
+ product_code: 'test_code',
+ item_quantity: 1.0,
+ tax_rate: 6.0,
+ tax_amount: 0.60,
+ unit_of_measure: 'lb',
+ commodity_code: 123,
+ discount_indicator: 'Y',
+ gross_net_indicator: 'Y',
+ tax_type: 'test',
+ unit_cost: 10.00
+ },
+ {
+ line_item_total: 9.00,
+ description: 'test_2',
+ product_code: 'test_2',
+ item_quantity: 1.0,
+ tax_rate: 7.0,
+ tax_amount: 0.70,
+ unit_of_measure: 'lb',
+ commodity_code: 123,
+ discount_indicator: 'Y',
+ gross_net_indicator: 'Y',
+ tax_type: 'test',
+ unit_cost: 14.00
+ }
+ ]
+ })
+ response = @gateway.purchase(@amount, l_three_visa, options)
+
+ assert_success response
+ assert_equal 'Success', response.message
+ assert_equal '1234A', response.params['customer-reference-number']
+ assert_equal '9', response.params['line-item-total']
+ end
+
+ def test_successful_purchase_with_unused_state_code
+ unrecognized_state_code_options = {
+ billing_address: {
+ city: 'Dresden',
+ state: 'Sachsen',
+ country: 'DE',
+ zip: '01069'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, unrecognized_state_code_options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_echeck_purchase
+ response = @gateway.purchase(@amount, @check, @options.merge(@valid_check_options))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/Authorization has failed for this transaction/, response.message)
+ assert_equal '14002', response.error_code
+ end
+
+ def test_failed_purchase_with_invalid_cabal_card
+ response = @gateway.purchase(@amount, @invalid_cabal_card, @options)
+ assert_failure response
+ assert_match(/'Card Number' should be a valid Credit Card/, response.message)
+ assert_equal '10001', response.error_code
+ end
+
+ def test_cvv_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'CVV not processed', response.cvv_result['message']
+ assert_equal 'P', response.cvv_result['code']
+ end
+
+ def test_avs_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Address not verified.', response.avs_result['message']
+ assert_equal 'I', response.avs_result['code']
+ end
+
+ def test_failed_echeck_purchase
+ response = @gateway.purchase(@amount, @invalid_check, @options.merge(@valid_check_options))
+ assert_failure response
+ assert_match(/ECP data validity check failed/, response.message)
+ assert_equal '10001', response.error_code
+ end
+
+ def test_failed_unauthorized_echeck_purchase
+ response = @gateway.purchase(@amount, @check, @options.merge({authorized_by_shopper: false}))
+ assert_failure response
+ assert_match(/The payment was not authorized by shopper/, response.message)
+ assert_equal '16004', response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', capture.message
+ end
+
+ def test_successful_authorize_and_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount - 1, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_3ds2_auth
+ auth = @gateway.authorize(@amount, @three_ds_master_card, @options_3ds2)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/Authorization has failed for this transaction/, response.message)
+ end
+
+ def test_partial_capture_succeeds_even_though_amount_is_ignored_by_gateway
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_match(/due to missing transaction ID/, response.message)
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'Success', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_match(/cannot be completed due to missing transaction ID/, response.message)
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_match(/cannot be completed due to missing transaction ID/, response.message)
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match(/Transaction failed because of payment processing failure/, response.message)
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @options)
+
+ assert_success response
+ assert_equal 'Success', response.message
+ assert response.authorization
+ assert_equal 'I', response.avs_result['code']
+ assert_equal 'P', response.cvv_result['code']
+ assert_match(/services\/2\/vaulted-shoppers/, response.params['content-location-header'])
+ end
+
+ def test_successful_echeck_store
+ assert response = @gateway.store(@check, @options.merge(@valid_check_options))
+
+ assert_success response
+ assert_equal 'Success', response.message
+ assert response.authorization
+ assert_match(/services\/2\/vaulted-shoppers/, response.params['content-location-header'])
+ end
+
+ def test_failed_store
+ assert response = @gateway.store(@invalid_card, @options)
+
+ assert_failure response
+ assert_match(/'Card Number' should be a valid Credit Card/, response.message)
+ assert_equal '10001', response.error_code
+ end
+
+ def test_failed_echeck_store
+ assert response = @gateway.store(@invalid_check, @options)
+
+ assert_failure response
+ assert_match(/ECP data validity check failed/, response.message)
+ assert_equal '10001', response.error_code
+ end
+
+ def test_successful_purchase_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_using_stored_echeck
+ assert store_response = @gateway.store(@check, @options.merge(@valid_check_options))
+ assert_success store_response
+ assert_match(/check/, store_response.authorization)
+
+ response = @gateway.purchase(@amount, store_response.authorization, @options.merge({authorized_by_shopper: true}))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_authorize_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.authorize(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_invalid_login
+ gateway = BlueSnapGateway.new(api_username: 'unknown', api_password: 'unknown')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Unable to authenticate. Please check your credentials.', response.message
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = BlueSnapGateway.new(api_username: 'unknown', api_password: 'unknown')
+ assert !gateway.verify_credentials
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:api_password], transcript)
+ end
+
+ def test_transcript_scrubbing_with_echeck
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @valid_check_options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@gateway.options[:api_password], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_borgun_test.rb b/test/remote/gateways/remote_borgun_test.rb
new file mode 100644
index 00000000000..32f7b1b6fc7
--- /dev/null
+++ b/test/remote/gateways/remote_borgun_test.rb
@@ -0,0 +1,176 @@
+require 'test_helper'
+
+class RemoteBorgunTest < Test::Unit::TestCase
+ def setup
+ # Borgun's test server has an improperly installed cert
+ BorgunGateway.ssl_strict = false
+
+ @gateway = BorgunGateway.new(fixtures(:borgun))
+
+ @amount = 100
+ @credit_card = credit_card('5587402000012011', year: 2018, month: 9, verification_value: 415)
+ @declined_card = credit_card('4155520000000002')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def teardown
+ BorgunGateway.ssl_strict = true
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_usd
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD'))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_without_options
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Error with ActionCode=121', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_authorize_and_capture_usd
+ auth = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD'))
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, currency: 'USD')
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_successful_refund_usd
+ purchase = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD'))
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, currency: 'USD')
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_void_with_no_currency_in_authorization
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ *new_auth, _ = auth.authorization.split('|')
+ assert void = @gateway.void(new_auth.join('|'))
+ assert_success void
+ end
+
+ def test_successful_void_usd
+ auth = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD'))
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_void_usd_with_options
+ auth = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'USD'))
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization, @options.merge(currency: 'USD'))
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ # This test does not consistently pass. When run multiple times within 1 minute,
+ # an ActiveMerchant::ConnectionError()
+ # exception is raised.
+ def test_invalid_login
+ gateway = BorgunGateway.new(
+ processor: '0',
+ merchant_id: '0',
+ username: 'not',
+ password: 'right'
+ )
+ authentication_exception = assert_raise ActiveMerchant::ResponseError, 'Failed with 401 [ISS.0084.9001] Invalid credentials' do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+ assert response = authentication_exception.response
+ assert_match(/Access Denied/, response.body)
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_bpoint_test.rb b/test/remote/gateways/remote_bpoint_test.rb
new file mode 100644
index 00000000000..c2ba913c86c
--- /dev/null
+++ b/test/remote/gateways/remote_bpoint_test.rb
@@ -0,0 +1,148 @@
+require 'test_helper'
+
+class RemoteBpointTest < Test::Unit::TestCase
+ def setup
+ @gateway = BpointGateway.new(fixtures(:bpoint))
+
+ @amount = 100
+ approved_year = '00'
+ declined_year = '01'
+ @credit_card = credit_card('4987654321098769', month: '99', year: approved_year)
+ @declined_card = credit_card('4987654321098769', month: '99', year: declined_year)
+ @error_card = credit_card('498765432109', month: '99', year: approved_year)
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, { crn1: 'TEST' })
+ assert_success response
+ assert_equal 'Success', response.message
+ token_key = 'AddTokenResult_Token'
+ assert_not_nil response.params[token_key]
+ assert_not_nil response.authorization
+ assert_equal response.params[token_key], response.authorization
+ end
+
+ def test_failed_store
+ response = @gateway.store(@error_card)
+ assert_failure response
+ assert_equal 'Error', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ response = @gateway.purchase(@amount, @credit_card, @options.merge({ crn1: 'ref'}))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ assert capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(@amount, auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void(@amount, '')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_invalid_login
+ gateway = BpointGateway.new(
+ username: 'abc',
+ password: '123',
+ merchant_number: 'xyz'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_equal 'invalid login', response.message
+ assert_failure response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_braintree_blue_test.rb b/test/remote/gateways/remote_braintree_blue_test.rb
index 352f170166c..d1031516fab 100644
--- a/test/remote/gateways/remote_braintree_blue_test.rb
+++ b/test/remote/gateways/remote_braintree_blue_test.rb
@@ -3,6 +3,7 @@
class RemoteBraintreeBlueTest < Test::Unit::TestCase
def setup
@gateway = BraintreeGateway.new(fixtures(:braintree_blue))
+ @braintree_backend = @gateway.instance_eval { @braintree_gateway }
@amount = 100
@declined_amount = 2000_00
@@ -11,7 +12,7 @@ def setup
@options = {
:order_id => '1',
- :billing_address => address(:country_name => "United States of America"),
+ :billing_address => address(:country_name => 'Canada'),
:description => 'Store Purchase'
}
end
@@ -19,82 +20,182 @@ def setup
def test_credit_card_details_on_store
assert response = @gateway.store(@credit_card)
assert_success response
- assert_equal '5100', response.params["braintree_customer"]["credit_cards"].first["last_4"]
- assert_equal('510510******5100', response.params["braintree_customer"]["credit_cards"].first["masked_number"])
- assert_equal('5100', response.params["braintree_customer"]["credit_cards"].first["last_4"])
- assert_equal('MasterCard', response.params["braintree_customer"]["credit_cards"].first["card_type"])
- assert_equal('510510', response.params["braintree_customer"]["credit_cards"].first["bin"])
- assert_match %r{^\d+$}, response.params["customer_vault_id"]
- assert_equal response.params["customer_vault_id"], response.authorization
+ assert_equal '5100', response.params['braintree_customer']['credit_cards'].first['last_4']
+ assert_equal('510510******5100', response.params['braintree_customer']['credit_cards'].first['masked_number'])
+ assert_equal('5100', response.params['braintree_customer']['credit_cards'].first['last_4'])
+ assert_equal('MasterCard', response.params['braintree_customer']['credit_cards'].first['card_type'])
+ assert_equal('510510', response.params['braintree_customer']['credit_cards'].first['bin'])
+ assert_match %r{^\d+$}, response.params['customer_vault_id']
+ assert_equal response.params['customer_vault_id'], response.authorization
+ assert_match %r{^\w+$}, response.params['credit_card_token']
+ assert_equal response.params['credit_card_token'], response.params['braintree_customer']['credit_cards'].first['token']
end
def test_successful_authorize
assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal '1000 Approved', response.message
- assert_equal 'authorized', response.params["braintree_transaction"]["status"]
+ assert_equal 'authorized', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_authorize_with_nil_and_empty_billing_address_options
+ credit_card = credit_card('5105105105105100')
+ options = {
+ :billing_address => {
+ :name => 'John Smith',
+ :phone => '123-456-7890',
+ :company => nil,
+ :address1 => nil,
+ :address2 => '',
+ :city => nil,
+ :state => nil,
+ :zip => nil,
+ :country => ''
+ }
+ }
+ assert response = @gateway.authorize(@amount, credit_card, options)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'authorized', response.params['braintree_transaction']['status']
end
def test_masked_card_number
assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_equal('510510******5100', response.params["braintree_transaction"]["credit_card_details"]["masked_number"])
- assert_equal('5100', response.params["braintree_transaction"]["credit_card_details"]["last_4"])
- assert_equal('MasterCard', response.params["braintree_transaction"]["credit_card_details"]["card_type"])
- assert_equal('510510', response.params["braintree_transaction"]["credit_card_details"]["bin"])
+ assert_equal('510510******5100', response.params['braintree_transaction']['credit_card_details']['masked_number'])
+ assert_equal('5100', response.params['braintree_transaction']['credit_card_details']['last_4'])
+ assert_equal('MasterCard', response.params['braintree_transaction']['credit_card_details']['card_type'])
+ assert_equal('510510', response.params['braintree_transaction']['credit_card_details']['bin'])
end
def test_successful_authorize_with_order_id
assert response = @gateway.authorize(@amount, @credit_card, :order_id => '123')
assert_success response
assert_equal '1000 Approved', response.message
- assert_equal '123', response.params["braintree_transaction"]["order_id"]
+ assert_equal '123', response.params['braintree_transaction']['order_id']
end
- def test_successful_authorize_with_merchant_account_id
- assert response = @gateway.authorize(@amount, @credit_card, :merchant_account_id => 'sandbox_credit_card_non_default')
+ def test_successful_purchase_with_hold_in_escrow
+ @options.merge({:merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id], :hold_in_escrow => true})
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal '1000 Approved', response.message
- assert_equal 'sandbox_credit_card_non_default', response.params["braintree_transaction"]["merchant_account_id"]
end
def test_successful_purchase_using_vault_id
assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'OK', response.message
- customer_vault_id = response.params["customer_vault_id"]
- assert_match(/\A\d{6,7}\z/, customer_vault_id)
+ customer_vault_id = response.params['customer_vault_id']
+ assert_match(/\A\d+\z/, customer_vault_id)
assert response = @gateway.purchase(@amount, customer_vault_id)
assert_success response
assert_equal '1000 Approved', response.message
- assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"]
- assert_equal customer_vault_id, response.params["braintree_transaction"]["customer_details"]["id"]
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ assert_equal customer_vault_id, response.params['braintree_transaction']['customer_details']['id']
end
def test_successful_purchase_using_vault_id_as_integer
assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'OK', response.message
- customer_vault_id = response.params["customer_vault_id"]
- assert_match /\A\d{6,7}\z/, customer_vault_id
+ customer_vault_id = response.params['customer_vault_id']
+ assert_match %r{\A\d+\z}, customer_vault_id
assert response = @gateway.purchase(@amount, customer_vault_id.to_i)
assert_success response
assert_equal '1000 Approved', response.message
- assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"]
- assert_equal customer_vault_id, response.params["braintree_transaction"]["customer_details"]["id"]
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ assert_equal customer_vault_id, response.params['braintree_transaction']['customer_details']['id']
end
- def test_successful_validate_on_store
- card = credit_card('4111111111111111', :verification_value => '101')
- assert response = @gateway.store(card, :verify_card => true)
+ def test_successful_purchase_using_card_token
+ assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'OK', response.message
+ credit_card_token = response.params['credit_card_token']
+ assert_match %r{^\w+$}, credit_card_token
+
+ assert response = @gateway.purchase(@amount, credit_card_token, :payment_method_token => true)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
end
- def test_successful_validate_on_store_with_verification_merchant_account
+ def test_successful_purchase_with_level_2_data
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:tax_amount => '20', :purchase_order_number => '6789'))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ end
+
+ def test_successful_purchase_with_level_2_and_3_data
+ options = {
+ :tax_amount => '20',
+ :purchase_order_number => '6789',
+ :shipping_amount => '300',
+ :discount_amount => '150',
+ :ships_from_postal_code => '90210',
+ :line_items => [
+ {
+ :name => 'Product Name',
+ :kind => 'debit',
+ :quantity => '10.0000',
+ :unit_amount => '9.5000',
+ :unit_of_measure => 'unit',
+ :total_amount => '95.00',
+ :tax_amount => '5.00',
+ :discount_amount => '0.00',
+ :product_code => '54321',
+ :commodity_code => '98765'
+ },
+ {
+ :name => 'Other Product Name',
+ :kind => 'debit',
+ :quantity => '1.0000',
+ :unit_amount => '2.5000',
+ :unit_of_measure => 'unit',
+ :total_amount => '90.00',
+ :tax_amount => '2.00',
+ :discount_amount => '1.00',
+ :product_code => '54322',
+ :commodity_code => '98766'
+ }
+ ]
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{number is not an accepted test number}, response.message
+ end
+
+ def test_successful_verify_with_device_data
+ # Requires Advanced Fraud Tools to be enabled
+ assert response = @gateway.verify(@credit_card, @options.merge({device_data: 'device data for verify'}))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+
+ assert transaction = response.params['braintree_transaction']
+ assert transaction['risk_data']
+ assert transaction['risk_data']['id']
+ assert_equal 'Approve', transaction['risk_data']['decision']
+ assert_equal false, transaction['risk_data']['device_data_captured']
+ assert_equal 'kount', transaction['risk_data']['fraud_service_provider']
+ end
+
+ def test_successful_validate_on_store
card = credit_card('4111111111111111', :verification_value => '101')
- assert response = @gateway.store(card, :verify_card => true, :verification_merchant_account_id => 'sandbox_credit_card_non_default')
+ assert response = @gateway.store(card, :verify_card => true)
assert_success response
assert_equal 'OK', response.message
end
@@ -121,12 +222,12 @@ def test_successful_store_with_invalid_card
def test_successful_store_with_billing_address
billing_address = {
- :address1 => "1 E Main St",
- :address2 => "Suite 403",
- :city => "Chicago",
- :state => "Illinois",
- :zip => "60622",
- :country_name => "United States of America"
+ :address1 => '1 E Main St',
+ :address2 => 'Suite 403',
+ :city => 'Chicago',
+ :state => 'Illinois',
+ :zip => '60622',
+ :country_name => 'United States of America'
}
credit_card = credit_card('5105105105105100')
assert response = @gateway.store(credit_card, :billing_address => billing_address)
@@ -136,56 +237,142 @@ def test_successful_store_with_billing_address
vault_id = response.params['customer_vault_id']
purchase_response = @gateway.purchase(@amount, vault_id)
response_billing_details = {
- "country_name"=>"United States of America",
- "region"=>"Illinois",
- "company"=>nil,
- "postal_code"=>"60622",
- "extended_address"=>"Suite 403",
- "street_address"=>"1 E Main St",
- "locality"=>"Chicago"
+ 'country_name'=>'United States of America',
+ 'region'=>'Illinois',
+ 'company'=>nil,
+ 'postal_code'=>'60622',
+ 'extended_address'=>'Suite 403',
+ 'street_address'=>'1 E Main St',
+ 'locality'=>'Chicago'
}
assert_equal purchase_response.params['braintree_transaction']['billing_details'], response_billing_details
end
+ def test_successful_store_with_nil_billing_address_options
+ billing_address = {
+ :name => 'John Smith',
+ :phone => '123-456-7890',
+ :company => nil,
+ :address1 => nil,
+ :address2 => nil,
+ :city => nil,
+ :state => nil,
+ :zip => nil,
+ :country_name => nil
+ }
+ credit_card = credit_card('5105105105105100')
+ assert response = @gateway.store(credit_card, :billing_address => billing_address)
+ assert_success response
+ assert_equal 'OK', response.message
+
+ vault_id = response.params['customer_vault_id']
+ purchase_response = @gateway.purchase(@amount, vault_id)
+ assert_success purchase_response
+ end
+
+ def test_successful_store_with_credit_card_token
+ credit_card = credit_card('5105105105105100')
+ credit_card_token = generate_unique_id
+ assert response = @gateway.store(credit_card, credit_card_token: credit_card_token)
+ assert_success response
+ assert_equal 'OK', response.message
+ assert_equal credit_card_token, response.params['braintree_customer']['credit_cards'][0]['token']
+ end
+
+ def test_successful_store_with_new_customer_id
+ credit_card = credit_card('5105105105105100')
+ customer_id = generate_unique_id
+ assert response = @gateway.store(credit_card, customer: customer_id)
+ assert_success response
+ assert_equal 'OK', response.message
+ assert_equal customer_id, response.authorization
+ assert_equal customer_id, response.params['braintree_customer']['id']
+ end
+
+ def test_successful_store_with_existing_customer_id
+ credit_card = credit_card('5105105105105100')
+ customer_id = generate_unique_id
+ assert response = @gateway.store(credit_card, customer: customer_id)
+ assert_success response
+ assert_equal 1, @braintree_backend.customer.find(customer_id).credit_cards.size
+
+ assert response = @gateway.store(credit_card, customer: customer_id)
+ assert_success response
+ assert_equal 2, @braintree_backend.customer.find(customer_id).credit_cards.size
+ assert_equal customer_id, response.params['customer_vault_id']
+ assert_equal customer_id, response.authorization
+ assert_not_nil response.params['credit_card_token']
+ end
+
+ def test_successful_store_with_existing_customer_id_and_nil_billing_address_options
+ credit_card = credit_card('5105105105105100')
+ customer_id = generate_unique_id
+ options = {
+ :customer => customer_id,
+ :billing_address => {
+ :name => 'John Smith',
+ :phone => '123-456-7890',
+ :company => nil,
+ :address1 => nil,
+ :address2 => nil,
+ :city => nil,
+ :state => nil,
+ :zip => nil,
+ :country_name => nil
+ }
+ }
+ assert response = @gateway.store(credit_card, options)
+ assert_success response
+ assert_equal 1, @braintree_backend.customer.find(customer_id).credit_cards.size
+
+ assert response = @gateway.store(credit_card, options)
+ assert_success response
+ assert_equal 2, @braintree_backend.customer.find(customer_id).credit_cards.size
+ assert_equal customer_id, response.params['customer_vault_id']
+ assert_equal customer_id, response.authorization
+ assert_not_nil response.params['credit_card_token']
+ end
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal '1000 Approved', response.message
- assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"]
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
end
- def test_avs_match
- assert response = @gateway.purchase(@amount, @credit_card,
- @options.merge(
- :billing_address => {:address1 => "1 E Main St", :zip => "60622"}
- )
- )
+ def test_successful_purchase_with_solution_id
+ ActiveMerchant::Billing::BraintreeBlueGateway.application_id = 'ABC123'
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal({'code' => nil, 'message' => nil, 'street_match' => 'M', 'postal_match' => 'M'}, response.avs_result)
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ ensure
+ ActiveMerchant::Billing::BraintreeBlueGateway.application_id = nil
end
- def test_transaction_succeeds_with_bad_avs_without_avs_rules
- assert response = @gateway.purchase(@amount, @credit_card,
- @options.merge(
- :billing_address => {:address1 => "200 E Main St", :zip => "20000"}
- )
- )
- assert_success response
- assert_equal({'code' => nil, 'message' => nil, 'street_match' => 'N', 'postal_match' => 'N'}, response.avs_result)
- end
+ def test_avs
+ assert_avs('1 Elm', '60622', 'M')
+ assert_avs('1 Elm', '20000', 'A')
+ assert_avs('1 Elm', '20001', 'B')
+ assert_avs('1 Elm', '', 'B')
- def test_transaction_fails_with_bad_avs_with_avs_rules
- gateway = BraintreeGateway.new(fixtures(:braintree_blue_with_processing_rules))
+ assert_avs('200 Elm', '60622', 'Z')
+ assert_avs('200 Elm', '20000', 'C')
+ assert_avs('200 Elm', '20001', 'C')
+ assert_avs('200 Elm', '', 'C')
- assert response = gateway.purchase(@amount, @credit_card,
- @options.merge(
- :billing_address => {:address1 => "200 E Main St", :zip => "20000"}
- )
- )
+ assert_avs('201 Elm', '60622', 'P')
+ assert_avs('201 Elm', '20000', 'N')
+ assert_avs('201 Elm', '20001', 'I')
+ assert_avs('201 Elm', '', 'I')
- assert_failure response
- assert_equal("Transaction declined - gateway rejected", response.message)
- assert_equal({'code' => nil, 'message' => nil, 'street_match' => 'N', 'postal_match' => 'N'}, response.avs_result)
+ assert_avs('', '60622', 'P')
+ assert_avs('', '20000', 'C')
+ assert_avs('', '20001', 'I')
+ assert_avs('', '', 'I')
+
+ assert_avs('1 Elm', '30000', 'E')
+ assert_avs('1 Elm', '30001', 'S')
end
def test_cvv_match
@@ -200,22 +387,62 @@ def test_cvv_no_match
assert_equal({'code' => 'N', 'message' => ''}, response.cvv_result)
end
- def test_transaction_fails_with_bad_cvv_with_cvv_rules
- gateway = BraintreeGateway.new(fixtures(:braintree_blue_with_processing_rules))
-
- assert response = gateway.purchase(@amount, credit_card('5105105105105100', :verification_value => '200'))
- assert_failure response
- assert_equal("Transaction declined - gateway rejected", response.message)
- assert_equal({'code' => 'N', 'message' => ''}, response.cvv_result)
- end
-
def test_successful_purchase_with_email
assert response = @gateway.purchase(@amount, @credit_card,
- :email => "customer@example.com"
+ :email => 'customer@example.com'
)
assert_success response
- transaction = response.params["braintree_transaction"]
- assert_equal 'customer@example.com', transaction["customer_details"]["email"]
+ transaction = response.params['braintree_transaction']
+ assert_equal 'customer@example.com', transaction['customer_details']['email']
+ end
+
+ def test_successful_purchase_with_phone
+ assert response = @gateway.purchase(@amount, @credit_card, :phone => '123-345-5678')
+ assert_success response
+ transaction = response.params['braintree_transaction']
+ assert_equal '123-345-5678', transaction['customer_details']['phone']
+ end
+
+ def test_successful_purchase_with_phone_from_address
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ transaction = response.params['braintree_transaction']
+ assert_equal '(555)555-5555', transaction['customer_details']['phone']
+ end
+
+ def test_successful_purchase_with_skip_advanced_fraud_checking_option
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_advanced_fraud_checking: true))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_purchase_with_skip_avs
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_avs: true))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'B', response.avs_result['code']
+ end
+
+ def test_successful_purchase_with_skip_cvv
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(skip_cvv: true))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'B', response.cvv_result['code']
+ end
+
+ def test_successful_purchase_with_device_data
+ # Requires Advanced Fraud Tools to be enabled
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(device_data: 'device data for purchase'))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+
+ assert transaction = response.params['braintree_transaction']
+ assert transaction['risk_data']
+ assert transaction['risk_data']['id']
+ assert_equal 'Approve', transaction['risk_data']['decision']
+ assert_equal false, transaction['risk_data']['device_data_captured']
+ assert_equal 'kount', transaction['risk_data']['fraud_service_provider']
end
def test_purchase_with_store_using_random_customer_id
@@ -224,9 +451,9 @@ def test_purchase_with_store_using_random_customer_id
)
assert_success response
assert_equal '1000 Approved', response.message
- assert_match(/\A\d{6,7}\z/, response.params["customer_vault_id"])
- assert_equal '510510', response.params["braintree_transaction"]["vault_customer"]["credit_cards"][0]["bin"]
- assert_equal '510510', Braintree::Customer.find(response.params["customer_vault_id"]).credit_cards[0].bin
+ assert_match(/\A\d+\z/, response.params['customer_vault_id'])
+ assert_equal '510510', response.params['braintree_transaction']['vault_customer']['credit_cards'][0]['bin']
+ assert_equal '510510', @braintree_backend.customer.find(response.params['customer_vault_id']).credit_cards[0].bin
end
def test_purchase_with_store_using_specified_customer_id
@@ -236,9 +463,37 @@ def test_purchase_with_store_using_specified_customer_id
)
assert_success response
assert_equal '1000 Approved', response.message
- assert_equal customer_id, response.params["customer_vault_id"]
- assert_equal '510510', response.params["braintree_transaction"]["vault_customer"]["credit_cards"][0]["bin"]
- assert_equal '510510', Braintree::Customer.find(response.params["customer_vault_id"]).credit_cards[0].bin
+ assert_equal customer_id, response.params['customer_vault_id']
+ assert_equal '510510', response.params['braintree_transaction']['vault_customer']['credit_cards'][0]['bin']
+ assert_equal '510510', @braintree_backend.customer.find(response.params['customer_vault_id']).credit_cards[0].bin
+ end
+
+ def test_purchase_with_transaction_source
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+ customer_vault_id = response.params['customer_vault_id']
+
+ assert response = @gateway.purchase(@amount, customer_vault_id, @options.merge(:transaction_source => 'unscheduled'))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ end
+
+ def test_purchase_using_specified_payment_method_token
+ assert response = @gateway.store(
+ credit_card('4111111111111111',
+ :first_name => 'Old First', :last_name => 'Old Last',
+ :month => 9, :year => 2012
+ ),
+ :email => 'old@example.com',
+ :phone => '321-654-0987'
+ )
+ payment_method_token = response.params['braintree_customer']['credit_cards'][0]['token']
+ assert response = @gateway.purchase(
+ @amount, payment_method_token, @options.merge(payment_method_token: true)
+ )
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal payment_method_token, response.params['braintree_transaction']['credit_card_details']['token']
end
def test_successful_purchase_with_addresses
@@ -265,72 +520,155 @@ def test_successful_purchase_with_addresses
:shipping_address => shipping_address
)
assert_success response
- transaction = response.params["braintree_transaction"]
- assert_equal '1 E Main St', transaction["billing_details"]["street_address"]
- assert_equal 'Suite 101', transaction["billing_details"]["extended_address"]
- assert_equal 'Widgets Co', transaction["billing_details"]["company"]
- assert_equal 'Chicago', transaction["billing_details"]["locality"]
- assert_equal 'IL', transaction["billing_details"]["region"]
- assert_equal '60622', transaction["billing_details"]["postal_code"]
- assert_equal 'United States of America', transaction["billing_details"]["country_name"]
- assert_equal '1 W Main St', transaction["shipping_details"]["street_address"]
- assert_equal 'Suite 102', transaction["shipping_details"]["extended_address"]
- assert_equal 'Widgets Company', transaction["shipping_details"]["company"]
- assert_equal 'Bartlett', transaction["shipping_details"]["locality"]
- assert_equal 'Illinois', transaction["shipping_details"]["region"]
- assert_equal '60103', transaction["shipping_details"]["postal_code"]
- assert_equal 'Mexico', transaction["shipping_details"]["country_name"]
+ transaction = response.params['braintree_transaction']
+ assert_equal '1 E Main St', transaction['billing_details']['street_address']
+ assert_equal 'Suite 101', transaction['billing_details']['extended_address']
+ assert_equal 'Widgets Co', transaction['billing_details']['company']
+ assert_equal 'Chicago', transaction['billing_details']['locality']
+ assert_equal 'IL', transaction['billing_details']['region']
+ assert_equal '60622', transaction['billing_details']['postal_code']
+ assert_equal 'United States of America', transaction['billing_details']['country_name']
+ assert_equal '1 W Main St', transaction['shipping_details']['street_address']
+ assert_equal 'Suite 102', transaction['shipping_details']['extended_address']
+ assert_equal 'Widgets Company', transaction['shipping_details']['company']
+ assert_equal 'Bartlett', transaction['shipping_details']['locality']
+ assert_equal 'Illinois', transaction['shipping_details']['region']
+ assert_equal '60103', transaction['shipping_details']['postal_code']
+ assert_equal 'Mexico', transaction['shipping_details']['country_name']
+ end
+
+ def test_successful_purchase_with_three_d_secure_pass_thru
+ three_d_secure_params = { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id', cavv_algorithm: 'algorithm', directory_response_status: 'directory', authentication_response_status: 'auth' }
+ response = @gateway.purchase(@amount, @credit_card,
+ three_d_secure: three_d_secure_params
+ )
+ assert_success response
+ end
+
+ def test_successful_purchase_with_some_three_d_secure_pass_thru_fields
+ three_d_secure_params = { version: '2.0', cavv: 'cavv', eci: '02', ds_transaction_id: 'trans_id' }
+ response = @gateway.purchase(@amount, @credit_card,
+ three_d_secure: three_d_secure_params
+ )
+ assert_success response
end
def test_unsuccessful_purchase_declined
assert response = @gateway.purchase(@declined_amount, @credit_card, @options)
assert_failure response
+ assert response.authorization.present?
assert_equal '2000 Do Not Honor', response.message
end
def test_unsuccessful_purchase_validation_error
- assert response = @gateway.purchase(@amount, @credit_card,
- @options.merge(:email => "invalid_email")
- )
+ assert response = @gateway.purchase(@amount, credit_card('51051051051051000'))
assert_failure response
- assert_equal 'Email is an invalid format. (81604)', response.message
- assert_equal nil, response.params["braintree_transaction"]
+ assert_match %r{Credit card number is invalid\. \(81715\)}, response.message
+ assert_equal({'processor_response_code'=>'91577'}, response.params['braintree_transaction'])
end
def test_authorize_and_capture
- amount = @amount
- assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal '1000 Approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_authorize_and_capture_with_apple_pay_card
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ :brand => 'visa',
+ :eci => '05',
+ :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+
+ assert auth = @gateway.authorize(@amount, credit_card, @options)
+ assert_success auth
+ assert_equal '1000 Approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_authorize_and_capture_with_android_pay_card
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :month => '01',
+ :year => '2024',
+ :source => :android_pay,
+ :transaction_id => '123456789',
+ :eci => '05'
+ )
+
+ assert auth = @gateway.authorize(@amount, credit_card, @options)
+ assert_success auth
+ assert_equal '1000 Approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_authorize_and_capture_with_google_pay_card
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :month => '01',
+ :year => '2024',
+ :source => :google_pay,
+ :transaction_id => '123456789',
+ :eci => '05'
+ )
+
+ assert auth = @gateway.authorize(@amount, credit_card, @options)
assert_success auth
assert_equal '1000 Approved', auth.message
assert auth.authorization
- assert capture = @gateway.capture(amount, auth.authorization)
+ assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
end
def test_authorize_and_void
- amount = @amount
- assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal '1000 Approved', auth.message
assert auth.authorization
assert void = @gateway.void(auth.authorization)
assert_success void
- assert_equal 'voided', void.params["braintree_transaction"]["status"]
+ assert_equal 'voided', void.params['braintree_transaction']['status']
+ end
+
+ def test_purchase_and_void
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ assert_equal 'voided', void.params['braintree_transaction']['status']
+ end
+
+ def test_capture_and_void
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+
+ assert void = @gateway.void(capture.authorization)
+ assert_success void
+ assert_equal 'voided', void.params['braintree_transaction']['status']
end
def test_failed_void
- amount = @amount
- assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal '1000 Approved', auth.message
assert auth.authorization
assert void = @gateway.void(auth.authorization)
assert_success void
- assert_equal 'voided', void.params["braintree_transaction"]["status"]
+ assert_equal 'voided', void.params['braintree_transaction']['status']
assert failed_void = @gateway.void(auth.authorization)
assert_failure failed_void
- assert_equal 'Transaction can only be voided if status is authorized or submitted_for_settlement. (91504)', failed_void.message
- assert_equal nil, failed_void.params["braintree_transaction"]
+ assert_match('Transaction can only be voided if status is authorized', failed_void.message)
+ assert_equal({'processor_response_code'=>'91504'}, failed_void.params['braintree_transaction'])
end
def test_failed_capture_with_invalid_transaction_id
@@ -340,7 +678,7 @@ def test_failed_capture_with_invalid_transaction_id
end
def test_invalid_login
- gateway = BraintreeBlueGateway.new(:merchant_id => "invalid", :public_key => "invalid", :private_key => "invalid")
+ gateway = BraintreeBlueGateway.new(:merchant_id => 'invalid', :public_key => 'invalid', :private_key => 'invalid')
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Braintree::AuthenticationError', response.message
@@ -350,53 +688,42 @@ def test_successful_add_to_vault_with_store_method
assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'OK', response.message
- assert_match(/\A\d{6,7}\z/, response.params["customer_vault_id"])
+ assert_match(/\A\d+\z/, response.params['customer_vault_id'])
end
def test_failed_add_to_vault
assert response = @gateway.store(credit_card('5105105105105101'))
assert_failure response
assert_equal 'Credit card number is invalid. (81715)', response.message
- assert_equal nil, response.params["braintree_customer"]
- assert_equal nil, response.params["customer_vault_id"]
+ assert_equal nil, response.params['braintree_customer']
+ assert_equal nil, response.params['customer_vault_id']
end
- def test_unstore
+ def test_unstore_customer
assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'OK', response.message
- assert customer_vault_id = response.params["customer_vault_id"]
+ assert customer_vault_id = response.params['customer_vault_id']
assert delete_response = @gateway.unstore(customer_vault_id)
assert_success delete_response
end
- def test_unstore_with_delete_method
+ def test_unstore_credit_card
assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'OK', response.message
- assert customer_vault_id = response.params["customer_vault_id"]
- assert delete_response = @gateway.delete(customer_vault_id)
+ assert credit_card_token = response.params['credit_card_token']
+ assert delete_response = @gateway.unstore(nil, credit_card_token: credit_card_token)
assert_success delete_response
end
- def test_successful_credit
- assert response = @gateway.credit(@amount, @credit_card, @options)
- assert_success response
- assert_equal '1002 Processed', response.message
- assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"]
- end
-
- def test_successful_credit_with_merchant_account_id
- assert response = @gateway.credit(@amount, @credit_card, :merchant_account_id => 'sandbox_credit_card_non_default')
+ def test_unstore_with_delete_method
+ assert response = @gateway.store(@credit_card)
assert_success response
- assert_equal '1002 Processed', response.message
- assert_equal 'submitted_for_settlement', response.params["braintree_transaction"]["status"]
- end
-
- def test_failed_credit
- assert response = @gateway.credit(@amount, credit_card('5105105105105101'), @options)
- assert_failure response
- assert_equal 'Credit card number is invalid. (81715)', response.message
+ assert_equal 'OK', response.message
+ assert customer_vault_id = response.params['customer_vault_id']
+ assert delete_response = @gateway.delete(customer_vault_id)
+ assert_success delete_response
end
def test_successful_update
@@ -405,19 +732,21 @@ def test_successful_update
:first_name => 'Old First', :last_name => 'Old Last',
:month => 9, :year => 2012
),
- :email => "old@example.com"
+ :email => 'old@example.com',
+ :phone => '321-654-0987'
)
assert_success response
assert_equal 'OK', response.message
- customer_vault_id = response.params["customer_vault_id"]
- assert_match(/\A\d{6,7}\z/, customer_vault_id)
- assert_equal "old@example.com", response.params["braintree_customer"]["email"]
- assert_equal "Old First", response.params["braintree_customer"]["first_name"]
- assert_equal "Old Last", response.params["braintree_customer"]["last_name"]
- assert_equal "411111", response.params["braintree_customer"]["credit_cards"][0]["bin"]
- assert_equal "09/2012", response.params["braintree_customer"]["credit_cards"][0]["expiration_date"]
- assert_not_nil response.params["braintree_customer"]["credit_cards"][0]["token"]
- assert_equal customer_vault_id, response.params["braintree_customer"]["id"]
+ customer_vault_id = response.params['customer_vault_id']
+ assert_match(/\A\d+\z/, customer_vault_id)
+ assert_equal 'old@example.com', response.params['braintree_customer']['email']
+ assert_equal '321-654-0987', response.params['braintree_customer']['phone']
+ assert_equal 'Old First', response.params['braintree_customer']['first_name']
+ assert_equal 'Old Last', response.params['braintree_customer']['last_name']
+ assert_equal '411111', response.params['braintree_customer']['credit_cards'][0]['bin']
+ assert_equal '09/2012', response.params['braintree_customer']['credit_cards'][0]['expiration_date']
+ assert_not_nil response.params['braintree_customer']['credit_cards'][0]['token']
+ assert_equal customer_vault_id, response.params['braintree_customer']['id']
assert response = @gateway.update(
customer_vault_id,
@@ -425,33 +754,34 @@ def test_successful_update
:first_name => 'New First', :last_name => 'New Last',
:month => 10, :year => 2014
),
- :email => "new@example.com"
+ :email => 'new@example.com',
+ :phone => '987-765-5432'
)
assert_success response
- assert_equal "new@example.com", response.params["braintree_customer"]["email"]
- assert_equal "New First", response.params["braintree_customer"]["first_name"]
- assert_equal "New Last", response.params["braintree_customer"]["last_name"]
- assert_equal "510510", response.params["braintree_customer"]["credit_cards"][0]["bin"]
- assert_equal "10/2014", response.params["braintree_customer"]["credit_cards"][0]["expiration_date"]
- assert_not_nil response.params["braintree_customer"]["credit_cards"][0]["token"]
- assert_equal customer_vault_id, response.params["braintree_customer"]["id"]
+ assert_equal 'new@example.com', response.params['braintree_customer']['email']
+ assert_equal '987-765-5432', response.params['braintree_customer']['phone']
+ assert_equal 'New First', response.params['braintree_customer']['first_name']
+ assert_equal 'New Last', response.params['braintree_customer']['last_name']
+ assert_equal '510510', response.params['braintree_customer']['credit_cards'][0]['bin']
+ assert_equal '10/2014', response.params['braintree_customer']['credit_cards'][0]['expiration_date']
+ assert_not_nil response.params['braintree_customer']['credit_cards'][0]['token']
+ assert_equal customer_vault_id, response.params['braintree_customer']['id']
end
def test_failed_customer_update
- assert response = @gateway.store(credit_card('4111111111111111'), :email => "email@example.com")
+ assert response = @gateway.store(credit_card('4111111111111111'), :email => 'email@example.com', :phone => '321-654-0987')
assert_success response
assert_equal 'OK', response.message
- assert customer_vault_id = response.params["customer_vault_id"]
+ assert customer_vault_id = response.params['customer_vault_id']
assert response = @gateway.update(
customer_vault_id,
- credit_card('5105105105105100'),
- :email => "invalid-email"
+ credit_card('51051051051051001')
)
assert_failure response
- assert_equal 'Email is an invalid format. (81604)', response.message
- assert_equal nil, response.params["braintree_customer"]
- assert_equal nil, response.params["customer_vault_id"]
+ assert_equal 'Credit card number is invalid. (81715)', response.message
+ assert_equal nil, response.params['braintree_customer']
+ assert_equal nil, response.params['customer_vault_id']
end
def test_failed_customer_update_invalid_vault_id
@@ -464,7 +794,7 @@ def test_failed_credit_card_update
assert response = @gateway.store(credit_card('4111111111111111'))
assert_success response
assert_equal 'OK', response.message
- assert customer_vault_id = response.params["customer_vault_id"]
+ assert customer_vault_id = response.params['customer_vault_id']
assert response = @gateway.update(
customer_vault_id,
@@ -478,7 +808,7 @@ def test_failed_credit_card_update_on_verify
assert response = @gateway.store(credit_card('4111111111111111'))
assert_success response
assert_equal 'OK', response.message
- assert customer_vault_id = response.params["customer_vault_id"]
+ assert customer_vault_id = response.params['customer_vault_id']
assert response = @gateway.update(
customer_vault_id,
@@ -490,9 +820,129 @@ def test_failed_credit_card_update_on_verify
end
def test_customer_does_not_have_credit_card_failed_update
- customer_without_credit_card = Braintree::Customer.create!
- assert response = @gateway.update(customer_without_credit_card.id, credit_card('5105105105105100'))
+ customer_without_credit_card = @braintree_backend.customer.create
+ assert response = @gateway.update(customer_without_credit_card.customer.id, credit_card('5105105105105100'))
assert_failure response
assert_equal 'Braintree::NotFoundError', response.message
end
+
+ def test_successful_credit
+ assert response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response, 'You must get credits enabled in your Sandbox account for this to pass.'
+ assert_equal '1002 Processed', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_failed_credit
+ assert response = @gateway.credit(@amount, credit_card('5105105105105101'), @options)
+ assert_failure response
+ assert_equal 'Credit card number is invalid. (81715)', response.message, 'You must get credits enabled in your Sandbox account for this to pass'
+ end
+
+ def test_successful_credit_with_merchant_account_id
+ assert response = @gateway.credit(@amount, @credit_card, :merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id])
+ assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml AND get credits enabled in your Sandbox account for this to pass.'
+ assert_equal '1002 Processed', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_authorize_with_merchant_account_id
+ assert response = @gateway.authorize(@amount, @credit_card, :merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id])
+ assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass.'
+ assert_equal '1000 Approved', response.message
+ assert_equal 'authorized', response.params['braintree_transaction']['status']
+ end
+
+ def test_authorize_with_descriptor
+ assert auth = @gateway.authorize(@amount, @credit_card, descriptor_name: 'company*theproduct', descriptor_phone: '1331131131', descriptor_url: 'company.com')
+ assert_success auth
+ end
+
+ def test_successful_validate_on_store_with_verification_merchant_account
+ card = credit_card('4111111111111111', :verification_value => '101')
+ assert response = @gateway.store(card, :verify_card => true, :verification_merchant_account_id => fixtures(:braintree_blue)[:merchant_account_id])
+ assert_success response, 'You must specify a valid :merchant_account_id key in your fixtures.yml for this to pass.'
+ assert_equal 'OK', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = BraintreeGateway.new(merchant_id: 'UNKNOWN', public_key: 'UNKONWN', private_key: 'UNKONWN')
+ assert !gateway.verify_credentials
+ end
+
+ def test_successful_merchant_purchase_initial
+ creds_options = stored_credential_options(:merchant, :recurring, :initial)
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
+
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ assert_not_nil response.params['braintree_transaction']['network_transaction_id']
+ end
+
+ def test_successful_subsequent_merchant_unscheduled_transaction
+ creds_options = stored_credential_options(:merchant, :unscheduled, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_subsequent_merchant_recurring_transaction
+ creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_cardholder_purchase_initial
+ creds_options = stored_credential_options(:cardholder, :recurring, :initial)
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_not_nil response.params['braintree_transaction']['network_transaction_id']
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_cardholder_purchase_recurring
+ creds_options = stored_credential_options(:cardholder, :recurring, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ def test_successful_cardholder_purchase_unscheduled
+ creds_options = stored_credential_options(:cardholder, :unscheduled, id: '020190722142652')
+ response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(stored_credential: creds_options))
+ assert_success response
+ assert_equal '1000 Approved', response.message
+ assert_equal 'submitted_for_settlement', response.params['braintree_transaction']['status']
+ end
+
+ private
+
+ def stored_credential_options(*args, id: nil)
+ stored_credential(*args, id: id)
+ end
+
+ def assert_avs(address1, zip, expected_avs_code)
+ response = @gateway.purchase(@amount, @credit_card, billing_address: {address1: address1, zip: zip})
+
+ assert_success response
+ assert_equal expected_avs_code, response.avs_result['code']
+ end
end
diff --git a/test/remote/gateways/remote_braintree_orange_test.rb b/test/remote/gateways/remote_braintree_orange_test.rb
index 460dc55636a..0816eb1821b 100644
--- a/test/remote/gateways/remote_braintree_orange_test.rb
+++ b/test/remote/gateways/remote_braintree_orange_test.rb
@@ -4,7 +4,7 @@ class RemoteBraintreeOrangeTest < Test::Unit::TestCase
def setup
@gateway = BraintreeGateway.new(fixtures(:braintree_orange))
- @amount = rand(10000) + 1001
+ @amount = rand(1001..11000)
@credit_card = credit_card('4111111111111111')
@check = check()
@declined_amount = rand(99)
@@ -27,7 +27,7 @@ def test_successful_purchase_with_echeck
:account_holder_type => 'personal',
:account_type => 'checking'
)
- assert response = @gateway.purchase(@amount, @check, @options)
+ assert response = @gateway.purchase(@amount, check, @options)
assert_equal 'This transaction has been approved', response.message
assert_success response
end
@@ -37,15 +37,15 @@ def test_successful_add_to_vault
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'This transaction has been approved', response.message
assert_success response
- assert_not_nil response.params["customer_vault_id"]
+ assert_not_nil response.params['customer_vault_id']
end
def test_successful_add_to_vault_with_store_method
assert response = @gateway.store(@credit_card)
assert_equal 'Customer Added', response.message
assert_success response
- assert_match %r{^\d+$}, response.params["customer_vault_id"]
- assert_equal response.params["customer_vault_id"], response.authorization
+ assert_match %r{^\d+$}, response.params['customer_vault_id']
+ assert_equal response.params['customer_vault_id'], response.authorization
end
def test_failed_add_to_vault_with_store_method
@@ -59,7 +59,7 @@ def test_successful_add_to_vault_and_use
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'This transaction has been approved', response.message
assert_success response
- assert_not_nil customer_id = response.params["customer_vault_id"]
+ assert_not_nil customer_id = response.params['customer_vault_id']
assert second_response = @gateway.purchase(@amount*2, customer_id, @options)
assert_equal 'This transaction has been approved', second_response.message
@@ -67,19 +67,19 @@ def test_successful_add_to_vault_and_use
end
def test_add_to_vault_with_custom_vault_id
- @options[:store] = rand(100000)+10001
+ @options[:store] = rand(10001..110000)
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'This transaction has been approved', response.message
assert_success response
- assert_equal @options[:store], response.params["customer_vault_id"].to_i
+ assert_equal @options[:store], response.params['customer_vault_id'].to_i
end
def test_add_to_vault_with_custom_vault_id_with_store_method
- @options[:billing_id] = rand(100000)+10001
+ @options[:billing_id] = rand(10001..110000)
assert response = @gateway.store(@credit_card, @options.dup)
assert_equal 'Customer Added', response.message
assert_success response
- assert_equal @options[:billing_id], response.params["customer_vault_id"].to_i
+ assert_equal @options[:billing_id], response.params['customer_vault_id'].to_i
end
def test_add_to_vault_with_store_and_check
@@ -139,7 +139,26 @@ def test_authorize_and_void
def test_failed_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
- assert response.message.match(/Invalid Transaction ID \/ Object ID specified:/)
+ assert response.message.match(/Invalid Transaction ID \/ Object ID specified:/)
+ end
+
+ def test_authorize_with_three_d_secure_pass_thru
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(eci: '05', xid: 'xid', cavv: 'cavv'))
+ assert_success auth
+ assert_equal 'This transaction has been approved', auth.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ end
+
+ def test_failed_verify
+ bogus_card = credit_card('4424222222222222')
+ assert response = @gateway.verify(bogus_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Credit Card Number}, response.message
end
def test_invalid_login
@@ -151,4 +170,14 @@ def test_invalid_login
assert_equal 'Invalid Username', response.message
assert_failure response
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@declined_amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
end
diff --git a/test/remote/gateways/remote_bridge_pay_test.rb b/test/remote/gateways/remote_bridge_pay_test.rb
new file mode 100644
index 00000000000..88f91a8212c
--- /dev/null
+++ b/test/remote/gateways/remote_bridge_pay_test.rb
@@ -0,0 +1,145 @@
+require 'test_helper'
+
+class RemoteBridgePayTest < Test::Unit::TestCase
+ def setup
+ @gateway = BridgePayGateway.new(fixtures(:bridge_pay))
+
+ @amount = 100
+ @credit_card = credit_card('4005550000000019')
+ @declined_card = credit_card('4000300011100000')
+
+ @check = check(
+ :name => 'John Doe',
+ :routing_number => '490000018',
+ :account_number => '1234567890',
+ :number => '1001'
+ )
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid Account Number', response.message
+ end
+
+ def test_successful_purchase_with_echeck
+ response = @gateway.purchase(150, @check, @options)
+ assert_success response
+ assert_equal 'APPROVAL', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(nil, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'Approved', response.responses.last.params['respmsg']
+ end
+
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Account Number}, response.message
+ end
+
+ def test_invalid_login
+ gateway = BridgePayGateway.new(
+ user_name: '',
+ password: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_store_and_purchase
+ store = @gateway.store(@credit_card)
+ assert_success store
+
+ purchase = @gateway.purchase(@amount, store.authorization)
+ assert_success purchase
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_cams_test.rb b/test/remote/gateways/remote_cams_test.rb
new file mode 100644
index 00000000000..e193a39ce7e
--- /dev/null
+++ b/test/remote/gateways/remote_cams_test.rb
@@ -0,0 +1,140 @@
+require 'test_helper'
+
+class RemoteCamsTest < Test::Unit::TestCase
+ @@amount = 100
+ def setup
+ @gateway = CamsGateway.new(fixtures(:cams))
+
+ @amount = rand(100..100000)
+
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('4000300015555555')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(%r{=#{@credit_card.verification_value}\b}, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert !(%r(Invalid Credit Card Number) =~ response.message).nil?
+ end
+
+ def test_successful_purchase_with_reference
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+
+ setup
+ response2 = @gateway.purchase(@amount, response.authorization, @options)
+ assert_equal 'SUCCESS', response2.message
+ end
+
+ def test_failed_purchase_with_reference
+ response = @gateway.purchase(@amount, '00000', @options)
+ assert_failure response
+ assert !(%r(Invalid Transaction ID) =~ response.message).nil?
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(nil, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Credit Card Number}, response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+
+ def test_invalid_login
+ gateway = CamsGateway.new(
+ username: '',
+ password: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_card_connect_test.rb b/test/remote/gateways/remote_card_connect_test.rb
new file mode 100644
index 00000000000..4ec9496c19d
--- /dev/null
+++ b/test/remote/gateways/remote_card_connect_test.rb
@@ -0,0 +1,228 @@
+require 'test_helper'
+
+class RemoteCardConnectTest < Test::Unit::TestCase
+ def setup
+ @gateway = CardConnectGateway.new(fixtures(:card_connect))
+
+ @amount = 100
+ @credit_card = credit_card('4788250000121443')
+ @declined_card = credit_card('4387751111111053')
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ @check = check(routing_number: '053000196')
+ @invalid_txn = '23221'
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approval', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ po_number: '5FSD4',
+ tax_amount: '50',
+ freight_amount: '29',
+ duty_amount: '67',
+ order_date: '20170507',
+ ship_from_date: '20877',
+ items: [
+ {
+ lineno: '1',
+ material: 'MATERIAL-1',
+ description: 'DESCRIPTION-1',
+ upc: 'UPC-1',
+ quantity: '1000',
+ uom: 'CS',
+ unitcost: '900',
+ netamnt: '150',
+ taxamnt: '117',
+ discamnt: '0'
+ },
+ {
+ lineno: '2',
+ material: 'MATERIAL-2',
+ description: 'DESCRIPTION-2',
+ upc: 'UPC-1',
+ quantity: '2000',
+ uom: 'CS',
+ unitcost: '450',
+ netamnt: '300',
+ taxamnt: '117',
+ discamnt: '0'
+ }
+ ]
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Approval Queued for Capture', response.message
+ end
+
+ def test_successful_purchase_3DS
+ three_ds_options = @options.merge(
+ secure_flag: 'se3453',
+ secure_value: '233frdf',
+ secure_xid: '334ef34'
+ )
+ response = @gateway.purchase(@amount, @credit_card, three_ds_options)
+ assert_success response
+ assert_equal 'Approval', response.message
+ end
+
+ def test_successful_purchase_with_profile
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+ purchase_response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success purchase_response
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Insufficient funds', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_includes ['Approval Queued for Capture', 'Approval Accepted'], capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Insufficient funds', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount - 1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, @invalid_txn)
+ assert_failure response
+ assert_equal 'Txn not found', response.message
+ end
+
+ def test_successful_echeck_purchase
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_failed_echeck_purchase
+ response = @gateway.purchase(@amount, check(routing_number: '23433'), @options)
+ assert_failure response
+ assert_equal 'Invalid card', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approval', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount - 1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, @invalid_txn)
+ assert_failure response
+ assert_equal 'Txn not found', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Approval', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void(@invalid_txn)
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Approval}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Insufficient funds}, response.message
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+
+ assert_success response
+ assert_equal 'Profile Saved', response.message
+ end
+
+ def test_successful_unstore
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ unstore_response = @gateway.unstore(store_response.authorization, @options)
+ assert_success unstore_response
+ end
+
+ def test_failed_unstore
+ response = @gateway.unstore('0|abcdefghijklmnopq', @options)
+ assert_failure response
+ end
+
+ def test_invalid_login
+ gateway = CardConnectGateway.new(username: '', password: '', merchant_id: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+
+ assert_failure response
+ assert_match %r{Unable to authenticate. Please check your credentials.}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_card_save_test.rb b/test/remote/gateways/remote_card_save_test.rb
index 5774414fc17..06ee56d1053 100644
--- a/test/remote/gateways/remote_card_save_test.rb
+++ b/test/remote/gateways/remote_card_save_test.rb
@@ -1,23 +1,23 @@
require 'test_helper'
-class RemoteCardSaveTest < Test::Unit::TestCase
+class RemoteCardSaveTest < Test::Unit::TestCase
def setup
@gateway = CardSaveGateway.new(fixtures(:card_save))
-
+
@amount = 100
@credit_card = credit_card('4976000000003436', :verification_value => '452')
@declined_card = credit_card('4221690000004963', :verification_value => '125')
@addresses = {'4976000000003436' => { :name => 'John Watson', :address1 => '32 Edward Street', :city => 'Camborne,', :state => 'Cornwall', :country => 'GB', :zip => 'TR14 8PA' },
'4221690000004963' => { :name => 'Ian Lee', :address1 => '274 Lymington Avenue', :city => 'London', :state => 'London', :country => 'GB', :zip => 'N22 6JN' }}
-
- @options = {
+
+ @options = {
:order_id => '1',
:billing_address => @addresses[@credit_card.number],
:description => 'Store Purchase'
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -25,7 +25,7 @@ def test_successful_purchase
end
def test_unsuccessful_purchase
- @options.merge!(:billing_address => @addresses[@declined_card.number])
+ @options[:billing_address] = @addresses[@declined_card.number]
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
assert_equal 'Card declined', response.message
@@ -49,8 +49,8 @@ def test_failed_capture
def test_invalid_login
gateway = CardSaveGateway.new(
- :login => '',
- :password => ''
+ :login => '',
+ :password => ''
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
diff --git a/test/remote/gateways/remote_card_stream_modern_test.rb b/test/remote/gateways/remote_card_stream_modern_test.rb
deleted file mode 100644
index 3b65bd9b279..00000000000
--- a/test/remote/gateways/remote_card_stream_modern_test.rb
+++ /dev/null
@@ -1,286 +0,0 @@
-require 'test_helper'
-
-class RemoteCardStreamModernTest < Test::Unit::TestCase
- def setup
- Base.mode = :test
-
- @gateway = CardStreamModernGateway.new(fixtures(:card_stream_modern))
-
- @amex = credit_card('374245455400001',
- :month => '12',
- :year => '2014',
- :verification_value => '4887',
- :brand => :american_express
- )
-
- @uk_maestro = credit_card('6759015050123445002',
- :month => '12',
- :year => '2014',
- :issue_number => '0',
- :verification_value => '309',
- :brand => :switch
- )
-
- @mastercard = credit_card('5301250070000191',
- :month => '12',
- :year => '2014',
- :verification_value => '419',
- :brand => :master
- )
-
- @visacreditcard = credit_card('4929421234600821',
- :month => '12',
- :year => '2014',
- :verification_value => '356',
- :brand => :visa
- )
-
- @visadebitcard = credit_card('4539791001730106',
- :month => '12',
- :year => '2014',
- :verification_value => '289',
- :brand => :visa
- )
-
- @declined_card = credit_card('4000300011112220',
- :month => '9',
- :year => '2014'
- )
-
- @amex_options = {
- :billing_address => {
- :address1 => 'The Hunts Way',
- :city => "",
- :state => "Leicester",
- :zip => 'SO18 1GW'
- },
- :order_id => generate_unique_id,
- :description => 'AM test purchase'
- }
-
- @visacredit_options = {
- :billing_address => {
- :address1 => "Flat 6, Primrose Rise",
- :address2 => "347 Lavender Road",
- :city => "",
- :state => "Northampton",
- :zip => 'NN17 8YG '
- },
- :order_id => generate_unique_id,
- :description => 'AM test purchase'
- }
-
- @visadebit_options = {
- :billing_address => {
- :address1 => 'Unit 5, Pickwick Walk',
- :address2 => "120 Uxbridge Road",
- :city => "Hatch End",
- :state => "Middlesex",
- :zip => "HA6 7HJ"
- },
- :order_id => generate_unique_id,
- :description => 'AM test purchase'
- }
-
- @mastercard_options = {
- :billing_address => {
- :address1 => '25 The Larches',
- :city => "Narborough",
- :state => "Leicester",
- :zip => 'LE10 2RT'
- },
- :order_id => generate_unique_id,
- :description => 'AM test purchase'
- }
-
- @uk_maestro_options = {
- :billing_address => {
- :address1 => 'The Parkway',
- :address2 => "5258 Larches Approach",
- :city => "Hull",
- :state => "North Humberside",
- :zip => 'HU10 5OP'
- },
- :order_id => generate_unique_id,
- :description => 'AM test purchase'
- }
- end
-
- def test_successful_visacreditcard_authorization_and_capture
- assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visacredit_options)
- assert_equal 'APPROVED', responseCapture.message
- assert_success responseCapture
- assert responseCapture.test?
- end
-
- def test_successful_visacreditcard_authorization_and_refund
- assert responseAuthorization = @gateway.authorize(284, @visacreditcard, @visacredit_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @visacredit_options)
- assert_equal 'APPROVED', responseRefund.message
- assert_success responseRefund
- assert responseRefund.test?
- end
-
- def test_successful_visacreditcard_authorization_and_void
- assert responseAuthorization = @gateway.authorize(284, @visacreditcard, @visacredit_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseRefund = @gateway.void(responseAuthorization.authorization, @visacredit_options)
- assert_equal 'APPROVED', responseRefund.message
- assert_success responseRefund
- assert responseRefund.test?
- end
-
- def test_successful_visadebitcard_authorization_and_capture
- assert responseAuthorization = @gateway.authorize(142, @visadebitcard, @visadebit_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visadebit_options)
- assert_equal 'APPROVED', responseCapture.message
- assert_success responseCapture
- assert responseCapture.test?
- end
-
- def test_successful_visadebitcard_authorization_and_refund
- assert responseAuthorization = @gateway.authorize(284, @visadebitcard, @visadebit_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @visadebit_options)
- assert_equal 'APPROVED', responseRefund.message
- assert_success responseRefund
- assert responseRefund.test?
- end
-
- def test_successful_amex_authorization_and_capture
- assert responseAuthorization = @gateway.authorize(142, @amex, @amex_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @amex_options)
- assert_equal 'APPROVED', responseCapture.message
- assert_success responseCapture
- assert responseCapture.test?
- end
-
- def test_successful_amex_authorization_and_refund
- assert responseAuthorization = @gateway.authorize(284, @amex, @amex_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @amex_options)
- assert_equal 'APPROVED', responseRefund.message
- assert_success responseRefund
- assert responseRefund.test?
- end
-
- def test_successful_mastercard_authorization_and_capture
- assert responseAuthorization = @gateway.authorize(142, @mastercard, @mastercard_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @mastercard_options)
- assert_equal 'APPROVED', responseCapture.message
- assert_success responseCapture
- assert responseCapture.test?
- end
-
- def test_successful_mastercard_authorization_and_refund
- assert responseAuthorization = @gateway.authorize(284, @mastercard, @mastercard_options)
- assert_equal 'APPROVED', responseAuthorization.message
- assert_success responseAuthorization
- assert responseAuthorization.test?
- assert !responseAuthorization.authorization.blank?
- assert responseRefund = @gateway.refund(142, responseAuthorization.authorization, @mastercard_options)
- assert_equal 'APPROVED', responseRefund.message
- assert_success responseRefund
- assert responseRefund.test?
- end
-
- def test_successful_visacreditcard_purchase
- assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options)
- assert_equal 'APPROVED', response.message
- assert_success response
- assert response.test?
- assert !response.authorization.blank?
- end
-
- def test_successful_visadebitcard_purchase
- assert response = @gateway.purchase(142, @visadebitcard, @visadebit_options)
- assert_equal 'APPROVED', response.message
- assert_success response
- assert response.test?
- assert !response.authorization.blank?
- end
-
- def test_successful_mastercard_purchase
- assert response = @gateway.purchase(142, @mastercard, @mastercard_options)
- assert_equal 'APPROVED', response.message
- assert_success response
- assert response.test?
- assert !response.authorization.blank?
- end
-
- def test_declined_mastercard_purchase
- assert response = @gateway.purchase(10000, @mastercard, @mastercard_options)
- assert_equal 'CARD DECLINED', response.message
- assert_failure response
- assert response.test?
- end
-
- def test_expired_mastercard
- @mastercard.year = 2012
- assert response = @gateway.purchase(142, @mastercard, @mastercard_options)
- assert_equal 'INVALID CARDEXPIRYDATE', response.message
- assert_failure response
- assert response.test?
- end
-
- def test_successful_maestro_purchase
- assert response = @gateway.purchase(142, @uk_maestro, @uk_maestro_options)
- assert_equal 'APPROVED', response.message
- assert_success response
- end
-
- def test_successful_amex_purchase
- assert response = @gateway.purchase(142, @amex, @amex_options)
- assert_equal 'APPROVED', response.message
- assert_success response
- assert response.test?
- assert !response.authorization.blank?
- end
-
- def test_invalid_login
- gateway = CardStreamModernGateway.new(
- :login => '',
- :password => ''
- )
- assert response = gateway.purchase(142, @mastercard, @mastercard_options)
- assert_equal 'MISSING MERCHANTID', response.message
- assert_failure response
- end
-
- def test_usd_merchant_currency
- assert response = @gateway.purchase(142, @mastercard, @mastercard_options.update(:currency => 'USD'))
- assert_equal 'APPROVED', response.message
- assert_success response
- assert response.test?
- end
-end
diff --git a/test/remote/gateways/remote_card_stream_test.rb b/test/remote/gateways/remote_card_stream_test.rb
index 1ae1de60cee..fd6d3e8e3ef 100644
--- a/test/remote/gateways/remote_card_stream_test.rb
+++ b/test/remote/gateways/remote_card_stream_test.rb
@@ -3,146 +3,429 @@
class RemoteCardStreamTest < Test::Unit::TestCase
def setup
Base.mode = :test
-
+
@gateway = CardStreamGateway.new(fixtures(:card_stream))
-
+
@amex = credit_card('374245455400001',
- :month => '12',
- :year => '2009',
- :verification_value => '4887',
- :brand => :american_express
- )
-
- @uk_maestro = credit_card('675940410531100173',
- :month => '12',
- :year => '2008',
- :issue_number => '0',
- :verification_value => '134',
- :brand => :switch
- )
-
- @solo = credit_card('676740340572345678',
- :month => '12',
- :year => '2008',
- :issue_number => '1',
- :verification_value => '773',
- :brand => :solo
- )
+ :month => '12',
+ :year => '2014',
+ :verification_value => '4887',
+ :brand => :american_express
+ )
@mastercard = credit_card('5301250070000191',
- :month => '12',
- :year => '2009',
- :verification_value => '419',
- :brand => :master
- )
+ :month => '12',
+ :year => '2014',
+ :verification_value => '419',
+ :brand => :master
+ )
+
+ @visacreditcard = credit_card('4929421234600821',
+ :month => '12',
+ :year => '2014',
+ :verification_value => '356',
+ :brand => :visa
+ )
+
+ @visadebitcard = credit_card('4539791001730106',
+ :month => '12',
+ :year => '2014',
+ :verification_value => '289',
+ :brand => :visa
+ )
@declined_card = credit_card('4000300011112220',
- :month => '9',
- :year => '2009'
- )
+ :month => '9',
+ :year => '2014'
+ )
- @mastercard_options = {
- :billing_address => {
- :address1 => '25 The Larches',
- :city => "Narborough",
- :state => "Leicester",
- :zip => 'LE10 2RT'
+ @amex_options = {
+ :billing_address => {
+ :address1 => 'The Hunts Way',
+ :city => '',
+ :state => 'Leicester',
+ :zip => 'SO18 1GW',
+ :country => 'GB'
},
:order_id => generate_unique_id,
- :description => 'Store purchase'
+ :description => 'AM test purchase',
+ :ip => '1.1.1.1'
}
-
- @uk_maestro_options = {
- :billing_address => {
- :address1 => 'The Parkway',
- :address2 => "Larches Approach",
- :city => "Hull",
- :state => "North Humberside",
- :zip => 'HU7 9OP'
+
+ @visacredit_options = {
+ :billing_address => {
+ :address1 => 'Flat 6, Primrose Rise',
+ :address2 => '347 Lavender Road',
+ :city => '',
+ :state => 'Northampton',
+ :zip => 'NN17 8YG',
+ :country => 'GB'
},
:order_id => generate_unique_id,
- :description => 'Store purchase'
+ :description => 'AM test purchase',
+ :ip => '1.1.1.1'
}
-
- @solo_options = {
+
+ @visacredit_descriptor_options = {
:billing_address => {
- :address1 => '5 Zigzag Road',
- :city => 'Isleworth',
+ :address1 => 'Flat 6, Primrose Rise',
+ :address2 => '347 Lavender Road',
+ :city => '',
+ :state => 'Northampton',
+ :zip => 'NN17 8YG',
+ :country => 'GB'
+ },
+ :merchant_name => 'merchant',
+ :dynamic_descriptor => 'product',
+ :ip => '1.1.1.1',
+ }
+
+ @visacredit_reference_options = {
+ :order_id => generate_unique_id,
+ :description => 'AM test purchase',
+ :ip => '1.1.1.1'
+ }
+
+ @visadebit_options = {
+ :billing_address => {
+ :address1 => 'Unit 5, Pickwick Walk',
+ :address2 => '120 Uxbridge Road',
+ :city => 'Hatch End',
:state => 'Middlesex',
- :zip => 'TW7 8FF'
+ :zip => 'HA6 7HJ',
+ :country => 'GB'
+ },
+ :order_id => generate_unique_id,
+ :description => 'AM test purchase',
+ :ip => '1.1.1.1'
+ }
+
+ @mastercard_options = {
+ :billing_address => {
+ :address1 => '25 The Larches',
+ :city => 'Narborough',
+ :state => 'Leicester',
+ :zip => 'LE10 2RT',
+ :country => 'GB'
},
:order_id => generate_unique_id,
- :description => 'Store purchase'
+ :description => 'AM test purchase',
+ :ip => '1.1.1.1'
}
+
+ @three_ds_enrolled_card = credit_card('4012001037141112',
+ :month => '12',
+ :year => '2020',
+ :brand => :visa
+ )
end
-
- def test_successful_mastercard_purchase
- assert response = @gateway.purchase(100, @mastercard, @mastercard_options)
+
+ def test_successful_visacreditcard_authorization_and_capture
+ assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options)
+ assert_equal 'APPROVED', responseAuthorization.message
+ assert_success responseAuthorization
+ assert responseAuthorization.test?
+ assert !responseAuthorization.authorization.blank?
+ assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visacredit_options)
+ assert_equal 'APPROVED', responseCapture.message
+ assert_success responseCapture
+ assert responseCapture.test?
+ end
+
+ def test_successful_visacreditcard_authorization_and_capture_no_billing_address
+ assert responseAuthorization = @gateway.authorize(142, @visacreditcard, @visacredit_options.delete(:billing_address))
+ assert_equal 'APPROVED', responseAuthorization.message
+ assert_success responseAuthorization
+ assert responseAuthorization.test?
+ assert !responseAuthorization.authorization.blank?
+ assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visacredit_options)
+ assert_equal 'APPROVED', responseCapture.message
+ assert_success responseCapture
+ assert responseCapture.test?
+ end
+
+ def test_successful_visacreditcard_purchase_and_refund_with_force_refund
+ assert responsePurchase = @gateway.purchase(284, @visacreditcard, @visacredit_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visacredit_options.merge(force_full_refund_if_unsettled: true))
+ assert_equal 'APPROVED', responseRefund.message
+ assert_success responseRefund
+ assert responseRefund.test?
+ end
+
+ def test_failed_visacreditcard_purchase_and_refund
+ assert responsePurchase = @gateway.purchase(284, @visacreditcard, @visacredit_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visacredit_options)
+ assert_failure responseRefund
+ assert_equal 'Can not REFUND this SALE transaction', responseRefund.message
+ assert responseRefund.test?
+ end
+
+ def test_successful_visacreditcard_purchase_with_dynamic_descriptors
+ assert responsePurchase = @gateway.purchase(284, @visacreditcard, @visacredit_descriptor_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+ end
+
+ def test_successful_visacreditcard_authorization_and_void
+ assert responseAuthorization = @gateway.authorize(284, @visacreditcard, @visacredit_options)
+ assert_equal 'APPROVED', responseAuthorization.message
+ assert_success responseAuthorization
+ assert responseAuthorization.test?
+ assert !responseAuthorization.authorization.blank?
+ assert responseVoid = @gateway.void(responseAuthorization.authorization, @visacredit_options)
+ assert_equal 'APPROVED', responseVoid.message
+ assert_success responseVoid
+ assert responseVoid.test?
+ end
+
+ def test_successful_visadebitcard_authorization_and_capture
+ assert responseAuthorization = @gateway.authorize(142, @visadebitcard, @visadebit_options)
+ assert_equal 'APPROVED', responseAuthorization.message
+ assert_success responseAuthorization
+ assert responseAuthorization.test?
+ assert !responseAuthorization.authorization.blank?
+ assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @visadebit_options)
+ assert_equal 'APPROVED', responseCapture.message
+ assert_success responseCapture
+ assert responseCapture.test?
+ end
+
+ def test_successful_visadebitcard_purchase_and_refund_with_force_refund
+ assert responsePurchase = @gateway.purchase(284, @visadebitcard, @visadebit_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visadebit_options.merge(force_full_refund_if_unsettled: true))
+ assert_equal 'APPROVED', responseRefund.message
+ assert_success responseRefund
+ assert responseRefund.test?
+ end
+
+ def test_failed_visadebitcard_purchase_and_refund
+ assert responsePurchase = @gateway.purchase(284, @visadebitcard, @visadebit_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @visadebit_options)
+ assert_equal 'Can not REFUND this SALE transaction', responseRefund.message
+ assert_failure responseRefund
+ assert responseRefund.test?
+ end
+
+ def test_successful_amex_authorization_and_capture
+ assert responseAuthorization = @gateway.authorize(142, @amex, @amex_options)
+ assert_equal 'APPROVED', responseAuthorization.message
+ assert_success responseAuthorization
+ assert responseAuthorization.test?
+ assert !responseAuthorization.authorization.blank?
+ assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @amex_options)
+ assert_equal 'APPROVED', responseCapture.message
+ assert_success responseCapture
+ assert responseCapture.test?
+ end
+
+ def test_successful_amex_purchase_and_refund_with_force_refund
+ assert responsePurchase = @gateway.purchase(284, @amex, @amex_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @amex_options.merge(force_full_refund_if_unsettled: true))
+ assert_equal 'APPROVED', responseRefund.message
+ assert_success responseRefund
+ assert responseRefund.test?
+ end
+
+ def test_failed_amex_purchase_and_refund
+ assert responsePurchase = @gateway.purchase(284, @amex, @amex_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @amex_options)
+ assert_equal 'Can not REFUND this SALE transaction', responseRefund.message
+ assert_failure responseRefund
+ assert responseRefund.test?
+ end
+
+ def test_successful_mastercard_authorization_and_capture
+ assert responseAuthorization = @gateway.authorize(142, @mastercard, @mastercard_options)
+ assert_equal 'APPROVED', responseAuthorization.message
+ assert_success responseAuthorization
+ assert responseAuthorization.test?
+ assert !responseAuthorization.authorization.blank?
+ assert responseCapture = @gateway.capture(142, responseAuthorization.authorization, @mastercard_options)
+ assert_equal 'APPROVED', responseCapture.message
+ assert_success responseCapture
+ assert responseCapture.test?
+ end
+
+ def test_successful_mastercard_purchase_and_refund_with_force_refund
+ assert responsePurchase = @gateway.purchase(284, @mastercard, @mastercard_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @mastercard_options.merge(force_full_refund_if_unsettled: true))
+ assert_equal 'APPROVED', responseRefund.message
+ assert_success responseRefund
+ assert responseRefund.test?
+ end
+
+ def test_failed_mastercard_purchase_and_refund
+ assert responsePurchase = @gateway.purchase(284, @mastercard, @mastercard_options)
+ assert_equal 'APPROVED', responsePurchase.message
+ assert_success responsePurchase
+ assert responsePurchase.test?
+ assert !responsePurchase.authorization.blank?
+
+ assert responseRefund = @gateway.refund(142, responsePurchase.authorization, @mastercard_options)
+ assert_equal 'Can not REFUND this SALE transaction', responseRefund.message
+ assert_failure responseRefund
+ assert responseRefund.test?
+ end
+
+ def test_successful_visacreditcard_purchase
+ assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options)
assert_equal 'APPROVED', response.message
assert_success response
assert response.test?
assert !response.authorization.blank?
end
-
- def test_declined_mastercard_purchase
- assert response = @gateway.purchase(10000, @mastercard, @mastercard_options)
- assert_equal 'CARD DECLINED', response.message
- assert_failure response
+
+ def test_successful_visacreditcard_purchase_via_reference
+ assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge({:type => '9'}))
+ assert_equal 'APPROVED', response.message
+ assert_success response
+ assert response.test?
+ assert response = @gateway.purchase(142, response.authorization, @visacredit_reference_options)
+ assert_equal 'APPROVED', response.message
+ assert_success response
assert response.test?
end
-
- def test_expired_mastercard
- @mastercard.year = 2005
- assert response = @gateway.purchase(100, @mastercard, @mastercard_options)
- assert_equal 'CARD EXPIRED', response.message
+
+ def test_failed_visacreditcard_purchase_via_reference
+ assert response = @gateway.purchase(142, 123, @visacredit_reference_options)
+ assert_match %r{INVALID_XREF}, response.message
assert_failure response
assert response.test?
end
- def test_successful_maestro_purchase
- assert response = @gateway.purchase(100, @uk_maestro, @uk_maestro_options)
- assert_equal 'APPROVED', response.message
+ def test_purchase_no_currency_specified_defaults_to_GBP
+ assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(currency: nil))
assert_success response
+ assert_equal '826', response.params['currencyCode']
+ assert_equal 'APPROVED', response.message
end
-
- def test_successful_solo_purchase
- assert response = @gateway.purchase(100, @solo, @solo_options)
+
+ def test_failed_purchase_non_existent_currency
+ assert response = @gateway.purchase(142, @visacreditcard, @visacredit_options.merge(currency: 'CEO'))
+ assert_failure response
+ assert_match %r{MISSING_CURRENCYCODE}, response.message
+ end
+
+ def test_successful_visadebitcard_purchase
+ assert response = @gateway.purchase(142, @visadebitcard, @visadebit_options)
assert_equal 'APPROVED', response.message
assert_success response
assert response.test?
assert !response.authorization.blank?
end
-
- def test_successful_amex_purchase
- assert response = @gateway.purchase(100, @amex, :order_id => generate_unique_id)
+
+ def test_successful_mastercard_purchase
+ assert response = @gateway.purchase(142, @mastercard, @mastercard_options)
assert_equal 'APPROVED', response.message
assert_success response
assert response.test?
assert !response.authorization.blank?
end
-
- def test_maestro_missing_start_date_and_issue_date
- @uk_maestro.issue_number = nil
- assert response = @gateway.purchase(100, @uk_maestro, @uk_maestro_options)
- assert_equal 'ISSUE NUMBER MISSING', response.message
+
+ def test_declined_mastercard_purchase
+ assert response = @gateway.purchase(10000, @mastercard, @mastercard_options)
+ assert_equal 'CARD DECLINED', response.message
assert_failure response
assert response.test?
end
-
+
+ def test_successful_amex_purchase
+ assert response = @gateway.purchase(142, @amex, @amex_options)
+ assert_equal 'APPROVED', response.message
+ assert_success response
+ assert response.test?
+ assert !response.authorization.blank?
+ end
+
def test_invalid_login
gateway = CardStreamGateway.new(
:login => '',
- :password => ''
+ :shared_secret => ''
)
- assert response = gateway.purchase(100, @mastercard, @mastercard_options)
- assert_equal 'MERCHANT ID MISSING', response.message
+ assert response = gateway.purchase(142, @mastercard, @mastercard_options)
+ assert_match %r{MISSING_MERCHANTID}, response.message
assert_failure response
end
-
- def test_unsupported_merchant_currency
- assert response = @gateway.purchase(100, @mastercard, @mastercard_options.update(:currency => 'USD'))
- assert_equal "ERROR 1052", response.message
+
+ def test_successful_verify
+ response = @gateway.verify(@mastercard, @mastercard_options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @mastercard_options)
assert_failure response
+ assert_match %r{INVALID_CARDNUMBER}, response.message
+ end
+
+ def test_successful_3dsecure_purchase
+ assert response = @gateway.purchase(1202, @three_ds_enrolled_card, @mastercard_options.merge(threeds_required: true))
+ assert_equal '3DS AUTHENTICATION REQUIRED', response.message
+ assert_equal '65802', response.params['responseCode']
+ assert response.test?
+ assert !response.authorization.blank?
+ assert !response.params['threeDSACSURL'].blank?
+ assert !response.params['threeDSMD'].blank?
+ assert !response.params['threeDSPaReq'].blank?
+ end
+
+ def test_successful_3dsecure_auth
+ assert response = @gateway.authorize(1202, @three_ds_enrolled_card, @mastercard_options.merge(threeds_required: true))
+ assert_equal '3DS AUTHENTICATION REQUIRED', response.message
+ assert_equal '65802', response.params['responseCode']
assert response.test?
+ assert !response.authorization.blank?
+ assert !response.params['threeDSACSURL'].blank?
+ assert !response.params['threeDSMD'].blank?
+ assert !response.params['threeDSPaReq'].blank?
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @visacreditcard, @visacredit_options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@visacreditcard.number, clean_transcript)
+ assert_scrubbed(@visacreditcard.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:shared_secret], clean_transcript)
end
end
diff --git a/test/remote/gateways/remote_cardknox_test.rb b/test/remote/gateways/remote_cardknox_test.rb
new file mode 100644
index 00000000000..211e1e9afcb
--- /dev/null
+++ b/test/remote/gateways/remote_cardknox_test.rb
@@ -0,0 +1,301 @@
+require 'test_helper'
+
+class RemoteCardknoxTest < Test::Unit::TestCase
+ def setup
+ @gateway = CardknoxGateway.new(fixtures(:cardknox))
+
+ @amount = rand(100..499)
+ @declined_amount = 500
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('4000300011112220', verification_value: '518')
+ @check = check(number: rand(0..100000))
+
+ @more_options = {
+ billing_address: address,
+ shipping_address: address,
+ order_id: generate_unique_id,
+ invoice: generate_unique_id,
+ name: 'Jim Smith',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ tip: 2,
+ tax: 3,
+ custom02: 'mycustom',
+ custom13: 'spelled right',
+ custom25: 'test 25',
+ pin: '312lkjasdnotvalid',
+ address: {
+ address1: '19 Laurel Valley Dr',
+ address2: 'Apt 1',
+ company: 'Widgets Inc',
+ city: 'Brownsburg',
+ state: 'IN',
+ zip: '46112',
+ country: 'US',
+ phone: '(555)555-5555',
+ fax: '(555)555-6666',
+ }
+ }
+
+ @options = {}
+ end
+
+ def test_successful_credit_card_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_credit_card_purchase_with_more_options
+ response = @gateway.purchase(@amount, @credit_card, @more_options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_credit_card_track_data_purchase
+ response = @gateway.purchase(@amount, credit_card_with_track_data, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_credit_card_token_purchase
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ assert purchase = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success purchase
+ assert_equal 'Success', purchase.message
+ end
+
+ def test_successful_check_purchase_with_options
+ response = @gateway.purchase(@amount, @check, @more_options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_check_token_purchase
+ response = @gateway.store(@check, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ assert purchase = @gateway.purchase(@amount, response.authorization)
+ assert_success purchase
+ assert_equal 'Success', purchase.message
+ end
+
+ def test_failed_credit_card_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid CVV', response.message
+ end
+
+ def test_successful_credit_card_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Success', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', auth.message
+ end
+
+ def test_successful_cardknox_token_authorize_and_capture
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ auth = @gateway.authorize(@amount, response.authorization, @options)
+ assert_success auth
+ assert_equal 'Success', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', auth.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid CVV', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Original transaction not specified', response.message
+ end
+
+ def test_credit_card_purchase_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_credit_card_authorize_partial_refund
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert refund = @gateway.refund(@amount-1, auth.authorization)
+ assert_failure refund
+ assert_equal 'Refund not allowed on non-captured auth.', refund.message
+ end
+
+ def test_failed_partial_check_refund # the gate way does not support this transaction
+ purchase = @gateway.purchase(@amount, @check, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_failure refund
+ assert_equal "Transaction is in a state that cannot be refunded\nParameter name: originalReferenceNumber", refund.message # "Only allowed to refund transactions that have settled. This is the best we can do for now testing wise."
+ end
+
+ def test_credit_card_capture_partial_refund
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+
+ assert refund = @gateway.refund(@amount-1, capture.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'UNSUPPORTED CARD TYPE', response.message
+ end
+
+ def test_successful_credit_card_authorize_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_successful_credit_card_capture_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+
+ assert void = @gateway.void(capture.authorization, @options)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_successful_credit_card_purchase_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert void = @gateway.void(purchase.authorization, @options)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_successful_credit_card_refund_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+
+ assert refund = @gateway.refund(@amount-1, capture.authorization)
+ assert_success refund
+
+ assert void = @gateway.void(refund.authorization, @options)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_successful_check_void
+ purchase = @gateway.purchase(@amount, @check, @options)
+ assert_success purchase
+
+ assert void = @gateway.void(purchase.authorization, @options)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Original transaction not specified', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Success}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Invalid CVV}, response.message
+ end
+
+ def test_successful_credit_card_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_credit_card_token_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+
+ assert store = @gateway.store(response.authorization)
+ assert_success store
+ assert_equal 'Success', store.message
+ end
+
+ def test_successful_check_store
+ response = @gateway.store(@check, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_store
+ response = @gateway.store('', @options)
+ assert_failure response
+ assert_equal 'Card or Magstripe Required', response.message
+ end
+
+ def test_invalid_login
+ gateway = CardknoxGateway.new(api_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Required: xKey}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_cardprocess_test.rb b/test/remote/gateways/remote_cardprocess_test.rb
new file mode 100644
index 00000000000..951c07e0ab3
--- /dev/null
+++ b/test/remote/gateways/remote_cardprocess_test.rb
@@ -0,0 +1,149 @@
+require 'test_helper'
+
+class RemoteCardprocessTest < Test::Unit::TestCase
+ def setup
+ @gateway = CardprocessGateway.new(fixtures(:cardprocess))
+
+ @amount = 100
+ @credit_card = credit_card('4200000000000000')
+ @credit_card_3ds = credit_card('4711100000000000')
+
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_match %r{^Request successfully processed}, response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_match %r{^Request successfully processed}, response.message
+ end
+
+ def test_failed_purchase
+ bad_credit_card = credit_card('4200000000000001')
+ response = @gateway.purchase(@amount, bad_credit_card, @options)
+ assert_failure response
+ assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code
+ assert_equal 'invalid creditcard, bank account number or bank name', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_match %r{^Request successfully processed}, capture.message
+ end
+
+ def test_failed_authorize
+ @gateway.instance_variable_set(:@test_options, {'customParameters[forceResultCode]' => '800.100.151'})
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'transaction declined (invalid card)', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '12345678123456781234567812345678')
+ assert_failure response
+ assert_equal 'capture needs at least one successful transaction of type (PA)', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_match %r{^Request successfully processed}, refund.message
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_match %r{^Request successfully processed}, response.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'invalid or missing parameter', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_match %r{^Request successfully processed}, void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'invalid or missing parameter', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{^Request successfully processed}, response.message
+ end
+
+ def test_failed_verify
+ @gateway.instance_variable_set(:@test_options, {'customParameters[forceResultCode]' => '600.200.100'})
+ response = @gateway.verify(@credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid Payment Method}, response.message
+ end
+
+ def test_invalid_login
+ gateway = CardprocessGateway.new(user_id: '00000000000000000000000000000000', password: 'qwerty', entity_id: '00000000000000000000000000000000')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'invalid authentication information', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ assert_scrubbed(@gateway.options[:entity_id], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_cashnet_test.rb b/test/remote/gateways/remote_cashnet_test.rb
new file mode 100644
index 00000000000..87aea788007
--- /dev/null
+++ b/test/remote/gateways/remote_cashnet_test.rb
@@ -0,0 +1,66 @@
+require 'test_helper'
+
+class CashnetTest < Test::Unit::TestCase
+ def setup
+ @gateway = CashnetGateway.new(fixtures(:cashnet))
+ @amount = 100
+ @credit_card = credit_card(
+ '5454545454545454',
+ month: 12,
+ year: 2015
+ )
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address
+ }
+ end
+
+ def test_successful_purchase_and_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert purchase.test?
+ assert_equal 'Success', purchase.message
+ assert purchase.authorization
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, {})
+ assert_success refund
+ assert refund.test?
+ assert_equal 'Success', refund.message
+ end
+
+ def test_successful_refund_with_options
+ assert purchase = @gateway.purchase(@amount, @credit_card, custcode: 'TheCustCode')
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, email: 'wow@example.com', custcode: 'TheCustCode')
+ assert_success refund
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(-44, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Negative amount is not allowed}, response.message
+ assert_equal '5', response.params['result']
+ end
+
+ def test_failed_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount + 50, purchase.authorization)
+ assert_failure refund
+ assert_match %r{Amount to refund exceeds}, refund.message
+ assert_equal '302', refund.params['result']
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_cecabank_test.rb b/test/remote/gateways/remote_cecabank_test.rb
new file mode 100644
index 00000000000..b44ff568ead
--- /dev/null
+++ b/test/remote/gateways/remote_cecabank_test.rb
@@ -0,0 +1,54 @@
+require 'test_helper'
+
+class RemoteCecabankTest < Test::Unit::TestCase
+ def setup
+ @gateway = CecabankGateway.new(fixtures(:cecabank))
+
+ @amount = 100
+ @credit_card = credit_card('5540500001000004', {:month => 12, :year => Time.now.year, :verification_value => 989})
+ @declined_card = credit_card('5540500001000004', {:month => 11, :year => Time.now.year + 1, :verification_value => 001})
+
+ @options = {
+ :order_id => generate_unique_id,
+ :description => 'Active Merchant Test Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, order_id: generate_unique_id)
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match 'ERROR', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert response = @gateway.refund(@amount, purchase.authorization, order_id: @options[:order_id])
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+
+ def test_unsuccessful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert response = @gateway.refund(@amount, purchase.authorization, @options.merge(currency: 'USD'))
+ assert_failure response
+ assert_match 'ERROR', response.message
+ end
+
+ def test_invalid_login
+ gateway = CecabankGateway.new(fixtures(:cecabank).merge(key: 'invalid'))
+
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'ERROR', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_cenpos_test.rb b/test/remote/gateways/remote_cenpos_test.rb
new file mode 100644
index 00000000000..a3474658bbc
--- /dev/null
+++ b/test/remote/gateways/remote_cenpos_test.rb
@@ -0,0 +1,192 @@
+require 'test_helper'
+
+class RemoteCenposTest < Test::Unit::TestCase
+ def setup
+ @gateway = CenposGateway.new(fixtures(:cenpos))
+
+ @amount = SecureRandom.random_number(10000)
+ @credit_card = credit_card('4111111111111111', month: 02, year: 18, verification_value: 999)
+ @declined_card = credit_card('4000300011112220')
+ @invalid_card = credit_card('9999999999999999')
+
+ @options = {
+ order_id: SecureRandom.random_number(1000000),
+ billing_address: address
+ }
+ end
+
+ def test_invalid_login
+ gateway = CenposGateway.new(
+ merchant_id: '',
+ password: '',
+ user_id: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'See transcript for detailed error description.', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_cvv_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_equal 'M', response.cvv_result['code']
+ end
+
+ def test_successful_purchase_avs_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_equal 'D', response.avs_result['code']
+ end
+
+ def test_successful_purchase_with_invoice_detail
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(invoice_detail: ''))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_customer_code
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(customer_code: '3214'))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_currency
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'EUR'))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Decline transaction', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+
+ def test_failed_purchase_cvv_result
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ %w(code message).each do |key|
+ assert_equal nil, response.cvv_result[key]
+ end
+ end
+
+ def test_failed_purchase_avs_result
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ %w(code message).each do |key|
+ assert_equal nil, response.avs_result[key]
+ end
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+\|.+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Decline transaction', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+
+ def test_failed_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+
+ @gateway.capture(@amount, response.authorization)
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_failure capture
+ assert_equal 'Duplicated force transaction.', capture.message
+ end
+
+ def test_successful_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_void_can_receive_order_id
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization, order_id: SecureRandom.random_number(1000000))
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ @gateway.void(response.authorization)
+ void = @gateway.void(response.authorization)
+ assert_failure void
+ assert_equal 'Original Transaction not found', void.message
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ assert_equal 'See transcript for detailed error description.', response.message
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card number', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Succeeded}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Decline transaction', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_certo_direct_test.rb b/test/remote/gateways/remote_certo_direct_test.rb
deleted file mode 100644
index 40df73ea669..00000000000
--- a/test/remote/gateways/remote_certo_direct_test.rb
+++ /dev/null
@@ -1,117 +0,0 @@
-require 'test_helper'
-
-class CertoDirectTest < Test::Unit::TestCase
- def setup
- Base.mode = :test
-
- @gateway = CertoDirectGateway.new(fixtures(:certo_direct))
- @amount = 100
- @credit_card = credit_card('4012888888881881', :month => 1)
- @options = {
- :billing_address => {
- :address1 => 'Infinite Loop 1',
- :country => 'US',
- :state => 'TX',
- :city => 'Gotham',
- :zip => '23456',
- :phone => '+1-132-12345678',
- :first_name => 'John',
- :last_name => 'Doe'
- },
- :email => 'john.doe@example.com',
- :currency => 'USD',
- :ip => '127.0.0.1',
- :description => 'Test Order of ActiveMerchant.'
- }
- end
-
- def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert response.test?
- assert_equal 'Transaction was successfully processed', response.message
- assert response.authorization
- end
-
- def test_expired_credit_card
- @credit_card.year = 2004
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert response.test?
- assert_equal 'invalid transaction data', response.message
- end
-
- def test_bad_login
- gateway = CertoDirectGateway.new(:login => 'X', :password => 'Y')
-
- assert response = gateway.purchase(@amount, @credit_card, @options)
-
- assert_equal Response, response.class
- assert_match(/Authentication was failed/, response.message)
- assert_equal false, response.success?
- end
-
- def test_fail_purchase
- @credit_card.month = 2
-
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert response.test?
- assert_equal 'Transaction was declined', response.message
- end
-
- def test_purchase_and_refund
- # purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
-
- assert_equal 'Transaction was successfully processed', response.message
- assert order_id = response.authorization
-
- # refund
- assert response = @gateway.refund(@amount, order_id, :reason => 'Merchant request.')
- assert_success response
- assert_equal 'Transaction was successfully processed', response.message
- assert response.authorization
- end
-
- def test_authorization_and_capture
- assert authorization = @gateway.authorize(@amount, @credit_card, @options)
- assert_success authorization
-
- assert capture = @gateway.capture(@amount, authorization.authorization)
- assert_success capture
- assert_equal 'Transaction was successfully processed', capture.message
- end
-
- def test_authorization_and_void
- assert authorization = @gateway.authorize(@amount, @credit_card, @options)
- assert_success authorization
-
- assert void = @gateway.void(@amount, authorization.authorization)
- assert_success void
- assert_equal 'Transaction was successfully processed', void.message
- end
-
- def test_sale_and_recurring
- assert sale = @gateway.purchase(@amount, @credit_card, @options)
- assert_success sale
-
- assert recurring = @gateway.recurring(sale.authorization)
- assert_success recurring
- assert_equal 'Recurring Transaction was successfully processed', recurring.message
- end
-
- def test_sale_and_recurring_overriding_details
- assert sale = @gateway.purchase(@amount, @credit_card, @options)
- assert_success sale
-
- assert recurring = @gateway.recurring(sale.authorization,
- :amount => 99,
- :currency => 'USD',
- :shipping => 1)
-
- assert_success recurring
- assert_equal 'Recurring Transaction was successfully processed', recurring.message
- end
-end
diff --git a/test/remote/gateways/remote_checkout_test.rb b/test/remote/gateways/remote_checkout_test.rb
new file mode 100644
index 00000000000..206442a70fa
--- /dev/null
+++ b/test/remote/gateways/remote_checkout_test.rb
@@ -0,0 +1,130 @@
+require 'test_helper'
+
+class RemoteCheckoutTest < Test::Unit::TestCase
+ def setup
+ @gateway = ActiveMerchant::Billing::CheckoutGateway.new(fixtures(:checkout))
+ @credit_card = credit_card(
+ '4543474002249996',
+ month: '06',
+ year: '2017',
+ verification_value: '956'
+ )
+ @declined_card = credit_card(
+ '4543474002249996',
+ month: '06',
+ year: '2018',
+ verification_value: '958'
+ )
+ @options = {
+ currency: 'CAD'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(100, @credit_card, @options)
+ assert_success response
+ assert_equal 'Successful', response.message
+ end
+
+ def test_successful_purchase_with_extra_options
+ response = @gateway.purchase(100, @credit_card, @options.merge(
+ currency: 'EUR',
+ email: 'bob@example.com',
+ order_id: generate_unique_id,
+ customer: generate_unique_id,
+ ip: '127.0.0.1'
+ ))
+ assert_success response
+ assert_equal 'Successful', response.message
+ end
+
+ def test_successful_purchase_without_billing_address
+ response = @gateway.purchase(100, @credit_card, @options)
+ assert_success response
+ assert_equal 'Successful', response.message
+ end
+
+ def test_successful_purchase_with_descriptors
+ response = @gateway.purchase(100, @credit_card, descriptor_name: 'TheName', descriptor_city: 'Wanaque')
+ assert_success response
+ assert_equal 'Successful', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(100, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Not Successful', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(100, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(100, auth.authorization, {currency: 'CAD'})
+ assert_success capture
+ assert_equal 'Successful', capture.message
+ end
+
+ # For backwards compatability with previous version where authorization was just transid.
+ def test_successful_authorize_and_capture_transid_only
+ auth = @gateway.authorize(100, @credit_card, @options)
+ assert_success auth
+
+ capture = @gateway.capture(100, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'Successful', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(100, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Not Successful', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(100, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ auth = @gateway.authorize(100, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void('||||')
+ assert_failure void
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(100, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(100, response.authorization, {currency: 'CAD'})
+ assert_success refund
+ assert_equal 'Successful', refund.message
+ end
+
+ def test_failed_refund
+ assert response = @gateway.purchase(100, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(100, '||||', {currency: 'CAD'})
+ assert_failure refund
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Successful', response.message
+
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'Successful', response.responses.last.params['result']
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_checkout_v2_test.rb b/test/remote/gateways/remote_checkout_v2_test.rb
new file mode 100644
index 00000000000..bec80aba153
--- /dev/null
+++ b/test/remote/gateways/remote_checkout_v2_test.rb
@@ -0,0 +1,248 @@
+require 'test_helper'
+
+class RemoteCheckoutV2Test < Test::Unit::TestCase
+ def setup
+ @gateway = CheckoutV2Gateway.new(fixtures(:checkout_v2))
+
+ @amount = 200
+ @credit_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2025')
+ @expired_card = credit_card('4242424242424242', verification_value: '100', month: '6', year: '2010')
+ @declined_card = credit_card('42424242424242424', verification_value: '234', month: '6', year: '2025')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Purchase',
+ email: 'longbob.longsen@example.com'
+ }
+ @additional_options = @options.merge(
+ card_on_file: true,
+ transaction_indicator: 2,
+ previous_charge_id: 'pay_123'
+ )
+ @additional_options_3ds = @options.merge(
+ execute_threed: true,
+ three_d_secure: {
+ version: '1.0.2',
+ eci: '06',
+ cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
+ xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY='
+ }
+ )
+ @additional_options_3ds2 = @options.merge(
+ execute_threed: true,
+ three_d_secure: {
+ version: '2.0.0',
+ eci: '06',
+ cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
+ ds_transaction_id: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY='
+ }
+ )
+ end
+
+ def test_transcript_scrubbing
+ declined_card = credit_card('4000300011112220', verification_value: '423')
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, declined_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(declined_card.number, transcript)
+ assert_scrubbed(declined_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:secret_key], transcript)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_additional_options
+ response = @gateway.purchase(@amount, @credit_card, @additional_options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_moto_flag
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(transaction_indicator: 3))
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_includes_avs_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'S', response.avs_result['code']
+ assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message']
+ end
+
+ def test_successful_authorize_includes_avs_result
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'S', response.avs_result['code']
+ assert_equal 'U.S.-issuing bank does not support AVS.', response.avs_result['message']
+ end
+
+ def test_successful_purchase_includes_cvv_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'Y', response.cvv_result['code']
+ end
+
+ def test_successful_authorize_includes_cvv_result
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal 'Y', response.cvv_result['code']
+ end
+
+ def test_successful_purchase_with_descriptors
+ options = @options.merge(descriptor_name: 'shop', descriptor_city: 'london')
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_minimal_options
+ response = @gateway.purchase(@amount, @credit_card, billing_address: address)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_without_phone_number
+ response = @gateway.purchase(@amount, @credit_card, billing_address: address.update(phone: ''))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_ip
+ response = @gateway.purchase(@amount, @credit_card, ip: '96.125.185.52')
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'request_invalid: card_number_invalid', response.message
+ end
+
+ def test_avs_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, billing_address: address.update(address1: 'Test_A'))
+ assert_failure response
+ assert_equal 'request_invalid: card_number_invalid', response.message
+ end
+
+ def test_avs_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, billing_address: address.update(address1: 'Test_A'))
+ assert_failure response
+ assert_equal 'request_invalid: card_number_invalid', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_authorize_and_capture_with_additional_options
+ auth = @gateway.authorize(@amount, @credit_card, @additional_options)
+ assert_success auth
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_authorize_and_capture_with_3ds
+ auth = @gateway.authorize(@amount, @credit_card, @additional_options_3ds)
+ assert_success auth
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_authorize_and_capture_with_3ds2
+ auth = @gateway.authorize(@amount, @credit_card, @additional_options_3ds2)
+ assert_success auth
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Succeeded}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{request_invalid: card_number_invalid}, response.message
+ end
+
+ def test_expired_card_returns_error_code
+ response = @gateway.purchase(@amount, @expired_card, @options)
+ assert_failure response
+ assert_equal 'request_invalid: card_expired', response.message
+ assert_equal 'request_invalid: card_expired', response.error_code
+ end
+end
diff --git a/test/remote/gateways/remote_citrus_pay_test.rb b/test/remote/gateways/remote_citrus_pay_test.rb
new file mode 100644
index 00000000000..cf7d131b942
--- /dev/null
+++ b/test/remote/gateways/remote_citrus_pay_test.rb
@@ -0,0 +1,134 @@
+require 'test_helper'
+
+class RemoteCitrusPayTest < Test::Unit::TestCase
+ def setup
+ CitrusPayGateway.ssl_strict = false # Sandbox has an improperly installed cert
+ @gateway = CitrusPayGateway.new(fixtures(:citrus_pay))
+
+ @amount = 100
+ @credit_card = credit_card('4987654321098769')
+ @declined_card = credit_card('5019994000124034')
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def teardown
+ CitrusPayGateway.ssl_strict = true
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_sans_options
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ more_options = @options.merge({
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ })
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_adds_3dsecure_id_to_authorize
+ more_options = @options.merge({
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ threed_secure_id: 'abc123'
+ })
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options))
+ assert_match 'No check has been performed for this merchant and 3D Secure Id.', response.message
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match %r{FAILURE}, response.message
+ end
+
+ def test_successful_authorize_and_capture
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^.+\|\d+$), response.authorization
+
+ assert capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/FAILURE/, response.message)
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'SUCCESS', response.responses.last.params['result']
+ end
+
+ def test_invalid_login
+ gateway = CitrusPayGateway.new(
+ :userid => 'nosuch',
+ :password => 'thing'
+ )
+ response = gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'ERROR - INVALID_REQUEST - Invalid credentials.', response.message
+ end
+
+ def test_transcript_scrubbing
+ card = credit_card('4987654321098769', verification_value: '134')
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = CitrusPayGateway.new(userid: 'unknown', password: 'unknown')
+ assert !gateway.verify_credentials
+ end
+
+end
diff --git a/test/remote/gateways/remote_clearhaus_test.rb b/test/remote/gateways/remote_clearhaus_test.rb
new file mode 100644
index 00000000000..9a716294dcb
--- /dev/null
+++ b/test/remote/gateways/remote_clearhaus_test.rb
@@ -0,0 +1,209 @@
+require 'test_helper'
+
+class RemoteClearhausTest < Test::Unit::TestCase
+ def setup
+ @gateway = ClearhausGateway.new(fixtures(:clearhaus))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('4200000000000000')
+ @options = {}
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_signing_request
+ gateway = ClearhausGateway.new(fixtures(:clearhaus_secure))
+
+ assert gateway.options[:private_key]
+ assert auth = gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ end
+
+ def test_cleans_whitespace_from_private_key
+ credentials = fixtures(:clearhaus_secure)
+ credentials[:private_key] = " #{credentials[:private_key]} "
+ gateway = ClearhausGateway.new(credentials)
+
+ assert gateway.options[:private_key]
+ assert auth = gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ end
+
+ def test_unsuccessful_signing_request
+ credentials = fixtures(:clearhaus_secure)
+ credentials[:private_key] = 'foo'
+ gateway = ClearhausGateway.new(credentials)
+
+ assert gateway.options[:private_key]
+ assert auth = gateway.authorize(@amount, @credit_card, @options)
+ assert_failure auth
+ assert_equal 'Neither PUB key nor PRIV key: not enough data', auth.message
+
+ credentials = fixtures(:clearhaus_secure)
+ credentials[:signing_key] = 'foo'
+ gateway = ClearhausGateway.new(credentials)
+
+ assert gateway.options[:signing_key]
+ assert auth = gateway.authorize(@amount, @credit_card, @options)
+ assert_failure auth
+ assert_equal 'invalid signing api-key', auth.message
+ end
+
+ def test_successful_purchase_without_cvv
+ gateway = ClearhausGateway.new(fixtures(:clearhaus_secure))
+ credit_card = credit_card('4111111111111111', verification_value: nil)
+ response = gateway.purchase(@amount, credit_card, @options)
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_text_on_statement
+ options = { text_on_statement: 'hello' }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ assert_success response
+ assert_equal response.params['text_on_statement'], 'hello'
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card number', response.message
+ assert_equal 40110, response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(currency: 'ABC'))
+ assert_failure response
+ assert_equal 'invalid currency', response.message
+ assert_equal 40140, response.error_code
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'z')
+ assert_failure response
+ assert_equal 'invalid transaction id', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '123')
+ assert_failure response
+ assert_equal 'invalid transaction id', response.message
+ end
+
+ def test_successful_refund_of_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+
+ assert refund = @gateway.refund(@amount, capture.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('123')
+ assert_failure response
+ assert_equal 'invalid transaction id', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'Approved', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Invalid card number}, response.message
+ end
+
+ def test_successful_authorize_with_nonfractional_currency
+ assert response = @gateway.authorize(100, @credit_card, @options.merge(:currency => 'KRW'))
+ assert_equal 1, response.params['amount']
+ assert_success response
+ end
+
+ def test_invalid_login
+ gateway = ClearhausGateway.new(api_key: 'test')
+
+ assert_raise ActiveMerchant::ResponseError do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_commercegate_test.rb b/test/remote/gateways/remote_commercegate_test.rb
new file mode 100644
index 00000000000..3ca37b555c2
--- /dev/null
+++ b/test/remote/gateways/remote_commercegate_test.rb
@@ -0,0 +1,91 @@
+require 'test_helper'
+
+class RemoteCommercegateTest < Test::Unit::TestCase
+ def setup
+ @gateway = CommercegateGateway.new(fixtures(:commercegate))
+
+ @amount = 1000
+
+ @options = {
+ address: address
+ }
+
+ @credit_card = credit_card(fixtures(:commercegate)[:card_number])
+ @expired_credit_card = credit_card(fixtures(:commercegate)[:card_number], year: Time.now.year-1)
+ end
+
+ def test_successful_authorize
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal response.params['action'], 'AUTH'
+ assert_equal 'U', response.avs_result['code']
+ assert_equal 'M', response.cvv_result['code']
+ end
+
+ def test_successful_authorize_without_options
+ assert response = @gateway.authorize(@amount, @credit_card)
+ assert_success response
+ assert_equal response.params['action'], 'AUTH'
+ assert_nil response.avs_result['code']
+ assert_equal 'M', response.cvv_result['code']
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal response.params['action'], 'SALE'
+ assert_equal 'U', response.avs_result['code']
+ assert_equal 'M', response.cvv_result['code']
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @expired_credit_card, @options)
+ assert_failure response
+ assert_not_nil response.message
+ end
+
+ def test_authorize_and_capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Success', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '123', @options)
+ assert_failure response
+ assert_equal 'Previous transaction not found', response.message
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert trans_id = response.params['transID']
+
+ assert response = @gateway.refund(@amount, trans_id, @options)
+ assert_success response
+ assert_equal response.params['action'], 'REFUND'
+ end
+
+ def test_successful_void
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert trans_id = response.params['transID']
+ assert response = @gateway.void(trans_id)
+ assert_success response
+ assert_equal response.params['action'], 'VOID_AUTH'
+ end
+
+ def test_invalid_login
+ gateway = CommercegateGateway.new(
+ login: '',
+ password: '',
+ site_id: '',
+ offer_id: ''
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_conekta_test.rb b/test/remote/gateways/remote_conekta_test.rb
new file mode 100644
index 00000000000..c466b8ad74c
--- /dev/null
+++ b/test/remote/gateways/remote_conekta_test.rb
@@ -0,0 +1,197 @@
+require 'test_helper'
+
+class RemoteConektaTest < Test::Unit::TestCase
+ def setup
+ @gateway = ConektaGateway.new(fixtures(:conekta))
+
+ @amount = 300
+
+ @credit_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '4242424242424242',
+ verification_value: '183',
+ month: '01',
+ year: '2019',
+ first_name: 'Mario F.',
+ last_name: 'Moreno Reyes'
+ )
+
+ @declined_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '4000000000000002',
+ verification_value: '183',
+ month: '01',
+ year: '2019',
+ first_name: 'Mario F.',
+ last_name: 'Moreno Reyes'
+ )
+
+ @options = {
+ :device_fingerprint => '41l9l92hjco6cuekf0c7dq68v4',
+ description: 'Blue clip',
+ billing_address: {
+ address1: 'Rio Missisipi #123',
+ address2: 'Paris',
+ city: 'Guerrero',
+ country: 'Mexico',
+ zip: '5555',
+ name: 'Mario Reyes',
+ phone: '12345678',
+ },
+ carrier: 'Estafeta',
+ email: 'bob@something.com',
+ line_items: [{
+ name: 'Box of Cohiba S1s',
+ description: 'Imported From Mex.',
+ unit_price: 20000,
+ quantity: 1,
+ sku: '7500244909',
+ type: 'food'
+ }]
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+ end
+
+ def test_successful_purchase_with_installments
+ assert response = @gateway.purchase(@amount * 300, @credit_card, @options.merge({monthly_installments: 3}))
+ assert_success response
+ assert_equal nil, response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ @options[:order_id] = response.params['id']
+ assert_success response
+ assert_equal nil, response.message
+
+ assert response = @gateway.refund(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal nil, response.message
+ end
+
+ def test_successful_void
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+
+ identifier = response.params['id']
+
+ assert response = @gateway.void(identifier)
+ assert_success response
+ assert_equal nil, response.message
+ end
+
+ def test_unsuccessful_void
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+
+ identifier = response.params['id']
+
+ assert response = @gateway.void(identifier)
+ assert_failure response
+ assert_equal 'El cargo no existe o no es apto para esta operación.', response.message
+ end
+
+ def test_unsuccessful_refund
+ assert response = @gateway.refund(@amount, '1', @options)
+ assert_failure response
+ assert_equal 'El recurso no ha sido encontrado.', response.message
+ end
+
+ def test_successful_authorize
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+ end
+
+ def test_unsuccessful_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_capture
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+
+ assert response = @gateway.capture(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal nil, response.message
+ end
+
+ def test_unsuccessful_capture
+ assert response = @gateway.capture(@amount, '1', @options)
+ assert_failure response
+ assert_equal 'El recurso no ha sido encontrado.', response.message
+ end
+
+ def test_successful_purchase_passing_more_details
+ more_options = {
+ customer: 'TheCustomerName',
+ shipping_address: {
+ address1: '33 Main Street',
+ address2: 'Apartment 3',
+ city: 'Wanaque',
+ state: 'NJ',
+ country: 'USA',
+ zip: '01085',
+ },
+ line_items: [
+ {
+ name: 'Box of Cohiba S1s',
+ description: 'Imported From Mex.',
+ unit_price: 20000,
+ quantity: 1,
+ sku: 'cohb_s1',
+ type: 'other_human_consumption'
+ },
+ {
+ name: 'Basic Toothpicks',
+ description: 'Wooden',
+ unit_price: 100,
+ quantity: 250,
+ sku: 'tooth_r3',
+ type: 'Extra pointy'
+ }
+ ]
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options))
+ assert_success response
+ assert_equal 'Wanaque', response.params['details']['shipment']['address']['city']
+ assert_equal 'Wooden', response.params['details']['line_items'][-1]['description']
+ assert_equal 'TheCustomerName', response.params['details']['name']
+ assert_equal 'Guerrero', response.params['details']['billing_address']['city']
+ end
+
+ def test_failed_purchase_with_no_details
+ assert response = @gateway.purchase(@amount, @credit_card, {})
+ assert_failure response
+ assert_equal 'Falta el correo del comprador.', response.message
+ end
+
+ def test_invalid_key
+ gateway = ConektaGateway.new(key: 'invalid_token')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Acceso no autorizado.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_creditcall_test.rb b/test/remote/gateways/remote_creditcall_test.rb
new file mode 100644
index 00000000000..527d60979a6
--- /dev/null
+++ b/test/remote/gateways/remote_creditcall_test.rb
@@ -0,0 +1,171 @@
+require 'test_helper'
+
+class RemoteCreditcallTest < Test::Unit::TestCase
+ def setup
+ @gateway = CreditcallGateway.new(fixtures(:creditcall))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('4000300011112220')
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_sans_options
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal response.params['Zip'], 'notchecked'
+ assert_equal response.params['Address'], 'notchecked'
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ manual_type: 'cnp'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ @amount = 1001
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'ExpiredCard', response.message
+ end
+
+ def test_successful_authorize
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Succeeded', auth.message
+ end
+
+ def test_successful_authorize_with_zip_verification
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(verify_zip: 'true'))
+ assert_success response
+ assert_equal response.params['Zip'], 'matched'
+ assert_equal response.params['Address'], 'notchecked'
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_authorize_with_address_verification
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(verify_address: 'true'))
+ assert_success response
+ assert_equal response.params['Zip'], 'notchecked'
+ assert_equal response.params['Address'], 'matched'
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ @amount = 1001
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'ExpiredCard', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'CardEaseReferenceInvalid', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ assert refund = @gateway.refund(@amount, '')
+ assert_failure refund
+ assert_equal 'CardEaseReferenceInvalid', refund.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'CardEaseReferenceInvalid', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Succeeded}, response.message
+ end
+
+ def test_failed_verify
+ @declined_card.number = ''
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{PAN Must be >= 13 Digits}, response.message
+ end
+
+ def test_invalid_login
+ gateway = CreditcallGateway.new(terminal_id: '', transaction_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid TerminalID - Must be 8 digit number}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:transaction_key], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_credorax_test.rb b/test/remote/gateways/remote_credorax_test.rb
new file mode 100644
index 00000000000..f826f157987
--- /dev/null
+++ b/test/remote/gateways/remote_credorax_test.rb
@@ -0,0 +1,587 @@
+require 'test_helper'
+
+class RemoteCredoraxTest < Test::Unit::TestCase
+ def setup
+ @gateway = CredoraxGateway.new(fixtures(:credorax))
+
+ @amount = 100
+ @credit_card = credit_card('4176661000001015', verification_value: '281', month: '12', year: '2022')
+ @fully_auth_card = credit_card('5223450000000007', brand: 'mastercard', verification_value: '090', month: '12', year: '2025')
+ @declined_card = credit_card('4176661000001111', verification_value: '681', month: '12', year: '2022')
+ @options = {
+ order_id: '1',
+ currency: 'EUR',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_invalid_login
+ gateway = CredoraxGateway.new(merchant_id: '', cipher_key: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal '1', response.params['H9']
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_extra_options
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(transaction_type: '10'))
+ assert_success response
+ assert_equal '1', response.params['H9']
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_auth_data_via_3ds1_fields
+ options = @options.merge(
+ eci: '02',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: '00000000000000000501'
+ )
+
+ response = @gateway.purchase(@amount, @fully_auth_card, options)
+ assert_success response
+ assert_equal '1', response.params['H9']
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_auth_data_via_normalized_3ds2_options
+ version = '2.0'
+ eci = '02'
+ cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA='
+ ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC'
+ options = @options.merge(
+ three_d_secure: {
+ version: version,
+ eci: eci,
+ cavv: cavv,
+ ds_transaction_id: ds_transaction_id
+ }
+ )
+
+ response = @gateway.purchase(@amount, @fully_auth_card, options)
+ assert_success response
+ assert_equal '1', response.params['H9']
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction not allowed for cardholder', response.message
+ end
+
+ def test_failed_purchase_invalid_auth_data_via_3ds1_fields
+ options = @options.merge(
+ eci: '02',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: 'this is not a valid xid, it will be rejected'
+ )
+
+ response = @gateway.purchase(@amount, @fully_auth_card, options)
+ assert_failure response
+ assert_equal '-9', response.params['Z2']
+ assert_match 'Parameter i8 is invalid', response.message
+ end
+
+ def test_failed_purchase_invalid_auth_data_via_normalized_3ds2_options
+ version = '2.0'
+ eci = '02'
+ cavv = 'BOGUS'
+ ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC'
+ options = @options.merge(
+ three_d_secure: {
+ version: version,
+ eci: eci,
+ cavv: cavv,
+ ds_transaction_id: ds_transaction_id
+ }
+ )
+
+ response = @gateway.purchase(@amount, @fully_auth_card, options)
+ assert_failure response
+ assert_equal '-9', response.params['Z2']
+ assert_match 'malformed', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_successful_authorize_with_auth_data_via_3ds1_fields
+ options = @options.merge(
+ eci: '02',
+ cavv: 'jJ81HADVRtXfCBATEp01CJUAAAA=',
+ xid: '00000000000000000501'
+ )
+
+ response = @gateway.authorize(@amount, @fully_auth_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
+ def test_successful_authorize_with_auth_data_via_normalized_3ds2_options
+ version = '2.0'
+ eci = '02'
+ cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA='
+ ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC'
+ options = @options.merge(
+ three_d_secure: {
+ version: version,
+ eci: eci,
+ cavv: cavv,
+ ds_transaction_id: ds_transaction_id
+ }
+ )
+
+ response = @gateway.authorize(@amount, @fully_auth_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction not allowed for cardholder', response.message
+ end
+
+ def test_failed_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ capture = @gateway.capture(0, auth.authorization)
+ assert_failure capture
+ assert_equal 'Invalid amount', capture.message
+ end
+
+ def test_successful_purchase_and_void
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_successful_authorize_and_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_successful_capture_and_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+
+ void = @gateway.void(capture.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Referred to transaction has not been found.', response.message
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_successful_refund_and_void
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+
+ void = @gateway.void(refund.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '123;123;123')
+ assert_failure response
+ assert_equal 'Referred to transaction has not been found.', response.message
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(0, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid amount', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction not allowed for cardholder', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_cvv_scrubbed(clean_transcript)
+ end
+
+ # #########################################################################
+ # # CERTIFICATION SPECIFIC REMOTE TESTS
+ # #########################################################################
+ #
+ # # Send [a5] currency code parameter as "AFN"
+ # def test_certification_error_unregistered_currency
+ # @options[:echo] = "33BE888"
+ # @options[:currency] = "AFN"
+ # response = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_failure response
+ # end
+ #
+ # # Send [b2] parameter as "6"
+ # def test_certification_error_unregistered_card
+ # @options[:echo] = "33BE889"
+ # response = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_failure response
+ # end
+ #
+ # # In the future, merchant expected to investigate each such case offline.
+ # def test_certification_error_no_response_from_the_gate
+ # @options[:echo] = "33BE88A"
+ # response = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_failure response
+ # end
+ #
+ # # Merchant is expected to verify if the code is "0" - in this case the
+ # # transaction should be considered approved. In all other cases the
+ # # offline investigation should take place.
+ # def test_certification_error_unknown_result_code
+ # @options[:echo] = "33BE88B"
+ # response = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_failure response
+ # end
+ #
+ # # Merchant is expected to verify if the code is "00" - in this case the
+ # # transaction should be considered approved. In all other cases the
+ # # transaction is declined. The exact reason should be investigated offline.
+ # def test_certification_error_unknown_response_reason_code
+ # @options[:echo] = "33BE88C"
+ # @options[:email] = "brucewayne@dccomics.com"
+ # @options[:billing_address] = {
+ # address1: "5050 Gotham Drive",
+ # city: "Toronto",
+ # zip: "B2M 1Y9",
+ # state: "ON",
+ # country: "CA",
+ # phone: "(0800)228626"
+ # }
+ #
+ # credit_card = credit_card('4176661000001015',
+ # brand: "visa",
+ # verification_value: "281",
+ # month: "12",
+ # year: "17",
+ # first_name: "Bruce",
+ # last_name: "Wayne")
+ #
+ # response = @gateway.purchase(@amount, credit_card, @options)
+ # assert_failure response
+ # end
+ #
+ # # All fields marked as mandatory are expected to be populated with the
+ # # above default values. Mandatory fields with no values on the
+ # # certification template should be populated with your own meaningful
+ # # values and comply with our API specifications. The d2 parameter is
+ # # mandatory during certification only to allow for tracking of tests.
+ # # Expected result of this test: Time out
+ # def test_certification_time_out
+ # @options[:echo] = "33BE88D"
+ # @options[:email] = "brucewayne@dccomics.com"
+ # @options[:billing_address] = {
+ # address1: "5050 Gotham Drive",
+ # city: "Toronto",
+ # zip: "B2M 1Y9",
+ # state: "ON",
+ # country: "CA",
+ # phone: "(0800)228626"
+ # }
+ #
+ # credit_card = credit_card('5473470000000010',
+ # brand: "master",
+ # verification_value: "939",
+ # month: "12",
+ # year: "17",
+ # first_name: "Bruce",
+ # last_name: "Wayne")
+ #
+ # response = @gateway.purchase(@amount, credit_card, @options)
+ # assert_failure response
+ # end
+ #
+ # # All fields marked as mandatory are expected to be populated
+ # # with the above default values. Mandatory fields with no values
+ # # on the certification template should be populated with your
+ # # own meaningful values and comply with our API specifications.
+ # # The d2 parameter is mandatory during certification only to
+ # # allow for tracking of tests.
+ # def test_certification_za_zb_zc
+ # @options[:echo] = "33BE88E"
+ # @options[:email] = "brucewayne@dccomics.com"
+ # @options[:billing_address] = {
+ # address1: "5050 Gotham Drive",
+ # city: "Toronto",
+ # zip: "B2M 1Y9",
+ # state: "ON",
+ # country: "CA",
+ # phone: "(0800)228626"
+ # }
+ #
+ # credit_card = credit_card('5473470000000010',
+ # verification_value: "939",
+ # month: "12",
+ # year: "17",
+ # first_name: "Bruce",
+ # last_name: "Wayne")
+ #
+ # purchase = @gateway.purchase(@amount, credit_card, @options)
+ # assert_success purchase
+ # assert_equal "Succeeded", purchase.message
+ #
+ # refund_options = {echo: "33BE892"}
+ # refund = @gateway.refund(@amount, purchase.authorization, refund_options)
+ # assert_success refund
+ # assert_equal "Succeeded", refund.message
+ #
+ # void_options = {echo: "33BE895"}
+ # void = @gateway.void(refund.authorization, void_options)
+ # assert_success void
+ # assert_equal "Succeeded", refund.message
+ # end
+ #
+ # # All fields marked as mandatory are expected to be populated
+ # # with the above default values. Mandatory fields with no values
+ # # on the certification template should be populated with your
+ # # own meaningful values and comply with our API specifications.
+ # # The d2 parameter is mandatory during certification only to
+ # # allow for tracking of tests.
+ # def test_certification_zg_zh
+ # @options[:echo] = "33BE88F"
+ # @options[:email] = "clark.kent@dccomics.com"
+ # @options[:billing_address] = {
+ # address1: "2020 Krypton Drive",
+ # city: "Toronto",
+ # zip: "S2M 1YR",
+ # state: "ON",
+ # country: "CA",
+ # phone: "(0800) 78737626"
+ # }
+ #
+ # credit_card = credit_card('4176661000001015',
+ # brand: "visa",
+ # verification_value: "281",
+ # month: "12",
+ # year: "17",
+ # first_name: "Clark",
+ # last_name: "Kent")
+ #
+ # response = @gateway.authorize(@amount, credit_card, @options)
+ # assert_success response
+ # assert_equal "Succeeded", response.message
+ #
+ # capture_options = {echo: "33BE890"}
+ # capture = @gateway.capture(@amount, response.authorization, capture_options)
+ # assert_success capture
+ # assert_equal "Succeeded", capture.message
+ # end
+ #
+ # # All fields marked as mandatory are expected to be populated
+ # # with the above default values. Mandatory fields with no values
+ # # on the certification template should be populated with your
+ # # own meaningful values and comply with our API specifications.
+ # # The d2 parameter is mandatory during certification only to
+ # # allow for tracking of tests.
+ # def test_certification_zg_zj
+ # @options[:echo] = "33BE88F"
+ # @options[:email] = "clark.kent@dccomics.com"
+ # @options[:billing_address] = {
+ # address1: "2020 Krypton Drive",
+ # city: "Toronto",
+ # zip: "S2M 1YR",
+ # state: "ON",
+ # country: "CA",
+ # phone: "(0800) 78737626"
+ # }
+ #
+ # credit_card = credit_card('4176661000001015',
+ # brand: "visa",
+ # verification_value: "281",
+ # month: "12",
+ # year: "17",
+ # first_name: "Clark",
+ # last_name: "Kent")
+ #
+ # response = @gateway.authorize(@amount, credit_card, @options)
+ # assert_success response
+ # assert_equal "Succeeded", response.message
+ #
+ # auth_void_options = {echo: "33BE891"}
+ # auth_void = @gateway.void(response.authorization, auth_void_options)
+ # assert_success auth_void
+ # assert_equal "Succeeded", auth_void.message
+ # end
+ #
+ # # All fields marked as mandatory are expected to be populated
+ # # with the above default values. Mandatory fields with no values
+ # # on the certification template should be populated with your
+ # # own meaningful values and comply with our API specifications.
+ # # The d2 parameter is mandatory during certification only to
+ # # allow for tracking of tests.
+ # #
+ # # Certification for independent credit (credit)
+ # def test_certification_zd
+ # @options[:echo] = "33BE893"
+ # @options[:email] = "wadewilson@marvel.com"
+ # @options[:billing_address] = {
+ # address1: "5050 Deadpool Drive",
+ # city: "Toronto",
+ # zip: "D2P 1Y9",
+ # state: "ON",
+ # country: "CA",
+ # phone: "+1(555)123-4567"
+ # }
+ #
+ # credit_card = credit_card('4176661000001015',
+ # brand: "visa",
+ # verification_value: "281",
+ # month: "12",
+ # year: "17",
+ # first_name: "Wade",
+ # last_name: "Wilson")
+ #
+ # response = @gateway.credit(@amount, credit_card, @options)
+ # assert_success response
+ # assert_equal "Succeeded", response.message
+ # end
+ #
+ # # Use the above values to fill the mandatory parameters in your
+ # # certification test transactions. Note:The d2 parameter is only
+ # # mandatory during certification to allow for tracking of tests.
+ # #
+ # # Certification for purchase void
+ # def test_certification_zf
+ # @options[:echo] = "33BE88E"
+ # @options[:email] = "brucewayne@dccomics.com"
+ # @options[:billing_address] = {
+ # address1: "5050 Gotham Drive",
+ # city: "Toronto",
+ # zip: "B2M 1Y9",
+ # state: "ON",
+ # country: "CA",
+ # phone: "(0800)228626"
+ # }
+ #
+ # credit_card = credit_card('5473470000000010',
+ # verification_value: "939",
+ # month: "12",
+ # year: "17",
+ # first_name: "Bruce",
+ # last_name: "Wayne")
+ #
+ # response = @gateway.purchase(@amount, credit_card, @options)
+ # assert_success response
+ # assert_equal "Succeeded", response.message
+ #
+ # void_options = {echo: "33BE894"}
+ # void = @gateway.void(response.authorization, void_options)
+ # assert_success void
+ # assert_equal "Succeeded", void.message
+ # end
+ #
+ # # Use the above values to fill the mandatory parameters in your
+ # # certification test transactions. Note:The d2 parameter is only
+ # # mandatory during certification to allow for tracking of tests.
+ # #
+ # # Certification for capture void
+ # def test_certification_zi
+ # @options[:echo] = "33BE88F"
+ # @options[:email] = "clark.kent@dccomics.com"
+ # @options[:billing_address] = {
+ # address1: "2020 Krypton Drive",
+ # city: "Toronto",
+ # zip: "S2M 1YR",
+ # state: "ON",
+ # country: "CA",
+ # phone: "(0800) 78737626"
+ # }
+ #
+ # credit_card = credit_card('4176661000001015',
+ # brand: "visa",
+ # verification_value: "281",
+ # month: "12",
+ # year: "17",
+ # first_name: "Clark",
+ # last_name: "Kent")
+ #
+ # authorize = @gateway.authorize(@amount, credit_card, @options)
+ # assert_success authorize
+ # assert_equal "Succeeded", authorize.message
+ #
+ # capture_options = {echo: "33BE890"}
+ # capture = @gateway.capture(@amount, authorize.authorization, capture_options)
+ # assert_success capture
+ # assert_equal "Succeeded", capture.message
+ #
+ # void_options = {echo: "33BE896"}
+ # void = @gateway.void(capture.authorization, void_options)
+ # assert_success void
+ # assert_equal "Succeeded", void.message
+ # end
+
+ private
+
+ def assert_cvv_scrubbed(transcript)
+ assert_match(/b5=\[FILTERED\]/, transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_ct_payment_certification_test.rb b/test/remote/gateways/remote_ct_payment_certification_test.rb
new file mode 100644
index 00000000000..7a4ef3988f4
--- /dev/null
+++ b/test/remote/gateways/remote_ct_payment_certification_test.rb
@@ -0,0 +1,243 @@
+require 'test_helper'
+
+class RemoteCtPaymentCertificationTest < Test::Unit::TestCase
+ def setup
+ @gateway = CtPaymentGateway.new(fixtures(:ct_payment))
+
+ @amount = 100
+ @declined_card = credit_card('4502244713161718')
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase',
+ merchant_terminal_number: ' ',
+ order_id: generate_unique_id[0, 11]
+ }
+ end
+
+ def test1
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(1, response)
+ end
+
+ def test2
+ @credit_card = credit_card('5194419000000007', month: '07', year: 2025)
+ @credit_card.brand = 'master'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(2, response)
+ end
+
+ def test3
+ @credit_card = credit_card('341400000000000', month: '07', year: 2025, verification_value: '1234')
+ @credit_card.brand = 'american_express'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(3, response)
+ end
+
+ def test6
+ @credit_card = credit_card('341400000000000', month: '07', year: 2025, verification_value: '1234')
+ @credit_card.brand = 'american_express'
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(6, response)
+ end
+
+ def test4
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(4, response)
+ end
+
+ def test5
+ @credit_card = credit_card('5194419000000007', month: '07', year: 2025)
+ @credit_card.brand = 'master'
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(5, response)
+ end
+
+ def test7
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ print_result(7, response)
+
+ capture_response = @gateway.capture(@amount, response.authorization, @options.merge(order_id: generate_unique_id[0, 11]))
+ print_result(10, capture_response)
+ end
+
+ def test8
+ @credit_card = credit_card('5194419000000007', month: '07', year: 2025)
+ @credit_card.brand = 'master'
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ print_result(8, response)
+
+ capture_response = @gateway.capture(@amount, response.authorization, @options.merge(order_id: generate_unique_id[0, 11]))
+ print_result(11, capture_response)
+ end
+
+ def test9
+ @credit_card = credit_card('341400000000000', month: '07', year: 2025, verification_value: '1234')
+ @credit_card.brand = 'american_express'
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(order_id: generate_unique_id[0, 11]))
+ print_result(9, response)
+
+ capture_response = @gateway.capture(@amount, response.authorization, @options)
+ print_result(12, capture_response)
+ end
+
+ def test13
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.purchase('000', @credit_card, @options)
+ print_result(13, response)
+ end
+
+ def test14
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.purchase(-100, @credit_card, @options)
+ print_result(14, response)
+ end
+
+ def test15
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.purchase('-1A0', @credit_card, @options)
+ print_result(15, response)
+ end
+
+ def test16
+ @credit_card = credit_card('5194419000000007', month: '07', year: 2025)
+ @credit_card.brand = 'visa'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(16, response)
+ end
+
+ def test17
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ @credit_card.brand = 'master'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(17, response)
+ end
+
+ def test18
+ @credit_card = credit_card('', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(18, response)
+ end
+
+ def test19
+ @credit_card = credit_card('4501123412341234', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(19, response)
+ end
+
+ def test20
+ # requires editing the model to run with a 3 digit expiration date
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(20, response)
+ end
+
+ def test21
+ @credit_card = credit_card('4501161107217214', month: 17, year: 2017)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(21, response)
+ end
+
+ def test22
+ @credit_card = credit_card('4501161107217214', month: '01', year: 2016)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(22, response)
+ end
+
+ def test24
+ @credit_card = credit_card('4502244713161718', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(24, response)
+ end
+
+ def test25
+ # Needs an edit to the Model to run
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(25, response)
+ end
+
+ def test26
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.credit('000', @credit_card, @options)
+ print_result(26, response)
+ end
+
+ def test27
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.credit(-100, @credit_card, @options)
+ print_result(27, response)
+ end
+
+ def test28
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.credit('-1A0', @credit_card, @options)
+ print_result(28, response)
+ end
+
+ def test29
+ @credit_card = credit_card('5194419000000007', month: '07', year: 2025)
+ @credit_card.brand = 'visa'
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(29, response)
+ end
+
+ def test30
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ @credit_card.brand = 'master'
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(30, response)
+ end
+
+ def test31
+ @credit_card = credit_card('', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(31, response)
+ end
+
+ def test32
+ @credit_card = credit_card('4501123412341234', month: '07', year: 2025)
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(32, response)
+ end
+
+ def test33
+ # requires edit to model to make 3 digit expiration date
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2)
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(33, response)
+ end
+
+ def test34
+ @credit_card = credit_card('4501161107217214', month: 17, year: 2017)
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(34, response)
+ end
+
+ def test35
+ @credit_card = credit_card('4501161107217214', month: '01', year: 2016)
+ response = @gateway.credit(@amount, @credit_card, @options)
+ print_result(35, response)
+ end
+
+ def test37
+ @credit_card = credit_card('4502244713161718', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(37, response)
+ end
+
+ def test38
+ # Needs an edit to the Model to run
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2025)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ print_result(38, response)
+ end
+
+ def print_result(test_number, response)
+ puts "Test #{test_number} | transaction number: #{response.params['transactionNumber']}, invoice number #{response.params['invoiceNumber']}, timestamp: #{response.params['timeStamp']}, result: #{response.params['returnCode']}"
+ puts response.inspect
+ end
+
+end
diff --git a/test/remote/gateways/remote_ct_payment_test.rb b/test/remote/gateways/remote_ct_payment_test.rb
new file mode 100644
index 00000000000..4fd0ba8738a
--- /dev/null
+++ b/test/remote/gateways/remote_ct_payment_test.rb
@@ -0,0 +1,173 @@
+require 'test_helper'
+
+class RemoteCtPaymentTest < Test::Unit::TestCase
+ def setup
+ @gateway = CtPaymentGateway.new(fixtures(:ct_payment))
+
+ @amount = 100
+ @credit_card = credit_card('4501161107217214', month: '07', year: 2020)
+ @declined_card = credit_card('4502244713161718')
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase',
+ order_id: generate_unique_id[0, 11],
+ email: 'bigbird@sesamestreet.com'
+
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction declined', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options.merge(order_id: generate_unique_id[0, 11]))
+ assert_success capture
+ assert_equal 'APPROVED', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction declined', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization, @options.merge(order_id: generate_unique_id[0, 11]))
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '0123456789asd;0123456789asdf;12345678', @options)
+ assert_failure response
+ assert_equal 'The original transaction number does not match any actual transaction', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options.merge(order_id: generate_unique_id[0, 11]))
+ assert_success refund
+ assert_equal 'APPROVED', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization, @options.merge(order_id: generate_unique_id[0, 11]))
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '0123456789asd;0123456789asdf;12345678', @options.merge(order_id: generate_unique_id[0, 11]))
+ assert_failure response
+ assert_equal 'The original transaction number does not match any actual transaction', response.message
+ end
+
+ def test_successful_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ assert_equal 'APPROVED', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('0123456789asd;0123456789asdf;12345678')
+ assert_failure response
+ assert_equal 'The original transaction number does not match any actual transaction', response.message
+ end
+
+ def test_successful_credit
+ assert response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @options)
+
+ assert_success response
+ assert !response.authorization.split(';')[3].nil?
+ end
+
+ def test_successful_purchase_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.purchase(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_successful_authorize_using_stored_card
+ assert store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+
+ response = @gateway.authorize(@amount, store_response.authorization, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{APPROVED}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Transaction declined}, response.message
+ end
+
+ def test_invalid_login
+ gateway = CtPaymentGateway.new(api_key: '', company_number: '12345', merchant_number: '12345')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid API KEY}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(Base64.strict_encode64(@credit_card.number), transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
+
+ def test_transcript_scrubbing_store
+ transcript = capture_transcript(@gateway) do
+ @gateway.store(@credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(Base64.strict_encode64(@credit_card.number), transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_culqi_test.rb b/test/remote/gateways/remote_culqi_test.rb
new file mode 100644
index 00000000000..d8241b2aef3
--- /dev/null
+++ b/test/remote/gateways/remote_culqi_test.rb
@@ -0,0 +1,176 @@
+require 'test_helper'
+
+class RemoteCulqiTest < Test::Unit::TestCase
+ def setup
+ CulqiGateway.ssl_strict = false # Sandbox has an improperly installed cert
+ @gateway = CulqiGateway.new(fixtures(:culqi))
+
+ @amount = 1000
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('4000300011112220', month: 06, year: 2016)
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address
+ }
+ end
+
+ def teardown
+ CulqiGateway.ssl_strict = true
+ end
+
+ def test_invalid_login
+ gateway = CulqiGateway.new(merchant_id: '', terminal_id: '', secret_key: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert_match %r{Approved}, purchase.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match %r{Failed}, response.message
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_match %r{Approved}, response.message
+ assert_match %r(^\d+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_match %r{Transaction has been successfully captured}, capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match %r{Failed}, response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ assert_match %r{Transaction has been successfully captured}, capture.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '0')
+ assert_failure response
+ assert_match %r{Transaction not found}, response.message
+ end
+
+ def test_successful_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization, @options)
+ assert_success void
+ assert_match %r{cancelled}, void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('0', @options)
+ assert_failure response
+ assert_match %r{Transaction not found}, response.message
+ end
+
+ def test_successful_refund
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ capture = @gateway.capture(@amount, auth.authorization)
+
+ refund = @gateway.refund(@amount, capture.authorization)
+ assert_success refund
+ assert_match %r{reversed}, refund.message
+ end
+
+ def test_partial_refund
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ capture = @gateway.capture(@amount, auth.authorization)
+
+ refund = @gateway.refund(@amount-1, capture.authorization)
+ assert_success refund
+ assert_match %r{reversed}, refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '0')
+ assert_failure response
+ assert_match %r{Transaction not found}, response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Approved}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Failed}, response.message
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = CulqiGateway.new(merchant_id: 'unknown', terminal_id: 'unknown', secret_key: 'unknown')
+ assert !gateway.verify_credentials
+ gateway = CulqiGateway.new(merchant_id: fixtures(:culqi)[:merchant_id], terminal_id: fixtures(:culqi)[:terminal_id], secret_key: 'unknown')
+ assert !gateway.verify_credentials
+ end
+
+ def test_successful_store_and_purchase
+ credit_card = credit_card('4929927409600297')
+
+ response = @gateway.store(credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id]))
+ assert_success response
+ assert_match %r{Card tokenized successfully}, response.message
+
+ purchase = @gateway.purchase(@amount, response.authorization, @options.merge(cvv: credit_card.verification_value))
+ assert_success purchase
+ assert_match %r{Successful}, purchase.message
+
+ response = @gateway.invalidate(response.authorization, @options.merge(partner_id: fixtures(:culqi)[:partner_id]))
+ assert_success response
+ assert_match %r{Token invalidated successfully}, response.message
+ end
+
+ def test_failed_store
+ credit_card = credit_card('4929927409600297')
+
+ store = @gateway.store(credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id]))
+ assert_success store
+ assert_match %r{Card tokenized successfully}, store.message
+
+ response = @gateway.store(credit_card, @options.merge(partner_id: fixtures(:culqi)[:partner_id]))
+ assert_failure response
+ assert_match %r{Card already tokenized for same merchant}, response.message
+
+ response = @gateway.invalidate(store.authorization, @options.merge(partner_id: fixtures(:culqi)[:partner_id]))
+ assert_success response
+ assert_match %r{Token invalidated successfully}, response.message
+ end
+
+ def test_transcript_scrubbing
+ assert @gateway.supports_scrubbing?
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_cyber_source_test.rb b/test/remote/gateways/remote_cyber_source_test.rb
index 0bf44938fd5..c295737a0a6 100644
--- a/test/remote/gateways/remote_cyber_source_test.rb
+++ b/test/remote/gateways/remote_cyber_source_test.rb
@@ -2,19 +2,41 @@
class RemoteCyberSourceTest < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
+ Base.mode = :test
- @gateway = CyberSourceGateway.new(fixtures(:cyber_source))
+ @gateway = CyberSourceGateway.new({nexus: 'NC'}.merge(fixtures(:cyber_source)))
- @credit_card = credit_card('4111111111111111')
+ @credit_card = credit_card('4111111111111111', verification_value: '321')
@declined_card = credit_card('801111111111111')
@pinless_debit_card = credit_card('4002269999999999')
+ @elo_credit_card = credit_card('5067310000000010',
+ verification_value: '321',
+ month: '12',
+ year: (Time.now.year + 2).to_s,
+ brand: :elo
+ )
+ @three_ds_unenrolled_card = credit_card('4000000000000051',
+ verification_value: '321',
+ month: '12',
+ year: (Time.now.year + 2).to_s,
+ brand: :visa
+ )
+ @three_ds_enrolled_card = credit_card('4000000000000002',
+ verification_value: '321',
+ month: '12',
+ year: (Time.now.year + 2).to_s,
+ brand: :visa
+ )
+ @three_ds_invalid_card = credit_card('4000000000000010',
+ verification_value: '321',
+ month: '12',
+ year: (Time.now.year + 2).to_s,
+ brand: :visa
+ )
@amount = 100
@options = {
- :billing_address => address,
-
:order_id => generate_unique_id,
:line_items => [
{
@@ -32,44 +54,71 @@ def setup
}
],
:currency => 'USD',
- :email => 'someguy1232@fakeemail.net',
:ignore_avs => 'true',
:ignore_cvv => 'true'
}
@subscription_options = {
:order_id => generate_unique_id,
- :email => 'someguy1232@fakeemail.net',
:credit_card => @credit_card,
- :billing_address => address,
:subscription => {
- :frequency => "weekly",
+ :frequency => 'weekly',
:start_date => Date.today.next_week,
:occurrences => 4,
:auto_renew => true,
:amount => 100
}
}
+
+ @issuer_additional_data = 'PR25000000000011111111111112222222sk111111111111111111111111111'
+ + '1111111115555555222233101abcdefghijkl7777777777777777777777777promotionCde'
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_network_tokenization_transcript_scrubbing
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ :brand => 'visa',
+ :eci => '05',
+ :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.authorize(@amount, credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(credit_card.payment_cryptogram, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
end
def test_successful_authorization
assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ assert_successful_response(response)
assert !response.authorization.blank?
end
- def test_successful_subscription_authorization
- assert response = @gateway.store(@credit_card, @subscription_options)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ def test_successful_authorization_with_issuer_additional_data
+ @options[:issuer_additional_data] = @issuer_additional_data
- assert response = @gateway.authorize(@amount, response.authorization, :order_id => generate_unique_id)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_authorization_with_elo
+ assert response = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_successful_response(response)
assert !response.authorization.blank?
end
@@ -80,91 +129,106 @@ def test_unsuccessful_authorization
assert_equal false, response.success?
end
- def test_authorize_and_auth_reversal
+ def test_authorize_and_void
assert auth = @gateway.authorize(@amount, @credit_card, @options)
- assert_equal 'Successful transaction', auth.message
assert_success auth
- assert auth.test?
-
- assert auth_reversal = @gateway.auth_reversal(@amount, auth.authorization)
- assert_equal 'Successful transaction', auth_reversal.message
- assert_success auth_reversal
- assert auth_reversal.test?
+ assert void = @gateway.void(auth.authorization, @options)
+ assert_equal 'Successful transaction', void.message
+ assert_success void
+ assert void.test?
end
- def test_successful_authorization_and_failed_auth_reversal
+ def test_capture_and_void
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
- assert_equal 'Successful transaction', auth.message
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert void = @gateway.void(capture.authorization, @options)
+ assert_equal 'Successful transaction', void.message
+ assert_success void
+ assert void.test?
+ end
- assert auth_reversal = @gateway.auth_reversal(@amount + 10, auth.authorization)
- assert_failure auth_reversal
- assert_equal 'One or more fields contains invalid data', auth_reversal.message
+ def test_capture_and_void_with_elo
+ assert auth = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert void = @gateway.void(capture.authorization, @options)
+ assert_equal 'Successful transaction', void.message
+ assert_success void
+ assert void.test?
end
def test_successful_tax_calculation
assert response = @gateway.calculate_tax(@credit_card, @options)
assert_equal 'Successful transaction', response.message
assert response.params['totalTaxAmount']
- assert_not_equal "0", response.params['totalTaxAmount']
+ assert_not_equal '0', response.params['totalTaxAmount']
assert_success response
- assert response.test?
end
- def test_successful_tax_calculation_with_nexus
- total_line_items_value = @options[:line_items].inject(0) do |sum, item|
- sum += item[:declared_value] * item[:quantity]
- end
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ end
- canada_gst_rate = 0.05
- ontario_pst_rate = 0.08
+ def test_successful_purchase_with_issuer_additional_data
+ @options[:issuer_additional_data] = @issuer_additional_data
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ end
- total_pst = total_line_items_value.to_f * ontario_pst_rate / 100
- total_gst = total_line_items_value.to_f * canada_gst_rate / 100
- total_tax = total_pst + total_gst
+ def test_successful_purchase_with_elo
+ assert response = @gateway.purchase(@amount, @elo_credit_card, @options)
+ assert_successful_response(response)
+ end
- assert response = @gateway.calculate_tax(@credit_card, @options.merge(:nexus => 'ON'))
+ def test_successful_purchase_sans_options
+ assert response = @gateway.purchase(@amount, @credit_card)
assert_equal 'Successful transaction', response.message
- assert response.params['totalTaxAmount']
- assert_equal total_pst, response.params['totalCountyTaxAmount'].to_f
- assert_equal total_gst, response.params['totalStateTaxAmount'].to_f
- assert_equal total_tax, response.params['totalTaxAmount'].to_f
assert_success response
- assert response.test?
end
- def test_successful_purchase
+ def test_successful_purchase_with_billing_address_override
+ @options[:billing_address] = address
+ @options[:email] = 'override@example.com'
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'Successful transaction', response.message
assert_success response
- assert response.test?
end
- def test_successful_pinless_debit_card_puchase
- assert response = @gateway.purchase(@amount, @pinless_debit_card, @options.merge(:pinless_debit_card => true))
+ def test_successful_purchase_with_long_country_name
+ @options[:billing_address] = address(country: 'united states', state: 'NC')
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'Successful transaction', response.message
assert_success response
- assert response.test?
end
- def test_successful_subscription_purchase
- assert response = @gateway.store(@credit_card, @subscription_options)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ def test_successful_purchase_without_decision_manager
+ @options[:decision_manager_enabled] = 'false'
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ end
- assert response = @gateway.purchase(@amount, response.authorization, :order_id => generate_unique_id)
+ def test_successful_purchase_with_decision_manager_profile
+ @options[:decision_manager_enabled] = 'true'
+ @options[:decision_manager_profile] = 'Regular'
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ end
+
+ def test_successful_pinless_debit_card_puchase
+ assert response = @gateway.purchase(@amount, @pinless_debit_card, @options.merge(:pinless_debit_card => true))
assert_equal 'Successful transaction', response.message
assert_success response
- assert response.test?
end
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_equal 'Invalid account number', response.message
assert_failure response
- assert response.test?
end
def test_authorize_and_capture
@@ -176,6 +240,15 @@ def test_authorize_and_capture
assert_success capture
end
+ def test_authorize_and_capture_with_elo
+ assert auth = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_success auth
+ assert_equal 'Successful transaction', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
def test_successful_authorization_and_failed_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -183,96 +256,161 @@ def test_successful_authorization_and_failed_capture
assert capture = @gateway.capture(@amount + 10, auth.authorization, @options)
assert_failure capture
- assert_equal "The requested amount exceeds the originally authorized amount", capture.message
+ assert_equal 'The requested amount exceeds the originally authorized amount', capture.message
end
def test_failed_capture_bad_auth_info
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
- assert capture = @gateway.capture(@amount, "a;b;c", @options)
+ assert @gateway.authorize(@amount, @credit_card, @options)
+ assert capture = @gateway.capture(@amount, 'a;b;c', @options)
assert_failure capture
end
def test_invalid_login
- gateway = CyberSourceGateway.new( :login => '', :password => '' )
- authentication_exception = assert_raise ActiveMerchant::ResponseError, 'Failed with 500 Internal Server Error' do
- gateway.purchase(@amount, @credit_card, @options)
- end
- assert response = authentication_exception.response
- assert_match(/wsse:InvalidSecurity/, response.body)
+ gateway = CyberSourceGateway.new(:login => 'asdf', :password => 'qwer')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal "wsse:FailedCheck: \nSecurity Data : UsernameToken authentication failed.\n", response.message
end
+ # Unable to test refunds for Elo cards, as the test account is setup to have
+ # Elo transactions routed to Comercio Latino which has very specific rules on
+ # refunds (i.e. that you cannot do a "Stand-Alone" refund). This means we need
+ # to go through a Capture cycle at least a day before submitting a refund.
def test_successful_refund
assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ assert_successful_response(response)
+
assert response = @gateway.refund(@amount, response.authorization)
assert_equal 'Successful transaction', response.message
assert_success response
- assert response.test?
end
- # Pinless debit payment can never be refunded.
- def test_unsuccessful_pinless_debit_card_refund
- assert response = @gateway.purchase(@amount, @pinless_debit_card, @options.merge(:pinless_debit_card => true))
- assert_equal 'Successful transaction', response.message
- assert_success response
+ def test_successful_validate_pinless_debit_card
+ assert response = @gateway.validate_pinless_debit_card(@pinless_debit_card, @options)
assert response.test?
- assert response = @gateway.refund(@amount, response.authorization)
- assert_equal 'One or more fields contains invalid data', response.message
- assert_equal false, response.success?
+ assert_equal 'Y', response.params['status']
+ assert_equal true, response.success?
end
- def test_successful_subscription_credit
- assert response = @gateway.store(@credit_card, @subscription_options)
- assert_equal 'Successful transaction', response.message
+ def test_network_tokenization_authorize_and_capture
+ credit_card = network_tokenization_credit_card('4111111111111111',
+ :brand => 'visa',
+ :eci => '05',
+ :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk='
+ )
+
+ assert auth = @gateway.authorize(@amount, credit_card, @options)
+ assert_success auth
+ assert_equal 'Successful transaction', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_authorize_with_mdd_fields
+ (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" }
+
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
- assert response.test?
+ end
- assert response = @gateway.credit(@amount, response.authorization, :order_id => generate_unique_id)
+ def test_successful_purchase_with_mdd_fields
+ (1..20).each { |e| @options["mdd_field_#{e}".to_sym] = "value #{e}" }
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ end
- assert_equal 'Successful transaction', response.message
+ def test_successful_authorize_with_nonfractional_currency
+ assert response = @gateway.authorize(100, @credit_card, @options.merge(:currency => 'JPY'))
+ assert_equal '1', response.params['amount']
assert_success response
+ end
+
+ def test_successful_subscription_authorization
+ assert response = @gateway.store(@credit_card, @subscription_options)
+ assert_successful_response(response)
+
+ assert response = @gateway.authorize(@amount, response.authorization, :order_id => generate_unique_id)
+ assert_successful_response(response)
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_subscription_purchase
+ assert response = @gateway.store(@credit_card, @subscription_options)
+ assert_successful_response(response)
+
+ assert response = @gateway.purchase(@amount, response.authorization, :order_id => generate_unique_id)
+ assert_successful_response(response)
+ end
+
+ def test_successful_subscription_purchase_with_elo
+ assert response = @gateway.store(@elo_credit_card, @subscription_options)
+ assert_successful_response(response)
+
+ assert response = @gateway.purchase(@amount, response.authorization, :order_id => generate_unique_id)
+ assert_successful_response(response)
+ end
+
+ def test_successful_standalone_credit_to_card
+ assert response = @gateway.credit(@amount, @credit_card, @options)
+ assert_successful_response(response)
+ end
+
+ def test_failed_standalone_credit_to_card
+ assert response = @gateway.credit(@amount, @declined_card, @options)
+
+ assert_equal 'Invalid account number', response.message
+ assert_failure response
assert response.test?
end
+ def test_successful_standalone_credit_to_subscription
+ assert response = @gateway.store(@credit_card, @subscription_options)
+ assert_successful_response(response)
+
+ assert response = @gateway.credit(@amount, response.authorization, :order_id => generate_unique_id)
+ assert_successful_response(response)
+ end
+
def test_successful_create_subscription
assert response = @gateway.store(@credit_card, @subscription_options)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ assert_successful_response(response)
+ end
+
+ def test_successful_create_subscription_with_elo
+ assert response = @gateway.store(@elo_credit_card, @subscription_options)
+ assert_successful_response(response)
end
def test_successful_create_subscription_with_setup_fee
assert response = @gateway.store(@credit_card, @subscription_options.merge(:setup_fee => 100))
+ assert_successful_response(response)
+ end
+
+ def test_successful_create_subscription_with_monthly_options
+ response = @gateway.store(@credit_card, @subscription_options.merge(:setup_fee => 99.0, :subscription => {:amount => 49.0, :automatic_renew => false, frequency: 'monthly'}))
assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ response = @gateway.retrieve(response.authorization, order_id: @subscription_options[:order_id])
+ assert_equal '0.49', response.params['recurringAmount']
+ assert_equal 'monthly', response.params['frequency']
end
def test_successful_update_subscription_creditcard
assert response = @gateway.store(@credit_card, @subscription_options)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ assert_successful_response(response)
assert response = @gateway.update(response.authorization, @credit_card, {:order_id => generate_unique_id, :setup_fee => 100})
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ assert_successful_response(response)
end
def test_successful_update_subscription_billing_address
assert response = @gateway.store(@credit_card, @subscription_options)
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+ assert_successful_response(response)
assert response = @gateway.update(response.authorization, nil,
{:order_id => generate_unique_id, :setup_fee => 100, billing_address: address, email: 'someguy1232@fakeemail.net'})
- assert_equal 'Successful transaction', response.message
- assert_success response
- assert response.test?
+
+ assert_successful_response(response)
end
def test_successful_delete_subscription
@@ -285,6 +423,16 @@ def test_successful_delete_subscription
assert response.test?
end
+ def test_successful_delete_subscription_with_elo
+ assert response = @gateway.store(@elo_credit_card, @subscription_options)
+ assert response.success?
+ assert response.test?
+
+ assert response = @gateway.unstore(response.authorization, :order_id => generate_unique_id)
+ assert response.success?
+ assert response.test?
+ end
+
def test_successful_retrieve_subscription
assert response = @gateway.store(@credit_card, @subscription_options)
assert response.success?
@@ -295,10 +443,130 @@ def test_successful_retrieve_subscription
assert response.test?
end
- def test_successful_validate_pinless_debit_card
- assert response = @gateway.validate_pinless_debit_card(@pinless_debit_card, @options)
+ def test_3ds_enroll_request_via_purchase
+ assert response = @gateway.purchase(1202, @three_ds_enrolled_card, @options.merge(payer_auth_enroll_service: true))
+ assert_equal '475', response.params['reasonCode']
+ assert !response.params['acsURL'].blank?
+ assert !response.params['paReq'].blank?
+ assert !response.params['xid'].blank?
+ assert !response.success?
+ end
+
+ def test_3ds_enroll_request_via_authorize
+ assert response = @gateway.authorize(1202, @three_ds_enrolled_card, @options.merge(payer_auth_enroll_service: true))
+ assert_equal '475', response.params['reasonCode']
+ assert !response.params['acsURL'].blank?
+ assert !response.params['paReq'].blank?
+ assert !response.params['xid'].blank?
+ assert !response.success?
+ end
+
+ def test_successful_3ds_requests_with_unenrolled_card
+ assert response = @gateway.purchase(1202, @three_ds_unenrolled_card, @options.merge(payer_auth_enroll_service: true))
+ assert response.success?
+
+ assert response = @gateway.authorize(1202, @three_ds_unenrolled_card, @options.merge(payer_auth_enroll_service: true))
+ assert response.success?
+ end
+
+ def test_successful_3ds_validate_purchase_request
+ assert response = @gateway.purchase(1202, @three_ds_enrolled_card, @options.merge(payer_auth_validate_service: true, pares: pares))
+ assert_equal '100', response.params['reasonCode']
+ assert_equal '0', response.params['authenticationResult']
+ assert response.success?
+ end
+
+ def test_failed_3ds_validate_purchase_request
+ assert response = @gateway.purchase(1202, @three_ds_invalid_card, @options.merge(payer_auth_validate_service: true, pares: pares))
+ assert_equal '476', response.params['reasonCode']
+ assert !response.success?
+ end
+
+ def test_successful_3ds_validate_authorize_request
+ assert response = @gateway.authorize(1202, @three_ds_enrolled_card, @options.merge(payer_auth_validate_service: true, pares: pares))
+ assert_equal '100', response.params['reasonCode']
+ assert_equal '0', response.params['authenticationResult']
+ assert response.success?
+ end
+
+ def test_failed_3ds_validate_authorize_request
+ assert response = @gateway.authorize(1202, @three_ds_invalid_card, @options.merge(payer_auth_validate_service: true, pares: pares))
+ assert_equal '476', response.params['reasonCode']
+ assert !response.success?
+ end
+
+ def test_successful_first_unscheduled_cof_transaction
+ @options[:stored_credential] = {
+ :initiator => 'cardholder',
+ :reason_type => 'unscheduled',
+ :initial_transaction => true,
+ :network_transaction_id => ''
+ }
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal 'Successful transaction', response.message
+ assert_success response
+ end
+
+ def test_successful_subsequent_unscheduled_cof_transaction
+ @options[:stored_credential] = {
+ :initiator => 'merchant',
+ :reason_type => 'unscheduled',
+ :initial_transaction => false,
+ :network_transaction_id => '016150703802094'
+ }
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal 'Successful transaction', response.message
+ assert_success response
+ end
+
+ def test_successful_first_recurring_cof_transaction
+ @options[:stored_credential] = {
+ :initiator => 'cardholder',
+ :reason_type => 'recurring',
+ :initial_transaction => true,
+ :network_transaction_id => ''
+ }
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal 'Successful transaction', response.message
+ assert_success response
+ end
+
+ def test_successful_subsequent_recurring_cof_transaction
+ @options[:stored_credential] = {
+ :initiator => 'merchant',
+ :reason_type => 'recurring',
+ :initial_transaction => false,
+ :network_transaction_id => '016150703802094'
+ }
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal 'Successful transaction', response.message
+ assert_success response
+ end
+
+ def pares
+ <<-PARES
+eNqdmFuTqkgSgN+N8D90zD46M4B3J+yOKO6goNyFN25yEUHkUsiv31K7T/ec6dg9u75YlWRlZVVmflWw1uNrGNJa6DfX8G0thVXlRuFLErz+tgm67sRlbJr3ky4G9LWn8N/e1nughtVD4dFawFAodT8OqbBx4NLdj/o8y3JqKlavSLsNr1VS5G/En/if4zX20UUTXf3Yzeu3teuXpCC/TeerMTFfY+/d9Tm8CvRbEB7dJqvX2LO7xj7H7Zt7q0JOd0nwpo3VacjVvMc4pZcXfcjFpMqLc6UHr2vsrrEO3Dp8G+P4Ap+PZy/E9C+c+AtfrrGHfH25mwPnokG2CRxfY18Fa7Q71zD3b2/LKXr0o7cOu0uRh0gDre1He419+nZx8zf87z+kepeu9cPbuk7OX31a3X0iFmvsIV9XtVs31Zu9xt5ba99t2zcAAAksNjsr4N5MVctyGIaN2H6E1vpQWYd+8obPkFPo/zEKZFFxTer4fHf174I1dncFe4Tzba0lUY4mu4Yv3TnLURDjur78hWEQwj/h5M/iGmHIYRzDVxhSCKok+tdvz1FhIOTH4n8aRrl5kSe+myW9W6PEkMI6LoKXH759Z0ZX75YITGWoP5CpP3ximv9xl+ATYoZsYt8b/bKyX5nlZ2evlftHFbvEfYKfDL2t1fAY3jMifDFU4fW3f/1KZdBJFFb1/+PKhxtfLXzYM92sCd8qN5U5lrrNDZOFzkiecUIszvyCVJjXj3FPzTX2w/f3hT2j+GW3noobXm8xXJ3KK2aZztNbVdsLWbbOASZgzSY45eYqFNiK5ReRNLKbzvZSIDJj+zqBzIEkIx1L9ZTabYeDJa/MV51fF9A0dxDvxzf5CiPmttuVVBLHxmZSNp53lnBcJzh+IS3YpejKebycHjQlvMggwkvHdZjhYBHf8M1R4ikKjHxMGxlCfuCv+IqmxjTRk9GMnO2ynnXsWMvZSYdlk+Vmvpz1pVns4v05ugRWIGZNMhxUGLzoqs+VDe14Jtzli63TT06WBvpJg2+2UVLie+5mgGDlEVjip+7EmZhCvRdndtQHmKm0vaUDejhYTRgglbR5qysx6I1gf+vTyWJ3ahaXNOWBUrXRYnwasbKlbi3XsJLNuA3g6+uXrHqPzCa8PSNxmKElubX7bGmNl4Z+LbuIEJT8SrnXIMnd7IUOz8XLI4DX3192xucDQGlI8NmnijOiqR/+/rJ9lRCvCqSv6a+7OCl+f6FeDW2N/TzPY2IqvNbJEdUVwqUkCLTVo32vtAhAgQSRQAFNgLRii5vCEeLWl4HCsKQCoJMyWwmcOEAYDBlLlGlKHa2DLRnJ5nCAhkoksypca9nxKfDvUhIUEmvIsX9WL96ZrZTxqvYs82aPjQi1bz7NaBIJHhYpCEXplJ2GA8ea4a7lXCRVgUxk06ai0DSoDecg4wIvE3ZC0ooOQhbinUQzNyn1OzkFM5kWXSS7PWVKNxx8SCV+2VE9EJ8+2TrITF1ScEjBh3WBgere5bJWUpb3ld9lPAMd+e6JNxGQJS4F9vuKdObLigRGbj2LyPyznEmqAZmnxS0DO9o+iCfXmsUeRZIKIXW8Djy0Tw8rks4yX62omWctI2Oc5d7ZvKGokEIKZDI6lfEp4VYQJ+9RAGBHAWUJ7s+HAyraoB4DSmYSEIl4LuOMDMYCIZJ71pj7U99OwbapLHXFMLI66s7eKosO9qmWU56LwmJCul2tccin+XTKE4tV7EatfZaSNCQFH9bYXMNCetuoK2kl0SN6An3f3xmIMwGIT8KlZZS5pV/wpTIz8FzIF9fhIK6EhVLuzEDAg4MI+sybxjVzA/TGuEmsEHDZbZFBtjKxdKfgilSRZDLRoGjQmpWlzUEZGeJ+7CK6jCNPPgQe2ZInYsxH5YEWZoId7i5G2RJNax3USyCJo1OXS/jNLKdCtZiMSaCR4jKPaXvXqjl/6Et+OMBDRoth7MfSnLa3o7ItpxyV8CZcmjrVbJtyWykIypti158qotvx1VkJTm48GzeYBAUaKIAsJhUcDkL9mUO8KjEgBUCiIEdZFKcBjhsxAkpL5cjGxN7nzMYgZElgguweT/ugZg5F0s5BfGT2cGCPWdzRQfCwpkzRoa8YasSpRuIhBMUdRVxBGyn1FouIkytA/p5XKp4iAEO2AMZRSKQkIPDhgLC0ZSKTIV5IsXXC55ue+a566chmgKyLBwZfHlr7igWzo4Dn4m63WjXm3kMV3G7GNc3KJz9Ur5pt1AxBnafhdFf03bi2pnQlT8pZhWNWN7Mu+6RtWe/I6AbUz1wcFd6puR7FdrSYDwcYP5lcIsJ0ZNh7zOxcqcSFOjoUhaui645OzZ5qHGeazOnrqlxJ1+2eSJtTNOo7bBrgyvIanQyHuh9xP/PqO4BROI0Alp6/AOzbLYAh/asAo/t78d0L1ZdQ/mVerrZ+yoQSCZ+wiqCpjNmbw2WNbXW0NyZqFNzU0Uh0dHgTEUqqABnwhAENTjfNUu9WLs751LE60N8xINGsmvkTJTLOqzag/g624UDS72hjelmXP9GmKz9kEmf/R7DR4Ak2ZEmdQv7pz4YmzU84fQHYHWZ+DjomBcrTYiVRuig6KJ1R5Z5dhD5kiRQeewAg3Jqc2SOv+8ASIgVnYOQsf9558pl8OIIWJ4KCQ4u+QWKmIqgK7g5MOZ+0XJ4jemPuucVRUPf5rma5LL6U7RxuXQ4ax+NodrIvC4k53wRDanhGdkGrnhJRq2/UajccHM67ebQItvRyk3PEnFrl1y5dFuT0PEFYMqbn0dG2dlx+js/7Yt7HZFuSVXvsV5OYiTYHec4EG7kxo+GgKfvamoPtDhry3CPLjaJN7okBAJeGPTl7z5+AgQolAQC3wBZtwRGA7U2ViJFJcmnxxgo+jjHdwGGkjs0G5UYccOYJ7XDmP7IgS+9QkEj8YY2OFIsk1WUi3MTJQTed7U3A2YUW3Vh3OND14irp4PiAhSYxHA2siFSZKN1jhOVFme2MOa7LKcst80SEKId+OjqM+9GBjoxIIZfNxsBWkyVmbmYUa4iJghm7gzu+8jeiAxMvJwhiR80zcl4FSr2Q01jx442ebHWlimZHrNQymRgOto7dtFMgbPTdxmG4ayKWQJ+Lp3K0OcQ1rU2jtLyw+XKXOqWoLo7ulVFHgTebYaLWXho+Sr1OPy7AcHCGCar/njbEqWk2ib1Z6iWb3cbm1eTZ6PVXIdCmCAJJ+AEBEYh0tx8xmanGGwngHKWVnCZ4E/qRkgaQ+OgfpYOS+5vi+XoroMHnreA/3XIQBP7LPefzlvPj1oBuOd3zlsOKrYegcC+p4YCPfRmFv5NSZiLpNpR1cLPusvQhw3/IUnIqKRWknr5yDBRNo2dkCVSPmdGNAUBGH8cXr2f29z15gBBCTrfuBb66/SokhoP/gglTIqUPSEjvkNC88QpHo0kEguNHRIaDj5igJAWIBjKgKTJRNmSkUNPwevRaVWGow9Vezev9QtlZJaWDcZpjs3SywiKsxD0p8RVKHQ6u49ExWZz6zY28KaVz4ntbnC0nGDi0G9GFeM2id5cJkwbRKezMS2ZrYcnsZzuDlqaRqx0XJS9F5h6VycYt8nF7TfnOCimzY5NpNyWLIBPzY4ZhNZdu8FKm+3pxwqZyqLHWzSsT5f2mQACop8+THcXu42wXhB5bmeepaHFBHFcOzM7lZZr4DPOPs/073eHgQ5sGD22dBAZE4SSx/vtijxSQsEuSy0gWSqEshkxiw9xVEJhqg78mbmrU3nxGzJe1fLxwDDO59rxHzgrpzPiHrvK8WlDJpo33y3MdhU7GZ81W6fFSHfnjYpbBcDjo4CLNjoAvSxRlLaU2W76plphc5At/tEhKra8VXiLN0FuM59Ddt5zgHZitL1vFyttHamkZ44sToxvD5ubwK/BtsWOfr03Yj1epz5esx7ekx8eu+/ePrx/B/g0UAjN8
+ PARES
+ end
+
+ def test_successful_verify_with_elo
+ response = @gateway.verify(@elo_credit_card, @options)
+ assert_equal 'Successful transaction', response.message
+ assert_success response
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = CyberSourceGateway.new(login: 'an_unknown_login', password: 'unknown_password')
+ assert !gateway.verify_credentials
+ end
+
+ private
+
+ def assert_successful_response(response)
+ assert_equal 'Successful transaction', response.message
+ assert_success response
assert response.test?
- assert_equal 'Y', response.params["status"]
- assert_equal true, response.success?
end
end
diff --git a/test/remote/gateways/remote_d_local_test.rb b/test/remote/gateways/remote_d_local_test.rb
new file mode 100644
index 00000000000..2dc9708d940
--- /dev/null
+++ b/test/remote/gateways/remote_d_local_test.rb
@@ -0,0 +1,246 @@
+require 'test_helper'
+
+class RemoteDLocalTest < Test::Unit::TestCase
+ def setup
+ @gateway = DLocalGateway.new(fixtures(:d_local))
+
+ @amount = 200
+ @credit_card = credit_card('4111111111111111')
+ @credit_card_naranja = credit_card('5895627823453005')
+ @cabal_credit_card = credit_card('5896 5700 0000 0004')
+ @invalid_cabal_card = credit_card('6035 2277 0000 0000')
+ # No test card numbers, all txns are approved by default,
+ # but errors can be invoked directly with the `description` field
+ @options = {
+ billing_address: address(country: 'Brazil'),
+ document: '42243309114',
+ currency: 'BRL'
+ }
+ @options_colombia = {
+ billing_address: address(country: 'Colombia'),
+ document: '11186456',
+ currency: 'COP'
+ }
+ @options_argentina = {
+ billing_address: address(country: 'Argentina'),
+ document: '10563145',
+ currency: 'ARS'
+ }
+ @options_mexico = {
+ billing_address: address(country: 'Mexico'),
+ document: '128475869794933',
+ currency: 'MXN'
+ }
+ @options_peru = {
+ billing_address: address(country: 'Peru'),
+ document: '184853849',
+ currency: 'PEN'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ def test_successful_purchase_naranja
+ response = @gateway.purchase(@amount, @credit_card_naranja, @options)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ def test_successful_purchase_cabal
+ response = @gateway.purchase(@amount, @cabal_credit_card, @options)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = @options.merge(
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ birth_date: '03-01-1970',
+ document2: '87648987569',
+ idempotency_key: generate_unique_id,
+ user_reference: generate_unique_id
+ )
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ # You may need dLocal to enable your test account to support individual countries
+ def test_successful_purchase_colombia
+ response = @gateway.purchase(100000, @credit_card, @options_colombia)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ def test_successful_purchase_argentina
+ response = @gateway.purchase(@amount, @credit_card, @options_argentina)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ def test_successful_purchase_mexico
+ response = @gateway.purchase(@amount, @credit_card, @options_mexico)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ def test_successful_purchase_peru
+ response = @gateway.purchase(@amount, @credit_card, @options_peru)
+ assert_success response
+ assert_match 'The payment was paid', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(description: '300'))
+ assert_failure response
+ assert_match 'The payment was rejected', response.message
+ end
+
+ def test_failed_purchase_with_cabal
+ response = @gateway.purchase(@amount, @invalid_cabal_card, @options)
+ assert_failure response
+ assert_match 'Payment not found', response.message
+ end
+
+ def test_failed_document_format
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(document: 'bad_document'))
+ assert_failure response
+ assert_match 'Invalid parameter: payer.document', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_match 'The payment was authorized', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_match 'The payment was paid', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_cabal
+ auth = @gateway.authorize(@amount, @cabal_credit_card, @options)
+ assert_success auth
+ assert_match 'The payment was authorized', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_match 'The payment was paid', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(description: '309'))
+ assert_failure response
+ assert_equal '309', response.error_code
+ assert_match 'Card expired', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'bad_id')
+ assert_failure response
+
+ assert_equal '4000', response.error_code
+ assert_match 'Payment not found', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options.merge(notification_url: 'http://example.com'))
+ assert_success refund
+ assert_match 'The refund was paid', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization, @options.merge(notification_url: 'http://example.com'))
+ assert_success refund
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ response = @gateway.refund(@amount+1, purchase.authorization, @options.merge(notification_url: 'http://example.com'))
+ assert_failure response
+ assert_match 'Amount exceeded', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_match 'The payment was cancelled', void.message
+ end
+
+ def test_failed_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+
+ response = @gateway.void(auth.authorization)
+ assert_failure response
+ assert_match 'Invalid transaction status', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{The payment was authorized}, response.message
+ end
+
+ def test_successful_verify_with_cabal
+ response = @gateway.verify(@cabal_credit_card, @options)
+ assert_success response
+ assert_match %r{The payment was authorized}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@credit_card, @options.merge(description: '315'))
+ assert_failure response
+ assert_equal '315', response.error_code
+ assert_match %r{Invalid security code}, response.message
+ end
+
+ def test_invalid_login
+ gateway = DLocalGateway.new(login: '', trans_key: '', secret_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid parameter}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:trans_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_data_cash_test.rb b/test/remote/gateways/remote_data_cash_test.rb
index 60e3701c485..9fb77b19a6f 100644
--- a/test/remote/gateways/remote_data_cash_test.rb
+++ b/test/remote/gateways/remote_data_cash_test.rb
@@ -9,18 +9,17 @@ def setup
@mastercard = CreditCard.new(
:number => '5120790000000034',
:month => 3,
- :year => Date.today.year + 2,
- :first_name => 'Mark',
+ :year => Date.today.year + 2,
+ :first_name => 'Mark',
:last_name => 'McBride',
- :brand => :master,
- :verification_value => '444'
+ :brand => :master
)
@mastercard_declined = CreditCard.new(
:number => '5473000000000106',
:month => 3,
- :year => Date.today.year + 2,
- :first_name => 'Mark',
+ :year => Date.today.year + 2,
+ :first_name => 'Mark',
:last_name => 'McBride',
:brand => :master,
:verification_value => '547'
@@ -29,8 +28,8 @@ def setup
@visa_delta = CreditCard.new(
:number => '4539792100000003',
:month => 3,
- :year => Date.today.year + 2,
- :first_name => 'Mark',
+ :year => Date.today.year + 2,
+ :first_name => 'Mark',
:last_name => 'McBride',
:brand => :visa,
:verification_value => '444'
@@ -45,11 +44,10 @@ def setup
:brand => :solo,
:issue_number => 5,
:start_month => 12,
- :start_year => 2006,
- :verification_value => 444
+ :start_year => 2006
)
- @address = {
+ @address = {
:name => 'Mark McBride',
:address1 => 'Flat 12/3',
:address2 => '45 Main Road',
@@ -60,8 +58,7 @@ def setup
}
@params = {
- :order_id => generate_unique_id,
- :billing_address => @address
+ :order_id => generate_unique_id
}
@amount = 198
@@ -75,19 +72,17 @@ def test_successful_purchase
assert response.test?
end
- #the amount is changed to £1.99 - the DC test server won't check the
- #address details - this is more a check on the passed ExtendedPolicy
+ # the amount is changed to £1.99 - the DC test server won't check the
+ # address details - this is more a check on the passed ExtendedPolicy
def test_successful_purchase_without_address_check
response = @gateway.purchase(199, @mastercard, @params)
assert_success response
- assert response.test?
end
# Note the Datacash test server regularly times out on switch requests
def test_successful_purchase_with_solo_card
response = @gateway.purchase(@amount, @solo, @params)
assert_success response
- assert response.test?
end
# this card number won't check the address details - testing extended
@@ -97,7 +92,6 @@ def test_successful_purchase_without_address_check2
response = @gateway.purchase(@amount, @solo, @params)
assert_success response
- assert response.test?
end
# Testing purchase with request to set up recurring payment account
@@ -105,7 +99,6 @@ def test_successful_purchase_without_account_set_up_and_repeat_payments
response = @gateway.purchase(@amount, @mastercard, @params)
assert_success response
assert response.authorization.to_s.split(';')[2].blank?
- assert response.test?
end
# Testing purchase with request to set up recurring payment account
@@ -116,49 +109,45 @@ def test_successful_purchase_with_account_set_up_and_repeat_payments
assert !response.authorization.to_s.split(';')[2].blank?
assert response.test?
- #Make second payment on the continuous authorization that was set up in the first purchase
+ # Make second payment on the continuous authorization that was set up in the first purchase
second_order_params = { :order_id => generate_unique_id }
purchase = @gateway.purchase(201, response.authorization, second_order_params)
assert_success purchase
- assert purchase.test?
end
def test_successful_purchase_with_account_set_up_and_repeat_payments_with_visa_delta_card
@params[:set_up_continuous_authority] = true
response = @gateway.purchase(@amount, @visa_delta, @params)
assert_success response
- assert !response.authorization.to_s.split(';')[2].blank?
- assert response.test?
+ assert !response.authorization.to_s.split(';')[2].blank?
- #Make second payment on the continuous authorization that was set up in the first purchase
+ # Make second payment on the continuous authorization that was set up in the first purchase
second_order_params = { :order_id => generate_unique_id }
purchase = @gateway.purchase(201, response.authorization, second_order_params)
assert_success purchase
- assert purchase.test?
end
def test_purchase_with_account_set_up_for_repeat_payments_fails_for_solo_card
@params[:set_up_continuous_authority] = true
response = @gateway.purchase(@amount, @solo, @params)
assert_equal '92', response.params['status'] # Error code for CA not supported
- assert_equal 'CA Not Supported', response.message
- assert response.test?
+ assert_equal 'CA Not Supported', response.message
end
def test_successful_authorization_and_capture_with_account_set_up_and_second_purchase
- #Authorize first payment
+ # Authorize first payment
@params[:set_up_continuous_authority] = true
first_authorization = @gateway.authorize(@amount, @mastercard, @params)
assert_success first_authorization
assert !first_authorization.authorization.to_s.split(';')[2].blank?
assert first_authorization.test?
- #Capture first payment
+ # Capture first payment
capture = @gateway.capture(@amount, first_authorization.authorization, @params)
assert_success capture
assert capture.test?
- #Collect second purchase
+ # Collect second purchase
second_order_params = { :order_id => generate_unique_id }
purchase = @gateway.purchase(201, first_authorization.authorization, second_order_params)
assert_success purchase
@@ -192,10 +181,10 @@ def test_invalid_expiry_month
end
def test_invalid_expiry_year
- @mastercard.year = 1999
+ @mastercard.number = 1000350000000353
response = @gateway.purchase(@amount, @mastercard, @params)
assert_failure response
- assert_equal 'Card has already expired', response.message
+ assert_equal 'expired', response.message
assert response.test?
end
@@ -206,7 +195,7 @@ def test_declined_card
assert response.test?
end
- def test_successful_authorization_and_capture
+ def test_successful_authorization_and_capture
authorization = @gateway.authorize(@amount, @mastercard, @params)
assert_success authorization
assert authorization.test?
@@ -219,11 +208,11 @@ def test_successful_authorization_and_capture
def test_unsuccessful_capture
response = @gateway.capture(@amount, ';1234', @params)
assert_failure response
- assert_equal 'AUTHCODE field required', response.message
+ assert_equal 'Invalid fulfill ref', response.message
assert response.test?
end
- def test_successful_authorization_and_void
+ def test_successful_authorization_and_void
authorization = @gateway.authorize(@amount, @mastercard, @params)
assert_success authorization
assert authorization.test?
@@ -243,113 +232,114 @@ def test_successfully_purchase_and_void
assert void.test?
end
- def test_successful_refund
+ def test_successful_credit
response = @gateway.credit(@amount, @mastercard, @params)
assert_success response
assert !response.params['datacash_reference'].blank?
assert !response.params['merchantreference'].blank?
-
+
assert response.test?
end
- def test_successful_transaction_refund
+ def test_successful_refund
purchase = @gateway.purchase(@amount, @mastercard, @params)
assert_success purchase
- assert purchase.test?
- refund = @gateway.credit(@amount, purchase.params['datacash_reference'])
+ refund = @gateway.refund(@amount, purchase.authorization)
assert_success refund
assert !refund.params['datacash_reference'].blank?
assert !refund.params['merchantreference'].blank?
-
assert refund.test?
end
- def test_successful_transaction_refund_with_money_set_to_nil
+ def test_successful_refund_with_money_set_to_nil
purchase = @gateway.purchase(@amount, @mastercard, @params)
assert_success purchase
- assert purchase.test?
- refund = @gateway.credit(nil, purchase.params['datacash_reference'])
+ refund = @gateway.refund(nil, purchase.authorization)
assert_success refund
- assert refund.test?
end
- def test_successful_transaction_refund_in_two_stages
+ def test_successful_partial_refund
purchase = @gateway.purchase(@amount, @mastercard, @params)
assert_success purchase
- assert purchase.test?
- first_partial_refund = @gateway.credit(100, purchase.params['datacash_reference'])
+ first_partial_refund = @gateway.refund(100, purchase.authorization)
assert_success first_partial_refund
- assert first_partial_refund.test?
- second_partial_refund = @gateway.credit(98, purchase.params['datacash_reference'])
+ second_partial_refund = @gateway.refund(98, purchase.authorization)
assert_success second_partial_refund
- assert second_partial_refund.test?
- end
-
- def test_successful_partial_transaction_refund
- purchase = @gateway.purchase(@amount, @mastercard, @params)
- assert_success purchase
- assert purchase.test?
-
- partial_refund = @gateway.credit(100, purchase.params['datacash_reference'])
- assert_success partial_refund
- assert partial_refund.test?
end
- def test_fail_to_refund_too_much
+ def test_failed_refund
purchase = @gateway.purchase(@amount, @mastercard, @params)
assert_success purchase
- assert purchase.test?
- refund_too_much = @gateway.credit(500, purchase.params['datacash_reference'])
+ refund_too_much = @gateway.refund(500, purchase.authorization)
assert_failure refund_too_much
assert_equal 'Refund amount > orig 1.98', refund_too_much.message
- assert refund_too_much.test?
end
- def test_fail_to_refund_with_declined_purchase_reference
+ def test_fail_to_refund_with_declined_purchase_reference
declined_purchase = @gateway.purchase(@amount, @mastercard_declined, @params)
assert_failure declined_purchase
- assert declined_purchase.test?
- refund = @gateway.credit(@amount, declined_purchase.params['datacash_reference'])
+ refund = @gateway.refund(@amount, declined_purchase.authorization)
assert_failure refund
assert_equal 'Cannot refund transaction', refund.message
- assert refund.test?
end
- def test_fail_to_refund_purchase_which_is_already_refunded
+ def test_fail_to_refund_purchase_which_is_already_refunded
purchase = @gateway.purchase(@amount, @mastercard, @params)
assert_success purchase
assert purchase.test?
- first_refund = @gateway.credit(nil, purchase.params['datacash_reference'])
+ first_refund = @gateway.refund(nil, purchase.authorization)
assert_success first_refund
- assert first_refund.test?
- second_refund = @gateway.credit(@amount, purchase.params['datacash_reference'])
+ second_refund = @gateway.refund(@amount, purchase.authorization)
assert_failure second_refund
assert_equal '1.98 > remaining funds 0.00', second_refund.message
- assert second_refund.test?
end
- # Check short merchant references are reformatted
- def test_merchant_reference_that_is_too_short
- @params[:order_id] = @params[:order_id].first(5)
+ def test_successful_refund_of_a_repeat_payment
+ @params[:set_up_continuous_authority] = true
response = @gateway.purchase(@amount, @mastercard, @params)
assert_success response
+ assert !response.authorization.to_s.split(';')[2].blank?
assert response.test?
+
+ # Make second payment on the continuous authorization that was set up in the first purchase
+ second_order_params = { :order_id => generate_unique_id }
+ purchase = @gateway.purchase(201, response.authorization, second_order_params)
+ assert_success purchase
+
+ # Refund payment that was made via the continuous authorization payment above
+ refund = @gateway.refund(201, purchase.authorization)
+ assert_success refund
end
- # Check long merchant references are reformatted
- def test_merchant_reference_that_is_too_long
+ def test_order_id_that_is_too_short
+ @params[:order_id] = @params[:order_id].first(5)
+ response = @gateway.purchase(@amount, @mastercard, @params)
+ assert_success response
+ end
+
+ def test_order_id_that_is_too_long
@params[:order_id] = "#{@params[:order_id]}1234356"
response = @gateway.purchase(@amount, @mastercard, @params)
assert_success response
assert response.test?
end
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @visa_delta, @params)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@visa_delta.number, transcript)
+ assert_scrubbed(@visa_delta.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_decidir_test.rb b/test/remote/gateways/remote_decidir_test.rb
new file mode 100644
index 00000000000..d8555619fe0
--- /dev/null
+++ b/test/remote/gateways/remote_decidir_test.rb
@@ -0,0 +1,168 @@
+require 'test_helper'
+
+class RemoteDecidirTest < Test::Unit::TestCase
+ def setup
+ @gateway_for_purchase = DecidirGateway.new(fixtures(:decidir_purchase))
+ @gateway_for_auth = DecidirGateway.new(fixtures(:decidir_authorize))
+
+ @amount = 100
+ @credit_card = credit_card('4507990000004905')
+ @declined_card = credit_card('4000300011112220')
+ @options = {
+ order_id: SecureRandom.uuid,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway_for_purchase.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'approved', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ card_holder_door_number: '1234',
+ card_holder_birthday: '01011980',
+ card_holder_identification_type: 'dni',
+ card_holder_identification_number: '123456',
+ installments: '12'
+ }
+
+ response = @gateway_for_purchase.purchase(@amount, credit_card('4509790112684851'), @options.merge(options))
+ assert_success response
+ assert_equal 'approved', response.message
+ assert response.authorization
+ end
+
+ def test_failed_purchase
+ response = @gateway_for_purchase.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'TARJETA INVALIDA', response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_failed_purchase_with_invalid_field
+ response = @gateway_for_purchase.purchase(@amount, @declined_card, @options.merge(installments: -1))
+ assert_failure response
+ assert_equal 'invalid_param: installments', response.message
+ assert_match 'invalid_request_error', response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway_for_auth.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'pre_approved', auth.message
+ assert auth.authorization
+
+ assert capture = @gateway_for_auth.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'approved', capture.message
+ assert capture.authorization
+ end
+
+ def test_failed_authorize
+ response = @gateway_for_auth.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'TARJETA INVALIDA', response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_failed_partial_capture
+ auth = @gateway_for_auth.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway_for_auth.capture(1, auth.authorization)
+ assert_failure capture
+ assert_equal 'amount: Amount out of ranges: 80 - 105', capture.message
+ assert_equal 'invalid_request_error', capture.error_code
+ assert_nil capture.authorization
+ end
+
+ def test_failed_capture
+ response = @gateway_for_auth.capture(@amount, '')
+
+ assert_equal 'not_found_error', response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_successful_refund
+ purchase = @gateway_for_purchase.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway_for_purchase.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'approved', refund.message
+ assert refund.authorization
+ end
+
+ def test_partial_refund
+ purchase = @gateway_for_purchase.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway_for_purchase.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ assert_equal 'approved', refund.message
+ assert refund.authorization
+ end
+
+ def test_failed_refund
+ response = @gateway_for_purchase.refund(@amount, '')
+ assert_failure response
+ assert_equal 'not_found_error', response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_successful_void
+ auth = @gateway_for_auth.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway_for_auth.void(auth.authorization)
+ assert_success void
+ assert_equal 'approved', void.message
+ assert void.authorization
+ end
+
+ def test_failed_void
+ response = @gateway_for_auth.void('')
+ assert_failure response
+ assert_equal 'not_found_error', response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_successful_verify
+ response = @gateway_for_auth.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{pre_approved}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway_for_auth.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{TARJETA INVALIDA}, response.message
+ end
+
+ def test_invalid_login
+ gateway = DecidirGateway.new(api_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid authentication credentials}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway_for_purchase) do
+ @gateway_for_purchase.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway_for_purchase.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway_for_purchase.options[:api_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_dibs_test.rb b/test/remote/gateways/remote_dibs_test.rb
new file mode 100644
index 00000000000..1e6b16e36b1
--- /dev/null
+++ b/test/remote/gateways/remote_dibs_test.rb
@@ -0,0 +1,180 @@
+require 'test_helper'
+
+class RemoteDibsTest < Test::Unit::TestCase
+ def setup
+ @gateway = DibsGateway.new(fixtures(:dibs))
+
+ cc_options = {
+ :month => 6,
+ :year => 24,
+ :verification_value => '684',
+ :brand => 'visa'
+ }
+
+ @amount = 100
+ @credit_card = credit_card('4711100000000000', cc_options)
+ @declined_card_auth = credit_card('4711000000000000', cc_options)
+ @declined_card_capture = credit_card('4711100000000001', cc_options)
+
+ @options = {
+ order_id: generate_unique_id
+ }
+ end
+
+ def test_invalid_login
+ gateway = DibsGateway.new(
+ merchant_id: '123456789',
+ secret_key: '987654321'
+ )
+ response = gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card_capture, @options)
+ assert_failure response
+ assert_match %r(DECLINE.+), response.message
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card_auth, @options)
+ assert_failure response
+ assert_match %r(DECLINE.+), response.message
+ end
+
+ def test_successful_authorize_and_failed_capture
+ response = @gateway.authorize(@amount, @declined_card_capture, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_failure capture
+ assert_match %r(DECLINE.+), capture.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_match %r(ERROR.+), response.message
+ end
+
+ def test_successful_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_failure capture
+ assert_match %r(DECLINE.+), capture.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_match %r(ERROR.+), response.message
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ assert_match %r(ERROR.+), response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Succeeded}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card_auth, @options)
+ assert_failure response
+ assert_match %r(DECLINE.+), response.message
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_not_nil response.params['ticketId']
+ assert_not_nil response.authorization
+ assert_equal response.params['ticketId'], response.authorization
+ end
+
+ def test_failed_store
+ response = @gateway.store(@declined_card_auth, @options)
+ assert_failure response
+ assert_match %r(DECLINE.+), response.message
+ end
+
+ def test_successful_authorize_with_stored_card
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_not_nil response.authorization
+ response = @gateway.authorize(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_successful_authorize_and_capture_with_stored_card
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_not_nil response.authorization
+ response = @gateway.authorize(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_digitzs_test.rb b/test/remote/gateways/remote_digitzs_test.rb
new file mode 100644
index 00000000000..285dd840f9d
--- /dev/null
+++ b/test/remote/gateways/remote_digitzs_test.rb
@@ -0,0 +1,136 @@
+require 'test_helper'
+
+class RemoteDigitzsTest < Test::Unit::TestCase
+ def setup
+ @gateway = DigitzsGateway.new(fixtures(:digitzs))
+
+ @amount = 500
+ @credit_card = credit_card('4747474747474747', verification_value: '999')
+ @declined_card = credit_card('4616161616161616')
+ @options = {
+ merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+
+ @options_card_split = {
+ merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385',
+ billing_address: address,
+ description: 'Split Purchase',
+ payment_type: 'card_split',
+ split_amount: 100,
+ split_merchant_id: 'spreedly-susanswidg-32270590-2095203-148657924'
+ }
+
+ @options_token_split = {
+ merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385',
+ billing_address: address,
+ description: 'Token Split Purchase',
+ payment_type: 'token_split',
+ split_amount: 100,
+ split_merchant_id: 'spreedly-susanswidg-32270590-2095203-148657924'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_token_purchase
+ assert store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ response = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_token_split_purchase
+ assert store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ response = @gateway.purchase(@amount, store.authorization, @options_token_split)
+ assert_success response
+ assert_equal 'Success', response.message
+ assert response.params['data']['attributes']['split']['splitId']
+ end
+
+ def test_successful_card_split_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options_card_split)
+ assert_success response
+ assert_equal 'Success', response.message
+ assert response.params['data']['attributes']['split']['splitId']
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Partner error: Credit card declined (transaction element shows reason for decline)', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'Success', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization, @options)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '', @options)
+ assert_failure response
+ assert_equal '"id" is not allowed to be empty', response.message
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_successful_store_without_billing_address
+ assert response = @gateway.store(@credit_card, {merchant_id: 'spreedly-susanswidg-32268973-2091076-148408385'})
+ assert_success response
+ end
+
+ def test_store_adds_card_to_existing_customer
+ assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'spreedly-susanswidg-32268973-2091076-148408385-5980208887457495-148700575'}))
+ assert_success response
+ end
+
+ def test_store_creates_new_customer_and_adds_card
+ assert response = @gateway.store(@credit_card, @options.merge({customer_id: 'nonexistant'}))
+ assert_success response
+ end
+
+ def test_invalid_login
+ gateway = DigitzsGateway.new(app_key: '', api_key: '', merchant_id: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Forbidden}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ assert_scrubbed(@gateway.options[:app_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_ebanx_test.rb b/test/remote/gateways/remote_ebanx_test.rb
new file mode 100644
index 00000000000..d1035d9b62b
--- /dev/null
+++ b/test/remote/gateways/remote_ebanx_test.rb
@@ -0,0 +1,218 @@
+require 'test_helper'
+
+class RemoteEbanxTest < Test::Unit::TestCase
+ def setup
+ @gateway = EbanxGateway.new(fixtures(:ebanx))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('5102026827345142')
+ @options = {
+ billing_address: address({
+ address1: '1040 Rua E',
+ city: 'Maracanaú',
+ state: 'CE',
+ zip: '61919-230',
+ country: 'BR',
+ phone_number: '8522847035'
+ }),
+ order_id: generate_unique_id,
+ document: '853.513.468-93'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = @options.merge({
+ order_id: generate_unique_id,
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ birth_date: '10/11/1980',
+ person_type: 'personal'
+ })
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
+ def test_successful_purchase_as_brazil_business_with_responsible_fields
+ options = @options.update(document: '32593371000110',
+ person_type: 'business',
+ responsible_name: 'Business Person',
+ responsible_document: '32593371000111',
+ responsible_birth_date: '1/11/1975')
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Accepted', response.message
+ end
+
+ def test_successful_purchase_as_colombian
+ options = @options.merge({
+ order_id: generate_unique_id,
+ ip: '127.0.0.1',
+ email: 'jose@example.com.co',
+ birth_date: '10/11/1980',
+ billing_address: address({
+ address1: '1040 Rua E',
+ city: 'Medellín',
+ state: 'AN',
+ zip: '29269',
+ country: 'CO',
+ phone_number: '8522847035'
+ })
+ })
+
+ response = @gateway.purchase(500, @credit_card, options)
+ assert_success response
+ assert_equal 'Sandbox - Test credit card, transaction captured', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Sandbox - Test credit card, transaction declined reason insufficientFunds', response.message
+ assert_equal 'NOK', response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Accepted', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Accepted', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Sandbox - Test credit card, transaction declined reason insufficientFunds', response.message
+ assert_equal 'NOK', response.error_code
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Parameters hash or merchant_payment_code not informed', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund_options = @options.merge({description: 'full refund'})
+ assert refund = @gateway.refund(@amount, purchase.authorization, refund_options)
+ assert_success refund
+ assert_equal 'Accepted', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund_options = @options.merge(description: 'refund due to returned item')
+ assert refund = @gateway.refund(@amount-1, purchase.authorization, refund_options)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_match('Parameter hash not informed', response.message)
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Accepted', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Parameter hash not informed', response.message
+ end
+
+ def test_successful_store_and_purchase
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ assert purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+ assert_equal 'Accepted', purchase.message
+ end
+
+ def test_successful_store_and_purchase_as_brazil_business
+ options = @options.update(document: '32593371000110',
+ person_type: 'business',
+ responsible_name: 'Business Person',
+ responsible_document: '32593371000111',
+ responsible_birth_date: '1/11/1975')
+
+ store = @gateway.store(@credit_card, options)
+ assert_success store
+
+ assert purchase = @gateway.purchase(@amount, store.authorization, options)
+ assert_success purchase
+ assert_equal 'Accepted', purchase.message
+ end
+
+ def test_failed_purchase_with_stored_card
+ store = @gateway.store(@declined_card, @options)
+ assert_success store
+
+ assert purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_failure purchase
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Accepted}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Accepted}, response.message
+ end
+
+ def test_invalid_login
+ gateway = EbanxGateway.new(integration_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Field integration_key is required}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:integration_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_efsnet_test.rb b/test/remote/gateways/remote_efsnet_test.rb
index f36387d9385..4e6f22499b7 100644
--- a/test/remote/gateways/remote_efsnet_test.rb
+++ b/test/remote/gateways/remote_efsnet_test.rb
@@ -1,22 +1,22 @@
require 'test_helper'
class RemoteEfsnetTest < Test::Unit::TestCase
-
+
def setup
- Base.gateway_mode = :test
+ Base.mode = :test
@gateway = EfsnetGateway.new(fixtures(:efsnet))
-
+
@credit_card = credit_card('4000100011112224')
-
+
@amount = 100
@declined_amount = 156
- @options = { :order_id => generate_unique_id,
+ @options = { :order_id => generate_unique_id,
:billing_address => address
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -45,10 +45,10 @@ def test_unsuccessful_purchase
def test_authorize_and_capture
amount = @amount
assert auth = @gateway.authorize(amount, @credit_card, @options)
- assert_success auth
+ assert_success auth
assert_equal 'Approved', auth.message
assert auth.authorization
-
+
assert capture = @gateway.capture(amount, auth.authorization, @options)
assert_success capture
end
diff --git a/test/remote/gateways/remote_elavon_test.rb b/test/remote/gateways/remote_elavon_test.rb
index 60159f36e87..1f89405e99a 100644
--- a/test/remote/gateways/remote_elavon_test.rb
+++ b/test/remote/gateways/remote_elavon_test.rb
@@ -3,14 +3,16 @@
class RemoteElavonTest < Test::Unit::TestCase
def setup
@gateway = ElavonGateway.new(fixtures(:elavon))
+ @multi_currency_gateway = ElavonGateway.new(fixtures(:elavon_multi_currency))
- @credit_card = credit_card('4222222222222')
+ @credit_card = credit_card('4124939999999990')
@bad_credit_card = credit_card('invalid')
@options = {
- :email => "paul@domain.com",
+ :email => 'paul@domain.com',
:description => 'Test Transaction',
- :billing_address => address
+ :billing_address => address,
+ :ip => '203.0.113.0'
}
@amount = 100
end
@@ -42,6 +44,16 @@ def test_authorize_and_capture
assert_success capture
end
+ def test_authorize_and_capture_with_auth_code
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVAL', auth.message
+ assert auth.authorization
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
def test_unsuccessful_capture
assert response = @gateway.capture(@amount, '', :credit_card => @credit_card)
assert_failure response
@@ -49,12 +61,24 @@ def test_unsuccessful_capture
end
def test_unsuccessful_authorization
- @credit_card.number = "1234567890123"
- assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert response = @gateway.authorize(@amount, @bad_credit_card, @options)
assert_failure response
assert_equal 'The Credit Card Number supplied in the authorization request appears to be invalid.', response.message
end
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVAL', response.message
+ assert_success response.responses.last, 'The void should succeed'
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@bad_credit_card, @options)
+ assert_failure response
+ assert_match %r{appears to be invalid}, response.message
+ end
+
def test_purchase_and_credit
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -80,7 +104,7 @@ def test_purchase_and_failed_refund
assert refund = @gateway.refund(@amount + 5, purchase.authorization)
assert_failure refund
- assert_equal 'The refund amount exceeds the original transaction amount.', refund.message
+ assert_match %r{exceed}i, refund.message
end
def test_purchase_and_successful_void
@@ -97,9 +121,128 @@ def test_purchase_and_unsuccessful_void
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
- assert response = @gateway.void(purchase.authorization)
+ assert @gateway.void(purchase.authorization)
assert response = @gateway.void(purchase.authorization)
assert_failure response
assert_equal 'The transaction ID is invalid for this transaction type', response.message
end
+
+ def test_authorize_and_successful_void
+ assert authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ assert response = @gateway.void(authorize.authorization)
+
+ assert_success response
+ assert response.authorization
+ end
+
+ def test_successful_store_without_verify
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_nil response.message
+ assert response.test?
+ end
+
+ def test_successful_store_with_verify_false
+ assert response = @gateway.store(@credit_card, @options.merge(verify: false))
+ assert_success response
+ assert_nil response.message
+ assert response.test?
+ end
+
+ def test_successful_store_with_verify_true
+ assert response = @gateway.store(@credit_card, @options.merge(verify: true))
+ assert_success response
+ assert_equal 'APPROVAL', response.message
+ assert response.test?
+ end
+
+ def test_unsuccessful_store
+ assert response = @gateway.store(@bad_credit_card, @options)
+ assert_failure response
+ assert_equal 'The Credit Card Number supplied in the authorization request appears to be invalid.', response.message
+ assert response.test?
+ end
+
+ def test_successful_update
+ store_response = @gateway.store(@credit_card, @options)
+ token = store_response.params['token']
+ credit_card = credit_card('4124939999999990', :month => 10)
+ assert response = @gateway.update(token, credit_card, @options)
+ assert_success response
+ assert response.test?
+ end
+
+ def test_unsuccessful_update
+ assert response = @gateway.update('ABC123', @credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid}i, response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_token
+ store_response = @gateway.store(@credit_card, @options)
+ token = store_response.params['token']
+ assert response = @gateway.purchase(@amount, token, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'APPROVAL', response.message
+ end
+
+ def test_failed_purchase_with_token
+ assert response = @gateway.purchase(@amount, 'ABC123', @options)
+ assert_failure response
+ assert response.test?
+ assert_match %r{invalid}i, response.message
+ end
+
+ def test_successful_purchase_with_custom_fields
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(custom_fields: {a_key: 'a value'}))
+
+ assert_success response
+ assert response.test?
+ assert_equal 'APPROVAL', response.message
+ assert response.authorization
+ end
+
+ def test_failed_purchase_with_multi_currency_terminal_setting_disabled
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'USD', multi_currency: true))
+
+ assert_failure response
+ assert response.test?
+ assert_equal 'Transaction currency is not allowed for this terminal. Your terminal must be setup with Multi currency', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_multi_currency_gateway_setting
+ assert response = @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'JPY'))
+
+ assert_success response
+ assert response.test?
+ assert_equal 'APPROVAL', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_with_multi_currency_transaction_setting
+ @multi_currency_gateway.options[:multi_currency] = false
+ assert response = @multi_currency_gateway.purchase(@amount, @credit_card, @options.merge(currency: 'JPY', multi_currency: true))
+
+ assert_success response
+ assert response.test?
+ assert_equal 'APPROVAL', response.message
+ assert response.authorization
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
end
diff --git a/test/remote/gateways/remote_element_test.rb b/test/remote/gateways/remote_element_test.rb
new file mode 100644
index 00000000000..4cc1b565471
--- /dev/null
+++ b/test/remote/gateways/remote_element_test.rb
@@ -0,0 +1,162 @@
+require 'test_helper'
+
+class RemoteElementTest < Test::Unit::TestCase
+ def setup
+ @gateway = ElementGateway.new(fixtures(:element))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @check = check
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_match %r{Street address and postal code do not match}, response.avs_result['message']
+ assert_match %r{CVV matches}, response.cvv_result['message']
+ end
+
+ def test_failed_purchase
+ @amount = 20
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_successful_purchase_with_echeck
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_payment_account_token
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_shipping_address
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(shipping_address: address(address1: 'Shipping')))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', capture.message
+ end
+
+ def test_failed_authorize
+ @amount = 20
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'TransactionID required', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'TransactionID required', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'TransactionAmount required', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Approved}, response.message
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_match %r{PaymentAccount created}, response.message
+ end
+
+ def test_invalid_login
+ gateway = ElementGateway.new(account_id: '', account_token: '', application_id: '', acceptor_id: '', application_name: '', application_version: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Request}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:account_token], transcript)
+ end
+
+ def test_transcript_scrubbing_with_echeck
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@gateway.options[:account_token], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_epay_test.rb b/test/remote/gateways/remote_epay_test.rb
index 8d150946904..3cde00ce108 100644
--- a/test/remote/gateways/remote_epay_test.rb
+++ b/test/remote/gateways/remote_epay_test.rb
@@ -2,7 +2,7 @@
class RemoteEpayTest < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
+ Base.mode = :test
@gateway = EpayGateway.new(fixtures(:epay))
@@ -10,96 +10,63 @@ def setup
@credit_card_declined = credit_card('3333333333333102')
@amount = 100
- @options = { :order_id => '1' }
+ @options = {order_id: '1'}
end
- def test_successful_authorization
- assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_equal "1", response.params['accept']
- assert_not_nil response.params['tid']
- assert_not_nil response.params['cur']
- assert_not_nil response.params['amount']
- assert_not_nil response.params['orderid']
- assert !response.authorization.blank?
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
+ assert !response.authorization.blank?
assert response.test?
end
- def test_failed_authorization
- assert response = @gateway.authorize(@amount, @credit_card_declined, @options)
- assert_equal '1', response.params['decline']
- assert_not_nil response.params['error']
- assert_not_nil response.params['errortext']
- assert_failure response
- assert response.test?
- end
-
- def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal '1', response.params['accept']
- assert_not_nil response.params['tid']
- assert_not_nil response.params['cur']
- assert_not_nil response.params['amount']
- assert_not_nil response.params['orderid']
- assert !response.authorization.blank?
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
- assert response.test?
+ assert !response.authorization.blank?
+
+ capture_response = @gateway.capture(@amount, response.authorization)
+ assert_success capture_response
end
- def test_failed_purchase
- assert response = @gateway.purchase(@amount, @credit_card_declined, @options)
- assert_equal '1', response.params['decline']
- assert_not_nil response.params['error']
- assert_not_nil response.params['errortext']
+ def test_failed_authorization
+ response = @gateway.authorize(@amount, @credit_card_declined, @options)
assert_failure response
- assert response.test?
end
- def test_successful_capture
- authorize_response = @gateway.authorize(@amount, @credit_card, @options)
-
- assert response = @gateway.capture(@amount, authorize_response.authorization)
- assert_equal 'true', response.params['result']
- assert_success response
- assert response.test?
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @credit_card_declined, @options)
+ assert_failure response
end
def test_failed_capture
- assert response = @gateway.capture(@amount, 0)
- assert_equal 'false', response.params['result']
+ response = @gateway.capture(@amount, 0)
assert_failure response
- assert response.test?
end
def test_successful_refund
- authorize_response = @gateway.purchase(@amount, @credit_card, @options)
-
- assert response = @gateway.refund(@amount, authorize_response.authorization)
- assert_equal 'true', response.params['result']
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert response.test?
+
+ refund_response = @gateway.refund(@amount, response.authorization)
+ assert_success refund_response
end
def test_failed_refund
- assert response_refund = @gateway.refund(@amount, 0)
- assert_equal 'false', response_refund.params['result']
- assert_failure response_refund
- assert response_refund.test?
+ response = @gateway.refund(@amount, 0)
+ assert_failure response
end
def test_successful_void
- authorize_response = @gateway.authorize(@amount, @credit_card, @options)
-
- assert response = @gateway.void(authorize_response.authorization)
- assert_equal 'true', response.params['result']
+ response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
- assert response.test?
+
+ void_response = @gateway.void(response.authorization)
+ assert_success void_response
end
def test_failed_void
- assert response_void = @gateway.void(0)
- assert_equal 'false', response_void.params['result']
- assert_failure response_void
- assert response_void.test?
+ response = @gateway.void(0)
+ assert_failure response
end
end
diff --git a/test/remote/gateways/remote_eway_managed_test.rb b/test/remote/gateways/remote_eway_managed_test.rb
index f4914ac6eff..1322e32676c 100644
--- a/test/remote/gateways/remote_eway_managed_test.rb
+++ b/test/remote/gateways/remote_eway_managed_test.rb
@@ -21,7 +21,7 @@ def setup
def test_successful_purchase
assert response = @gateway.purchase(@amount, @valid_customer_id, @options)
- assert_equal "00,Transaction Approved(Test Gateway)", response.message
+ assert_equal '00,Transaction Approved(Test Gateway)', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
@@ -41,7 +41,7 @@ def test_invalid_login
def test_store_credit_card
assert response = @gateway.store(@credit_card, @options)
assert_success response
- assert_equal "OK", response.message
+ assert_equal 'OK', response.message
assert !response.token.blank?
assert_not_nil response.token
end
@@ -49,7 +49,7 @@ def test_store_credit_card
def test_update_credit_card
assert response = @gateway.update(@valid_customer_id, @credit_card, @options)
assert_success response
- assert_equal "OK", response.message
+ assert_equal 'OK', response.message
assert response.token.blank?
end
@@ -64,7 +64,7 @@ def test_store_invalid_credit_card
def test_retrieve
assert response = @gateway.retrieve(@valid_customer_id)
assert_success response
- assert_equal "OK", response.message
+ assert_equal 'OK', response.message
assert response.test?
end
end
diff --git a/test/remote/gateways/remote_eway_rapid_test.rb b/test/remote/gateways/remote_eway_rapid_test.rb
index e768c8ae329..4feec8d4e6b 100644
--- a/test/remote/gateways/remote_eway_rapid_test.rb
+++ b/test/remote/gateways/remote_eway_rapid_test.rb
@@ -5,117 +5,395 @@ def setup
@gateway = EwayRapidGateway.new(fixtures(:eway_rapid))
@amount = 100
- @failed_amount = 105
- @credit_card = credit_card("4444333322221111")
+ @failed_amount = -100
+ @credit_card = credit_card('4444333322221111')
@options = {
- :order_id => "1",
- :billing_address => address,
- :description => "Store Purchase",
- :redirect_url => "http://bogus.com"
+ order_id: '1',
+ invoice: 'I1234',
+ billing_address: address,
+ description: 'Store Purchase',
+ redirect_url: 'http://bogus.com'
}
end
- def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ def test_successful_purchase_with_billing_address
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_address_match(customer, @options[:billing_address])
+ end
+
+ def test_successful_purchase_with_address
+ @options[:billing_address] = nil
+ @options[:address] = address
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
assert_success response
- assert_equal "Transaction Approved", response.message
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_address_match(customer, @options[:address])
+ end
+
+ def test_successful_purchase_without_address
+ email = 'test@example.com'
+
+ @options[:billing_address] = nil
+ @options[:email] = email
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_equal customer['FirstName'], @credit_card.first_name
+ assert_equal customer['LastName'], @credit_card.last_name
+ assert_equal customer['Email'], email
+ end
+
+ def test_successful_purchase_with_shipping_address
+ @options[:shipping_address] = address
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ # eWAY Rapid does not include the shipping address in the request response,
+ # so we can only test that the transaction is successful.
end
def test_fully_loaded_purchase
- assert response = @gateway.purchase(@amount, @credit_card,
- :redirect_url => "http://awesomesauce.com",
- :ip => "0.0.0.0",
- :application_id => "Woohoo",
- :description => "Description",
- :order_id => "orderid1",
- :currency => "AUD",
- :email => "jim@example.com",
- :billing_address => {
- :title => "Mr.",
- :name => "Jim Awesome Smith",
- :company => "Awesome Co",
- :address1 => "1234 My Street",
- :address2 => "Apt 1",
- :city => "Ottawa",
- :state => "ON",
- :zip => "K1C2N6",
- :country => "CA",
- :phone => "(555)555-5555",
- :fax => "(555)555-6666"
+ response = @gateway.purchase(@amount, @credit_card,
+ redirect_url: 'http://awesomesauce.com',
+ ip: '0.0.0.0',
+ application_id: 'Woohoo',
+ partner_id: 'Woohoo',
+ transaction_type: 'Purchase',
+ description: 'Description',
+ order_id: 'orderid1',
+ invoice: 'I1234',
+ currency: 'AUD',
+ email: 'jim@example.com',
+ billing_address: {
+ title: 'Mr.',
+ name: 'Jim Awesome Smith',
+ company: 'Awesome Co',
+ address1: '1234 My Street',
+ address2: 'Apt 1',
+ city: 'Ottawa',
+ state: 'ON',
+ zip: 'K1C2N6',
+ country: 'CA',
+ phone: '(555)555-5555',
+ fax: '(555)555-6666'
},
- :shipping_address => {
- :title => "Ms.",
- :name => "Baker",
- :company => "Elsewhere Inc.",
- :address1 => "4321 Their St.",
- :address2 => "Apt 2",
- :city => "Chicago",
- :state => "IL",
- :zip => "60625",
- :country => "US",
- :phone => "1115555555",
- :fax => "1115556666"
+ shipping_address: {
+ title: 'Ms.',
+ name: 'Baker',
+ company: 'Elsewhere Inc.',
+ address1: '4321 Their St.',
+ address2: 'Apt 2',
+ city: 'Chicago',
+ state: 'IL',
+ zip: '60625',
+ country: 'US',
+ phone: '1115555555',
+ fax: '1115556666'
}
)
assert_success response
end
+ def test_successful_purchase_with_overly_long_fields
+ options = {
+ order_id: 'OrderId must be less than 50 characters otherwise it fails',
+ invoice: 'Max 12 chars',
+ description: 'EWay Rapid transactions fail if the description is more than 64 characters.',
+ billing_address: {
+ address1: 'The Billing Address 1 Cannot Be More Than Fifty Characters.',
+ address2: 'The Billing Address 2 Cannot Be More Than Fifty Characters.',
+ city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart',
+ },
+ shipping_address: {
+ address1: 'The Shipping Address 1 Cannot Be More Than Fifty Characters.',
+ address2: 'The Shipping Address 2 Cannot Be More Than Fifty Characters.',
+ city: 'TheCityCannotBeMoreThanFiftyCharactersOrItAllFallsApart',
+ }
+ }
+ @credit_card.first_name = 'FullNameOnACardMustBeLessThanFiftyCharacters'
+ @credit_card.last_name = 'LastName'
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+ end
+
def test_failed_purchase
- assert response = @gateway.purchase(@failed_amount, @credit_card, @options)
+ response = @gateway.purchase(@failed_amount, @credit_card, @options)
assert_failure response
- assert_equal "Do Not Honour", response.message
+ assert_equal 'Invalid Payment TotalAmount', response.message
end
- def test_failed_setup_purchase
- assert response = @gateway.setup_purchase(@amount, :redirect_url => "")
+ def test_successful_authorize_with_billing_address
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_address_match(customer, @options[:billing_address])
+ end
+
+ def test_successful_authorize_with_address
+ @options[:billing_address] = nil
+ @options[:address] = address
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_address_match(customer, @options[:address])
+ end
+
+ def test_successful_authorize_without_address
+ email = 'test@example.com'
+
+ @options[:billing_address] = nil
+ @options[:email] = email
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_equal customer['FirstName'], @credit_card.first_name
+ assert_equal customer['LastName'], @credit_card.last_name
+ assert_equal customer['Email'], email
+ end
+
+ def test_successful_authorize_with_shipping_address
+ @options[:shipping_address] = address
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ # eWAY Rapid does not include the shipping address in the request response,
+ # so we can only test that the transaction is successful.
+ end
+
+ def test_successful_authorize_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved Successful', authorize.message
+
+ capture = @gateway.capture(nil, authorize.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@failed_amount, @credit_card, @options)
assert_failure response
- assert_equal "V6047", response.message
+ assert_equal '', response.message
end
- def test_failed_run_purchase
- setup_response = @gateway.setup_purchase(@amount, @options)
- assert_success setup_response
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'bogus')
+ assert_failure response
+ assert_equal 'Invalid Auth Transaction ID for Capture/Void', response.message
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+ end
- assert response = @gateway.send(:run_purchase, "bogus", @credit_card, setup_response.params["formactionurl"])
+ def test_failed_void
+ response = @gateway.void('bogus')
assert_failure response
- assert_match(%r{Not Found}, response.message)
+ assert_equal 'Failed', response.message
end
- def test_failed_status
- setup_response = @gateway.setup_purchase(@failed_amount, @options)
- assert_success setup_response
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
- assert run_response = @gateway.send(:run_purchase, setup_response.authorization, @credit_card, setup_response.params["formactionurl"])
- assert_success run_response
+ response = @gateway.refund(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+ end
- response = @gateway.status(run_response.authorization)
+ def test_failed_refund
+ response = @gateway.refund(@amount, 'fakeid', @options)
assert_failure response
- assert_equal "Do Not Honour", response.message
- assert_equal run_response.authorization, response.authorization
+ assert_equal 'Invalid DirectRefundRequest, Transaction ID', response.message
end
def test_successful_store
- @options[:billing_address].merge!(:title => "Dr.")
- assert response = @gateway.store(@credit_card, @options)
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_address_match(customer, @options[:billing_address])
+ end
+
+ def test_successful_store_with_shipping_address
+ @options[:shipping_address] = address
+
+ response = @gateway.store(@credit_card, @options)
+
assert_success response
- assert_equal "Transaction Approved", response.message
+ assert_equal 'Transaction Approved Successful', response.message
+
+ # eWAY Rapid does not include the shipping address in the request response,
+ # so we can only test that the transaction is successful.
end
def test_failed_store
- @options[:billing_address].merge!(:country => nil)
- assert response = @gateway.store(@credit_card, @options)
+ @options[:billing_address][:country] = nil
+ response = @gateway.store(@credit_card, @options)
assert_failure response
- assert_equal "V6045,V6044", response.message
+ assert_equal 'V6044', response.params['Errors']
+ assert_equal 'Customer CountryCode Required', response.message
+ end
+
+ def test_successful_update_with_billing_address
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+ response = @gateway.update(response.authorization, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+ assert_address_match(customer, @options[:billing_address])
+ end
+
+ def test_successful_update_with_address
+ @options[:billing_address] = nil
+ @options[:address] = address
+
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ response = @gateway.update(response.authorization, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_address_match(customer, @options[:address])
+ end
+
+ def test_successful_update_without_address
+ email = 'test@example.com'
+ @options[:email] = email
+
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ @options[:billing_address] = nil
+
+ response = @gateway.update(response.authorization, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ customer = response.params['Customer']
+
+ assert_equal customer['FirstName'], @credit_card.first_name
+ assert_equal customer['LastName'], @credit_card.last_name
+ assert_equal customer['Email'], email
+ end
+
+ def test_successful_update_with_shipping_address
+ @options[:shipping_address] = address
+
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ response = @gateway.update(response.authorization, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ # eWAY Rapid does not include the shipping address in the request response,
+ # so we can only test that the transaction is successful.
+ end
+
+ def test_successful_store_purchase
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
+
+ response = @gateway.purchase(@amount, response.authorization, transaction_type: 'MOTO')
+ assert_success response
+ assert_equal 'Transaction Approved Successful', response.message
end
def test_invalid_login
gateway = EwayRapidGateway.new(
- :login => "bogus",
- :password => "bogus"
- )
- assert response = gateway.purchase(@amount, @credit_card, @options)
+ login: 'bogus',
+ password: 'bogus'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "Unauthorized", response.message
+ assert_equal 'Unauthorized', response.message
+ end
+
+ def test_transcript_scrubbing
+ credit_card_success = credit_card('4444333322221111', verification_value: 976225)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(100, credit_card_success, {})
+ end
+
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card_success.number, clean_transcript)
+ assert_scrubbed(credit_card_success.verification_value.to_s, clean_transcript)
+ end
+
+ private
+
+ def assert_address_match(customer, address)
+ assert_equal customer['FirstName'], address[:name].split[0]
+ assert_equal customer['LastName'], address[:name].split[1]
+ assert_equal customer['CompanyName'], address[:company]
+ assert_equal customer['Street1'], address[:address1]
+ assert_equal customer['Street2'], address[:address2]
+ assert_equal customer['City'], address[:city]
+ assert_equal customer['State'], address[:state]
+ assert_equal customer['PostalCode'], address[:zip]
+ assert_equal customer['Country'], address[:country].to_s.downcase
+ assert_equal customer['Phone'], address[:phone]
+ assert_equal customer['Fax'], address[:fax]
end
end
diff --git a/test/remote/gateways/remote_eway_test.rb b/test/remote/gateways/remote_eway_test.rb
index af0396d0124..a8d7d583874 100644
--- a/test/remote/gateways/remote_eway_test.rb
+++ b/test/remote/gateways/remote_eway_test.rb
@@ -17,7 +17,7 @@ def setup
:state => 'WA',
:country => 'AU',
:zip => '2000'
- } ,
+ },
:description => 'purchased items'
}
end
@@ -26,7 +26,7 @@ def test_invalid_amount
assert response = @gateway.purchase(101, @credit_card_success, @params)
assert_failure response
assert response.test?
- assert_equal EwayGateway::MESSAGES["01"], response.message
+ assert_equal EwayGateway::MESSAGES['01'], response.message
end
def test_purchase_success_with_verification_value
@@ -34,7 +34,7 @@ def test_purchase_success_with_verification_value
assert response.authorization
assert_success response
assert response.test?
- assert_equal EwayGateway::MESSAGES["00"], response.message
+ assert_equal EwayGateway::MESSAGES['00'], response.message
end
def test_purchase_success_without_verification_value
@@ -44,7 +44,7 @@ def test_purchase_success_without_verification_value
assert response.authorization
assert_success response
assert response.test?
- assert_equal EwayGateway::MESSAGES["00"], response.message
+ assert_equal EwayGateway::MESSAGES['00'], response.message
end
def test_purchase_error
@@ -68,6 +68,17 @@ def test_failed_refund
assert response = @gateway.refund(200, response.authorization)
assert_failure response
- assert_match /Error.*Your refund could not be processed./, response.message
+ assert_match %r{Error.*Your refund could not be processed.}, response.message
+ end
+
+ def test_transcript_scrubbing
+ @credit_card_success.verification_value = '431'
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(100, @credit_card_success, @params)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card_success.number, clean_transcript)
+ assert_scrubbed(@credit_card_success.verification_value.to_s, clean_transcript)
end
end
diff --git a/test/remote/gateways/remote_exact_test.rb b/test/remote/gateways/remote_exact_test.rb
index 2b9be676c8f..8843e170d74 100644
--- a/test/remote/gateways/remote_exact_test.rb
+++ b/test/remote/gateways/remote_exact_test.rb
@@ -1,30 +1,28 @@
require 'test_helper'
class RemoteExactTest < Test::Unit::TestCase
-
def setup
-
@gateway = ExactGateway.new(fixtures(:exact))
@credit_card = credit_card
@amount = 100
- @options = {
+ @options = {
:order_id => '1',
:billing_address => address,
:description => 'Store Purchase'
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_match /Transaction Normal/, response.message
+ assert_match %r{Transaction Normal}, response.message
assert_success response
end
def test_unsuccessful_purchase
# ask for error 13 response (Amount Error) via dollar amount 5,000 + error
@amount = 501300
- assert response = @gateway.purchase(@amount, @credit_card, @options )
- assert_match /Transaction Normal/, response.message
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match %r{Transaction Normal}, response.message
assert_failure response
end
@@ -35,7 +33,7 @@ def test_purchase_and_credit
assert credit = @gateway.credit(@amount, purchase.authorization)
assert_success credit
end
-
+
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -43,18 +41,18 @@ def test_authorize_and_capture
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
end
-
+
def test_failed_capture
- assert response = @gateway.capture(@amount, '')
+ assert response = @gateway.capture(@amount, 'bogus')
assert_failure response
- assert_match /Precondition Failed/i, response.message
+ assert_match %r{Invalid Transaction Tag}i, response.message
end
-
+
def test_invalid_login
- gateway = ExactGateway.new( :login => "NotARealUser",
- :password => "NotARealPassword" )
+ gateway = ExactGateway.new(:login => 'NotARealUser',
+ :password => 'NotARealPassword')
assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_equal "Invalid Logon", response.message
+ assert_match %r{^Invalid Login}, response.message
assert_failure response
end
end
diff --git a/test/remote/gateways/remote_ezic_test.rb b/test/remote/gateways/remote_ezic_test.rb
new file mode 100644
index 00000000000..7aacb3f3a74
--- /dev/null
+++ b/test/remote/gateways/remote_ezic_test.rb
@@ -0,0 +1,119 @@
+require 'test_helper'
+
+class RemoteEzicTest < Test::Unit::TestCase
+ def setup
+ @gateway = EzicGateway.new(fixtures(:ezic))
+
+ @amount = 100
+ @failed_amount = 19088
+ @credit_card = credit_card('4000100011112224')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'TEST APPROVED', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@failed_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'TEST DECLINED', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'TEST CAPTURED', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@failed_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'TEST DECLINED', response.message
+ end
+
+ def test_failed_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount+30, auth.authorization)
+ assert_failure capture
+ assert_match(/Settlement amount cannot exceed authorized amount/, capture.message)
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'TEST RETURNED', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ assert_equal 'TEST RETURNED', refund.message
+ assert_equal '-0.99', refund.params['settle_amount']
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount + 49, purchase.authorization)
+ assert_failure refund
+ assert_match(/Amount of refunds exceed original sale/, refund.message)
+ end
+
+ def test_failed_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ assert void = @gateway.void(authorize.authorization)
+ assert_failure void
+ assert_equal 'Processor/Network Error', void.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'TEST APPROVED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(credit_card(''), @options)
+ assert_failure response
+ assert_match %r{Missing card or check number}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
+
+ def test_invalid_login
+ gateway = EzicGateway.new(account_id: '11231')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match(/Invalid account number/, response.message)
+ end
+end
diff --git a/test/remote/gateways/remote_fat_zebra_test.rb b/test/remote/gateways/remote_fat_zebra_test.rb
index 13515c43936..28943103a33 100644
--- a/test/remote/gateways/remote_fat_zebra_test.rb
+++ b/test/remote/gateways/remote_fat_zebra_test.rb
@@ -10,7 +10,7 @@ def setup
@options = {
:order_id => rand(100000).to_s,
- :ip => "123.1.2.3"
+ :ip => '1.2.3.4'
}
end
@@ -20,40 +20,123 @@ def test_successful_purchase
assert_equal 'Approved', response.message
end
- def test_unsuccessful_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
+ def test_successful_multi_currency_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal 'USD', response.params['response']['currency']
+ end
+
+ def test_unsuccessful_multi_currency_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'XYZ'))
assert_failure response
- assert_equal 'Declined', response.message
+ assert_match(/Currency XYZ is not valid for this merchant/, response.message)
+ end
+
+ def test_successful_purchase_sans_cvv
+ @credit_card.verification_value = nil
+ assert response = @gateway.purchase(@amount, @credit_card, recurring: true)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase_sans_cvv
+ @credit_card.verification_value = nil
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_failure response
+ assert_equal 'CVV is required', response.message
+ end
+
+ def test_successful_purchase_with_no_options
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ assert auth_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth_response
+ assert_equal 'Approved', auth_response.message
+
+ assert capture_response = @gateway.capture(@amount, auth_response.authorization, @options)
+ assert_success capture_response
+ assert_equal 'Approved', capture_response.message
end
- def test_invalid_data
- @options.delete(:ip)
+ def test_multi_currency_authorize_and_capture
+ assert auth_response = @gateway.authorize(@amount, @credit_card, @options.merge(:currency => 'USD'))
+ assert_success auth_response
+ assert_equal 'Approved', auth_response.message
+ assert_equal 'USD', auth_response.params['response']['currency']
+
+ assert capture_response = @gateway.capture(@amount, auth_response.authorization, @options.merge(:currency => 'USD'))
+ assert_success capture_response
+ assert_equal 'Approved', capture_response.message
+ assert_equal 'USD', capture_response.params['response']['currency']
+ end
+
+ def test_successful_partial_capture
+ assert auth_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth_response
+ assert_equal 'Approved', auth_response.message
+
+ assert capture_response = @gateway.capture(@amount - 1, auth_response.authorization, @options)
+ assert_success capture_response
+ assert_equal 'Approved', capture_response.message
+ assert_equal @amount - 1, capture_response.params['response']['captured_amount']
+ end
+
+ def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal "Customer ip can't be blank", response.message
+ assert_equal 'Declined', response.message
end
def test_refund
- purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
- assert response = @gateway.refund(@amount, purchase.authorization, rand(1000000).to_s)
+ assert response = @gateway.refund(@amount, purchase.authorization, @options)
assert_success response
- assert_match /Approved/, response.message
+ assert_match %r{Approved}, response.message
end
def test_invalid_refund
+ @gateway.purchase(@amount, @credit_card, @options)
+
+ assert response = @gateway.refund(@amount, '', @options)
+ assert_failure response
+ assert_match %r{Invalid credit card for unmatched refund}, response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert response = @gateway.void(auth.authorization, @options)
+ assert_success response
+ assert_match %r{Voided}, response.message
+ end
+
+ def test_successful_void_refund
purchase = @gateway.purchase(@amount, @credit_card, @options)
+ puts purchase.inspect
+ refund = @gateway.refund(@amount, purchase.authorization, @options)
- assert response = @gateway.refund(@amount, nil, rand(1000000).to_s)
+ assert response = @gateway.void(refund.authorization, @options)
+ assert_success response
+ assert_match %r{Voided}, response.message
+ end
+
+ def test_failed_void
+ assert response = @gateway.void('123', @options)
assert_failure response
- assert_match /Original transaction is required/, response.message
+ assert_match %r{Not Found}, response.message
end
def test_store
assert card = @gateway.store(@credit_card)
assert_success card
- assert_false card.authorization.nil?
+ assert_not_nil card.authorization
end
def test_purchase_with_token
@@ -62,6 +145,31 @@ def test_purchase_with_token
assert_success purchase
end
+ def test_successful_purchase_with_descriptor
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:merchant => 'Merchant', :merchant_location => 'Location'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_metadata
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:metadata => { :description => 'Invoice #1234356' }))
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_equal 'Invoice #1234356', response.params['response']['metadata']['description']
+ end
+
+ def test_successful_purchase_with_3DS_information
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:cavv => 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', :xid => 'MGVmMmNlMzI4NjAyOWU2ZDgwNTQ=', :sli => '05'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase_with_incomplete_3DS_information
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:cavv => 'MDRjN2MxZTAxYjllNTBkNmM2MTA=', :sli => '05'))
+ assert_failure response
+ assert_match %r{Extra/xid is required for SLI 05}, response.message
+ end
+
def test_invalid_login
gateway = FatZebraGateway.new(
:username => 'invalid',
@@ -71,4 +179,14 @@ def test_invalid_login
assert_failure response
assert_equal 'Invalid Login', response.message
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
end
diff --git a/test/remote/gateways/remote_federated_canada_test.rb b/test/remote/gateways/remote_federated_canada_test.rb
index b39c9dd846c..3b48d1795af 100644
--- a/test/remote/gateways/remote_federated_canada_test.rb
+++ b/test/remote/gateways/remote_federated_canada_test.rb
@@ -10,8 +10,8 @@ def setup
@credit_card = credit_card('4111111111111111') # Visa
- @options = {
- :order_id => ActiveMerchant::Utils.generate_unique_id,
+ @options = {
+ :order_id => generate_unique_id,
:billing_address => address,
:description => 'Active Merchant Remote Test Purchase'
}
@@ -21,61 +21,57 @@ def test_gateway_should_exist
assert @gateway
end
- def test_validity_of_credit_card
- assert @credit_card.valid?
- end
-
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "Transaction Approved", response.message
+ assert_equal 'Transaction Approved', response.message
end
def test_unsuccessful_purchase
assert response = @gateway.purchase(@declined_amount, @credit_card, @options)
assert_failure response
- assert_equal "Transaction Declined", response.message
+ assert_equal 'Transaction Declined', response.message
end
def test_successful_authorization
assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
- assert_equal "Transaction Approved", response.message
+ assert_equal 'Transaction Approved', response.message
end
def test_failed_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
- assert_equal "Error in transaction data or system error", response.message
+ assert_equal 'Error in transaction data or system error', response.message
end
def test_purchase_and_refund
assert auth = @gateway.purchase(@amount, @credit_card, @options)
assert_success auth
- assert_equal "Transaction Approved", auth.message
+ assert_equal 'Transaction Approved', auth.message
assert auth.authorization
assert capture = @gateway.refund(@amount, auth.authorization)
- assert_equal "Transaction Approved", capture.message
+ assert_equal 'Transaction Approved', capture.message
assert_success capture
end
def test_authorize_and_void
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
- assert_equal "Transaction Approved", auth.message
+ assert_equal 'Transaction Approved', auth.message
assert auth.authorization
assert capture = @gateway.void(auth.authorization)
- assert_equal "Transaction Approved", capture.message
+ assert_equal 'Transaction Approved', capture.message
assert_success capture
end
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
- assert_equal "Transaction Approved", auth.message
+ assert_equal 'Transaction Approved', auth.message
assert auth.authorization
assert capture = @gateway.capture(@amount, auth.authorization)
- assert_equal "Transaction Approved", capture.message
+ assert_equal 'Transaction Approved', capture.message
assert_success capture
end
@@ -86,6 +82,6 @@ def test_invalid_login
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "Error in transaction data or system error", response.message
+ assert_equal 'Error in transaction data or system error', response.message
end
end
diff --git a/test/remote/gateways/remote_finansbank_test.rb b/test/remote/gateways/remote_finansbank_test.rb
index c5b80782b86..753df513fe9 100644
--- a/test/remote/gateways/remote_finansbank_test.rb
+++ b/test/remote/gateways/remote_finansbank_test.rb
@@ -1,21 +1,18 @@
+# encoding: utf-8
+
require 'test_helper'
class RemoteFinansbankTest < Test::Unit::TestCase
def setup
- if RUBY_VERSION < '1.9' && $KCODE == "NONE"
- @original_kcode = $KCODE
- $KCODE = 'u'
- end
-
@gateway = FinansbankGateway.new(fixtures(:finansbank))
@amount = 100
- @credit_card = credit_card('4000100011112224')
+ @credit_card = credit_card('4022774022774026', month: 12, year: 14, verification_value: '000')
@declined_card = credit_card('4000300011112220')
@options = {
- :order_id => '#' + ActiveMerchant::Utils.generate_unique_id,
+ :order_id => '#' + generate_unique_id,
:billing_address => address,
:description => 'Store Purchase',
:email => 'xyz@gmail.com'
@@ -29,6 +26,14 @@ def teardown
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
+ assert_not_nil response.params['order_id']
+ assert_not_nil response.params['response']
+ assert_not_nil response.params['auth_code']
+ assert_not_nil response.params['trxdate']
+ assert_not_nil response.params['numcode']
+ assert_not_nil response.params['trans_id']
+ assert_nil response.params['errorcode']
+ assert_equal response.params['order_id'], response.authorization
end
def test_unsuccessful_purchase
@@ -51,6 +56,37 @@ def test_failed_capture
assert_failure response
end
+ def test_credit
+ assert response = @gateway.credit(@amount, @credit_card)
+ assert_success response
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert void = @gateway.refund(@amount, response.authorization)
+ assert_success void
+ assert_equal response.params['order_id'], void.params['order_id']
+ end
+
+ def test_unsuccessful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert void = @gateway.refund(@amount + 100, response.authorization)
+ assert_failure void
+ assert_nil void.params['order_id']
+ assert_equal 'Declined (Reason: 99 - Net miktardan fazlasi iade edilemez.)', void.message
+ assert_equal 'CORE-2503', void.params['errorcode']
+ end
+
+ def test_void
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal response.params['order_id'], void.params['order_id']
+ end
+
def test_invalid_login
gateway = FinansbankGateway.new(
:login => '',
@@ -59,5 +95,7 @@ def test_invalid_login
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
+ assert_equal 'Declined (Reason: 99 - System based initialization problem. Please try again later.)', response.message
+ assert_equal '2100', response.params['errorcode']
end
end
diff --git a/test/remote/gateways/remote_first_giving_test.rb b/test/remote/gateways/remote_first_giving_test.rb
new file mode 100644
index 00000000000..aa37014c925
--- /dev/null
+++ b/test/remote/gateways/remote_first_giving_test.rb
@@ -0,0 +1,58 @@
+require 'test_helper'
+
+class RemoteFirstGivingTest < Test::Unit::TestCase
+
+ def setup
+ @gateway = FirstGivingGateway.new(fixtures(:first_giving))
+
+ @amount = 100
+ @credit_card = credit_card('4457010000000009')
+ @declined_card = credit_card('445701000000000')
+
+ @options = {
+ billing_address: address(state: 'MA', zip: '01803', country: 'US'),
+ ip: '127.0.0.1'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal(
+ 'Unfortunately, we were unable to perform credit card number validation. The credit card number validator responded with the following message ccNumber failed data validation for the following reasons : creditcardLength: 445701000000000 contains an invalid amount of digits.',
+ response.message,
+ response.params.inspect
+ )
+ end
+
+ def test_successful_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert response = @gateway.refund(@amount, purchase.authorization)
+ assert_equal 'REFUND_REQUESTED_AWAITING_REFUND', response.message
+ end
+
+ def test_failed_refund
+ assert response = @gateway.refund(@amount, '1234')
+ assert_failure response
+ assert_equal 'An error occurred. Please check your input and try again.', response.message
+ end
+
+ def test_invalid_login
+ gateway = FirstGivingGateway.new(
+ application_key: '25151616',
+ security_token: '63131jnkj',
+ charity_id: '1234'
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'An error occurred. Please check your input and try again.', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_first_pay_test.rb b/test/remote/gateways/remote_first_pay_test.rb
index 6baab89c057..6e174f76976 100644
--- a/test/remote/gateways/remote_first_pay_test.rb
+++ b/test/remote/gateways/remote_first_pay_test.rb
@@ -3,85 +3,130 @@
class RemoteFirstPayTest < Test::Unit::TestCase
def setup
@gateway = FirstPayGateway.new(fixtures(:first_pay))
-
+
@amount = 100
- @credit_card = credit_card('4111111111111111', {:first_name => 'Test', :last_name => 'Person'})
- @declined_card = credit_card('4111111111111111')
-
- @options = {
- :order_id => '1',
- :billing_address => address({:name => 'Test Person', :city => 'New York', :state => 'NY', :zip => '10002', :country => 'US'}),
- :description => 'Test Purchase'
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ order_id: SecureRandom.hex(24),
+ billing_address: address,
+ description: 'Store Purchase'
}
end
-
+
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal('CAPTURED', response.message)
+ assert_equal 'Approved', response.message
end
- def test_unsuccessful_purchase
- # > $500 results in decline
- @amount = 51000
- assert response = @gateway.purchase(@amount, @declined_card, @options)
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal("51-INSUFFICIENT FUNDS", response.message)
+ assert_equal 'Declined', response.message
end
-
- def test_invalid_login
- gateway = FirstPayGateway.new(:login => '', :password => '')
- assert response = gateway.purchase(@amount, @credit_card, @options)
+
+ def test_failed_purchase_with_no_address
+ @options.delete(:billing_address)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
assert_failure response
- assert_equal '703-INVALID VENDOR ID AND PASS CODE', response.message
+ assert_equal 'Address is invalid (street, city, zip, state and or country fields)', response.message
end
-
- def test_successful_credit
- # purchase first
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert_equal('CAPTURED', response.message)
- assert_not_nil(response.params["auth"])
- assert_not_nil(response.authorization)
-
- @options[:credit_card] = @credit_card
-
- assert response = @gateway.credit(@amount, response.authorization, @options)
- assert_success response
- assert_not_nil(response.authorization)
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
end
-
- def test_failed_credit
- @options[:credit_card] = @credit_card
-
- assert response = @gateway.credit(@amount, '000000', @options)
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '1234')
assert_failure response
- assert_nil(response.authorization)
- assert_equal('PARENT TRANSACTION NOT FOUND', response.message)
end
-
- def test_failed_unlinked_credit
- assert_raise ArgumentError do
- @gateway.credit(@amount, @credit_card)
- end
+
+ def test_successful_refund
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+
+ # Not sure why purchase tx is not refundable??
+ # purchase = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_success purchase
+
+ assert refund = @gateway.refund(@amount, capture.authorization)
+ assert_success refund
end
-
+
+ def test_partial_refund
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+
+ # Not sure why purchase tx is not refundable??
+ # purchase = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_success purchase
+
+ assert refund = @gateway.refund(@amount / 2, capture.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '1234')
+ assert_failure response
+ end
+
def test_successful_void
- # purchase first
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert_equal('CAPTURED', response.message)
- assert_not_nil(response.params["auth"])
- assert_not_nil(response.authorization)
-
- assert_success response
- assert_not_nil(response.authorization)
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
end
-
- def test_failed_void
- assert response = @gateway.void(@amount, @credit_card, @options)
+
+ def test_failed_void
+ response = @gateway.void('1')
assert_failure response
- assert_equal('PARENT TRANSACTION NOT FOUND', response.message)
end
-
+
+ def test_invalid_login
+ gateway = FirstPayGateway.new(
+ transaction_center_id: '1234',
+ gateway_id: 'abcd'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match(/Merchant: 1234 has encountered error #DTO-200-TC./, response.error_code)
+ end
+
+ def test_recurring_payment
+ @options.merge!({recurring: 1, recurring_start_date: DateTime.now.strftime('%m/%d/%Y'), recurring_end_date: DateTime.now.strftime('%m/%d/%Y'), recurring_type: 'monthly'})
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_transcript_scrubbing
+ @credit_card.verification_value = 789
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:gateway_id], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_firstdata_e4_test.rb b/test/remote/gateways/remote_firstdata_e4_test.rb
old mode 100644
new mode 100755
index 6b402bb2968..f7d88e60197
--- a/test/remote/gateways/remote_firstdata_e4_test.rb
+++ b/test/remote/gateways/remote_firstdata_e4_test.rb
@@ -5,12 +5,18 @@ def setup
@gateway = FirstdataE4Gateway.new(fixtures(:firstdata_e4))
@credit_card = credit_card
@bad_credit_card = credit_card('4111111111111113')
+ @credit_card_with_track_data = credit_card_with_track_data('4003000123456781')
@amount = 100
@options = {
:order_id => '1',
:billing_address => address,
:description => 'Store Purchase'
}
+ @options_with_authentication_data = @options.merge({
+ eci: '5',
+ cavv: 'TESTCAVV',
+ xid: 'TESTXID'
+ })
end
def test_successful_purchase
@@ -19,10 +25,71 @@ def test_successful_purchase
assert_success response
end
+ def test_successful_purchase_with_network_tokenization
+ @credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: nil
+ )
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Normal - Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_specified_currency
+ options_with_specified_currency = @options.merge({currency: 'GBP'})
+ assert response = @gateway.purchase(@amount, @credit_card, options_with_specified_currency)
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ assert_equal 'GBP', response.params['currency']
+ end
+
+ def test_successful_purchase_with_track_data
+ assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_level_3
+ level_3_xml = <<-LEVEL3
+
+ 107.20
+ 3
+ The Description
+ 2.33
+
+ LEVEL3
+
+ response = @gateway.purchase(500, @credit_card, @options.merge(level_3: level_3_xml))
+ assert_success response
+ assert_equal 'Transaction Normal - Approved', response.message
+ end
+
+ def test_successful_purchase_with_tax_fields
+ response = @gateway.purchase(500, @credit_card, @options.merge(tax1_amount: 50, tax1_number: 'A458'))
+ assert_success response
+ assert_equal '50.0', response.params['tax1_amount']
+ assert_equal '', response.params['tax1_number'], 'E4 blanks this out in the response'
+ end
+
+ def test_successful_purchase_with_customer_ref
+ response = @gateway.purchase(500, @credit_card, @options.merge(customer: '267'))
+ assert_success response
+ assert_equal '267', response.params['customer_ref']
+ end
+
+ def test_successful_purchase_with_card_authentication
+ assert response = @gateway.purchase(@amount, @credit_card, @options_with_authentication_data)
+ assert_equal response.params['cavv'], @options_with_authentication_data[:cavv]
+ assert_equal response.params['ecommerce_flag'], @options_with_authentication_data[:eci]
+ assert_equal response.params['xid'], @options_with_authentication_data[:xid]
+ assert_success response
+ end
+
def test_unsuccessful_purchase
# ask for error 13 response (Amount Error) via dollar amount 5,000 + error
@amount = 501300
- assert response = @gateway.purchase(@amount, @credit_card, @options )
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_match(/Transaction Normal/, response.message)
assert_failure response
end
@@ -31,14 +98,16 @@ def test_bad_creditcard_number
assert response = @gateway.purchase(@amount, @bad_credit_card, @options)
assert_match(/Invalid Credit Card/, response.message)
assert_failure response
+ assert_equal response.error_code, 'invalid_number'
end
def test_trans_error
# ask for error 42 (unable to send trans) as the cents bit...
@amount = 500042
- assert response = @gateway.purchase(@amount, @credit_card, @options )
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_match(/Unable to Send Transaction/, response.message) # 42 is 'unable to send trans'
assert_failure response
+ assert_equal response.error_code, 'processing_error'
end
def test_purchase_and_credit
@@ -49,9 +118,30 @@ def test_purchase_and_credit
assert_success credit
end
+ def test_purchase_and_credit_with_specified_currency
+ options_with_specified_currency = @options.merge({currency: 'GBP'})
+ assert purchase = @gateway.purchase(@amount, @credit_card, options_with_specified_currency)
+ assert_success purchase
+ assert purchase.authorization
+ assert_equal 'GBP', purchase.params['currency']
+ assert credit = @gateway.refund(@amount, purchase.authorization, options_with_specified_currency)
+ assert_success credit
+ assert_equal 'GBP', credit.params['currency']
+ end
+
def test_purchase_and_void
- assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert purchase = @gateway.purchase(29234, @credit_card, @options)
assert_success purchase
+
+ assert purchase.authorization
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ end
+
+ def test_purchase_and_void_with_even_dollar_amount
+ assert purchase = @gateway.purchase(5000, @credit_card, @options)
+ assert_success purchase
+
assert purchase.authorization
assert void = @gateway.void(purchase.authorization)
assert_success void
@@ -71,19 +161,92 @@ def test_failed_capture
assert_match(/Invalid Authorization Number/i, response.message)
end
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Transaction Normal - Approved', response.message
+ assert_equal '0.0', response.params['dollar_amount']
+ assert_equal '05', response.params['transaction_type']
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@bad_credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Credit Card Number}, response.message
+ assert_equal response.error_code, 'invalid_number'
+ end
+
def test_invalid_login
- gateway = FirstdataE4Gateway.new(:login => "NotARealUser",
- :password => "NotARealPassword" )
+ gateway = FirstdataE4Gateway.new(:login => 'NotARealUser',
+ :password => 'NotARealPassword')
assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_match /Unauthorized Request/, response.message
+ assert_match %r{Unauthorized Request}, response.message
assert_failure response
end
def test_response_contains_cvv_and_avs_results
response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal 'M', response.cvv_result["code"]
- assert_equal '1', response.avs_result["code"]
+ assert_equal 'M', response.cvv_result['code']
+ assert_equal '4', response.avs_result['code']
+ end
+
+ def test_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ end
+
+ def test_refund_with_specified_currency
+ options_with_specified_currency = @options.merge({currency: 'GBP'})
+ assert purchase = @gateway.purchase(@amount, @credit_card, options_with_specified_currency)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+ assert_equal 'GBP', purchase.params['currency']
+
+ assert response = @gateway.refund(50, purchase.authorization, options_with_specified_currency)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ assert_equal 'GBP', response.params['currency']
+ end
+
+ def test_refund_with_track_data
+ assert purchase = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = FirstdataE4Gateway.new(login: 'unknown', password: 'unknown')
+ assert !gateway.verify_credentials
+ gateway = FirstdataE4Gateway.new(login: fixtures(:firstdata_e4)[:login], password: 'unknown')
+ assert !gateway.verify_credentials
+ end
+
+ def test_transcript_scrubbing
+ cc_with_different_cvc = credit_card(verification_value: '999')
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, cc_with_different_cvc, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(cc_with_different_cvc.number, transcript)
+ assert_scrubbed(cc_with_different_cvc.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
end
end
diff --git a/test/remote/gateways/remote_firstdata_e4_v27_test.rb b/test/remote/gateways/remote_firstdata_e4_v27_test.rb
new file mode 100644
index 00000000000..0bf4fc8c79c
--- /dev/null
+++ b/test/remote/gateways/remote_firstdata_e4_v27_test.rb
@@ -0,0 +1,271 @@
+require 'test_helper'
+
+class RemoteFirstdataE4V27Test < Test::Unit::TestCase
+ def setup
+ @gateway = FirstdataE4V27Gateway.new(fixtures(:firstdata_e4_v27))
+ @credit_card = credit_card
+ @credit_card_master = credit_card('5500000000000004', :brand => 'master')
+ @bad_credit_card = credit_card('4111111111111113')
+ @credit_card_with_track_data = credit_card_with_track_data('4003000123456781')
+ @amount = 100
+ @options = {
+ :order_id => '1',
+ :billing_address => address,
+ :description => 'Store Purchase'
+ }
+ @options_with_authentication_data = @options.merge({
+ eci: '5',
+ cavv: 'TESTCAVV',
+ xid: 'TESTXID'
+ })
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_network_tokenization
+ @credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: nil
+ )
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Normal - Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_track_data
+ assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_level_3
+ level_3_xml = <<-LEVEL3
+
+ 107.20
+ 3
+ The Description
+ 2.33
+
+ LEVEL3
+
+ response = @gateway.purchase(500, @credit_card, @options.merge(level_3: level_3_xml))
+ assert_success response
+ assert_equal 'Transaction Normal - Approved', response.message
+ end
+
+ def test_successful_purchase_with_tax_fields
+ response = @gateway.purchase(500, @credit_card, @options.merge(tax1_amount: 50, tax1_number: 'A458'))
+ assert_success response
+ assert_equal '50.0', response.params['tax1_amount']
+ assert_equal '', response.params['tax1_number'], 'E4 blanks this out in the response'
+ end
+
+ def test_successful_purchase_with_customer_ref
+ response = @gateway.purchase(500, @credit_card, @options.merge(customer: '267'))
+ assert_success response
+ assert_equal '267', response.params['customer_ref']
+ end
+
+ def test_successful_purchase_with_card_authentication
+ assert response = @gateway.purchase(@amount, @credit_card, @options_with_authentication_data)
+ assert_equal response.params['cavv'], @options_with_authentication_data[:cavv]
+ assert_equal response.params['ecommerce_flag'], @options_with_authentication_data[:eci]
+ assert_equal response.params['xid'], @options_with_authentication_data[:xid]
+ assert_success response
+ end
+
+ def test_successful_purchase_with_stored_credentials_initial
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'unscheduled',
+ initiator: 'customer'
+ }
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ assert_equal '1', response.params['stored_credentials_indicator']
+ assert_equal 'U', response.params['stored_credentials_schedule']
+ assert_not_nil response.params['stored_credentials_transaction_id']
+ end
+
+ def test_successful_purchase_with_stored_credentials_initial_master
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'unscheduled',
+ initiator: 'customer'
+ }
+ }
+ assert response = @gateway.purchase(@amount, @credit_card_master, @options.merge(stored_credential))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ assert_equal 'S', response.params['stored_credentials_indicator']
+ assert_equal 'U', response.params['stored_credentials_schedule']
+ assert_not_nil response.params['stored_credentials_transaction_id']
+ end
+
+ def test_successful_purchase_with_stored_credentials_subsequent_recurring
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'recurring',
+ initiator: 'merchant'
+ }
+ }
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ assert_equal 'S', response.params['stored_credentials_indicator']
+ assert_equal 'S', response.params['stored_credentials_schedule']
+ assert_not_nil response.params['stored_credentials_transaction_id']
+ end
+
+ def test_unsuccessful_purchase
+ # ask for error 13 response (Amount Error) via dollar amount 5,000 + error
+ @amount = 501300
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_failure response
+ end
+
+ def test_bad_creditcard_number
+ assert response = @gateway.purchase(@amount, @bad_credit_card, @options)
+ assert_match(/Invalid Credit Card/, response.message)
+ assert_failure response
+ assert_equal response.error_code, 'invalid_number'
+ end
+
+ def test_trans_error
+ # ask for error 42 (unable to send trans) as the cents bit...
+ @amount = 500042
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Unable to Send Transaction/, response.message) # 42 is 'unable to send trans'
+ assert_failure response
+ assert_equal response.error_code, 'processing_error'
+ end
+
+ def test_purchase_and_credit
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert purchase.authorization
+ assert credit = @gateway.refund(@amount, purchase.authorization)
+ assert_success credit
+ end
+
+ def test_purchase_and_void
+ assert purchase = @gateway.purchase(29234, @credit_card, @options)
+ assert_success purchase
+
+ assert purchase.authorization
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ end
+
+ def test_purchase_and_void_with_even_dollar_amount
+ assert purchase = @gateway.purchase(5000, @credit_card, @options)
+ assert_success purchase
+
+ assert purchase.authorization
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ end
+
+ def test_authorize_and_capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, 'ET838747474;frob')
+ assert_failure response
+ assert_match(/Invalid Authorization Number/i, response.message)
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Transaction Normal - Approved', response.message
+ assert_equal '0.0', response.params['dollar_amount']
+ assert_equal '05', response.params['transaction_type']
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@bad_credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Credit Card Number}, response.message
+ assert_equal response.error_code, 'invalid_number'
+ end
+
+ def test_invalid_login
+ gateway = FirstdataE4V27Gateway.new(:login => 'NotARealUser',
+ :password => 'NotARealPassword',
+ :key_id => 'NotARealKey',
+ :hmac_key => 'NotARealHMAC')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_match %r{Unauthorized Request}, response.message
+ assert_failure response
+ end
+
+ def test_response_contains_cvv_and_avs_results
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'M', response.cvv_result['code']
+ assert_equal '4', response.avs_result['code']
+ end
+
+ def test_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ end
+
+ def test_refund_with_track_data
+ assert purchase = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = FirstdataE4V27Gateway.new(login: 'unknown', password: 'unknown', key_id: 'unknown', hmac_key: 'unknown')
+ assert !gateway.verify_credentials
+ gateway = FirstdataE4V27Gateway.new(login: fixtures(:firstdata_e4)[:login], password: 'unknown', key_id: 'unknown', hmac_key: 'unknown')
+ assert !gateway.verify_credentials
+ end
+
+ def test_transcript_scrubbing
+ cc_with_different_cvc = credit_card(verification_value: '999')
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, cc_with_different_cvc, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(cc_with_different_cvc.number, transcript)
+ assert_scrubbed(cc_with_different_cvc.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ assert_scrubbed(@gateway.options[:hmac_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_flo2cash_simple_test.rb b/test/remote/gateways/remote_flo2cash_simple_test.rb
new file mode 100644
index 00000000000..f952d7c0be1
--- /dev/null
+++ b/test/remote/gateways/remote_flo2cash_simple_test.rb
@@ -0,0 +1,69 @@
+require 'test_helper'
+
+class RemoteFlo2cashSimpleTest < Test::Unit::TestCase
+ def setup
+ Base.mode = :test
+
+ @gateway = Flo2cashSimpleGateway.new(fixtures(:flo2cash_simple))
+
+ @amount = 100
+ @credit_card = credit_card('5123456789012346', brand: :master, month: 5, year: 2017, verification_value: 111)
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_invalid_login
+ gateway = Flo2cashSimpleGateway.new(
+ username: 'N/A',
+ password: 'N/A',
+ account_id: '100'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication error. Username and/or Password are incorrect', response.message
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction Declined - Bank Error', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'Original transaction not found', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_flo2cash_test.rb b/test/remote/gateways/remote_flo2cash_test.rb
new file mode 100644
index 00000000000..0c0fe0972f3
--- /dev/null
+++ b/test/remote/gateways/remote_flo2cash_test.rb
@@ -0,0 +1,93 @@
+require 'test_helper'
+
+class RemoteFlo2cashTest < Test::Unit::TestCase
+ def setup
+ Base.mode = :test
+
+ @gateway = Flo2cashGateway.new(fixtures(:flo2cash))
+
+ @amount = 100
+ @credit_card = credit_card('5123456789012346', brand: :master, month: 5, year: 2017, verification_value: 111)
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_invalid_login
+ gateway = Flo2cashGateway.new(
+ username: 'N/A',
+ password: 'N/A',
+ account_id: '100'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication error. Username and/or Password are incorrect', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction Declined - Bank Error', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.responses.first.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\w+$), response.authorization
+
+ assert capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction Declined - Bank Error', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Original transaction not found', response.message
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'Original transaction not found', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_forte_test.rb b/test/remote/gateways/remote_forte_test.rb
new file mode 100644
index 00000000000..8a2df5bccc8
--- /dev/null
+++ b/test/remote/gateways/remote_forte_test.rb
@@ -0,0 +1,211 @@
+require 'test_helper'
+
+class RemoteForteTest < Test::Unit::TestCase
+ def setup
+ @gateway = ForteGateway.new(fixtures(:forte))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('1111111111111111')
+
+ @check = check
+ @bad_check = check({
+ :name => 'Jim Smith',
+ :bank_name => 'Bank of Elbonia',
+ :routing_number => '1234567890',
+ :account_number => '0987654321',
+ :account_holder_type => '',
+ :account_type => 'checking',
+ :number => '0'
+ })
+
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase',
+ order_id: '1'
+ }
+ end
+
+ def test_invalid_login
+ gateway = ForteGateway.new(api_key: 'InvalidKey', secret: 'InvalidSecret', location_id: '11', account_id: '323')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'combination not found.', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'TEST APPROVAL', response.message
+ end
+
+ def test_successful_purchase_with_echeck
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_purchase_with_echeck
+ response = @gateway.purchase(@amount, @bad_check, @options)
+ assert_failure response
+ assert_equal 'INVALID TRN', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ address: address
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal '1', response.params['order_number']
+ assert_equal 'TEST APPROVAL', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'INVALID CREDIT CARD NUMBER', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ wait_for_authorization_to_clear
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'APPROVED', capture.message
+ end
+
+ def test_successful_authorize_capture_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ wait_for_authorization_to_clear
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_match auth.authorization.split('#')[0], capture.authorization
+ assert_match auth.authorization.split('#')[1], capture.authorization
+ assert_equal 'APPROVED', capture.message
+
+ void = @gateway.void(capture.authorization)
+ assert_success void
+ end
+
+ def test_failed_authorize
+ @amount = 1985
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'INVALID CREDIT CARD NUMBER', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ wait_for_authorization_to_clear
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '', @options)
+ assert_failure response
+ assert_match 'field transaction_id', response.message
+ end
+
+ def test_successful_credit
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.credit(@amount, @credit_card, @options)
+ assert_success refund
+ assert_equal 'TEST APPROVAL', refund.message
+ end
+
+ def test_partial_credit
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.credit(@amount-1, @credit_card, @options)
+ assert_success refund
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ wait_for_authorization_to_clear
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'APPROVED', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_match 'field transaction_id', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ wait_for_authorization_to_clear
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'TEST APPROVAL', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '', @options)
+ assert_failure response
+ assert_match 'field authorization_code', response.message
+ assert_match 'field original_transaction_id', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{TEST APPROVAL}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{INVALID CREDIT CARD NUMBER}, response.message
+ end
+
+ def test_transcript_scrubbing
+ @credit_card.verification_value = 789
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
+
+ private
+
+ def wait_for_authorization_to_clear
+ sleep(10)
+ end
+
+end
diff --git a/test/remote/gateways/remote_garanti_test.rb b/test/remote/gateways/remote_garanti_test.rb
index 45db100bcc6..189fba6ddd0 100644
--- a/test/remote/gateways/remote_garanti_test.rb
+++ b/test/remote/gateways/remote_garanti_test.rb
@@ -4,19 +4,14 @@
class RemoteGarantiTest < Test::Unit::TestCase
def setup
- if RUBY_VERSION < '1.9' && $KCODE == "NONE"
- @original_kcode = $KCODE
- $KCODE = 'u'
- end
-
@gateway = GarantiGateway.new(fixtures(:garanti))
- @amount = 1 # 1 cents = 0.01$
- @declined_card = credit_card('4000100011112224')
- @credit_card = credit_card('4000300011112220')
+ @amount = 100 # 1 cents = 0.01$
+ @declined_card = credit_card('4282209027132017')
+ @credit_card = credit_card('4282209027132016', month: 5, year: 2018, verification_value: 358)
@options = {
- :order_id => ActiveMerchant::Utils.generate_unique_id,
+ :order_id => generate_unique_id,
:billing_address => address,
:description => 'Store Purchase'
}
@@ -35,7 +30,7 @@ def test_successful_purchase
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_match /Declined/, response.message
+ assert_match %r{Declined}, response.message
end
def test_authorize_and_capture
@@ -51,19 +46,18 @@ def test_authorize_and_capture
def test_failed_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
- assert_match /Declined/, response.message
+ assert_match %r{Declined}, response.message
end
def test_invalid_login
gateway = GarantiGateway.new(
- :provision_user_id => 'PROVAUT',
- :user_id => 'PROVAUT',
- :terminal_id => '10000174',
+ :login => 'PROVAUT',
+ :terminal_id => '30691300',
:merchant_id => '',
:password => ''
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal '0651', response.params["reason_code"]
+ assert_equal '0651', response.params['reason_code']
end
end
diff --git a/test/remote/gateways/remote_global_collect_test.rb b/test/remote/gateways/remote_global_collect_test.rb
new file mode 100644
index 00000000000..19eab0296fa
--- /dev/null
+++ b/test/remote/gateways/remote_global_collect_test.rb
@@ -0,0 +1,179 @@
+require 'test_helper'
+
+class RemoteGlobalCollectTest < Test::Unit::TestCase
+ def setup
+ @gateway = GlobalCollectGateway.new(fixtures(:global_collect))
+
+ @amount = 100
+ @credit_card = credit_card('4567350000427977')
+ @declined_card = credit_card('5424180279791732')
+ @accepted_amount = 4005
+ @rejected_amount = 2997
+ @options = {
+ email: 'example@example.com',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@accepted_amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_fraud_fields
+ options = @options.merge(
+ fraud_fields:
+ {
+ 'website' => 'www.example.com',
+ 'giftMessage' => 'Happy Day!'
+ }
+ )
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = @options.merge(
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ sdk_identifier: 'Channel',
+ sdk_creator: 'Bob',
+ integrator: 'Bill',
+ creator: 'Super',
+ name: 'Cala',
+ version: '1.0',
+ extension_ID: '5555555'
+ )
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_very_long_name
+ credit_card = credit_card('4567350000427977', { first_name: 'thisisaverylongfirstname'})
+
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_blank_name
+ credit_card = credit_card('4567350000427977', { first_name: nil, last_name: nil})
+
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@rejected_amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Not authorised', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Not authorised', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount - 1, auth.authorization)
+ assert_success capture
+ assert_equal 99, capture.params['payment']['paymentOutput']['amountOfMoney']['amount']
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '123', @options)
+ assert_failure response
+ assert_match %r{UNKNOWN_PAYMENT_ID}, response.message
+ end
+
+ # Because payments are not fully authorized immediately, refunds can only be
+ # tested on older transactions (~24hrs old should be fine)
+ #
+ # def test_successful_refund
+ # txn = REPLACE WITH PREVIOUS TRANSACTION AUTHORIZATION
+ #
+ # assert refund = @gateway.refund(@accepted_amount, txn)
+ # assert_success refund
+ # assert_equal 'Succeeded', refund.message
+ # end
+ #
+ # def test_partial_refund
+ # txn = REPLACE WITH PREVIOUS TRANSACTION AUTHORIZATION
+ #
+ # assert refund = @gateway.refund(@amount-1, REPLACE WITH PREVIOUS TRANSACTION AUTHORIZATION)
+ # assert_success refund
+ # end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '123')
+ assert_failure response
+ assert_match %r{UNKNOWN_PAYMENT_ID}, response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('123')
+ assert_failure response
+ assert_match %r{UNKNOWN_PAYMENT_ID}, response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Not authorised', response.message
+ end
+
+ def test_invalid_login
+ gateway = GlobalCollectGateway.new(merchant_id: '', api_key_id: '', secret_api_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{MISSING_OR_INVALID_AUTHORIZATION}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@gateway.options[:secret_api_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_global_transport_test.rb b/test/remote/gateways/remote_global_transport_test.rb
new file mode 100644
index 00000000000..eac5264fe9c
--- /dev/null
+++ b/test/remote/gateways/remote_global_transport_test.rb
@@ -0,0 +1,141 @@
+require 'test_helper'
+
+class RemoteGlobalTransportTest < Test::Unit::TestCase
+ def setup
+ @gateway = GlobalTransportGateway.new(fixtures(:global_transport))
+
+ @credit_card = credit_card('4003002345678903')
+
+ @options = {
+ email: 'john@example.com',
+ order_id: '1',
+ billing_address: address,
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(500, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_partial_purchase
+ @credit_card = credit_card('4111111111111111')
+ response = @gateway.purchase(2354, @credit_card, @options)
+ assert_success response
+ assert_equal 'Partial Approval', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(2304, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(500, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(500, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(40000, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_successful_partial_authorize_and_capture
+ @credit_card = credit_card('4111111111111111')
+ auth = @gateway.authorize(2354, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Partial Approval', auth.message
+
+ assert capture = @gateway.capture(2000, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ auth = @gateway.authorize(500, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(1000, auth.authorization)
+ assert_failure capture
+ assert_match(/must be less than or equal to the original amount/, capture.message)
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(500, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(500, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(500, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(490, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(500, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(1000, purchase.authorization)
+ assert_failure refund
+ assert_match(/Refund Exceeds Available Refund Amount/, refund.message)
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(500, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_failed_void
+ assert void = @gateway.void('UnknownAuthorization')
+ assert_failure void
+ assert_equal 'Invalid PNRef', void.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(credit_card('4003'), @options)
+ assert_failure response
+ assert_equal 'Invalid Account Number', response.message
+ end
+
+ def test_invalid_login
+ gateway = GlobalTransportGateway.new(global_user_name: '', global_password: '', term_type: '')
+ response = gateway.purchase(500, @credit_card, @options)
+ assert_failure response
+ assert_equal('Invalid Login Information', response.message)
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(500, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:global_password], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_hdfc_test.rb b/test/remote/gateways/remote_hdfc_test.rb
index 67488f56c25..e3f2ed395d5 100644
--- a/test/remote/gateways/remote_hdfc_test.rb
+++ b/test/remote/gateways/remote_hdfc_test.rb
@@ -1,5 +1,4 @@
require 'test_helper'
-require 'remote/integrations/remote_integration_helper'
class RemoteHdfcTest < Test::Unit::TestCase
def setup
@@ -8,48 +7,48 @@ def setup
@gateway = HdfcGateway.new(fixtures(:hdfc))
@amount = 100
- @credit_card = credit_card("4012001037141112")
+ @credit_card = credit_card('4012001037141112')
# Use an American Express card to simulate a failure since HDFC does not
# support any proper decline cards outside of 3D secure failures.
- @declined_card = credit_card("377182068239368", :brand => :american_express)
+ @declined_card = credit_card('377182068239368', :brand => :american_express)
@options = {
:order_id => generate_unique_id,
:billing_address => address,
- :description => "Store Purchase"
+ :description => 'Store Purchase'
}
end
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "Succeeded", response.message
+ assert_equal 'Succeeded', response.message
end
def test_failed_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal "Invalid Brand.", response.message
- assert_equal "GW00160", response.params["error_code_tag"]
+ assert_equal 'Invalid Brand.', response.message
+ assert_equal 'GW00160', response.params['error_code_tag']
end
def test_successful_authorize_and_capture
assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
- assert_equal "Succeeded", response.message
+ assert_equal 'Succeeded', response.message
assert_match %r(^\d+\|.+$), response.authorization
assert capture = @gateway.capture(@amount, response.authorization)
assert_success capture
- assert_equal "Succeeded", capture.message
+ assert_equal 'Succeeded', capture.message
end
def test_failed_authorize
assert response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
- assert_equal "Invalid Brand.", response.message
- assert_equal "GW00160", response.params["error_code_tag"]
+ assert_equal 'Invalid Brand.', response.message
+ assert_equal 'GW00160', response.params['error_code_tag']
end
def test_successful_refund
@@ -58,7 +57,7 @@ def test_successful_refund
assert refund = @gateway.refund(@amount, response.authorization)
assert_success refund
- assert_equal "Succeeded", refund.message
+ assert_equal 'Succeeded', refund.message
end
def test_passing_billing_address
@@ -68,11 +67,11 @@ def test_passing_billing_address
def test_invalid_login
gateway = HdfcGateway.new(
- :login => "",
- :password => ""
+ :login => '',
+ :password => ''
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "TranPortal ID required.", response.message
+ assert_equal 'TranPortal ID required.', response.message
end
end
diff --git a/test/remote/gateways/remote_hps_test.rb b/test/remote/gateways/remote_hps_test.rb
new file mode 100644
index 00000000000..832d73703a9
--- /dev/null
+++ b/test/remote/gateways/remote_hps_test.rb
@@ -0,0 +1,412 @@
+require 'test_helper'
+
+class RemoteHpsTest < Test::Unit::TestCase
+ def setup
+ @gateway = HpsGateway.new(fixtures(:hps))
+
+ @amount = 100
+ @declined_amount = 1034
+ @credit_card = credit_card('4000100011112224')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_without_cardholder
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_details
+ @options[:description] = 'Description'
+ @options[:order_id] = '12345'
+ @options[:customer_id] = '654321'
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_descriptor
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(descriptor_name: 'Location Name'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_no_address
+ options = {
+ order_id: '1',
+ description: 'Store Purchase'
+ }
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'The card was declined.', response.message
+ end
+
+ def test_successful_authorize_with_details
+ @options[:description] = 'Description'
+ @options[:order_id] = '12345'
+ @options[:customer_id] = '654321'
+
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_authorize_no_address
+ options = {
+ order_id: '1',
+ description: 'Store Authorize'
+ }
+ response = @gateway.authorize(@amount, @credit_card, options)
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@declined_amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Success', refund.params['GatewayRspMsg']
+ assert_equal '0', refund.params['GatewayRspCode']
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ assert_equal 'Success', refund.params['GatewayRspMsg']
+ assert_equal '0', refund.params['GatewayRspCode']
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Success', void.params['GatewayRspMsg']
+ end
+
+ def test_failed_void
+ response = @gateway.void('123')
+ assert_failure response
+ assert_match %r{rejected}i, response.message
+ end
+
+ def test_empty_login
+ gateway = HpsGateway.new(secret_api_key: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication error. Please double check your service configuration.', response.message
+ end
+
+ def test_nil_login
+ gateway = HpsGateway.new(secret_api_key: nil)
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication error. Please double check your service configuration.', response.message
+ end
+
+ def test_invalid_login
+ gateway = HpsGateway.new(secret_api_key: 'Bad_API_KEY')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication error. Please double check your service configuration.', response.message
+ end
+
+ def test_successful_get_token_from_auth
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(store: true))
+
+ assert_success response
+ assert_equal 'Visa', response.params['CardType']
+ assert_equal 'Success', response.params['TokenRspMsg']
+ assert_not_nil response.params['TokenValue']
+ end
+
+ def test_successful_get_token_from_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(store: true))
+
+ assert_success response
+ assert_equal 'Visa', response.params['CardType']
+ assert_equal 'Success', response.params['TokenRspMsg']
+ assert_not_nil response.params['TokenValue']
+ end
+
+ def test_successful_purchase_with_token_from_auth
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(store: true))
+
+ assert_success response
+ assert_equal 'Visa', response.params['CardType']
+ assert_equal 'Success', response.params['TokenRspMsg']
+ assert_not_nil response.params['TokenValue']
+ token = response.params['TokenValue']
+
+ purchase = @gateway.purchase(@amount, token, @options)
+ assert_success purchase
+ assert_equal 'Success', purchase.message
+ end
+
+ def test_successful_purchase_with_swipe_no_encryption
+ @credit_card.track_data = '%B547888879888877776?;5473500000000014=25121019999888877776?'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase_with_swipe_bad_track_data
+ @credit_card.track_data = '%B547888879888877776?;?'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_failure response
+ assert_equal 'Transaction was rejected because the track data could not be read.', response.message
+ end
+
+ def test_successful_purchase_with_swipe_encryption_type_01
+ @options[:encryption_type] = '01'
+ @credit_card.track_data = '<E1052711%B5473501000000014^MC TEST CARD^251200000000000000000000000000000000?|GVEY/MKaKXuqqjKRRueIdCHPPoj1gMccgNOtHC41ymz7bIvyJJVdD3LW8BbwvwoenI+|+++++++C4cI2zjMp|11;5473501000000014=25120000000000000000?|8XqYkQGMdGeiIsgM0pzdCbEGUDP|+++++++C4cI2zjMp|00|||/wECAQECAoFGAgEH2wYcShV78RZwb3NAc2VjdXJlZXhjaGFuZ2UubmV0PX50qfj4dt0lu9oFBESQQNkpoxEVpCW3ZKmoIV3T93zphPS3XKP4+DiVlM8VIOOmAuRrpzxNi0TN/DWXWSjUC8m/PI2dACGdl/hVJ/imfqIs68wYDnp8j0ZfgvM26MlnDbTVRrSx68Nzj2QAgpBCHcaBb/FZm9T7pfMr2Mlh2YcAt6gGG1i2bJgiEJn8IiSDX5M2ybzqRT86PCbKle/XCTwFFe1X|>'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_swipe_encryption_type_02
+ @options[:encryption_type] = '02'
+ @options[:encrypted_track_number] = 2
+ @options[:ktb] = '/wECAQECAoFGAgEH3QgVTDT6jRZwb3NAc2VjdXJlZXhjaGFuZ2UubmV0Nkt08KRSPigRYcr1HVgjRFEvtUBy+VcCKlOGA3871r3SOkqDvH2+30insdLHmhTLCc4sC2IhlobvWnutAfylKk2GLspH/pfEnVKPvBv0hBnF4413+QIRlAuGX6+qZjna2aMl0kIsjEY4N6qoVq2j5/e5I+41+a2pbm61blv2PEMAmyuCcAbN3/At/1kRZNwN6LSUg9VmJO83kOglWBe1CbdFtncq'
+ @credit_card.track_data = '7SV2BK6ESQPrq01iig27E74SxMg'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def tests_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def tests_failed_verify
+ @credit_card.number = 12345
+
+ response = @gateway.verify(@credit_card, @options)
+
+ assert_failure response
+ assert_equal 'The card number is not a valid credit card number.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:secret_api_key], transcript)
+ end
+
+ def test_transcript_scrubbing_with_cryptogram
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :apple_pay
+ )
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(@gateway.options[:secret_api_key], transcript)
+ assert_scrubbed(credit_card.payment_cryptogram, transcript)
+ end
+
+ def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :apple_pay
+ )
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ source: :apple_pay
+ )
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :apple_pay
+ )
+ assert response = @gateway.authorize(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ source: :apple_pay
+ )
+ assert response = @gateway.authorize(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_three_d_secure_visa
+ @credit_card.number = '4012002000060016'
+ @credit_card.brand = 'visa'
+
+ options = {
+ :three_d_secure => {
+ :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :eci => '05',
+ :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_three_d_secure_mastercard
+ @credit_card.number = '5473500000000014'
+ @credit_card.brand = 'master'
+
+ options = {
+ :three_d_secure => {
+ :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :eci => '05',
+ :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_three_d_secure_discover
+ @credit_card.number = '6011000990156527'
+ @credit_card.brand = 'discover'
+
+ options = {
+ :three_d_secure => {
+ :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :eci => '05',
+ :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_three_d_secure_amex
+ @credit_card.number = '372700699251018'
+ @credit_card.brand = 'american_express'
+
+ options = {
+ :three_d_secure => {
+ :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :eci => '05',
+ :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_three_d_secure_jcb
+ @credit_card.number = '372700699251018'
+ @credit_card.brand = 'jcb'
+
+ options = {
+ :three_d_secure => {
+ :cavv => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :eci => '05',
+ :xid => 'TTBCSkVTa1ZpbDI1bjRxbGk5ODE='
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_iats_payments_test.rb b/test/remote/gateways/remote_iats_payments_test.rb
index 28842f46d27..34d604b7b92 100644
--- a/test/remote/gateways/remote_iats_payments_test.rb
+++ b/test/remote/gateways/remote_iats_payments_test.rb
@@ -6,8 +6,8 @@ def setup
@gateway = IatsPaymentsGateway.new(fixtures(:iats_payments))
@amount = 100
- @credit_card = credit_card('4111111111111111')
- @check = check
+ @credit_card = credit_card('4222222222222220')
+ @check = check(routing_number: '111111111', account_number: '12345678')
@options = {
:order_id => generate_unique_id,
:billing_address => address,
@@ -19,37 +19,122 @@ def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert response.test?
- assert_equal 'This transaction has been approved', response.message
+ assert_equal 'Success', response.message
assert response.authorization
end
def test_failed_purchase
- assert response = @gateway.purchase(200, @credit_card, @options)
+ credit_card = credit_card('4111111111111111')
+ assert response = @gateway.purchase(200, credit_card, @options)
assert_failure response
assert response.test?
- assert_equal 'This transaction has been declined', response.message
+ assert response.message.include?('REJECT')
end
- def test_bad_login
+ def test_successful_check_purchase
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Success', response.message
+ assert response.authorization
+ end
+
+ # Not possible to test failure case since tx failure is delayed from time of
+ # submission w/ ACH, even for test txs.
+ # def test_failed_check_purchase
+ # response = @gateway.purchase(125, @check, @options)
+ # assert_failure response
+ # assert response.test?
+ # assert_nil response.authorization
+ # end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ credit_card = credit_card('4111111111111111')
+ purchase = @gateway.purchase(@amount, credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_failure refund
+ end
+
+ def test_successful_check_refund
+ purchase = @gateway.purchase(@amount, @check, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+
+ # This is a dubious test. Basically testing that the refund failed b/c
+ # the original purchase hadn't yet cleared. No way to test immediate failure
+ # due to the delay in original tx processing, even for text txs.
+ assert_failure refund
+ assert_equal 'REJECT: 3', refund.message
+ end
+
+ def test_failed_check_refund
+ assert refund = @gateway.refund(@amount, 'invalidref')
+ assert_failure refund
+ assert_equal 'REJECT: 39', refund.message
+ end
+
+ def test_successful_store_and_unstore
+ assert store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert store.authorization
+ assert_equal 'Success', store.message
+
+ assert unstore = @gateway.unstore(store.authorization, @options)
+ assert_success unstore
+ assert_equal 'Success', unstore.message
+ end
+
+ def test_failed_store
+ credit_card = credit_card('4111')
+ assert store = @gateway.store(credit_card, @options)
+ assert_failure store
+ assert_match(/Invalid credit card number/, store.message)
+ end
+
+ def test_invalid_login
gateway = IatsPaymentsGateway.new(
- :login => 'X',
- :password => 'Y'
+ :agent_code => 'X',
+ :password => 'Y',
+ :region => 'na'
)
assert response = gateway.purchase(@amount, @credit_card)
+ assert_failure response
+ end
- assert_equal Response, response.class
- assert_equal ["action",
- "authorization_code",
- "avs_result_code",
- "card_code",
- "response_code",
- "response_reason_code",
- "response_reason_text",
- "transaction_id"], response.params.keys.sort
+ def test_purchase_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
- assert_match(/The merchant API Login ID is invalid or the account is inactive/, response.message)
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:agent_code], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_check_purchase_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
- assert_equal false, response.success?
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@gateway.options[:agent_code], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
end
+
end
diff --git a/test/remote/gateways/remote_ideal_rabobank_test.rb b/test/remote/gateways/remote_ideal_rabobank_test.rb
deleted file mode 100644
index 4afa4dd084b..00000000000
--- a/test/remote/gateways/remote_ideal_rabobank_test.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-require 'test_helper'
-
-class RemoteIdealRabobankTest < Test::Unit::TestCase
- def setup
- Base.gateway_mode = :test
-
- @gateway = IdealRabobankGateway.new(fixtures(:ideal_rabobank))
-
- @options = {
- :issuer_id => '0151',
- :return_url => 'http://www.return.url',
- :order_id => '1234567890123456',
- :currency => 'EUR',
- :description => 'A description',
- :entrance_code => '1234'
- }
- end
-
- def test_issuers
- response = @gateway.issuers
- list = response.issuer_list
-
- assert_equal 3, list.length
- assert_equal 'Test Issuer', list[0]['issuerName']
- assert_equal '0121', list[0]['issuerID']
- assert_equal 'Short', list[0]['issuerList']
- end
-
-
- def test_set_purchase
- response = @gateway.setup_purchase(550, @options)
-
- assert_success response
- assert response.test?
- assert_nil response.error, "Response should not have an error"
- end
-
- def test_return_errors
- response = @gateway.setup_purchase(0.5, @options)
- assert_failure response
- assert_equal 'BR1210', response.error[ 'errorCode']
- assert_not_nil response.error['errorMessage'], "Response should contain an Error message"
- assert_not_nil response.error['errorDetail'], "Response should contain an Error Detail message"
- assert_not_nil response.error['consumerMessage'],"Response should contain an Consumer Error message"
- end
-
- # default payment should succeed
- def test_purchase_successful
- response = @gateway.setup_purchase(100, @options)
-
- assert_success response
-
- assert_equal '1234567890123456', response.transaction['purchaseID']
- assert_equal '0020', response.params['AcquirerTrxRes']['Acquirer']['acquirerID']
- assert_not_nil response.service_url, "Response should contain a service url for payment"
-
- # now authorize the payment, issuer simulator has completed the payment
- response = @gateway.capture(response.transaction['transactionID'])
-
- assert_success response
- assert_equal 'Success', response.transaction['status']
- assert_equal 'DEN HAAG', response.transaction['consumerCity']
- assert_equal "Hr J A T Verf\303\274rth en/of Mw T V Chet", response.transaction['consumerName']
- end
-
- # payment of 200 should cancel
- def test_purchase_cancel
- response = @gateway.setup_purchase(200, @options)
-
- assert_success response
- # now try to authorize the payment, issuer simulator has cancelled the payment
- response = @gateway.capture(response.transaction['transactionID'])
-
- assert_failure response
- assert_equal 'Cancelled', response.transaction['status'], 'Transaction should cancel'
- end
-
- # payment of 300 should expire
- def test_transaction_expired
- response = @gateway.setup_purchase(300, @options)
-
- # now try to authorize the payment, issuer simulator let the payment expire
- response = @gateway.capture(response.transaction['transactionID'])
-
- assert_failure response
- assert_equal 'Expired', response.transaction['status'], 'Transaction should expire'
- end
-
- # payment of 400 should remain open
- def test_transaction_opened
- response = @gateway.setup_purchase(400, @options)
-
- # now try to authorize the payment, issuer simulator keeps the payment open
- response = @gateway.capture(response.transaction['transactionID'])
-
- assert_failure response
- assert_equal 'Open', response.transaction['status'], 'Transaction should remain open'
- end
-
- # payment of 500 should fail at issuer
- def test_transaction_failed
- response = @gateway.setup_purchase(500, @options)
-
- # now try to authorize the payment, issuer simulator lets the payment fail
- response = @gateway.capture(response.transaction['transactionID'])
- assert_failure response
- assert_equal 'Failure', response.transaction['status'], 'Transaction should fail'
- end
-
- # payment of 700 should be unknown at issuer
- def test_transaction_unknown
- response = @gateway.setup_purchase(700, @options)
-
- # now try to authorize the payment, issuer simulator lets the payment fail
- response = @gateway.capture(response.transaction['transactionID'])
-
- assert_failure response
- assert_equal 'SO1000', response.error[ 'errorCode'], 'ErrorCode should be correct'
- end
-
-end
diff --git a/test/remote/gateways/remote_inspire_test.rb b/test/remote/gateways/remote_inspire_test.rb
index 678f1118780..4e137b4d3ff 100644
--- a/test/remote/gateways/remote_inspire_test.rb
+++ b/test/remote/gateways/remote_inspire_test.rb
@@ -1,30 +1,23 @@
-require File.dirname(__FILE__) + '/../../test_helper'
+require 'test_helper'
class RemoteBraintreeTest < Test::Unit::TestCase
def setup
@gateway = InspireGateway.new(fixtures(:inspire))
- @amount = rand(10000) + 1001
+ @amount = rand(1001..11000)
@credit_card = credit_card('4111111111111111', :brand => 'visa')
@declined_amount = rand(99)
@options = { :order_id => generate_unique_id,
:billing_address => address
}
end
-
+
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal 'This transaction has been approved', response.message
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- end
-
- def test_successful_purchase_with_old_naming
- gateway = InspireGateway.new(fixtures(:inspire))
- assert response = gateway.purchase(@amount, @credit_card, @options)
assert_equal 'This transaction has been approved', response.message
- assert_success response
end
-
+
def test_successful_purchase_with_echeck
check = ActiveMerchant::Billing::Check.new(
:name => 'Fredd Bloggs',
@@ -33,106 +26,127 @@ def test_successful_purchase_with_echeck
:account_holder_type => 'personal',
:account_type => 'checking'
)
- assert response = @gateway.purchase(@amount, check, @options)
- assert_equal 'This transaction has been approved', response.message
+ response = @gateway.purchase(@amount, check, @options)
assert_success response
+ assert_equal 'This transaction has been approved', response.message
end
-
+
def test_successful_add_to_vault
@options[:store] = true
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal 'This transaction has been approved', response.message
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_not_nil response.params["customer_vault_id"]
+ assert_equal 'This transaction has been approved', response.message
end
def test_successful_add_to_vault_with_store_method
- assert response = @gateway.store(@credit_card)
- assert_equal 'This transaction has been approved', response.message
+ response = @gateway.store(@credit_card)
assert_success response
- assert_not_nil response.params["customer_vault_id"]
+ assert_equal 'This transaction has been approved', response.message
end
def test_successful_add_to_vault_and_use
@options[:store] = true
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal 'This transaction has been approved', response.message
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_not_nil customer_id = response.params["customer_vault_id"]
-
- assert second_response = @gateway.purchase(@amount*2, customer_id, @options)
+ assert_equal 'This transaction has been approved', response.message
+ assert_not_nil customer_id = response.params['customer_vault_id']
+
+ second_response = @gateway.purchase(@amount*2, customer_id, @options)
assert_equal 'This transaction has been approved', second_response.message
- assert second_response.success?
+ assert_success second_response
end
-
+
def test_add_to_vault_with_custom_vault_id
- @options[:store] = rand(100000)+10001
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal 'This transaction has been approved', response.message
+ @options[:store] = rand(10001..110000)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal @options[:store], response.params["customer_vault_id"].to_i
+ assert_equal 'This transaction has been approved', response.message
+ assert_equal @options[:store], response.params['customer_vault_id'].to_i
end
-
+
def test_add_to_vault_with_custom_vault_id_with_store_method
- @options[:billing_id] = rand(100000)+10001
- assert response = @gateway.store(@credit_card, @options.dup)
- assert_equal 'This transaction has been approved', response.message
+ @options[:billing_id] = rand(10001..110000)
+ response = @gateway.store(@credit_card, @options.dup)
assert_success response
- assert_equal @options[:billing_id], response.params["customer_vault_id"].to_i
+ assert_equal 'This transaction has been approved', response.message
+ assert_equal @options[:billing_id], response.params['customer_vault_id'].to_i
end
-
+
def test_update_vault
test_add_to_vault_with_custom_vault_id
@credit_card = credit_card('4111111111111111', :month => 10)
- assert response = @gateway.update(@options[:store], @credit_card)
+ response = @gateway.update(@options[:store], @credit_card)
assert_success response
assert_equal 'Customer Update Successful', response.message
end
-
+
def test_delete_from_vault
test_add_to_vault_with_custom_vault_id
- assert response = @gateway.delete(@options[:store])
+ response = @gateway.delete(@options[:store])
assert_success response
assert_equal 'Customer Deleted', response.message
end
def test_delete_from_vault_with_unstore_method
test_add_to_vault_with_custom_vault_id
- assert response = @gateway.unstore(@options[:store])
+ response = @gateway.unstore(@options[:store])
assert_success response
assert_equal 'Customer Deleted', response.message
end
def test_declined_purchase
- assert response = @gateway.purchase(@declined_amount, @credit_card, @options)
- assert_equal 'This transaction has been declined', response.message
+ response = @gateway.purchase(@declined_amount, @credit_card, @options)
assert_failure response
+ assert_equal 'This transaction has been declined', response.message
end
def test_authorize_and_capture
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal 'This transaction has been approved', auth.message
assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization)
- assert_equal 'This transaction has been approved', capture.message
+
+ capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
+ assert_equal 'This transaction has been approved', capture.message
end
-
+
def test_authorize_and_void
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal 'This transaction has been approved', auth.message
assert auth.authorization
- assert void = @gateway.void(auth.authorization)
- assert_equal 'Transaction Void Successful', void.message
+
+ void = @gateway.void(auth.authorization)
assert_success void
+ assert_equal 'Transaction Void Successful', void.message
end
def test_failed_capture
- assert response = @gateway.capture(@amount, '')
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_match %r{Invalid Transaction ID \/ Object ID specified:}, response.message
+ end
+
+ def test_refund
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+
+ response = @gateway.refund(nil, response.authorization)
+ assert_success response
+ end
+
+ def test_partial_refund
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+
+ response = @gateway.refund(@amount-500, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, 'bogus')
assert_failure response
- assert response.message.match(/Invalid Transaction ID \/ Object ID specified:/)
end
def test_invalid_login
@@ -140,10 +154,8 @@ def test_invalid_login
:login => '',
:password => ''
)
- assert response = gateway.purchase(@amount, @credit_card, @options)
+ response = gateway.purchase(@amount, @credit_card, @options)
assert_equal 'Invalid Username', response.message
assert_failure response
end
end
-
-
diff --git a/test/remote/gateways/remote_instapay_test.rb b/test/remote/gateways/remote_instapay_test.rb
old mode 100755
new mode 100644
index e43ad00d8ec..5a8286cf234
--- a/test/remote/gateways/remote_instapay_test.rb
+++ b/test/remote/gateways/remote_instapay_test.rb
@@ -1,7 +1,7 @@
require 'test_helper'
class RemoteInstapayTest < Test::Unit::TestCase
-
+
def setup
@gateway = InstapayGateway.new(fixtures(:instapay))
@@ -38,24 +38,24 @@ def test_failed_authorization
assert response = @gateway.authorize(@amount, @declined_card, @options)
assert_failure response
end
-
+
def test_authorization_and_capture
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
-
+
assert capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
assert_equal InstapayGateway::SUCCESS_MESSAGE, capture.message
end
-
+
def test_invalid_login
gateway = InstapayGateway.new(
:login => 'X',
:password => 'Y'
)
-
+
assert response = gateway.purchase(@amount, @credit_card)
assert_failure response
- assert_equal "Invalid merchant", response.message
+ assert_equal 'Invalid merchant', response.message
end
end
diff --git a/test/remote/gateways/remote_ipp_test.rb b/test/remote/gateways/remote_ipp_test.rb
new file mode 100644
index 00000000000..f7e62cad46b
--- /dev/null
+++ b/test/remote/gateways/remote_ipp_test.rb
@@ -0,0 +1,84 @@
+require 'test_helper'
+
+class RemoteIppTest < Test::Unit::TestCase
+ def setup
+ @gateway = IppGateway.new(fixtures(:ipp))
+
+ @credit_card = credit_card('4005550000000001')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase',
+ }
+ end
+
+ def test_dump_transcript
+ skip('Transcript scrubbing for this gateway has been tested.')
+ dump_transcript_and_fail(@gateway, @amount, @credit_card, @options)
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(200, @credit_card, @options)
+ assert_success response
+ assert_equal '', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(105, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Do Not Honour', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(200, @credit_card, @options)
+ assert_success response
+ response = @gateway.capture(200, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(105, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(200, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(200, @credit_card, @options)
+ response = @gateway.refund(200, response.authorization, @options)
+ assert_success response
+ assert_equal '', response.message
+ end
+
+ def test_failed_refund
+ response = @gateway.purchase(200, @credit_card, @options)
+ response = @gateway.refund(105, response.authorization, @options)
+ assert_failure response
+ assert_equal 'Do Not Honour', response.message
+ end
+
+ def test_invalid_login
+ gateway = IppGateway.new(
+ username: '',
+ password: ''
+ )
+ response = gateway.purchase(200, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_iridium_test.rb b/test/remote/gateways/remote_iridium_test.rb
index 793613ded61..dc033cabe3a 100644
--- a/test/remote/gateways/remote_iridium_test.rb
+++ b/test/remote/gateways/remote_iridium_test.rb
@@ -7,14 +7,17 @@ def setup
@gateway = IridiumGateway.new(fixtures(:iridium))
@amount = 100
+ @avs_card = credit_card('4921810000005462', {:verification_value => '441'})
+ @cv2_card = credit_card('4976000000003436', {:verification_value => '777'})
+ @avs_cv2_card = credit_card('4921810000005462', {:verification_value => '777'})
@credit_card = credit_card('4976000000003436', {:verification_value => '452'})
@declined_card = credit_card('4221690000004963')
- our_address = address(:address1 => "32 Edward Street",
- :address2 => "Camborne",
- :state => "Cornwall",
- :zip => "TR14 8PA",
- :country => "826")
+ our_address = address(:address1 => '32 Edward Street',
+ :address2 => 'Camborne',
+ :state => 'Cornwall',
+ :zip => 'TR14 8PA',
+ :country => '826')
@options = {
:order_id => generate_unique_id,
:billing_address => our_address,
@@ -32,7 +35,28 @@ def test_successful_purchase
def test_failed_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Card declined', response.message
+ assert_match %r{Card declined}i, response.message
+ end
+
+ def test_avs_failure
+ assert response = @gateway.purchase(@amount, @avs_card, @options)
+ assert_failure response
+ assert_equal response.avs_result['street_match'], 'N'
+ assert_equal response.avs_result['postal_match'], 'N'
+ end
+
+ def test_cv2_failure
+ assert response = @gateway.purchase(@amount, @cv2_card, @options)
+ assert_failure response
+ assert_equal response.cvv_result['code'], 'N'
+ end
+
+ def test_avs_cv2_failure
+ assert response = @gateway.purchase(@amount, @avs_cv2_card, @options)
+ assert_failure response
+ assert_equal response.avs_result['street_match'], 'N'
+ assert_equal response.avs_result['postal_match'], 'N'
+ assert_equal response.cvv_result['code'], 'N'
end
def test_authorize_and_capture
@@ -62,7 +86,7 @@ def test_successful_authorization
def test_failed_authorization
assert response = @gateway.authorize(@amount, @declined_card, @options)
assert response.test?
- assert_equal 'Card declined', response.message
+ assert_match %r{Card declined}i, response.message
assert_equal false, response.success?
end
@@ -77,8 +101,8 @@ def test_successful_authorization_and_failed_capture
end
def test_failed_capture_bad_auth_info
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
- assert capture = @gateway.capture(@amount, "a;b;c", @options)
+ assert @gateway.authorize(@amount, @credit_card, @options)
+ assert capture = @gateway.capture(@amount, 'a;b;c', @options)
assert_failure capture
end
@@ -94,7 +118,7 @@ def test_successful_purchase_by_reference
def test_failed_purchase_by_reference
assert response = @gateway.authorize(1, @credit_card, @options)
assert_success response
- assert(reference = response.authorization)
+ assert response.authorization
assert response = @gateway.purchase(@amount, 'bogusref', {:order_id => generate_unique_id})
assert_failure response
@@ -134,7 +158,7 @@ def test_successful_void
end
def test_failed_void
- assert response = @gateway.void("bogus")
+ assert response = @gateway.void('bogus')
assert_failure response
end
diff --git a/test/remote/gateways/remote_itransact_test.rb b/test/remote/gateways/remote_itransact_test.rb
index 3ec0172e6b8..d949b17232d 100644
--- a/test/remote/gateways/remote_itransact_test.rb
+++ b/test/remote/gateways/remote_itransact_test.rb
@@ -1,36 +1,35 @@
require 'test_helper'
class RemoteItransactTest < Test::Unit::TestCase
-
def setup
@gateway = ItransactGateway.new(fixtures(:itransact))
-
+
@amount = 1065
@credit_card = credit_card('4000100011112224')
@declined_card = credit_card('4000300011112220')
-
- @options = {
+
+ @options = {
:order_id => '1',
:billing_address => address,
:description => 'Store Purchase'
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_nil response.message
end
-# As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a
-# production gateway account in test mode.
-# def test_unsuccessful_purchase
-# assert response = @gateway.purchase(@amount, @credit_card, @options)
-# assert_failure response
-# assert_equal 'DECLINE', response.params['error_category']
-# assert_equal 'Code: NBE001 Your credit card was declined by the credit card processing network. Please use another card and resubmit your transaction.', response.message
-# end
+ # As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a
+ # production gateway account in test mode.
+ # def test_unsuccessful_purchase
+ # assert response = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_failure response
+ # assert_equal 'DECLINE', response.params['error_category']
+ # assert_equal 'Code: NBE001 Your credit card was declined by the credit card processing network. Please use another card and resubmit your transaction.', response.message
+ # end
def test_authorize_and_capture
amount = @amount
@@ -42,13 +41,13 @@ def test_authorize_and_capture
assert_success capture
end
-# As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a
-# production gateway account in test mode.
-# def test_failed_capture
-# assert response = @gateway.capture(@amount, '9999999999')
-# assert_failure response
-# assert_equal 'REPLACE WITH GATEWAY FAILURE MESSAGE', response.message
-# end
+ # As of March 8, 2012, iTransact does not provide a way to generate unsuccessful transactions through use of a
+ # production gateway account in test mode.
+ # def test_failed_capture
+ # assert response = @gateway.capture(@amount, '9999999999')
+ # assert_failure response
+ # assert_equal 'REPLACE WITH GATEWAY FAILURE MESSAGE', response.message
+ # end
def test_authorize_and_void
amount = @amount
@@ -65,11 +64,11 @@ def test_void
assert_success void
end
-# As of Sep 19, 2012, iTransact REQUIRES the total amount for the refund.
-# def test_refund
-# assert refund = @gateway.refund(nil, '9999999999')
-# assert_success refund
-# end
+ # As of Sep 19, 2012, iTransact REQUIRES the total amount for the refund.
+ # def test_refund
+ # assert refund = @gateway.refund(nil, '9999999999')
+ # assert_success refund
+ # end
def test_refund_partial
assert refund = @gateway.refund(555, '9999999999') # $5.55 in cents
diff --git a/test/remote/gateways/remote_iveri_test.rb b/test/remote/gateways/remote_iveri_test.rb
new file mode 100644
index 00000000000..16733284a46
--- /dev/null
+++ b/test/remote/gateways/remote_iveri_test.rb
@@ -0,0 +1,164 @@
+require 'test_helper'
+
+class RemoteIveriTest < Test::Unit::TestCase
+ def setup
+ @gateway = IveriGateway.new(fixtures(:iveri))
+
+ @amount = 100
+ @credit_card = credit_card('4242424242424242')
+ @bad_card = credit_card('2121212121212121')
+ @timeout_card = credit_card('5454545454545454')
+ @invalid_card = credit_card('1111222233334444')
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_equal '100', response.params['amount']
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ currency: 'ZAR'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_3ds_params
+ options = {
+ eci: 'ThreeDSecure',
+ xid: SecureRandom.hex(14),
+ cavv: SecureRandom.hex(14)
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @bad_card, @options)
+ assert_failure response
+ assert_includes ['Denied', 'Hot card', 'Please call'], response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @bad_card, @options)
+ assert_failure response
+ assert_includes ['Denied', 'Hot card', 'Please call'], response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Missing PAN', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '', @options)
+ assert_failure response
+ assert_match %r{Credit is not supported}, response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Missing OriginalMerchantTrace', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@bad_card, @options)
+ assert_failure response
+ assert_includes ['Denied', 'Hot card', 'Please call'], response.message
+ end
+
+ def test_successful_verify_credentials
+ assert @gateway.verify_credentials
+ end
+
+ def test_failed_verify_credentials
+ gateway = IveriGateway.new(app_id: '11111111-1111-1111-1111-111111111111', cert_id: '11111111-1111-1111-1111-111111111111')
+ assert !gateway.verify_credentials
+ end
+
+ def test_invalid_login
+ gateway = IveriGateway.new(app_id: '', cert_id: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'No CertificateID specified', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:cert_id], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_jetpay_test.rb b/test/remote/gateways/remote_jetpay_test.rb
index 3ba2729f3a8..d5d331a72f1 100644
--- a/test/remote/gateways/remote_jetpay_test.rb
+++ b/test/remote/gateways/remote_jetpay_test.rb
@@ -4,12 +4,11 @@ class RemoteJetpayTest < Test::Unit::TestCase
def setup
@gateway = JetpayGateway.new(fixtures(:jetpay))
-
+
@credit_card = credit_card('4000300020001000')
@declined_card = credit_card('4000300020001000')
-
+
@options = {
- :order_id => '1',
:billing_address => address(:country => 'US', :zip => '75008'),
:shipping_address => address(:country => 'US'),
:email => 'test@test.com',
@@ -18,73 +17,141 @@ def setup
:tax => 7
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(9900, @credit_card, @options)
assert_success response
- assert_equal "APPROVED", response.message
+ assert_equal 'APPROVED', response.message
assert_not_nil response.authorization
- assert_not_nil response.params["approval"]
+ assert_not_nil response.params['approval']
end
-
+
def test_unsuccessful_purchase
assert response = @gateway.purchase(5205, @declined_card, @options)
assert_failure response
- assert_equal "Do not honor.", response.message
+ assert_equal 'Do not honor.', response.message
+ end
+
+ def test_successful_purchase_with_origin
+ assert response = @gateway.purchase(9900, @credit_card, {:origin => 'RECURRING'})
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_not_nil response.authorization
+ assert_not_nil response.params['approval']
end
-
+
def test_authorize_and_capture
assert auth = @gateway.authorize(9900, @credit_card, @options)
assert_success auth
assert_equal 'APPROVED', auth.message
assert_not_nil auth.authorization
- assert_not_nil auth.params["approval"]
-
+ assert_not_nil auth.params['approval']
+
assert capture = @gateway.capture(9900, auth.authorization)
assert_success capture
end
-
+
+ def test_partial_capture
+ assert auth = @gateway.authorize(9900, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+ assert_not_nil auth.authorization
+ assert_not_nil auth.params['approval']
+
+ assert capture = @gateway.capture(4400, auth.authorization)
+ assert_success capture
+ end
+
+ def test_ud_fields_on_purchase
+ assert response = @gateway.purchase(9900, @credit_card, @options.merge(ud_field_1: 'Value1', ud_field_2: 'Value2', ud_field3: 'Value3'))
+ assert_success response
+ end
+
+ def test_ud_fields_on_capture
+ assert auth = @gateway.authorize(9900, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(9900, auth.authorization, @options.merge(ud_field_1: 'Value1', ud_field_2: 'Value2', ud_field3: 'Value3'))
+ assert_success capture
+ end
+
def test_void
# must void a valid auth
assert auth = @gateway.authorize(9900, @credit_card, @options)
assert_success auth
assert_equal 'APPROVED', auth.message
assert_not_nil auth.authorization
- assert_not_nil auth.params["approval"]
-
+ assert_not_nil auth.params['approval']
+
assert void = @gateway.void(auth.authorization)
assert_success void
end
-
- def test_refund
+
+ def test_purchase_refund_with_token
+ assert response = @gateway.purchase(9900, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_not_nil response.authorization
+ assert_not_nil response.params['approval']
+
+ # linked to a specific transaction_id
+ assert credit = @gateway.refund(9900, response.authorization)
+ assert_success credit
+ assert_not_nil(credit.authorization)
+ assert_not_nil(response.params['approval'])
+ assert_equal [response.params['transaction_id'], response.params['approval'], 9900, response.params['token']].join(';'), response.authorization
+ end
+
+ def test_capture_refund_with_token
+ assert auth = @gateway.authorize(9900, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+ assert_not_nil auth.authorization
+ assert_not_nil auth.params['approval']
+ assert_equal [auth.params['transaction_id'], auth.params['approval'], 9900, auth.params['token']].join(';'), auth.authorization
+
+ assert capture = @gateway.capture(9900, auth.authorization)
+ assert_success capture
+ assert_equal [capture.params['transaction_id'], capture.params['approval'], 9900, auth.params['token']].join(';'), capture.authorization
+
+ # linked to a specific transaction_id
+ assert refund = @gateway.refund(9900, capture.authorization)
+ assert_success refund
+ assert_not_nil(refund.authorization)
+ assert_not_nil(refund.params['approval'])
+ end
+
+ def test_refund_backwards_compatible
# no need for csv
card = credit_card('4242424242424242', :verification_value => nil)
-
+
assert response = @gateway.purchase(9900, card, @options)
assert_success response
- assert_equal "APPROVED", response.message
+ assert_equal 'APPROVED', response.message
assert_not_nil response.authorization
- assert_not_nil response.params["approval"]
-
+ assert_not_nil response.params['approval']
+
+ old_authorization = [response.params['transaction_id'], response.params['approval'], 9900].join(';')
+
# linked to a specific transaction_id
- assert credit = @gateway.refund(9900, response.authorization, :credit_card => card)
+ assert credit = @gateway.refund(9900, old_authorization, :credit_card => card)
assert_success credit
assert_not_nil(credit.authorization)
- assert_not_nil(response.params["approval"])
- assert_equal [response.params['transaction_id'], response.params["approval"], 9900].join(";"), response.authorization
+ assert_not_nil(response.params['approval'])
+ assert_equal [response.params['transaction_id'], response.params['approval'], 9900, response.params['token']].join(';'), response.authorization
end
-
+
def test_credit
# no need for csv
card = credit_card('4242424242424242', :verification_value => nil)
-
+
# no link to a specific transaction_id
assert credit = @gateway.credit(9900, card)
assert_success credit
assert_not_nil(credit.authorization)
- assert_not_nil(credit.params["approval"])
+ assert_not_nil(credit.params['approval'])
end
-
+
def test_failed_capture
assert response = @gateway.capture(9900, '7605f7c5d6e8f74deb')
assert_failure response
@@ -95,15 +162,27 @@ def test_invalid_login
gateway = JetpayGateway.new(:login => 'bogus')
assert response = gateway.purchase(9900, @credit_card, @options)
assert_failure response
-
- assert_equal 'Terminal ID Not Found.', response.message
+
+ assert_equal 'Bad Terminal ID.', response.message
end
-
+
def test_missing_login
gateway = JetpayGateway.new(:login => '')
assert response = gateway.purchase(9900, @credit_card, @options)
assert_failure response
-
+
assert_equal 'No response returned (missing credentials?).', response.message
end
+
+ def test_transcript_scrubbing
+ @amount = 9900
+ @credit_card.verification_value = '421'
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
end
diff --git a/test/remote/gateways/remote_jetpay_v2_certification_test.rb b/test/remote/gateways/remote_jetpay_v2_certification_test.rb
new file mode 100644
index 00000000000..be182a8be37
--- /dev/null
+++ b/test/remote/gateways/remote_jetpay_v2_certification_test.rb
@@ -0,0 +1,348 @@
+require 'test_helper'
+
+class RemoteJetpayV2CertificationTest < Test::Unit::TestCase
+
+ def setup
+ @gateway = JetpayV2Gateway.new(fixtures(:jetpay_v2))
+
+ @unique_id = ''
+
+ @options = {
+ :device => 'spreedly',
+ :application => 'spreedly',
+ :developer_id => 'GenkID',
+ :billing_address => address(:address1 => '1234 Fifth Street', :address2 => '', :city => 'Beaumont', :state => 'TX', :country => 'US', :zip => '77708'),
+ :shipping_address => address(:address1 => '1234 Fifth Street', :address2 => '', :city => 'Beaumont', :state => 'TX', :country => 'US', :zip => '77708'),
+ :email => 'test@test.com',
+ :ip => '127.0.0.1'
+ }
+ end
+
+ def teardown
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+ end
+
+ def test_certification_cnp1_authorize_mastercard
+ @options[:order_id] = 'CNP1'
+ amount = 1000
+ master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '121')
+ assert response = @gateway.authorize(amount, master, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp2_authorize_visa
+ @options[:order_id] = 'CNP2'
+ amount = 1105
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '121')
+ assert response = @gateway.authorize(amount, visa, @options)
+ assert_failure response
+ assert_equal 'Do not honor.', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp3_cnp4_authorize_and_capture_amex
+ @options[:order_id] = 'CNP3'
+ amount = 1200
+ amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1221')
+ assert response = @gateway.authorize(amount, amex, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'CNP4'
+ assert response = @gateway.capture(amount, response.authorization, @options)
+ assert_success response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp5_purchase_discover
+ @options[:order_id] = 'CNP5'
+ amount = 1300
+ discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '121')
+ assert response = @gateway.purchase(amount, discover, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp6_purchase_visa
+ @options[:order_id] = 'CNP6'
+ amount = 1405
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120')
+ assert response = @gateway.purchase(amount, visa, @options)
+ assert_failure response
+ assert_equal 'Do not honor.', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp7_authorize_mastercard
+ @options[:order_id] = 'CNP7'
+ amount = 1500
+ master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '120')
+ assert response = @gateway.authorize(amount, master, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp8_authorize_visa
+ @options[:order_id] = 'CNP8'
+ amount = 1605
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120')
+ assert response = @gateway.authorize(amount, visa, @options)
+ assert_failure response
+ assert_equal 'Do not honor.', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp9_cnp10_authorize_and_capture_amex
+ @options[:order_id] = 'CNP9'
+ amount = 1700
+ amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1220')
+ assert response = @gateway.authorize(amount, amex, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'CNP10'
+ assert response = @gateway.capture(amount, response.authorization, @options)
+ assert_success response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cnp11_purchase_discover
+ @options[:order_id] = 'CNP11'
+ amount = 1800
+ discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '120')
+ assert response = @gateway.purchase(amount, discover, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_rec01_recurring_mastercard
+ @options[:order_id] = 'REC01'
+ @options[:origin] = 'RECURRING'
+ @options[:billing_address] = nil
+ @options[:shipping_address] = nil
+ amount = 2000
+ master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '120')
+ assert response = @gateway.purchase(amount, master, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_rec02_recurring_visa
+ @options[:order_id] = 'REC02'
+ @options[:origin] = 'RECURRING'
+ amount = 2100
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '')
+ assert response = @gateway.purchase(amount, visa, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_rec03_recurring_amex
+ @options[:order_id] = 'REC03'
+ @options[:origin] = 'RECURRING'
+ amount = 2200
+ amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1221')
+ assert response = @gateway.purchase(amount, amex, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_corp07_corp08_authorize_and_capture_discover
+ @options[:order_id] = 'CORP07'
+ amount = 2500
+ discover = credit_card('6011111111111117', :month => 12, :year => 2018, :brand => 'discover', :verification_value => '120')
+ assert response = @gateway.authorize(amount, discover, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'CORP08'
+ assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '200'))
+ assert_success response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_corp09_corp10_authorize_and_capture_visa
+ @options[:order_id] = 'CORP09'
+ amount = 5000
+ visa = credit_card('4111111111111111', :month => 12, :year => 2018, :brand => 'visa', :verification_value => '120')
+ assert response = @gateway.authorize(amount, visa, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'CORP10'
+ assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '0', :tax_exempt => 'true'))
+ assert_success response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_corp11_corp12_authorize_and_capture_mastercard
+ @options[:order_id] = 'CORP11'
+ amount = 7500
+ master = credit_card('5111111111111118', :month => 12, :year => 2018, :brand => 'master', :verification_value => '120')
+ assert response = @gateway.authorize(amount, master, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'CORP12'
+ assert response = @gateway.capture(amount, response.authorization, @options.merge(:tax_amount => '0', :tax_exempt => 'false', :purchase_order => '456456'))
+ assert_success response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cred02_credit_visa
+ @options[:order_id] = 'CRED02'
+ amount = 100
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120')
+ assert response = @gateway.credit(amount, visa, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_cred03_credit_amex
+ @options[:order_id] = 'CRED03'
+ amount = 200
+ amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1220')
+ assert response = @gateway.credit(amount, amex, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_void03_void04_purchase_void_visa
+ @options[:order_id] = 'VOID03'
+ amount = 300
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120')
+ assert response = @gateway.purchase(amount, visa, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'VOID04'
+ transaction_id, approval, _amount, token = response.authorization.split(';')
+ amount = 500
+ authorization = [transaction_id, approval, amount, token].join(';')
+ assert response = @gateway.void(authorization, @options)
+ assert_failure response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_void07_void08_void09_authorize_capture_void_discover
+ @options[:order_id] = 'VOID07'
+ amount = 400
+ discover = credit_card('6011111111111117', :month => 12, :year => 2017, :brand => 'discover', :verification_value => '120')
+ assert response = @gateway.authorize(amount, discover, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'VOID08'
+ amount = 600
+ assert response = @gateway.capture(amount, response.authorization, @options)
+ assert_success response
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'VOID09'
+ assert response = @gateway.void(response.authorization, @options)
+ assert_success response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_void12_void13_credit_void_visa
+ @options[:order_id] = 'VOID12'
+ amount = 800
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '120')
+ assert response = @gateway.credit(amount, visa, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ puts "\n#{@options[:order_id]}: #{@unique_id}"
+
+ @options[:order_id] = 'VOID13'
+ assert response = @gateway.void(response.authorization, @options)
+ assert_success response
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_tok15_tokenize_mastercard
+ @options[:order_id] = 'TOK15'
+ master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '101')
+ assert response = @gateway.store(master, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_equal 'TOKENIZED', response.params['response_text']
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_tok16_authorize_with_token_request_visa
+ @options[:order_id] = 'TOK16'
+ amount = 3100
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '101')
+ assert response = @gateway.authorize(amount, visa, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ _transaction_id, _approval, _amount, token = response.authorization.split(';')
+ assert_equal token, response.params['token']
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_tok17_purchase_with_token_request_amex
+ @options[:order_id] = 'TOK17'
+ amount = 3200
+ amex = credit_card('378282246310005', :month => 12, :year => 2017, :brand => 'american_express', :verification_value => '1001')
+ assert response = @gateway.purchase(amount, amex, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ _transaction_id, _approval, _amount, token = response.authorization.split(';')
+ assert_equal token, response.params['token']
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_tok18_authorize_using_token_mastercard
+ master = credit_card('5111111111111118', :month => 12, :year => 2017, :brand => 'master', :verification_value => '101')
+ assert response = @gateway.store(master, @options)
+ assert_success response
+
+ @options[:order_id] = 'TOK18'
+ amount = 3300
+ assert response = @gateway.authorize(amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+ def test_certification_tok19_purchase_using_token_visa
+ visa = credit_card('4111111111111111', :month => 12, :year => 2017, :brand => 'visa', :verification_value => '101')
+ assert response = @gateway.store(visa, @options)
+ assert_success response
+
+ @options[:order_id] = 'TOK19'
+ amount = 3400
+ assert response = @gateway.purchase(amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ @unique_id = response.params['unique_id']
+ end
+
+end
diff --git a/test/remote/gateways/remote_jetpay_v2_test.rb b/test/remote/gateways/remote_jetpay_v2_test.rb
new file mode 100644
index 00000000000..10f1ea6322f
--- /dev/null
+++ b/test/remote/gateways/remote_jetpay_v2_test.rb
@@ -0,0 +1,205 @@
+require 'test_helper'
+
+class RemoteJetpayV2Test < Test::Unit::TestCase
+
+ def setup
+ @gateway = JetpayV2Gateway.new(fixtures(:jetpay_v2))
+
+ @credit_card = credit_card('4000300020001000')
+
+ @amount_approved = 9900
+ @amount_declined = 5205
+
+ @options = {
+ :device => 'spreedly',
+ :application => 'spreedly',
+ :developer_id => 'GenkID',
+ :billing_address => address(:city => 'Durham', :state => 'NC', :country => 'US', :zip => '27701'),
+ :shipping_address => address(:city => 'Durham', :state => 'NC', :country => 'US', :zip => '27701'),
+ :email => 'test@test.com',
+ :ip => '127.0.0.1',
+ :order_id => '12345'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount_approved, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_not_nil response.authorization
+ assert_not_nil response.params['approval']
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(@amount_declined, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Do not honor.', response.message
+ end
+
+ def test_successful_purchase_with_minimal_options
+ assert response = @gateway.purchase(@amount_approved, @credit_card, {:device => 'spreedly', :application => 'spreedly'})
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_not_nil response.authorization
+ assert_not_nil response.params['approval']
+ end
+
+ def test_successful_purchase_with_additional_options
+ options = @options.merge(
+ ud_field_1: 'Value1',
+ ud_field_2: 'Value2',
+ ud_field_3: 'Value3'
+ )
+ assert response = @gateway.purchase(@amount_approved, @credit_card, options)
+ assert_success response
+ end
+
+ def test_successful_authorize_and_capture
+ assert auth = @gateway.authorize(@amount_approved, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+ assert_not_nil auth.authorization
+ assert_not_nil auth.params['approval']
+
+ assert capture = @gateway.capture(@amount_approved, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_successful_authorize_and_capture_with_tax
+ assert auth = @gateway.authorize(@amount_approved, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+ assert_not_nil auth.authorization
+ assert_not_nil auth.params['approval']
+
+ assert capture = @gateway.capture(@amount_approved, auth.authorization, @options.merge(:tax_amount => '990', :purchase_order => 'ABC12345'))
+ assert_success capture
+ end
+
+ def test_successful_partial_capture
+ assert auth = @gateway.authorize(9900, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+ assert_not_nil auth.authorization
+ assert_not_nil auth.params['approval']
+
+ assert capture = @gateway.capture(4400, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount_approved, '7605f7c5d6e8f74deb', @options)
+ assert_failure response
+ assert_equal 'Transaction Not Found.', response.message
+ end
+
+ def test_successful_void
+ assert auth = @gateway.authorize(@amount_approved, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+ assert_not_nil auth.authorization
+ assert_not_nil auth.params['approval']
+
+ assert void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ end
+
+ def test_failed_void
+ assert void = @gateway.void('bogus', @options)
+ assert_failure void
+ end
+
+ def test_successful_purchase_refund
+ assert response = @gateway.purchase(@amount_approved, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_not_nil response.authorization
+ assert_not_nil response.params['approval']
+
+ assert refund = @gateway.refund(@amount_approved, response.authorization, @options)
+ assert_success refund
+ assert_not_nil(refund.authorization)
+ assert_not_nil(response.params['approval'])
+ assert_equal [response.params['transaction_id'], response.params['approval'], @amount_approved, response.params['token']].join(';'), response.authorization
+ end
+
+ def test_successful_capture_refund
+ assert auth = @gateway.authorize(@amount_approved, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+ assert_not_nil auth.authorization
+ assert_not_nil auth.params['approval']
+ assert_equal [auth.params['transaction_id'], auth.params['approval'], @amount_approved, auth.params['token']].join(';'), auth.authorization
+
+ assert capture = @gateway.capture(@amount_approved, auth.authorization, @options)
+ assert_success capture
+ assert_equal [capture.params['transaction_id'], capture.params['approval'], @amount_approved, auth.params['token']].join(';'), capture.authorization
+
+ assert refund = @gateway.refund(@amount_approved, capture.authorization, @options)
+ assert_success refund
+ assert_not_nil(refund.authorization)
+ assert_not_nil(refund.params['approval'])
+ end
+
+ def test_failed_refund
+ assert refund = @gateway.refund(@amount_approved, 'bogus', @options)
+ assert_failure refund
+ end
+
+ def test_successful_credit
+ card = credit_card('4242424242424242', :verification_value => nil)
+
+ assert credit = @gateway.credit(@amount_approved, card, @options)
+ assert_success credit
+ assert_not_nil(credit.authorization)
+ assert_not_nil(credit.params['approval'])
+ end
+
+ def test_failed_credit
+ card = credit_card('2424242424242424', :verification_value => nil)
+
+ assert credit = @gateway.credit(@amount_approved, card, @options)
+ assert_failure credit
+ assert_match %r{Invalid card format}, credit.message
+ end
+
+ def test_successful_verify
+ assert verify = @gateway.verify(@credit_card, @options)
+ assert_success verify
+ end
+
+ def test_failed_verify
+ card = credit_card('2424242424242424', :verification_value => nil)
+
+ assert verify = @gateway.verify(card, @options)
+ assert_failure verify
+ assert_match %r{Invalid card format}, verify.message
+ end
+
+ def test_invalid_login
+ gateway = JetpayV2Gateway.new(:login => 'bogus')
+ assert response = gateway.purchase(@amount_approved, @credit_card, @options)
+ assert_failure response
+
+ assert_equal 'Bad Terminal ID.', response.message
+ end
+
+ def test_missing_login
+ gateway = JetpayV2Gateway.new(:login => '')
+ assert response = gateway.purchase(@amount_approved, @credit_card, @options)
+ assert_failure response
+
+ assert_equal 'No response returned (missing credentials?).', response.message
+ end
+
+ def test_transcript_scrubbing
+ @credit_card.verification_value = '421'
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount_approved, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_komoju_test.rb b/test/remote/gateways/remote_komoju_test.rb
new file mode 100644
index 00000000000..5214eb01fbf
--- /dev/null
+++ b/test/remote/gateways/remote_komoju_test.rb
@@ -0,0 +1,72 @@
+require 'test_helper'
+require 'securerandom'
+
+class RemoteKomojuTest < Test::Unit::TestCase
+ def setup
+ @gateway = KomojuGateway.new(fixtures(:komoju))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('4123111111111059')
+ @fraudulent_card = credit_card('4123111111111083')
+
+ @options = {
+ :order_id => generate_unique_id,
+ :description => 'Store Purchase',
+ :tax => '10.0',
+ :ip => '192.168.0.1',
+ :email => 'valid@email.com',
+ :browser_language => 'en',
+ :browser_user_agent => 'user_agent'
+ }
+ end
+
+ def test_successful_credit_card_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization.present?
+ assert_equal 'Transaction succeeded', response.message
+ assert_equal 100, response.params['amount']
+ assert_equal '1111', response.params['payment_details']['last_four_digits']
+ assert_equal true, response.params['captured_at'].present?
+ end
+
+ def test_successful_credit_card_purchase_with_minimal_options
+ response = @gateway.purchase(@amount, @credit_card, {})
+ assert_success response
+ assert response.authorization.present?
+ assert_equal 'Transaction succeeded', response.message
+ assert_equal 100, response.params['amount']
+ assert_equal '1111', response.params['payment_details']['last_four_digits']
+ assert_equal true, response.params['captured_at'].present?
+ end
+
+ def test_successful_credit_card_refund
+ purchase_response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase_response
+ assert purchase_response.authorization.present?
+ refund_response = @gateway.refund(@amount, purchase_response.authorization, {})
+ assert_success refund_response
+ assert_equal 'refunded', refund_response.params['status']
+ end
+
+ def test_failed_credit_card_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert response.authorization.blank?
+ assert_equal 'card_declined', response.error_code
+ end
+
+ def test_detected_fraud
+ response = @gateway.purchase(@amount, @fraudulent_card, @options)
+ assert_failure response
+ assert response.authorization.blank?
+ assert_equal 'fraudulent', response.error_code
+ end
+
+ def test_invalid_login
+ gateway = KomojuGateway.new(:login => 'abc')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_kushki_test.rb b/test/remote/gateways/remote_kushki_test.rb
new file mode 100644
index 00000000000..b4eb060de00
--- /dev/null
+++ b/test/remote/gateways/remote_kushki_test.rb
@@ -0,0 +1,107 @@
+require 'test_helper'
+
+class RemoteKushkiTest < Test::Unit::TestCase
+ def setup
+ @gateway = KushkiGateway.new(fixtures(:kushki))
+ @amount = 100
+ @credit_card = credit_card('4000100011112224', verification_value: '777')
+ @declined_card = credit_card('4000300011112220')
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_successful_purchase_with_options
+ options = {
+ currency: 'USD',
+ amount: {
+ subtotal_iva_0: '4.95',
+ subtotal_iva: '10',
+ iva: '1.54',
+ ice: '3.50'
+ }
+ }
+
+ amount = 100 * (
+ options[:amount][:subtotal_iva_0].to_f +
+ options[:amount][:subtotal_iva].to_f +
+ options[:amount][:iva].to_f +
+ options[:amount][:ice].to_f
+ )
+
+ response = @gateway.purchase(amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+$), response.authorization
+ end
+
+ def test_failed_purchase
+ options = {
+ amount: {
+ subtotal_iva: '200'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @declined_card, options)
+ assert_failure response
+ assert_equal 'Monto de la transacción es diferente al monto de la venta inicial', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, nil)
+ assert_failure refund
+ assert_equal 'Missing Authentication Token', refund.message
+ end
+
+ def test_successful_void
+ purchase = @gateway.purchase(@amount, @credit_card)
+ assert_success purchase
+
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('000')
+ assert_failure response
+ assert_equal 'El monto de la transacción es requerido', response.message
+ end
+
+ def test_invalid_login
+ gateway = KushkiGateway.new(public_merchant_id: '', private_merchant_id: '')
+
+ response = gateway.purchase(@amount, @credit_card)
+ assert_failure response
+ assert_match %r{ID de comercio no válido}, response.message
+ end
+
+ def test_transcript_scrubbing
+ assert @gateway.supports_scrubbing?
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:private_merchant_id], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_latitude19_test.rb b/test/remote/gateways/remote_latitude19_test.rb
new file mode 100644
index 00000000000..ac977badebc
--- /dev/null
+++ b/test/remote/gateways/remote_latitude19_test.rb
@@ -0,0 +1,172 @@
+require 'test_helper'
+
+class RemoteLatitude19Test < Test::Unit::TestCase
+ def setup
+ @gateway = Latitude19Gateway.new(fixtures(:latitude19))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224', verification_value: '747')
+ @declined_card = credit_card('0000000000000000')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address
+ }
+ end
+
+ def test_invalid_login
+ gateway = Latitude19Gateway.new(account_number: '', configuration_id: '', secret: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert response.test?
+ end
+
+ # def test_failed_purchase
+ # response = @gateway.purchase(@amount, @declined_card, @options)
+ # assert_failure response
+ # assert_equal "REPLACE WITH FAILED MESSAGE", response.message
+ # end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_match %r(^auth\|\w+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization, @options)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ # def test_failed_authorize
+ # response = @gateway.authorize(@amount, @declined_card, @options)
+ # assert_failure response
+ # assert_equal "REPLACE WITH FAILED MESSAGE", response.message
+ # assert_equal "REPLACE WITH FAILED CODE", response.params["error"]
+ # end
+
+ def test_failed_capture
+ authorization = 'auth' + '|' + SecureRandom.hex(6)
+ response = @gateway.capture(@amount, authorization, @options)
+ assert_failure response
+ assert_equal 'Not submitted', response.message
+ assert_equal '400', response.error_code
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ assert_equal 'Approved', void.message
+
+ # response = @gateway.authorize(@amount, @credit_card, @options)
+ # assert_success response
+ # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", response.message
+
+ # capture = @gateway.capture(@amount, response.authorization, @options)
+ # assert_success capture
+ # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", capture.message
+
+ # void = @gateway.void(capture.authorization, @options)
+ # assert_success void
+ # assert_equal "pgwResponseCodeDescription|Approved|responseText|00 -- APPROVAL|processorResponseCode|00", void.message
+
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ void = @gateway.void(purchase.authorization, @options)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_failed_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ authorization = auth.authorization[0..9] + 'XX'
+ response = @gateway.void(authorization, @options)
+
+ assert_failure response
+ assert_equal 'Not submitted', response.message
+ assert_equal '400', response.error_code
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ # def test_failed_credit
+ # response = @gateway.credit(@amount, @declined_card, @options)
+ # assert_failure response
+ # assert_equal "REPLACE WITH FAILED MESSAGE", response.message
+ # end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ # def test_failed_verify
+ # response = @gateway.verify(@declined_card, @options)
+ # assert_failure response
+ # assert_equal "REPLACE WITH FAILED MESSAGE", response.message
+ # assert_equal "REPLACE WITH FAILED CODE", response.params["error"]
+ # end
+
+ def test_successful_store
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert_equal 'Approved', store.message
+
+ purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+ assert_equal 'Approved', purchase.message
+
+ credit = @gateway.credit(@amount, store.authorization, @options)
+ assert_success credit
+ assert_equal 'Approved', credit.message
+
+ verify = @gateway.verify(store.authorization, @options)
+ assert_success verify
+ assert_equal 'Approved', verify.message
+ end
+
+ # def test_failed_store
+ # response = @gateway.store(@declined_card, @options)
+ # assert_failure response
+ # assert_equal "REPLACE WITH FAILED MESSAGE", response.message
+ # assert_equal "REPLACE WITH FAILED CODE", response.params["error"]
+ # end
+
+ # def test_dump_transcript
+ # #skip("Transcript scrubbing for this gateway has been tested.")
+
+ # # This test will run a purchase transaction on your gateway
+ # # and dump a transcript of the HTTP conversation so that
+ # # you can use that transcript as a reference while
+ # # implementing your scrubbing logic
+ # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options)
+ # end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:secret], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_linkpoint_test.rb b/test/remote/gateways/remote_linkpoint_test.rb
index feb126ad850..1a51911712d 100644
--- a/test/remote/gateways/remote_linkpoint_test.rb
+++ b/test/remote/gateways/remote_linkpoint_test.rb
@@ -1,16 +1,16 @@
#
-# In order for this test to pass, a valid store number and PEM file
-# are required. Unfortunately, with LinkPoint YOU CAN'T JUST USE ANY
-# OLD STORE NUMBER. Also, you can't just generate your own PEM file.
-# You'll need to use a special PEM file provided by LinkPoint.
+# In order for this test to pass, a valid store number and PEM file
+# are required. Unfortunately, with LinkPoint YOU CAN'T JUST USE ANY
+# OLD STORE NUMBER. Also, you can't just generate your own PEM file.
+# You'll need to use a special PEM file provided by LinkPoint.
#
-# Go to http://www.linkpoint.com/support/sup_teststore.asp to set up
+# Go to http://www.linkpoint.com/support/sup_teststore.asp to set up
# a test account. Once you receive your test account you can get your
# pem file by clicking the Support link on the navigation menu and then
# clicking the Download Center link.
#
# You will also want to change your test account's fraud settings
-# while running these tests. Click the admin link at the top of
+# while running these tests. Click the admin link at the top of
# LinkPoint Central. Then click "set lockout times" under Fraud Settings
# You will want to set Duplicate lockout time to 0 so that you can run
# the tests more than once without triggering this fraud detection.
@@ -23,9 +23,9 @@
class LinkpointTest < Test::Unit::TestCase
def setup
Base.mode = :test
-
+
@gateway = LinkpointGateway.new(fixtures(:linkpoint))
-
+
# Test credit card numbers
# American Express: 371111111111111
# Discover: 6011-1111-1111-1111
@@ -38,84 +38,100 @@ def setup
@credit_card = credit_card('4111111111111111')
@options = { :order_id => generate_unique_id, :billing_address => address }
end
-
+
def test_successful_authorization
assert response = @gateway.authorize(1000, @credit_card, @options)
-
+
assert_instance_of Response, response
assert_success response
- assert_equal "APPROVED", response.params["approved"]
+ assert_equal 'APPROVED', response.params['approved']
end
-
+
def test_successful_authorization_and_capture
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
assert authorization.test?
-
+
assert capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
assert_equal 'ACCEPTED', capture.message
end
-
+
def test_successful_purchase_without_cvv2_code
@credit_card.verification_value = nil
-
+
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "APPROVED", response.params["approved"]
- assert_equal 'NNN', response.params["avs"]
+ assert_equal 'APPROVED', response.params['approved']
+ assert_equal 'NNN', response.params['avs']
end
-
+
def test_successful_purchase_with_cvv2_code
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "APPROVED", response.params["approved"]
- assert_equal 'NNNM', response.params["avs"]
+ assert_equal 'APPROVED', response.params['approved']
+ assert_equal 'NNNM', response.params['avs']
end
-
+
def test_successful_purchase_and_void
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
-
+
assert void = @gateway.void(purchase.authorization)
assert_success void
end
-
+
def test_successfull_purchase_and_credit
assert purchase = @gateway.purchase(2400, @credit_card, @options)
assert_success purchase
-
+
assert credit = @gateway.credit(2400, purchase.authorization)
assert_success credit
end
def test_successfull_purchase_with_item_entity
- @options.merge!({:line_items => [
- {:id => '123456', :description => "Logo T-Shirt", :price => "12.00", :quantity => '1', :options =>
- [{:name => "Color", :value => "Red"}, {:name => "Size", :value => "XL"}]},
- {:id => '111', :description => "keychain", :price => "3.00", :quantity => '1'}]})
+ @options[:line_items] = [
+ {:id => '123456', :description => 'Logo T-Shirt', :price => '12.00', :quantity => '1',
+ :options => [{:name => 'Color', :value => 'Red'}, {:name => 'Size', :value => 'XL'}]},
+ {:id => '111', :description => 'keychain', :price => '3.00', :quantity => '1'}
+ ]
assert purchase = @gateway.purchase(1500, @credit_card, @options)
assert_success purchase
-
end
def test_successful_recurring_payment
- assert response = @gateway.recurring(2400, @credit_card,
- :order_id => generate_unique_id,
+ assert response = @gateway.recurring(2400, @credit_card,
+ :order_id => generate_unique_id,
:installments => 12,
- :startdate => "immediate",
+ :startdate => 'immediate',
:periodicity => :monthly,
:billing_address => address
)
-
+
assert_success response
- assert_equal "APPROVED", response.params["approved"]
+ assert_equal 'APPROVED', response.params['approved']
end
-
+
def test_declined_purchase_with_invalid_credit_card
@credit_card.number = '1111111111111111'
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "DECLINED", response.params["approved"]
+ assert_equal 'DECLINED', response.params['approved']
+ end
+
+ def test_cleans_whitespace_from_pem
+ @gateway = LinkpointGateway.new(fixtures(:linkpoint).merge(pem: ' ' + fixtures(:linkpoint)[:pem]))
+ assert response = @gateway.authorize(1000, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
end
end
diff --git a/test/remote/gateways/remote_litle_certification_test.rb b/test/remote/gateways/remote_litle_certification_test.rb
index 4ae6eb5b1ea..3a9558c701f 100644
--- a/test/remote/gateways/remote_litle_certification_test.rb
+++ b/test/remote/gateways/remote_litle_certification_test.rb
@@ -2,15 +2,16 @@
class RemoteLitleCertification < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
- @gateway = LitleGateway.new(fixtures(:litle).merge(:url => "https://cert.litle.com/vap/communicator/online"))
+ Base.mode = :test
+ @gateway = LitleGateway.new(fixtures(:litle))
+ @gateway.test_url = 'https://payments.vantivprelive.com/vap/communicator/online'
end
def test1
credit_card = CreditCard.new(
:number => '4457010000000009',
:month => '01',
- :year => '2014',
+ :year => '2021',
:verification_value => '349',
:brand => 'visa'
)
@@ -18,7 +19,7 @@ def test1
options = {
:order_id => '1',
:billing_address => {
- :name => 'John Smith',
+ :name => 'John & Mary Smith',
:address1 => '1 Main St.',
:city => 'Burlington',
:state => 'MA',
@@ -27,24 +28,24 @@ def test1
}
}
- auth_assertions(10010, credit_card, options, :avs => "X", :cvv => "M")
+ auth_assertions(10100, credit_card, options, :avs => 'X', :cvv => 'M')
- # 1: authorize avs
- authorize_avs_assertions(credit_card, options, :avs => "X", :cvv => "M")
+ authorize_avs_assertions(credit_card, options, :avs => 'X', :cvv => 'M')
- sale_assertions(10010, credit_card, options, :avs => "X", :cvv => "M")
+ sale_assertions(10100, credit_card, options, :avs => 'X', :cvv => 'M')
end
def test2
credit_card = CreditCard.new(:number => '5112010000000003', :month => '02',
- :year => '2014', :brand => 'master',
- :verification_value => '261')
+ :year => '2021', :brand => 'master',
+ :verification_value => '261',
+ :name => 'Mike J. Hammer')
options = {
:order_id => '2',
:billing_address => {
- :name => 'Mike J. Hammer',
:address1 => '2 Main St.',
+ :address2 => 'Apt. 222',
:city => 'Riverside',
:state => 'RI',
:zip => '02915',
@@ -52,19 +53,18 @@ def test2
}
}
- auth_assertions(20020, credit_card, options, :avs => "Z", :cvv => "M")
+ auth_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M')
- # 2: authorize avs
- authorize_avs_assertions(credit_card, options, :avs => "Z", :cvv => "M")
+ authorize_avs_assertions(credit_card, options, :avs => 'Z', :cvv => 'M')
- sale_assertions(20020, credit_card, options, :avs => "Z", :cvv => "M")
+ sale_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M')
end
def test3
credit_card = CreditCard.new(
:number => '6011010000000003',
:month => '03',
- :year => '2014',
+ :year => '2021',
:verification_value => '758',
:brand => 'discover'
)
@@ -80,19 +80,18 @@ def test3
:country => 'US'
}
}
- auth_assertions(30030, credit_card, options, :avs => "Z", :cvv => "M")
+ auth_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M')
- # 3: authorize avs
- authorize_avs_assertions(credit_card, options, :avs => "Z", :cvv => "M")
+ authorize_avs_assertions(credit_card, options, :avs => 'Z', :cvv => 'M')
- sale_assertions(30030, credit_card, options, :avs => "Z", :cvv => "M")
+ sale_assertions(10100, credit_card, options, :avs => 'Z', :cvv => 'M')
end
def test4
credit_card = CreditCard.new(
:number => '375001000000005',
:month => '04',
- :year => '2014',
+ :year => '2021',
:brand => 'american_express'
)
@@ -108,17 +107,37 @@ def test4
}
}
- auth_assertions(40040, credit_card, options, :avs => "A", :cvv => nil)
+ auth_assertions(10100, credit_card, options, :avs => 'A', :cvv => nil)
+
+ authorize_avs_assertions(credit_card, options, :avs => 'A')
+
+ sale_assertions(10100, credit_card, options, :avs => 'A', :cvv => nil)
+ end
+
+ def test5
+ credit_card = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new(
+ :number => '4100200300011001',
+ :month => '05',
+ :year => '2021',
+ :verification_value => '463',
+ :brand => 'visa',
+ :payment_cryptogram => 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ )
+
+ options = {
+ :order_id => '5'
+ }
+
+ auth_assertions(10100, credit_card, options, :avs => 'U', :cvv => 'M')
- # 4: authorize avs
- authorize_avs_assertions(credit_card, options, :avs => "A")
+ authorize_avs_assertions(credit_card, options, :avs => 'U', :cvv => 'M')
- sale_assertions(40040, credit_card, options, :avs => "A", :cvv => nil)
+ sale_assertions(10100, credit_card, options, :avs => 'U', :cvv => 'M')
end
def test6
credit_card = CreditCard.new(:number => '4457010100000008', :month => '06',
- :year => '2014', :brand => 'visa',
+ :year => '2021', :brand => 'visa',
:verification_value => '992')
options = {
@@ -134,30 +153,33 @@ def test6
}
# 6: authorize
- assert response = @gateway.authorize(60060, credit_card, options)
+ assert response = @gateway.authorize(10100, credit_card, options)
assert !response.success?
- assert_equal '110', response.params['litleOnlineResponse']['authorizationResponse']['response']
+ assert_equal '110', response.params['response']
assert_equal 'Insufficient Funds', response.message
- assert_equal "I", response.avs_result["code"]
- assert_equal "P", response.cvv_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ assert_equal 'P', response.cvv_result['code']
+ puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}"
# 6. sale
- assert response = @gateway.purchase(60060, credit_card, options)
+ assert response = @gateway.purchase(10100, credit_card, options)
assert !response.success?
- assert_equal '110', response.params['litleOnlineResponse']['saleResponse']['response']
+ assert_equal '110', response.params['response']
assert_equal 'Insufficient Funds', response.message
- assert_equal "I", response.avs_result["code"]
- assert_equal "P", response.cvv_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ assert_equal 'P', response.cvv_result['code']
+ puts "Test #{options[:order_id]} Sale: #{txn_id(response)}"
# 6A. void
assert response = @gateway.void(response.authorization, {:order_id => '6A'})
- assert_equal '360', response.params['litleOnlineResponse']['voidResponse']['response']
- assert_equal 'No transaction found with specified litleTxnId', response.message
+ assert_equal '360', response.params['response']
+ assert_equal 'No transaction found with specified transaction Id', response.message
+ puts "Test #{options[:order_id]}A: #{txn_id(response)}"
end
def test7
credit_card = CreditCard.new(:number => '5112010100000002', :month => '07',
- :year => '2014', :brand => 'master',
+ :year => '2021', :brand => 'master',
:verification_value => '251')
options = {
@@ -173,28 +195,30 @@ def test7
}
# 7: authorize
- assert response = @gateway.authorize(70070, credit_card, options)
+ assert response = @gateway.authorize(10100, credit_card, options)
assert !response.success?
- assert_equal '301', response.params['litleOnlineResponse']['authorizationResponse']['response']
+ assert_equal '301', response.params['response']
assert_equal 'Invalid Account Number', response.message
- assert_equal "I", response.avs_result["code"]
- assert_equal "N", response.cvv_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ assert_equal 'N', response.cvv_result['code']
+ puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}"
# 7: authorize avs
- authorize_avs_assertions(credit_card, options, :avs => "I", :cvv => "N", :message => "Invalid Account Number", :success => false)
+ authorize_avs_assertions(credit_card, options, :avs => 'I', :cvv => 'N', :message => 'Invalid Account Number', :success => false)
# 7. sale
- assert response = @gateway.purchase(70070, credit_card, options)
+ assert response = @gateway.purchase(10100, credit_card, options)
assert !response.success?
- assert_equal '301', response.params['litleOnlineResponse']['saleResponse']['response']
+ assert_equal '301', response.params['response']
assert_equal 'Invalid Account Number', response.message
- assert_equal "I", response.avs_result["code"]
- assert_equal "N", response.cvv_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ assert_equal 'N', response.cvv_result['code']
+ puts "Test #{options[:order_id]} Sale: #{txn_id(response)}"
end
def test8
credit_card = CreditCard.new(:number => '6011010100000002', :month => '08',
- :year => '2014', :brand => 'discover',
+ :year => '2021', :brand => 'discover',
:verification_value => '184')
options = {
@@ -210,28 +234,30 @@ def test8
}
# 8: authorize
- assert response = @gateway.authorize(80080, credit_card, options)
+ assert response = @gateway.authorize(10100, credit_card, options)
assert !response.success?
- assert_equal '123', response.params['litleOnlineResponse']['authorizationResponse']['response']
+ assert_equal '123', response.params['response']
assert_equal 'Call Discover', response.message
- assert_equal "I", response.avs_result["code"]
- assert_equal "P", response.cvv_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ assert_equal 'P', response.cvv_result['code']
+ puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}"
# 8: authorize avs
- authorize_avs_assertions(credit_card, options, :avs => "I", :cvv => "P", :message => "Call Discover", :success => false)
+ authorize_avs_assertions(credit_card, options, :avs => 'I', :cvv => 'P', :message => 'Call Discover', :success => false)
# 8: sale
assert response = @gateway.purchase(80080, credit_card, options)
assert !response.success?
- assert_equal '123', response.params['litleOnlineResponse']['saleResponse']['response']
+ assert_equal '123', response.params['response']
assert_equal 'Call Discover', response.message
- assert_equal "I", response.avs_result["code"]
- assert_equal "P", response.cvv_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ assert_equal 'P', response.cvv_result['code']
+ puts "Test #{options[:order_id]} Sale: #{txn_id(response)}"
end
def test9
credit_card = CreditCard.new(:number => '375001010000003', :month => '09',
- :year => '2014', :brand => 'american_express',
+ :year => '2021', :brand => 'american_express',
:verification_value => '0421')
options = {
@@ -247,92 +273,597 @@ def test9
}
# 9: authorize
- assert response = @gateway.authorize(90090, credit_card, options)
-
+ assert response = @gateway.authorize(10100, credit_card, options)
assert !response.success?
- assert_equal '303', response.params['litleOnlineResponse']['authorizationResponse']['response']
+ assert_equal '303', response.params['response']
assert_equal 'Pick Up Card', response.message
- assert_equal "I", response.avs_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ puts "Test #{options[:order_id]} Authorize: #{txn_id(response)}"
# 9: authorize avs
- authorize_avs_assertions(credit_card, options, :avs => "I", :message => "Pick Up Card", :success => false)
+ authorize_avs_assertions(credit_card, options, :avs => 'I', :message => 'Pick Up Card', :success => false)
# 9: sale
- assert response = @gateway.purchase(90090, credit_card, options)
+ assert response = @gateway.purchase(10100, credit_card, options)
assert !response.success?
- assert_equal '303', response.params['litleOnlineResponse']['saleResponse']['response']
+ assert_equal '303', response.params['response']
assert_equal 'Pick Up Card', response.message
- assert_equal "I", response.avs_result["code"]
+ assert_equal 'I', response.avs_result['code']
+ puts "Test #{options[:order_id]} Sale: #{txn_id(response)}"
end
# Authorization Reversal Tests
+ def test32
+ credit_card = CreditCard.new(:number => '4457010000000009', :month => '01',
+ :year => '2021', :brand => 'visa',
+ :verification_value => '349')
+
+ options = {
+ :order_id => '32',
+ :billing_address => {
+ :name => 'John Smith',
+ :address1 => '1 Main St.',
+ :city => 'Burlington',
+ :state => 'MA',
+ :zip => '01803-3747',
+ :country => 'US'
+ }
+ }
+
+ assert auth_response = @gateway.authorize(10010, credit_card, options)
+ assert_success auth_response
+ assert_equal '11111 ', auth_response.params['authCode']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+
+ assert capture_response = @gateway.capture(5050, auth_response.authorization, options)
+ assert_success capture_response
+ puts "Test #{options[:order_id]}A: #{txn_id(capture_response)}"
+
+ assert reversal_response = @gateway.void(auth_response.authorization, options)
+ assert_failure reversal_response
+ assert 'Authorization amount has already been depleted', reversal_response.message
+ puts "Test #{options[:order_id]}B: #{txn_id(reversal_response)}"
+ end
+
+ def test33
+ credit_card = CreditCard.new(:number => '5112010000000003', :month => '01',
+ :year => '2021', :brand => 'master',
+ :verification_value => '261')
+
+ options = {
+ :order_id => '33',
+ :billing_address => {
+ :name => 'Mike J. Hammer',
+ :address1 => '2 Main St.',
+ :address2 => 'Apt. 222',
+ :city => 'Riverside',
+ :state => 'RI',
+ :zip => '02915',
+ :country => 'US',
+ :payment_cryptogram => 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ }
+ }
+
+ assert auth_response = @gateway.authorize(20020, credit_card, options)
+ assert_success auth_response
+ assert_equal '22222 ', auth_response.params['authCode']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+
+ assert reversal_response = @gateway.void(auth_response.authorization, options)
+ assert_success reversal_response
+ puts "Test #{options[:order_id]}A: #{txn_id(reversal_response)}"
+ end
+
def test34
- credit_card = CreditCard.new(:number => '6011010000000003', :month => '03',
- :year => '2014', :brand => 'discover',
+ credit_card = CreditCard.new(:number => '6011010000000003', :month => '01',
+ :year => '2021', :brand => 'discover',
:verification_value => '758')
options = {
- :order_id => '34',
- :billing_address => {
- :name => 'Eileen Jones',
- :address1 => '3 Main St.',
- :city => 'Bloomfield',
- :state => 'CT',
- :zip => '06002',
- :country => 'US'
- }
+ :order_id => '34',
+ :billing_address => {
+ :name => 'Eileen Jones',
+ :address1 => '3 Main St.',
+ :city => 'Bloomfield',
+ :state => 'CT',
+ :zip => '06002',
+ :country => 'US'
+ }
}
assert auth_response = @gateway.authorize(30030, credit_card, options)
assert_success auth_response
+ assert '33333 ', auth_response.params['authCode']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
- credit_card = CreditCard.new(:number => '4024720001231239', :month => '12',
- :year => '2014', :brand => 'visa')
- assert auth_response2 = @gateway.authorize(18699, credit_card, :order_id => '29')
-
- assert reversal_response = @gateway.void(auth_response2.authorization)
+ assert reversal_response = @gateway.void(auth_response.authorization, options)
assert_success reversal_response
+ puts "Test #{options[:order_id]}A: #{txn_id(reversal_response)}"
end
- def test36
+ def test35
+ credit_card = CreditCard.new(:number => '375001000000005', :month => '01',
+ :year => '2021', :brand => 'american_express')
+
options = {
- :order_id => '36'
+ :order_id => '35',
+ :billing_address => {
+ :name => 'Bob Black',
+ :address1 => '4 Main St.',
+ :city => 'Laurel',
+ :state => 'MD',
+ :zip => '20708',
+ :country => 'US'
+ }
}
- credit_card = CreditCard.new(:number => '375000026600004', :month => '05',
- :year => '2014', :brand => 'american_express',
- :verification_value => '261')
+ assert auth_response = @gateway.authorize(10100, credit_card, options)
+ assert_success auth_response
+ assert_equal '44444 ', auth_response.params['authCode']
+ assert_equal 'A', auth_response.avs_result['code']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+
+ assert capture_response = @gateway.capture(5050, auth_response.authorization, options)
+ assert_success capture_response
+ puts "Test #{options[:order_id]}A: #{txn_id(capture_response)}"
+
+ assert reversal_response = @gateway.void(auth_response.authorization, options)
+ assert_failure reversal_response
+ assert 'Reversal amount does not match Authorization amount', reversal_response.message
+ puts "Test #{options[:order_id]}B: #{txn_id(reversal_response)}"
+ end
+
+ def test36
+ credit_card = CreditCard.new(:number => '375000026600004', :month => '01',
+ :year => '2021', :brand => 'american_express')
+
+ options = {
+ :order_id => '36'
+ }
assert auth_response = @gateway.authorize(20500, credit_card, options)
assert_success auth_response
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+
+ assert reversal_response = @gateway.void(auth_response.authorization, options)
+ assert_failure reversal_response
+ assert 'Reversal amount does not match Authorization amount', reversal_response.message
+ puts "Test #{options[:order_id]}A: #{txn_id(reversal_response)}"
+ end
+
+ # Echeck
+ def test37
+ check = check(
+ name: 'Tom Black',
+ routing_number: '053100300',
+ account_number: '10@BC99999',
+ account_type: 'Checking'
+ )
+ options = {
+ :order_id => '37',
+ :billing_address => {
+ :name => 'Tom Black',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert auth_response = @gateway.authorize(3001, check, options)
+ assert_failure auth_response
+ assert_equal 'Invalid Account Number', auth_response.message
+ assert_equal '301', auth_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+ end
+
+ def test38
+ check = check(
+ name: 'John Smith',
+ routing_number: '011075150',
+ account_number: '1099999999',
+ account_type: 'Checking'
+ )
+ options = {
+ :order_id => '38',
+ :billing_address => {
+ :name => 'John Smith',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert auth_response = @gateway.authorize(3002, check, options)
+ assert_success auth_response
+ assert_equal 'Approved', auth_response.message
+ assert_equal '000', auth_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+ end
- assert reversal_response = @gateway.void(auth_response.authorization, amount: 10000)
- assert !reversal_response.success?
- assert_equal '336', reversal_response.params['litleOnlineResponse']['authReversalResponse']['response']
+ def test39
+ check = check(
+ name: 'Robert Jones',
+ routing_number: '053100300',
+ account_number: '3099999999',
+ account_type: 'Corporate'
+ )
+ options = {
+ :order_id => '39',
+ :billing_address => {
+ :name => 'John Smith',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :company => 'Good Goods Inc',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert auth_response = @gateway.authorize(3003, check, options)
+ assert_failure auth_response
+ assert_equal 'Decline - Negative Information on File', auth_response.message
+ assert_equal '950', auth_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+ end
+
+ def test40
+ declined_authorize_check = check(
+ name: 'Peter Green',
+ routing_number: '011075150',
+ account_number: '8099999999',
+ account_type: 'Corporate'
+ )
+ options = {
+ :order_id => '40',
+ :billing_address => {
+ :name => 'Peter Green',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :company => 'Green Co',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert auth_response = @gateway.authorize(3004, declined_authorize_check, options)
+ assert_failure auth_response
+ assert_equal 'Absolute Decline', auth_response.message
+ assert_equal '951', auth_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(auth_response)}"
+ end
+
+ def test41
+ check = check(
+ name: 'Mike Hammer',
+ routing_number: '053100300',
+ account_number: '10@BC99999',
+ account_type: 'Checking'
+ )
+ options = {
+ :order_id => '41',
+ :billing_address => {
+ :name => 'Mike Hammer',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(2008, check, options)
+ assert_failure purchase_response
+ assert_equal 'Invalid Account Number', purchase_response.message
+ assert_equal '301', purchase_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}"
+ end
+
+ def test42
+ check = check(
+ name: 'Tom Black',
+ routing_number: '011075150',
+ account_number: '4099999992',
+ account_type: 'Checking'
+ )
+ options = {
+ :order_id => '42',
+ :billing_address => {
+ :name => 'Tom Black',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(2004, check, options)
+ assert_success purchase_response
+ assert_equal 'Approved', purchase_response.message
+ assert_equal '000', purchase_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}"
+ end
+
+ def test43
+ check = check(
+ name: 'Peter Green',
+ routing_number: '011075150',
+ account_number: '6099999992',
+ account_type: 'Corporate'
+ )
+ options = {
+ :order_id => '43',
+ :billing_address => {
+ :name => 'Peter Green',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :company => 'Green Co',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(2007, check, options)
+ assert_success purchase_response
+ assert_equal 'Approved', purchase_response.message
+ assert_equal '000', purchase_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}"
+ end
+
+ def test44
+ check = check(
+ name: 'Peter Green',
+ routing_number: '053133052',
+ account_number: '9099999992',
+ account_type: 'Corporate'
+ )
+ options = {
+ :order_id => '44',
+ :billing_address => {
+ :name => 'Peter Green',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :company => 'Green Co',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(2009, check, options)
+ assert_failure purchase_response
+ assert_equal 'Invalid Bank Routing Number', purchase_response.message
+ assert_equal '900', purchase_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(purchase_response)}"
+ end
+
+ def test45
+ check = check(
+ name: 'John Smith',
+ routing_number: '053100300',
+ account_number: '10@BC99999',
+ account_type: 'Checking'
+ )
+ options = {
+ :order_id => '45',
+ :billing_address => {
+ :name => 'John Smith',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert refund_response = @gateway.refund(1001, check, options)
+ assert_failure refund_response
+ assert_equal 'Invalid Account Number', refund_response.message
+ assert_equal '301', refund_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(refund_response)}"
+ end
+
+ def test46
+ check = check(
+ name: 'Robert Jones',
+ routing_number: '011075150',
+ account_number: '3099999999',
+ account_type: 'Corporate'
+ )
+ options = {
+ :order_id => '46',
+ :order_source => 'telephone',
+ :billing_address => {
+ :name => 'Robert Jones',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444',
+ :company => 'Widget Inc'
+ }
+ }
+ assert purchase_response = @gateway.purchase(1003, check, options)
+ sleep(10)
+ assert refund_response = @gateway.refund(1003, purchase_response.authorization, options)
+ assert_success refund_response
+ assert_equal 'Approved', refund_response.message
+ assert_equal '000', refund_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(refund_response)}"
+ end
+
+ def test47
+ check = check(
+ name: 'Peter Green',
+ routing_number: '211370545',
+ account_number: '6099999993',
+ account_type: 'Corporate'
+ )
+ options = {
+ :order_id => '47',
+ :billing_address => {
+ :name => 'Peter Green',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :company => 'Green Co',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(1007, check, options)
+ assert refund_response = @gateway.refund(1007, purchase_response.authorization, options)
+ assert_success refund_response
+ assert_equal 'Approved', refund_response.message
+ assert_equal '000', refund_response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(refund_response)}"
+ end
+
+ def test48
+ check = check(
+ name: 'Peter Green',
+ routing_number: '011075150',
+ account_number: '6099999992',
+ account_type: 'Corporate'
+ )
+ options = {
+ :order_id => '43',
+ :billing_address => {
+ :name => 'Peter Green',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :company => 'Green Co',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(2007, check, options)
+ assert_success purchase_response
+ assert refund_response = @gateway.refund(2007, purchase_response.authorization, options)
+ assert_equal '000', refund_response.params['response']
+ puts "Test 48: #{txn_id(refund_response)}"
+ end
+
+ def test49
+ assert refund_response = @gateway.refund(2007, 2)
+ assert_failure refund_response
+ assert_equal '360', refund_response.params['response']
+ assert_equal 'No transaction found with specified transaction Id', refund_response.message
+ puts "Test 49: #{txn_id(refund_response)}"
+ end
+
+ def test_echeck_void1
+ check = check(
+ name: 'Tom Black',
+ routing_number: '011075150',
+ account_number: '4099999992',
+ account_type: 'Checking'
+ )
+ options = {
+ :order_id => '42',
+ :id => '236222',
+ :billing_address => {
+ :name => 'Tom Black',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(2004, check, options)
+ assert_success purchase_response
+ sleep(10)
+ assert void_response = @gateway.void(purchase_response.authorization)
+ assert_equal '000', void_response.params['response']
+ puts "Test void1: #{txn_id(void_response)}"
+ end
+
+ def test_echeck_void2
+ check = check(
+ name: 'Robert Jones',
+ routing_number: '011075150',
+ account_number: '3099999999',
+ account_type: 'Checking'
+ )
+ options = {
+ :order_id => '46',
+ :id => '232222',
+ :billing_address => {
+ :name => 'Robert Jones',
+ :address1 => '8 Main St.',
+ :city => 'Manchester',
+ :state => 'NH',
+ :zip => '03101',
+ :country => 'US',
+ :email => 'test@test.com',
+ :phone => '2233334444'
+ }
+ }
+ assert purchase_response = @gateway.purchase(1003, check, options)
+ assert_success purchase_response
+ sleep(20)
+ assert void_response = @gateway.void(purchase_response.authorization)
+ assert_equal '000', void_response.params['response']
+ puts "Test void2: #{txn_id(void_response)}"
+ end
+
+ def test_echeck_void3
+ assert void_response = @gateway.void(2)
+ assert_failure void_response
+ assert_equal '360', void_response.params['response']
+ assert_equal 'No transaction found with specified transaction Id', void_response.message
+ puts "Test void3: #{txn_id(void_response)}"
end
# Explicit Token Registration Tests
def test50
credit_card = CreditCard.new(:number => '4457119922390123')
options = {
- :order_id => '50'
+ :order_id => '50'
}
# store
store_response = @gateway.store(credit_card, options)
assert_success store_response
+ assert_equal '445711', store_response.params['bin']
+ assert_equal 'VI', store_response.params['type']
+ assert_equal '0123', store_response.params['litleToken'][-4, 4]
+ assert_equal '801', store_response.params['response']
assert_equal 'Account number was successfully registered', store_response.message
- assert_equal '445711', store_response.params['litleOnlineResponse']['registerTokenResponse']['bin']
- assert_equal 'VI', store_response.params['litleOnlineResponse']['registerTokenResponse']['type'] #type is on Object in 1.8.7 - later versions can use .registerTokenResponse.type
- assert_equal '801', store_response.params['litleOnlineResponse']['registerTokenResponse']['response']
- assert_equal '0123', store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'][-4,4]
+ puts "Test #{options[:order_id]}: #{txn_id(response)}"
end
def test51
credit_card = CreditCard.new(:number => '4457119999999999')
- options = {
- :order_id => '51'
+ options = {
+ :order_id => '51'
}
# store
@@ -340,14 +871,14 @@ def test51
assert_failure store_response
assert_equal 'Credit card number was invalid', store_response.message
- assert_equal '820', store_response.params['litleOnlineResponse']['registerTokenResponse']['response']
- assert_equal nil, store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken']
+ assert_equal '820', store_response.params['response']
+ assert_equal nil, store_response.params['litleToken']
end
def test52
credit_card = CreditCard.new(:number => '4457119922390123')
- options = {
- :order_id => '52'
+ options = {
+ :order_id => '52'
}
# store
@@ -355,10 +886,47 @@ def test52
assert_success store_response
assert_equal 'Account number was previously registered', store_response.message
- assert_equal '445711', store_response.params['litleOnlineResponse']['registerTokenResponse']['bin']
- assert_equal 'VI', store_response.params['litleOnlineResponse']['registerTokenResponse']['type'] #type is on Object in 1.8.7 - later versions can use .registerTokenResponse.type
- assert_equal '802', store_response.params['litleOnlineResponse']['registerTokenResponse']['response']
- assert_equal '0123', store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'][-4,4]
+ assert_equal '445711', store_response.params['bin']
+ assert_equal 'VI', store_response.params['type']
+ assert_equal '802', store_response.params['response']
+ assert_equal '0123', store_response.params['litleToken'][-4, 4]
+ puts "Test #{options[:order_id]}: #{txn_id(store_response)}"
+ end
+
+ def test53
+ check = check(
+ routing_number: '011100012',
+ account_number: '1099999998'
+ )
+ options = {
+ :order_id => '53'
+ }
+
+ store_response = @gateway.store(check, options)
+
+ assert_success store_response
+ assert_equal '998', store_response.params['eCheckAccountSuffix']
+ assert_equal 'EC', store_response.params['type']
+ assert_equal '801', store_response.params['response']
+ assert_equal 'Account number was successfully registered', store_response.message
+ puts "Test #{options[:order_id]}: #{txn_id(store_response)}"
+ end
+
+ def test54
+ check = check(
+ routing_number: '1145_7895',
+ account_number: '1022222102'
+ )
+ options = {
+ :order_id => '54'
+ }
+
+ store_response = @gateway.store(check, options)
+
+ assert_failure store_response
+ assert_equal '900', store_response.params['response']
+ assert_equal 'Invalid Bank Routing Number', store_response.message
+ puts "Test #{options[:order_id]}: #{txn_id(store_response)}"
end
# Implicit Token Registration Tests
@@ -368,23 +936,19 @@ def test55
:year => '2014',
:brand => 'master',
:verification_value => '987')
- options = {
- :order_id => '55'
+ options = {
+ :order_id => '55'
}
# authorize
assert response = @gateway.authorize(15000, credit_card, options)
- #"tokenResponse" => { "litleToken" => "1712000118270196",
- # "tokenResponseCode" => "802",
- # "tokenMessage" => "Account number was previously registered",
- # "type" => "MC",
- # "bin" => "543510" }
assert_success response
assert_equal 'Approved', response.message
- assert_equal '0196', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['litleToken'][-4,4]
- assert %w(801 802).include? response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['tokenResponseCode']
- assert_equal 'MC', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['type']
- assert_equal '543510', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['bin']
+ assert_equal '0196', response.params['tokenResponse_litleToken'][-4, 4]
+ assert %w(801 802).include? response.params['tokenResponse_tokenResponseCode']
+ assert_equal 'MC', response.params['tokenResponse_type']
+ assert_equal '543510', response.params['tokenResponse_bin']
+ puts "Test #{options[:order_id]}: #{txn_id(response)}"
end
def test56
@@ -393,15 +957,16 @@ def test56
:year => '2014',
:brand => 'master',
:verification_value => '987')
- options = {
- :order_id => '56'
+ options = {
+ :order_id => '56'
}
# authorize
assert response = @gateway.authorize(15000, credit_card, options)
assert_failure response
- assert_equal '301', response.params['litleOnlineResponse']['authorizationResponse']['response']
+ assert_equal '301', response.params['response']
+ puts "Test #{options[:order_id]}: #{txn_id(response)}"
end
def test57_58
@@ -410,8 +975,8 @@ def test57_58
:year => '2014',
:brand => 'master',
:verification_value => '987')
- options = {
- :order_id => '57'
+ options = {
+ :order_id => '57'
}
# authorize card
@@ -419,19 +984,20 @@ def test57_58
assert_success response
assert_equal 'Approved', response.message
- assert_equal '0196', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['litleToken'][-4,4]
- assert %w(801 802).include? response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['tokenResponseCode']
- assert_equal 'MC', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['type']
- assert_equal '543510', response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['bin']
+ assert_equal '0196', response.params['tokenResponse_litleToken'][-4, 4]
+ assert %w(801 802).include? response.params['tokenResponse_tokenResponseCode']
+ assert_equal 'MC', response.params['tokenResponse_type']
+ assert_equal '543510', response.params['tokenResponse_bin']
+ puts "Test #{options[:order_id]}: #{txn_id(response)}"
# authorize token
- token = response.params['litleOnlineResponse']['authorizationResponse']['tokenResponse']['litleToken']
+ token = response.params['tokenResponse_litleToken']
options = {
- :order_id => '58',
- :token => {
- :month => credit_card.month,
- :year => credit_card.year
- }
+ :order_id => '58',
+ :token => {
+ :month => credit_card.month,
+ :year => credit_card.year
+ }
}
# authorize
@@ -439,55 +1005,114 @@ def test57_58
assert_success response
assert_equal 'Approved', response.message
+ puts "Test #{options[:order_id]}: #{txn_id(response)}"
end
def test59
- token = '1712990000040196'
+ token = '1111000100092332'
options = {
- :order_id => '59',
- :token => {
- :month => '11',
- :year => '2014'
- }
+ :order_id => '59',
+ :token => {
+ :month => '11',
+ :year => '2021'
+ }
}
# authorize
assert response = @gateway.authorize(15000, token, options)
assert_failure response
- assert_equal '822', response.params['litleOnlineResponse']['authorizationResponse']['response']
+ assert_equal '822', response.params['response']
assert_equal 'Token was not found', response.message
+ puts "Test #{options[:order_id]}: #{txn_id(response)}"
end
def test60
token = '171299999999999'
options = {
- :order_id => '60',
- :token => {
- :month => '11',
- :year => '2014'
- }
+ :order_id => '60',
+ :token => {
+ :month => '11',
+ :year => '2014'
+ }
}
# authorize
assert response = @gateway.authorize(15000, token, options)
assert_failure response
- assert_equal '823', response.params['litleOnlineResponse']['authorizationResponse']['response']
+ assert_equal '823', response.params['response']
assert_equal 'Token was invalid', response.message
+ puts "Test #{options[:order_id]}: #{txn_id(response)}"
+ end
+
+ def test_apple_pay_purchase
+ options = {
+ :order_id => transaction_id,
+ }
+ decrypted_apple_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new(
+ {
+ month: '01',
+ year: '2021',
+ brand: 'visa',
+ number: '4457000300000007',
+ payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ })
+
+ assert response = @gateway.purchase(10010, decrypted_apple_pay, options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_android_pay_purchase
+ options = {
+ :order_id => transaction_id,
+ }
+ decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new(
+ {
+ source: :android_pay,
+ month: '01',
+ year: '2021',
+ brand: 'visa',
+ number: '4457000300000007',
+ payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ })
+
+ assert response = @gateway.purchase(10010, decrypted_android_pay, options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_three_d_secure
+ three_d_secure_assertions('3DS1', '4100200300000004', 'visa', '3dsAuthenticated', '0')
+ three_d_secure_assertions('3DS2', '4100200300000012', 'visa', '3dsAuthenticated', '1')
+ three_d_secure_assertions('3DS3', '4100200300000103', 'visa', '3dsAuthenticated', '2')
+ three_d_secure_assertions('3DS4', '4100200300001002', 'visa', '3dsAuthenticated', 'A')
+ three_d_secure_assertions('3DS5', '4100200300000020', 'visa', '3dsAuthenticated', '3')
+ three_d_secure_assertions('3DS6', '4100200300000038', 'visa', '3dsAuthenticated', '4')
+ three_d_secure_assertions('3DS7', '4100200300000046', 'visa', '3dsAuthenticated', '5')
+ three_d_secure_assertions('3DS8', '4100200300000053', 'visa', '3dsAuthenticated', '6')
+ three_d_secure_assertions('3DS9', '4100200300000061', 'visa', '3dsAuthenticated', '7')
+ three_d_secure_assertions('3DS10', '4100200300000079', 'visa', '3dsAuthenticated', '8')
+ three_d_secure_assertions('3DS11', '4100200300000087', 'visa', '3dsAuthenticated', '9')
+ three_d_secure_assertions('3DS12', '4100200300000095', 'visa', '3dsAuthenticated', 'B')
+ three_d_secure_assertions('3DS13', '4100200300000111', 'visa', '3dsAuthenticated', 'C')
+ three_d_secure_assertions('3DS14', '4100200300000129', 'visa', '3dsAuthenticated', 'D')
+ three_d_secure_assertions('3DS15', '5112010200000001', 'master', '3dsAttempted', nil)
+ three_d_secure_assertions('3DS16', '5112010200000001', 'master', '3dsAttempted', nil)
end
def test_authorize_and_purchase_and_credit_with_token
options = {
- :order_id => transaction_id,
- :billing_address => {
- :name => 'John Smith',
- :address1 => '1 Main St.',
- :city => 'Burlington',
- :state => 'MA',
- :zip => '01803-3747',
- :country => 'US'
- }
+ :order_id => transaction_id,
+ :billing_address => {
+ :name => 'John Smith',
+ :address1 => '1 Main St.',
+ :city => 'Burlington',
+ :state => 'MA',
+ :zip => '01803-3747',
+ :country => 'US'
+ }
}
credit_card = CreditCard.new(:number => '5435101234510196',
@@ -536,63 +1161,71 @@ def test_authorize_and_purchase_and_credit_with_token
private
- def auth_assertions(amount, card, options, assertions)
+ def auth_assertions(amount, card, options, assertions={})
# 1: authorize
assert response = @gateway.authorize(amount, card, options)
assert_success response
assert_equal 'Approved', response.message
- assert_equal assertions[:avs], response.avs_result["code"]
- assert_equal assertions[:cvv], response.cvv_result["code"] if assertions[:cvv]
- assert_equal options[:order_id], response.params['litleOnlineResponse']['authorizationResponse']['id']
+ assert_equal assertions[:avs], response.avs_result['code'] if assertions[:avs]
+ assert_equal assertions[:cvv], response.cvv_result['code'] if assertions[:cvv]
+ assert_equal auth_code(options[:order_id]), response.params['authCode']
# 1A: capture
- id = transaction_id
- assert response = @gateway.capture(amount, response.authorization, {:id => id})
+ assert response = @gateway.capture(amount, response.authorization, {:id => transaction_id})
assert_equal 'Approved', response.message
- assert_equal id, response.params['litleOnlineResponse']['captureResponse']['id']
# 1B: credit
- id = transaction_id
- assert response = @gateway.credit(amount, response.authorization, {:id => id})
+ assert response = @gateway.credit(amount, response.authorization, {:id => transaction_id})
assert_equal 'Approved', response.message
- assert_equal id, response.params['litleOnlineResponse']['creditResponse']['id']
# 1C: void
- id = transaction_id
- assert response = @gateway.void(response.authorization, {:id => id})
+ assert response = @gateway.void(response.authorization, {:id => transaction_id})
assert_equal 'Approved', response.message
- assert_equal id, response.params['litleOnlineResponse']['voidResponse']['id']
end
def authorize_avs_assertions(credit_card, options, assertions={})
- assert response = @gateway.authorize(0, credit_card, options)
+ assert response = @gateway.authorize(000, credit_card, options)
assert_equal assertions.key?(:success) ? assertions[:success] : true, response.success?
assert_equal assertions[:message] || 'Approved', response.message
- assert_equal assertions[:avs], response.avs_result["code"], caller.inspect
- assert_equal assertions[:cvv], response.cvv_result["code"], caller.inspect if assertions[:cvv]
- assert_equal options[:order_id], response.params['litleOnlineResponse']['authorizationResponse']['id']
+ assert_equal assertions[:avs], response.avs_result['code'], caller.inspect
+ assert_equal assertions[:cvv], response.cvv_result['code'], caller.inspect if assertions[:cvv]
end
- def sale_assertions(amount, card, options, assertions)
+ def sale_assertions(amount, card, options, assertions={})
# 1: sale
assert response = @gateway.purchase(amount, card, options)
assert_success response
assert_equal 'Approved', response.message
- assert_equal assertions[:avs], response.avs_result["code"]
- assert_equal assertions[:cvv], response.cvv_result["code"] if assertions[:cvv]
- assert_equal options[:order_id], response.params['litleOnlineResponse']['saleResponse']['id']
+ assert_equal assertions[:avs], response.avs_result['code'] if assertions[:avs]
+ assert_equal assertions[:cvv], response.cvv_result['code'] if assertions[:cvv]
+ # assert_equal auth_code(options[:order_id]), response.params['authCode']
# 1B: credit
- id = transaction_id
- assert response = @gateway.credit(amount, response.authorization, {:id => id})
+ assert response = @gateway.credit(amount, response.authorization, {:id => transaction_id})
assert_equal 'Approved', response.message
- assert_equal id, response.params['litleOnlineResponse']['creditResponse']['id']
# 1C: void
- id = transaction_id
- assert response = @gateway.void(response.authorization, {:id => id})
+ assert response = @gateway.void(response.authorization, {:id => transaction_id})
assert_equal 'Approved', response.message
- assert_equal id, response.params['litleOnlineResponse']['voidResponse']['id']
+ end
+
+ def three_d_secure_assertions(test_id, card, type, source, result)
+ credit_card = CreditCard.new(:number => card, :month => '01',
+ :year => '2021', :brand => type,
+ :verification_value => '261',
+ :name => 'Mike J. Hammer')
+
+ options = {
+ order_id: test_id,
+ order_source: source,
+ cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ }
+
+ assert response = @gateway.authorize(10100, credit_card, options)
+ assert_success response
+ assert_equal result, response.params['fraudResult_authenticationResult']
+ puts "Test #{test_id}: #{txn_id(response)}"
end
def transaction_id
@@ -604,4 +1237,12 @@ def transaction_id
# minLength = N/A maxLength = 25
generate_unique_id[0, 24]
end
+
+ def auth_code(order_id)
+ order_id * 5 + ' '
+ end
+
+ def txn_id(response)
+ response.authorization.split(';')[0]
+ end
end
diff --git a/test/remote/gateways/remote_litle_test.rb b/test/remote/gateways/remote_litle_test.rb
old mode 100755
new mode 100644
index 44bcdca4542..d5014b59420
--- a/test/remote/gateways/remote_litle_test.rb
+++ b/test/remote/gateways/remote_litle_test.rb
@@ -2,41 +2,82 @@
class RemoteLitleTest < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
@gateway = LitleGateway.new(fixtures(:litle))
@credit_card_hash = {
- :first_name => 'John',
- :last_name => 'Smith',
- :month => '01',
- :year => '2012',
- :brand => 'visa',
- :number => '4457010000000009',
- :verification_value => '349'
+ first_name: 'John',
+ last_name: 'Smith',
+ month: '01',
+ year: '2012',
+ brand: 'visa',
+ number: '4457010000000009',
+ verification_value: '349'
}
@options = {
- :order_id=>'1',
- :billing_address=>{
- :name => 'John Smith',
- :company => 'testCompany',
- :address1 => '1 Main St.',
- :city => 'Burlington',
- :state => 'MA',
- :country => 'USA',
- :zip => '01803-3747',
- :phone => '1234567890'
+ order_id: '1',
+ email: 'wow@example.com',
+ billing_address: {
+ company: 'testCompany',
+ address1: '1 Main St.',
+ city: 'Burlington',
+ state: 'MA',
+ country: 'USA',
+ zip: '01803-3747',
+ phone: '1234567890'
}
}
@credit_card1 = CreditCard.new(@credit_card_hash)
@credit_card2 = CreditCard.new(
- :first_name => "Joe",
- :last_name => "Green",
- :month => "06",
- :year => "2012",
- :brand => "visa",
- :number => "4457010100000008",
- :verification_value => "992"
+ first_name: 'Joe',
+ last_name: 'Green',
+ month: '06',
+ year: '2012',
+ brand: 'visa',
+ number: '4457010100000008',
+ verification_value: '992'
+ )
+ @credit_card_nsf = CreditCard.new(
+ first_name: 'Joe',
+ last_name: 'Green',
+ month: '06',
+ year: '2012',
+ brand: 'visa',
+ number: '4488282659650110',
+ verification_value: '992'
+ )
+ @decrypted_apple_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new(
+ {
+ month: '01',
+ year: '2012',
+ brand: 'visa',
+ number: '44444444400009',
+ payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ })
+ @decrypted_android_pay = ActiveMerchant::Billing::NetworkTokenizationCreditCard.new(
+ {
+ source: :android_pay,
+ month: '01',
+ year: '2021',
+ brand: 'visa',
+ number: '4457000300000007',
+ payment_cryptogram: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ })
+ @check = check(
+ name: 'Tom Black',
+ routing_number: '011075150',
+ account_number: '4099999992',
+ account_type: 'checking'
+ )
+ @authorize_check = check(
+ name: 'John Smith',
+ routing_number: '011075150',
+ account_number: '1099999999',
+ account_type: 'checking'
+ )
+ @store_check = check(
+ routing_number: '011100012',
+ account_number: '1099999998'
)
end
@@ -46,6 +87,55 @@ def test_successful_authorization
assert_equal 'Approved', response.message
end
+ def test_successful_authorization_with_merchant_data
+ options = @options.merge(
+ affiliate: 'some-affiliate',
+ campaign: 'super-awesome-campaign',
+ merchant_grouping_id: 'brilliant-group'
+ )
+ assert @gateway.authorize(10010, @credit_card1, options)
+ end
+
+ def test_successful_authorization_with_echeck
+ options = @options.merge({
+ order_id: '38',
+ order_source: 'telephone'
+ })
+ assert response = @gateway.authorize(3002, @authorize_check, options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_authorization_with_paypage_registration_with_month_year_verification
+ paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new(
+ 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==',
+ month: '11',
+ year: '12',
+ verification_value: '123',
+ name: 'Joe Payer'
+ )
+
+ assert response = @gateway.authorize(5090, paypage_registration, billing_address: address)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_authorization_with_paypage_registration_without_month_year_verification_name
+ paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new(
+ 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw=='
+ )
+
+ assert response = @gateway.authorize(5090, paypage_registration, billing_address: address)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_avs_and_cvv_result
+ assert response = @gateway.authorize(10010, @credit_card1, @options)
+ assert_equal 'X', response.avs_result['code']
+ assert_equal 'M', response.cvv_result['code']
+ end
+
def test_unsuccessful_authorization
assert response = @gateway.authorize(60060, @credit_card2,
{
@@ -65,68 +155,438 @@ def test_unsuccessful_authorization
end
def test_successful_purchase
- #Litle sale
assert response = @gateway.purchase(10010, @credit_card1, @options)
assert_success response
assert_equal 'Approved', response.message
end
- def test_unsuccessful_purchase
- assert response = @gateway.purchase(60060, @credit_card2, {
- :order_id=>'6',
- :billing_address=>{
- :name => 'Joe Green',
- :address1 => '6 Main St.',
- :city => 'Derry',
- :state => 'NH',
- :zip => '03038',
- :country => 'US'
- },
+ def test_successful_purchase_with_some_empty_address_parts
+ assert response = @gateway.purchase(10010, @credit_card1, {
+ order_id: '1',
+ email: 'wow@example.com',
+ billing_address: {
}
+ })
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_debt_repayment_flag
+ assert response = @gateway.purchase(10010, @credit_card1, @options.merge(debt_repayment: true))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_3ds_fields
+ options = @options.merge({
+ order_source: '3dsAuthenticated',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA='
+ })
+ assert response = @gateway.purchase(10010, @credit_card1, options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay
+ assert response = @gateway.purchase(10010, @decrypted_apple_pay)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_android_pay
+ assert response = @gateway.purchase(10000, @decrypted_android_pay)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_paypage_registration_with_month_year_verification
+ paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new(
+ 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw==',
+ month: '11',
+ year: '12',
+ verification_value: '123',
+ name: 'Joe Payer'
+ )
+
+ assert response = @gateway.purchase(5090, paypage_registration, billing_address: address)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_merchant_data
+ options = @options.merge(
+ affiliate: 'some-affiliate',
+ campaign: 'super-awesome-campaign',
+ merchant_grouping_id: 'brilliant-group'
)
+ assert response = @gateway.purchase(10010, @credit_card1, options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_echeck
+ options = @options.merge({
+ order_id: '42',
+ order_source: 'telephone'
+ })
+ assert response = @gateway.purchase(2004, @check, options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_paypage_registration_without_month_year_verification
+ paypage_registration = ActiveMerchant::Billing::LitlePaypageRegistration.new(
+ 'XkNDRGZDTGZyS2RBSTVCazJNSmdWam5TQ2gyTGhydFh0Mk5qZ0Z3cVp5VlNBN00rcGRZdHF6amFRWEttbVBnYw=='
+ )
+
+ assert response = @gateway.purchase(5090, paypage_registration, billing_address: address)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(60060, @credit_card2, {
+ :order_id=>'6',
+ :billing_address=>{
+ :name => 'Joe Green',
+ :address1 => '6 Main St.',
+ :city => 'Derry',
+ :state => 'NH',
+ :zip => '03038',
+ :country => 'US'
+ },
+ })
assert_failure response
assert_equal 'Insufficient Funds', response.message
end
- def test_authorization_capture_refund_void
- #Auth
- assert auth_response = @gateway.authorize(10010, @credit_card1, @options)
+ def test_authorize_capture_refund_void
+ assert auth = @gateway.authorize(10010, @credit_card1, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+
+ assert refund = @gateway.refund(nil, capture.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+
+ assert void = @gateway.void(refund.authorization)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_authorize_and_capture_with_stored_credential_recurring
+ credit_card = CreditCard.new(@credit_card_hash.merge(
+ number: '4100200300011001',
+ month: '05',
+ year: '2021',
+ verification_value: '463'
+ ))
+
+ initial_options = @options.merge(
+ order_id: 'Net_Id1',
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'recurring',
+ initiator: 'merchant',
+ network_transaction_id: nil
+ }
+ )
+ assert auth = @gateway.authorize(4999, credit_card, initial_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert network_transaction_id = auth.params['networkTransactionId']
+
+ assert capture = @gateway.capture(4999, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+
+ used_options = @options.merge(
+ order_id: 'Net_Id1a',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'recurring',
+ initiator: 'merchant',
+ network_transaction_id: network_transaction_id
+ }
+ )
+ assert auth = @gateway.authorize(4999, credit_card, used_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ assert capture = @gateway.capture(4999, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_authorize_and_capture_with_stored_credential_installment
+ credit_card = CreditCard.new(@credit_card_hash.merge(
+ number: '4457010000000009',
+ month: '01',
+ year: '2021',
+ verification_value: '349'
+ ))
+
+ initial_options = @options.merge(
+ order_id: 'Net_Id2',
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'installment',
+ initiator: 'merchant',
+ network_transaction_id: nil
+ }
+ )
+ assert auth = @gateway.authorize(5500, credit_card, initial_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert network_transaction_id = auth.params['networkTransactionId']
+
+ assert capture = @gateway.capture(5500, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+
+ used_options = @options.merge(
+ order_id: 'Net_Id2a',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'installment',
+ initiator: 'merchant',
+ network_transaction_id: network_transaction_id
+ }
+ )
+ assert auth = @gateway.authorize(5500, credit_card, used_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ assert capture = @gateway.capture(5500, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_authorize_and_capture_with_stored_credential_mit_card_on_file
+ credit_card = CreditCard.new(@credit_card_hash.merge(
+ number: '4457000800000002',
+ month: '01',
+ year: '2021',
+ verification_value: '349'
+ ))
+
+ initial_options = @options.merge(
+ order_id: 'Net_Id3',
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'unscheduled',
+ initiator: 'merchant',
+ network_transaction_id: nil
+ }
+ )
+ assert auth = @gateway.authorize(5500, credit_card, initial_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert network_transaction_id = auth.params['networkTransactionId']
+
+ assert capture = @gateway.capture(5500, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+
+ used_options = @options.merge(
+ order_id: 'Net_Id3a',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'unscheduled',
+ initiator: 'merchant',
+ network_transaction_id: network_transaction_id
+ }
+ )
+ assert auth = @gateway.authorize(2500, credit_card, used_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ assert capture = @gateway.capture(2500, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_authorize_and_capture_with_stored_credential_cit_card_on_file
+ credit_card = CreditCard.new(@credit_card_hash.merge(
+ number: '4457000800000002',
+ month: '01',
+ year: '2021',
+ verification_value: '349'
+ ))
+
+ initial_options = @options.merge(
+ order_id: 'Net_Id3',
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'unscheduled',
+ initiator: 'cardholder',
+ network_transaction_id: nil
+ }
+ )
+ assert auth = @gateway.authorize(5500, credit_card, initial_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert network_transaction_id = auth.params['networkTransactionId']
+
+ assert capture = @gateway.capture(5500, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+
+ used_options = @options.merge(
+ order_id: 'Net_Id3b',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'unscheduled',
+ initiator: 'cardholder',
+ network_transaction_id: network_transaction_id
+ }
+ )
+ assert auth = @gateway.authorize(4000, credit_card, used_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ assert capture = @gateway.capture(4000, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_purchase_with_stored_credential_cit_card_on_file_non_ecommerce
+ credit_card = CreditCard.new(@credit_card_hash.merge(
+ number: '4457000800000002',
+ month: '01',
+ year: '2021',
+ verification_value: '349'
+ ))
+
+ initial_options = @options.merge(
+ order_id: 'Net_Id3',
+ order_source: '3dsAuthenticated',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'unscheduled',
+ initiator: 'cardholder',
+ network_transaction_id: nil
+ }
+ )
+ assert auth = @gateway.purchase(5500, credit_card, initial_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert network_transaction_id = auth.params['networkTransactionId']
+
+ used_options = @options.merge(
+ order_id: 'Net_Id3b',
+ order_source: '3dsAuthenticated',
+ xid: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ cavv: 'BwABBJQ1AgAAAAAgJDUCAAAAAAA=',
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'unscheduled',
+ initiator: 'cardholder',
+ network_transaction_id: network_transaction_id
+ }
+ )
+ assert auth = @gateway.purchase(4000, credit_card, used_options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ end
+
+ def test_void_with_echeck
+ options = @options.merge({
+ order_id: '42',
+ order_source: 'telephone'
+ })
+ assert sale = @gateway.purchase(2004, @check, options)
+
+ assert void = @gateway.void(sale.authorization)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_void_authorization
+ assert auth = @gateway.authorize(10010, @credit_card1, @options)
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_unsuccessful_void
+ assert void = @gateway.void('123456789012345360;authorization;100')
+ assert_failure void
+ assert_equal 'No transaction found with specified litleTxnId', void.message
+ end
+
+ def test_partial_refund
+ assert purchase = @gateway.purchase(10010, @credit_card1, @options)
+
+ assert refund = @gateway.refund(444, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_partial_refund_with_echeck
+ options = @options.merge({
+ order_id: '82',
+ order_source: 'telephone'
+ })
+ assert purchase = @gateway.purchase(2004, @check, options)
- assert_success auth_response
- assert_equal 'Approved', auth_response.message
+ assert refund = @gateway.refund(444, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_partial_capture
+ assert auth = @gateway.authorize(10010, @credit_card1, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ assert capture = @gateway.capture(5005, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_full_amount_capture
+ assert auth = @gateway.authorize(10010, @credit_card1, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
- #Capture the auth
- assert capture_response = @gateway.capture(10010, auth_response.authorization)
- assert_success capture_response
- assert_equal 'Approved', capture_response.message
+ assert capture = @gateway.capture(10010, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
- #Credit against the Capture
- capture_litle_txn_id = capture_response.params['litleOnlineResponse']['captureResponse']['litleTxnId']
- assert credit_response = @gateway.refund(10010, capture_litle_txn_id)
- assert_success credit_response
- assert_equal 'Approved', credit_response.message
+ def test_nil_amount_capture
+ assert auth = @gateway.authorize(10010, @credit_card1, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
- #Void that credit
- credit_litle_txn_id = credit_response.params['litleOnlineResponse']['creditResponse']['litleTxnId']
- assert void_response = @gateway.void(credit_litle_txn_id)
- assert_success void_response
- assert_equal 'Approved', void_response.message
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
end
def test_capture_unsuccessful
- assert capture_response = @gateway.capture(10010, 123456789012345360)
+ assert capture_response = @gateway.capture(10010, '123456789012345360')
assert_failure capture_response
assert_equal 'No transaction found with specified litleTxnId', capture_response.message
end
def test_refund_unsuccessful
- assert credit_response = @gateway.refund(10010, 123456789012345360)
+ assert credit_response = @gateway.refund(10010, '123456789012345360')
assert_failure credit_response
assert_equal 'No transaction found with specified litleTxnId', credit_response.message
end
def test_void_unsuccessful
- assert void_response = @gateway.void(123456789012345360)
+ assert void_response = @gateway.void('123456789012345360')
assert_failure void_response
assert_equal 'No transaction found with specified litleTxnId', void_response.message
end
@@ -137,10 +597,20 @@ def test_store_successful
assert_success store_response
assert_equal 'Account number was successfully registered', store_response.message
- assert_equal '445711', store_response.params['litleOnlineResponse']['registerTokenResponse']['bin']
- assert_equal 'VI', store_response.params['litleOnlineResponse']['registerTokenResponse']['type'] #type is on Object in 1.8.7 - later versions can use .registerTokenResponse.type
- assert_equal '801', store_response.params['litleOnlineResponse']['registerTokenResponse']['response']
- assert_equal '1111222233330123', store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken']
+ assert_equal '445711', store_response.params['bin']
+ assert_equal 'VI', store_response.params['type']
+ assert_equal '801', store_response.params['response']
+ assert_equal '1111222233330123', store_response.params['litleToken']
+ end
+
+ def test_store_with_paypage_registration_id_successful
+ paypage_registration_id = 'cDZJcmd1VjNlYXNaSlRMTGpocVZQY1NNlYE4ZW5UTko4NU9KK3p1L1p1VzE4ZWVPQVlSUHNITG1JN2I0NzlyTg='
+ assert store_response = @gateway.store(paypage_registration_id, :order_id => '50')
+
+ assert_success store_response
+ assert_equal 'Account number was successfully registered', store_response.message
+ assert_equal '801', store_response.params['response']
+ assert_equal '1111222233334444', store_response.params['litleToken']
end
def test_store_unsuccessful
@@ -149,29 +619,92 @@ def test_store_unsuccessful
assert_failure store_response
assert_equal 'Credit card number was invalid', store_response.message
- assert_equal '820', store_response.params['litleOnlineResponse']['registerTokenResponse']['response']
+ assert_equal '820', store_response.params['response']
end
def test_store_and_purchase_with_token_successful
credit_card = CreditCard.new(@credit_card_hash.merge(:number => '4100280190123000'))
assert store_response = @gateway.store(credit_card, :order_id => '50')
+ assert_success store_response
+
+ token = store_response.authorization
+ assert_equal store_response.params['litleToken'], token
+
+ assert response = @gateway.purchase(10010, token)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+ def test_echeck_store_and_purchase
+ assert store_response = @gateway.store(@store_check)
assert_success store_response
+ assert_equal 'Account number was successfully registered', store_response.message
token = store_response.authorization
- assert_equal store_response.params['litleOnlineResponse']['registerTokenResponse']['litleToken'], token
-
- options = {
- :order_id => '12345',
- :token => {
- :month => credit_card.month,
- :year => credit_card.year
- }
- }
+ assert_equal store_response.params['litleToken'], token
+
+ assert response = @gateway.purchase(10010, token)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
- assert response = @gateway.purchase(10010, token, options)
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card1, @options)
assert_success response
assert_equal 'Approved', response.message
+ assert_success response.responses.last, 'The void should succeed'
end
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@credit_card_nsf, @options)
+ assert_failure response
+ assert_match %r{Insufficient Funds}, response.message
+ end
+
+ def test_successful_purchase_with_dynamic_descriptors
+ assert response = @gateway.purchase(10010, @credit_card1, @options.merge(
+ descriptor_name: 'SuperCompany',
+ descriptor_phone: '9193341121'
+ ))
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_unsuccessful_xml_schema_validation
+ credit_card = CreditCard.new(@credit_card_hash.merge(:number => '123456'))
+ assert store_response = @gateway.store(credit_card, :order_id => '51')
+
+ assert_failure store_response
+ assert_match(/^Error validating xml data against the schema/, store_response.message)
+ assert_equal '1', store_response.params['response']
+ end
+
+ def test_purchase_scrubbing
+ credit_card = CreditCard.new(@credit_card_hash.merge(verification_value: '999'))
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(10010, credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_echeck_scrubbing
+ options = @options.merge({
+ order_id: '42',
+ order_source: 'telephone'
+ })
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(2004, @check, options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_maxipago_test.rb b/test/remote/gateways/remote_maxipago_test.rb
new file mode 100644
index 00000000000..4e6c852937a
--- /dev/null
+++ b/test/remote/gateways/remote_maxipago_test.rb
@@ -0,0 +1,131 @@
+require 'test_helper'
+
+class RemoteMaxipagoTest < Test::Unit::TestCase
+ def setup
+ @gateway = MaxipagoGateway.new(fixtures(:maxipago))
+
+ @amount = 1000
+ @invalid_amount = 2009
+ @credit_card = credit_card('4111111111111111', verification_value: '444')
+ @invalid_card = credit_card('4111111111111111', year: Time.now.year - 1)
+
+ @options = {
+ order_id: '12345',
+ billing_address: address,
+ description: 'Store Purchase',
+ installments: 3
+ }
+ end
+
+ def test_successful_authorize
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_failed_authorize
+ assert response = @gateway.authorize(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_equal 'The transaction has an expired credit card.', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ amount = @amount
+ authorize = @gateway.authorize(amount, @credit_card, @options)
+ assert_success authorize
+
+ capture = @gateway.capture(amount, authorize.authorization, @options)
+ assert_success capture
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_sans_options
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_currency
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(currency: 'CLP'))
+ assert_success response
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(@invalid_amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, 'bogus')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'VOIDED', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('NOAUTH|0000000')
+ assert_failure response
+ assert_equal 'Unable to validate, original void transaction not found', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'CAPTURED', refund.message
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund_amount = @amount + 10
+ refund = @gateway.refund(refund_amount, purchase.authorization, @options)
+ assert_failure refund
+ assert_equal 'The Return amount is greater than the amount that can be returned.', refund.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'AUTHORIZED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@invalid_card, @options)
+ assert_failure response
+ assert_equal 'The transaction has an expired credit card.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:password], clean_transcript)
+ end
+
+ def test_invalid_login
+ gateway = MaxipagoGateway.new(
+ login: '',
+ password: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_mercado_pago_test.rb b/test/remote/gateways/remote_mercado_pago_test.rb
new file mode 100644
index 00000000000..f6a9e0fc0bb
--- /dev/null
+++ b/test/remote/gateways/remote_mercado_pago_test.rb
@@ -0,0 +1,243 @@
+require 'test_helper'
+
+class RemoteMercadoPagoTest < Test::Unit::TestCase
+ def setup
+ @gateway = MercadoPagoGateway.new(fixtures(:mercado_pago))
+ @argentina_gateway = MercadoPagoGateway.new(fixtures(:mercado_pago_argentina))
+
+ @amount = 500
+ @credit_card = credit_card('4509953566233704')
+ @elo_credit_card = credit_card('5067268650517446',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737'
+ )
+ @cabal_credit_card = credit_card('6035227716427021',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737'
+ )
+ @declined_card = credit_card('4000300011112220')
+ @options = {
+ billing_address: address,
+ shipping_address: address,
+ email: 'user+br@example.com',
+ description: 'Store Purchase'
+ }
+ @processing_options = {
+ binary_mode: false,
+ processing_mode: 'gateway',
+ merchant_account_id: fixtures(:mercado_pago)[:merchant_account_id],
+ fraud_scoring: true,
+ fraud_manual_review: true
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'accredited', response.message
+ end
+
+ def test_successful_purchase_with_elo
+ response = @gateway.purchase(@amount, @elo_credit_card, @options)
+ assert_success response
+ assert_equal 'accredited', response.message
+ end
+
+ def test_successful_purchase_with_cabal
+ response = @argentina_gateway.purchase(@amount, @cabal_credit_card, @options)
+ assert_success response
+ assert_equal 'accredited', response.message
+ end
+
+ def test_successful_purchase_with_binary_false
+ @options.update(binary_mode: false)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'pending_capture', response.message
+ end
+
+ # Requires setup on merchant account
+ def test_successful_purchase_with_processing_mode_gateway
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(@processing_options))
+ assert_success response
+ assert_equal 'accredited', response.message
+ end
+
+ def test_successful_purchase_with_american_express
+ amex_card = credit_card('375365153556885', brand: 'american_express', verification_value: '1234')
+
+ response = @gateway.purchase(@amount, amex_card, @options)
+ assert_success response
+ assert_equal 'accredited', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'rejected', response.error_code
+ assert_equal 'cc_rejected_other_reason', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'pending_capture', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'accredited', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_elo
+ auth = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_success auth
+ assert_equal 'pending_capture', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'accredited', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_cabal
+ auth = @argentina_gateway.authorize(@amount, @cabal_credit_card, @options)
+ assert_success auth
+ assert_equal 'pending_capture', auth.message
+
+ assert capture = @argentina_gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'accredited', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'cc_rejected_other_reason', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount+1, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'accredited', capture.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'json_parse_error', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal nil, refund.message
+ end
+
+ def test_successful_refund_with_elo
+ purchase = @gateway.purchase(@amount, @elo_credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal nil, refund.message
+ end
+
+ def test_successful_refund_with_cabal
+ purchase = @argentina_gateway.purchase(@amount, @cabal_credit_card, @options)
+ assert_success purchase
+
+ assert refund = @argentina_gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal nil, refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'Not Found', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'by_collector', void.message
+ end
+
+ def test_successful_void_with_elo
+ auth = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'by_collector', void.message
+ end
+
+ def test_successful_void_with_cabal
+ auth = @argentina_gateway.authorize(@amount, @cabal_credit_card, @options)
+ assert_success auth
+
+ assert void = @argentina_gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'by_collector', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'json_parse_error', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{pending_capture}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{cc_rejected_other_reason}, response.message
+ end
+
+ def test_invalid_login
+ gateway = MercadoPagoGateway.new(access_token: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid access parameters}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:access_token], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_merchant_e_solutions_test.rb b/test/remote/gateways/remote_merchant_e_solutions_test.rb
index 7ce2c5aaf8c..7226be98b4d 100644
--- a/test/remote/gateways/remote_merchant_e_solutions_test.rb
+++ b/test/remote/gateways/remote_merchant_e_solutions_test.rb
@@ -2,7 +2,7 @@
class RemoteMerchantESolutionTest < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
+ Base.mode = :test
@gateway = MerchantESolutionsGateway.new(fixtures(:merchant_esolutions))
@@ -11,6 +11,7 @@ def setup
@declined_card = credit_card('4111111111111112')
@options = {
+ :order_id => '123',
:billing_address => {
:name => 'John Doe',
:address1 => '123 State Street',
@@ -39,7 +40,14 @@ def test_successful_purchase
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Card No. Error', response.message
+ assert_equal 'Invalid card number', response.message
+ end
+
+ def test_purchase_with_long_order_id
+ options = {order_id: 'thisislongerthan17characters'}
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
end
def test_authorize_and_capture
@@ -49,7 +57,7 @@ def test_authorize_and_capture
assert_equal 'This transaction has been approved', auth.message
assert auth.authorization
let_mes_catch_up
- assert capture = @gateway.capture(amount, auth.authorization)
+ assert capture = @gateway.capture(amount, auth.authorization, @options)
assert_success capture
assert_equal 'This transaction has been approved', capture.message
end
@@ -141,7 +149,7 @@ def test_unsuccessful_avs_check_with_bad_zip
}
assert response = @gateway.purchase(@amount, @credit_card, options)
assert_equal 'A', response.avs_result['code']
- assert_equal 'Street address matches, but 5-digit and 9-digit postal code do not match.', response.avs_result['message']
+ assert_equal 'Street address matches, but postal code does not match.', response.avs_result['message']
assert_equal 'Y', response.avs_result['street_match']
assert_equal 'N', response.avs_result['postal_match']
end
@@ -149,7 +157,7 @@ def test_unsuccessful_avs_check_with_bad_zip
def test_successful_cvv_check
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'M', response.cvv_result['code']
- assert_equal 'Match', response.cvv_result['message']
+ assert_equal 'CVV matches', response.cvv_result['message']
end
def test_unsuccessful_cvv_check
@@ -163,7 +171,7 @@ def test_unsuccessful_cvv_check
})
assert response = @gateway.purchase(@amount, credit_card, @options)
assert_equal 'N', response.cvv_result['code']
- assert_equal 'No Match', response.cvv_result['message']
+ assert_equal 'CVV does not match', response.cvv_result['message']
end
def test_invalid_login
@@ -181,4 +189,25 @@ def test_connection_failure_404_notfound_with_purchase
assert_failure response
assert_equal 'Failed with 404 Not Found', response.message
end
+
+ def test_successful_purchase_with_3dsecure_params
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(
+ { :xid => 'ERERERERERERERERERERERERERE=',
+ :cavv => 'ERERERERERERERERERERERERERE='
+ }))
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_match(%r{cvv2\=\[FILTERED\]}, transcript)
+ assert_no_match(%r{cvv2=#{@credit_card.verification_value}}, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_merchant_one_test.rb b/test/remote/gateways/remote_merchant_one_test.rb
index 936cb70235e..0e4f98fb23e 100644
--- a/test/remote/gateways/remote_merchant_one_test.rb
+++ b/test/remote/gateways/remote_merchant_one_test.rb
@@ -31,34 +31,34 @@ def test_successful_purchase
assert_equal 'SUCCESS', response.message
end
- def test_unsuccessful_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
- assert_failure response
- assert response.message.include? 'Invalid Credit Card Number'
- end
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert response.message.include? 'Invalid Credit Card Number'
+ end
- def test_authorize_and_capture
- amount = @amount
- assert auth = @gateway.authorize(amount, @credit_card, @options)
- assert_success auth
- assert_equal 'SUCCESS', auth.message
- assert auth.authorization, auth.to_yaml
- assert capture = @gateway.capture(amount, auth.authorization)
- assert_success capture
- end
+ def test_authorize_and_capture
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'SUCCESS', auth.message
+ assert auth.authorization, auth.to_yaml
+ assert capture = @gateway.capture(amount, auth.authorization)
+ assert_success capture
+ end
- def test_failed_capture
+ def test_failed_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
- end
+ end
- def test_invalid_login
- gateway = MerchantOneGateway.new(
- :username => 'nnn',
- :password => 'nnn'
- )
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_equal 'Authentication Failed', response.message
- end
+ def test_invalid_login
+ gateway = MerchantOneGateway.new(
+ :username => 'nnn',
+ :password => 'nnn'
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication Failed', response.message
+ end
end
diff --git a/test/remote/gateways/remote_merchant_partners_test.rb b/test/remote/gateways/remote_merchant_partners_test.rb
new file mode 100644
index 00000000000..846415b674b
--- /dev/null
+++ b/test/remote/gateways/remote_merchant_partners_test.rb
@@ -0,0 +1,162 @@
+require 'test_helper'
+
+class RemoteMerchantPartnersTest < Test::Unit::TestCase
+ def setup
+ @gateway = MerchantPartnersGateway.new(fixtures(:merchant_partners))
+
+ @amount = 100
+ @credit_card = credit_card('4003000123456781')
+ @declined_card = credit_card('4003000123456782')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_invalid_login
+ gateway = MerchantPartnersGateway.new(
+ account_id: 'TEST0',
+ merchant_pin: '1'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/Invalid account/, response.message)
+ assert response.params['result'].start_with?('DECLINED')
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/Invalid account/, response.message)
+ assert response.params['result'].start_with?('DECLINED')
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'BAD')
+ assert_failure response
+ assert_equal 'Missing account number', response.message
+ assert response.params['result'].start_with?('DECLINED')
+ end
+
+ def test_successful_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Invalid acct type', response.message
+ assert response.params['result'].start_with?('DECLINED')
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ assert_equal 'Missing account number', response.message
+ assert response.params['result'].start_with?('DECLINED')
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/Invalid account/, response.message)
+ assert response.params['result'].start_with?('DECLINED')
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Succeeded}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match(/Invalid account/, response.message)
+ assert response.params['result'].start_with?('DECLINED')
+ end
+
+ def test_successful_store_and_purchase
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+
+ purchase = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success purchase
+ assert_equal 'Succeeded', purchase.message
+ end
+
+ def test_successful_store_and_credit
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+
+ credit = @gateway.credit(@amount, response.authorization, @options)
+ assert_success credit
+ assert_equal 'Succeeded', credit.message
+ end
+
+ def test_failed_store
+ response = @gateway.store(@declined_card, @options)
+ assert_failure response
+
+ # Test gateway bombs w/ live-transaction error so can't test
+ # assert_equal "Invalid account number", response.message
+ # assert_equal "", response.error_code
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:merchant_pin], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_merchant_ware_test.rb b/test/remote/gateways/remote_merchant_ware_test.rb
index 399f0552ce6..b4b4dc2c899 100644
--- a/test/remote/gateways/remote_merchant_ware_test.rb
+++ b/test/remote/gateways/remote_merchant_ware_test.rb
@@ -4,7 +4,7 @@ class RemoteMerchantWareTest < Test::Unit::TestCase
def setup
@gateway = MerchantWareGateway.new(fixtures(:merchant_ware))
- @amount = rand(1000) + 200
+ @amount = rand(200..1199)
@credit_card = credit_card('5424180279791732', {:brand => 'master'})
@@ -21,7 +21,7 @@ def test_successful_authorization
end
def test_unsuccessful_authorization
- @credit_card.number = "1234567890123"
+ @credit_card.number = '1234567890123'
assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
end
@@ -33,7 +33,7 @@ def test_successful_purchase
end
def test_unsuccessful_purchase
- @credit_card.number = "1234567890123"
+ @credit_card.number = '1234567890123'
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
end
diff --git a/test/remote/gateways/remote_merchant_ware_version_four_test.rb b/test/remote/gateways/remote_merchant_ware_version_four_test.rb
index 117ef58205f..155bdc2e460 100644
--- a/test/remote/gateways/remote_merchant_ware_version_four_test.rb
+++ b/test/remote/gateways/remote_merchant_ware_version_four_test.rb
@@ -3,18 +3,17 @@
class RemoteMerchantWareVersionFourTest < Test::Unit::TestCase
def setup
@gateway = MerchantWareVersionFourGateway.new(fixtures(:merchant_ware_version_four))
-
- @amount = rand(1000) + 200
-
+ @amount = rand(200..1199)
@credit_card = credit_card('5424180279791732', {:brand => 'master'})
+ @declined_card = credit_card('1234567890123')
@options = {
- :order_id => generate_unique_id[0,8],
+ :order_id => generate_unique_id[0, 8],
:billing_address => address
}
@reference_purchase_options = {
- :order_id => generate_unique_id[0,8]
+ :order_id => generate_unique_id[0, 8]
}
end
@@ -25,7 +24,7 @@ def test_successful_authorization
end
def test_unsuccessful_authorization
- @credit_card.number = "1234567890123"
+ @credit_card.number = '1234567890123'
assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
end
@@ -37,7 +36,7 @@ def test_successful_purchase
end
def test_unsuccessful_purchase
- @credit_card.number = "1234567890123"
+ @credit_card.number = '1234567890123'
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
end
@@ -71,8 +70,8 @@ def test_purchase_and_reference_purchase
assert purchase.authorization
assert reference_purchase = @gateway.purchase(@amount,
- purchase.authorization,
- @reference_purchase_options)
+ purchase.authorization,
+ @reference_purchase_options)
assert_success reference_purchase
assert_not_nil reference_purchase.authorization
end
@@ -101,4 +100,27 @@ def test_invalid_login
assert_failure response
assert_equal 'Invalid Credentials.', response.message
end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card number.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_merchant_warrior_test.rb b/test/remote/gateways/remote_merchant_warrior_test.rb
index dadef6c86a4..d9c5e826c4a 100644
--- a/test/remote/gateways/remote_merchant_warrior_test.rb
+++ b/test/remote/gateways/remote_merchant_warrior_test.rb
@@ -10,13 +10,13 @@ def setup
@credit_card = credit_card(
'5123456789012346',
:month => 5,
- :year => 17,
+ :year => Time.now.year + 2,
:verification_value => '123',
:brand => 'master'
)
@options = {
- :address => {
+ :billing_address => {
:name => 'Longbob Longsen',
:country => 'AU',
:state => 'Queensland',
@@ -24,7 +24,7 @@ def setup
:address1 => '123 test st',
:zip => '4000'
},
- :transaction_product => 'TestProduct'
+ :description => 'TestProduct'
}
end
@@ -32,13 +32,13 @@ def test_successful_authorize
assert auth = @gateway.authorize(@success_amount, @credit_card, @options)
assert_success auth
assert_equal 'Transaction approved', auth.message
- assert_not_nil auth.params["transaction_id"]
- assert_equal auth.params["transaction_id"], auth.authorization
+ assert_not_nil auth.params['transaction_id']
+ assert_equal auth.params['transaction_id'], auth.authorization
assert capture = @gateway.capture(@success_amount, auth.authorization)
assert_success capture
- assert_not_nil capture.params["transaction_id"]
- assert_equal capture.params["transaction_id"], capture.authorization
+ assert_not_nil capture.params['transaction_id']
+ assert_equal capture.params['transaction_id'], capture.authorization
assert_not_equal auth.authorization, capture.authorization
end
@@ -46,16 +46,16 @@ def test_successful_purchase
assert purchase = @gateway.purchase(@success_amount, @credit_card, @options)
assert_equal 'Transaction approved', purchase.message
assert_success purchase
- assert_not_nil purchase.params["transaction_id"]
- assert_equal purchase.params["transaction_id"], purchase.authorization
+ assert_not_nil purchase.params['transaction_id']
+ assert_equal purchase.params['transaction_id'], purchase.authorization
end
def test_failed_purchase
assert purchase = @gateway.purchase(@failure_amount, @credit_card, @options)
assert_equal 'Transaction declined', purchase.message
assert_failure purchase
- assert_not_nil purchase.params["transaction_id"]
- assert_equal purchase.params["transaction_id"], purchase.authorization
+ assert_not_nil purchase.params['transaction_id']
+ assert_equal purchase.params['transaction_id'], purchase.authorization
end
def test_successful_refund
@@ -68,7 +68,7 @@ def test_successful_refund
def test_failed_refund
assert refund = @gateway.refund(@success_amount, 'invalid-transaction-id')
- assert_match /Invalid transactionID/, refund.message
+ assert_match %r{Invalid transactionID}, refund.message
assert_failure refund
end
@@ -78,7 +78,7 @@ def test_capture_too_much
assert_equal 'Transaction approved', auth.message
assert capture = @gateway.capture(400, auth.authorization)
- assert_match /Capture amount is greater than the transaction amount/, capture.message
+ assert_match %r{Capture amount is greater than the transaction amount}, capture.message
assert_failure capture
end
@@ -103,4 +103,39 @@ def test_token_auth
assert capture = @gateway.capture(@success_amount, auth.authorization)
assert_success capture
end
+
+ def test_successful_purchase_with_funky_names
+ @credit_card.first_name = 'Phillips & Sons'
+ @credit_card.last_name = "Other-Things; MW. doesn't like"
+ @options[:billing_address][:name] = 'Merchant Warrior wants % alphanumerics'
+
+ assert purchase = @gateway.purchase(@success_amount, @credit_card, @options)
+ assert_equal 'Transaction approved', purchase.message
+ assert_success purchase
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@success_amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_match(%r{paymentCardCSC\=\[FILTERED\]}, transcript)
+ assert_no_match(%r{paymentCardCSC=#{@credit_card.verification_value}}, transcript)
+ assert_scrubbed(@gateway.options[:api_passphrase], transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
+
+ def test_transcript_scrubbing_store
+ transcript = capture_transcript(@gateway) do
+ @gateway.store(@credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:api_passphrase], transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_mercury_certification_test.rb b/test/remote/gateways/remote_mercury_certification_test.rb
index 9020387e86c..56cb9de3234 100644
--- a/test/remote/gateways/remote_mercury_certification_test.rb
+++ b/test/remote/gateways/remote_mercury_certification_test.rb
@@ -1,5 +1,5 @@
require 'test_helper'
-require "support/mercury_helper"
+require 'support/mercury_helper'
class RemoteMercuryCertificationTest < Test::Unit::TestCase
include MercuryHelper
@@ -9,130 +9,106 @@ class RemoteMercuryCertificationTest < Test::Unit::TestCase
def test_sale_and_reversal
close_batch(tokenization_gateway)
- sale = tokenization_gateway.purchase(101, visa, options("1"))
+ sale = tokenization_gateway.purchase(101, visa, options('1'))
assert_success sale
- assert_equal "AP", sale.params["text_response"]
+ assert_equal 'AP', sale.params['text_response']
reversal = tokenization_gateway.void(sale.authorization, options.merge(:try_reversal => true))
assert_success reversal
- assert_equal "REVERSED", reversal.params["text_response"]
+ assert_equal 'REVERSED', reversal.params['text_response']
end
def test_sale_and_void
close_batch(tokenization_gateway)
- sale = tokenization_gateway.purchase(103, visa, options("1"))
+ sale = tokenization_gateway.purchase(103, visa, options('1'))
assert_success sale
- assert_equal "AP", sale.params["text_response"]
+ assert_equal 'AP', sale.params['text_response']
void = tokenization_gateway.void(sale.authorization, options)
assert_success void
- assert_equal "AP", void.params["text_response"]
- end
-
- def test_preauth_capture_and_reversal
- close_batch(tokenization_gateway)
-
- cc = credit_card(
- "4005550000000480",
- :brand => "visa",
- :month => "12",
- :year => "15",
- :verification_value => "123"
- )
-
- preauth = tokenization_gateway.authorize(106, cc, options("1"))
- assert_success preauth
- assert_equal "AP", preauth.params["text_response"]
-
- capture = tokenization_gateway.capture(106, preauth.authorization, options)
- assert_success capture
- assert_equal "AP", capture.params["text_response"]
-
- reversal = tokenization_gateway.void(capture.authorization, options.merge(:try_reversal => true))
- assert_success reversal
- assert_equal "REVERSED", reversal.params["text_response"]
+ assert_equal 'AP', void.params['text_response']
end
def test_return
close_batch(tokenization_gateway)
- credit = tokenization_gateway.credit(109, visa, options("1"))
+ credit = tokenization_gateway.credit(109, visa, options('1'))
assert_success credit
- assert_equal "AP", credit.params["text_response"]
+ assert_equal 'AP', credit.params['text_response']
end
def test_preauth_and_reversal
close_batch(tokenization_gateway)
- preauth = tokenization_gateway.authorize(113, disc, options("1"))
+ preauth = tokenization_gateway.authorize(113, disc, options('1'))
assert_success preauth
- assert_equal "AP", preauth.params["text_response"]
+ assert_equal 'AP', preauth.params['text_response']
reversal = tokenization_gateway.void(preauth.authorization, options.merge(:try_reversal => true))
assert_success reversal
- assert_equal "REVERSED", reversal.params["text_response"]
+ assert_equal 'REVERSED', reversal.params['text_response']
end
def test_preauth_capture_and_reversal
close_batch(tokenization_gateway)
- preauth = tokenization_gateway.authorize(106, visa, options("1"))
+ preauth = tokenization_gateway.authorize(106, visa, options('1'))
assert_success preauth
- assert_equal "AP", preauth.params["text_response"]
+ assert_equal 'AP', preauth.params['text_response']
capture = tokenization_gateway.capture(206, preauth.authorization, options)
assert_success capture
- assert_equal "AP", capture.params["text_response"]
+ assert_equal 'AP', capture.params['text_response']
void = tokenization_gateway.void(capture.authorization, options)
assert_success void
- assert_equal "AP", void.params["text_response"]
+ assert_equal 'AP', void.params['text_response']
end
private
def tokenization_gateway
@tokenization_gateway ||= MercuryGateway.new(
- :login => "023358150511666",
- :password => "xyz"
+ :login => '023358150511666',
+ :password => 'xyz'
)
end
def visa
@visa ||= credit_card(
- "4003000123456781",
- :brand => "visa",
- :month => "12",
- :year => "15",
- :verification_value => "123"
+ '4003000123456781',
+ :brand => 'visa',
+ :month => '12',
+ :year => '15',
+ :verification_value => '123'
)
end
def disc
@disc ||= credit_card(
- "6011000997235373",
- :brand => "discover",
- :month => "12",
- :year => "15",
- :verification_value => "362"
+ '6011000997235373',
+ :brand => 'discover',
+ :month => '12',
+ :year => '15',
+ :verification_value => '362'
)
end
def mc
@mc ||= credit_card(
- "5439750001500248",
- :brand => "master",
- :month => "12",
- :year => "15",
- :verification_value => "123"
+ '5439750001500248',
+ :brand => 'master',
+ :month => '12',
+ :year => '15',
+ :verification_value => '123'
)
end
def options(order_id=nil, other={})
{
:order_id => order_id,
- :description => "ActiveMerchant",
+ :description => 'ActiveMerchant',
}.merge(other)
end
end
diff --git a/test/remote/gateways/remote_mercury_test.rb b/test/remote/gateways/remote_mercury_test.rb
index 5fda0efb03c..4dd1a95b80c 100644
--- a/test/remote/gateways/remote_mercury_test.rb
+++ b/test/remote/gateways/remote_mercury_test.rb
@@ -1,5 +1,5 @@
require 'test_helper'
-require "support/mercury_helper"
+require 'support/mercury_helper'
class RemoteMercuryTest < Test::Unit::TestCase
include MercuryHelper
@@ -9,24 +9,27 @@ def setup
@amount = 100
- @credit_card = credit_card("4003000123456781", :brand => "visa", :month => "12", :year => "15")
+ @credit_card = credit_card('4003000123456781', :brand => 'visa', :month => '12', :year => '18')
+
+ @track_1_data = '%B4003000123456781^LONGSEN/L. ^18121200000000000000**123******?*'
+ @track_2_data = ';5413330089010608=2512101097750213?'
@options = {
- :order_id => "1",
- :description => "ActiveMerchant"
+ :order_id => 'c111111111.1',
+ :description => 'ActiveMerchant'
}
@options_with_billing = @options.merge(
:merchant => '999',
:billing_address => {
- :address1 => '4 Corporate Square',
+ :address1 => '4 Corporate SQ',
:zip => '30329'
}
)
@full_options = @options_with_billing.merge(
:ip => '123.123.123.123',
- :merchant => "Open Dining",
- :customer => "Tim",
- :tax => "5"
+ :merchant => 'Open Dining',
+ :customer => 'Tim',
+ :tax => '5'
)
close_batch
@@ -45,15 +48,7 @@ def test_successful_authorize_and_capture
def test_failed_authorize
response = @gateway.authorize(1100, @credit_card, @options)
assert_failure response
- assert_equal "DECLINE", response.message
- end
-
- def test_reversal
- response = @gateway.authorize(100, @credit_card, @options)
- assert_success response
-
- void = @gateway.void(response.authorization, @options.merge(:try_reversal => true))
- assert_success void
+ assert_equal 'DECLINE', response.message
end
def test_purchase_and_void
@@ -68,20 +63,29 @@ def test_successful_purchase
response = @gateway.purchase(50, @credit_card, @options)
assert_success response
- assert_equal "0.50", response.params["purchase"]
+ assert_equal '0.50', response.params['purchase']
+ end
+
+ def test_store
+ response = @gateway.store(@credit_card, @options)
+
+ assert_success response
+ assert response.params.has_key?('record_no')
+ assert response.params['record_no'] != ''
end
def test_credit
response = @gateway.credit(50, @credit_card, @options)
assert_success response
- assert_equal "0.50", response.params["purchase"], response.inspect
+ assert_equal '0.50', response.params['purchase'], response.inspect
end
def test_failed_purchase
response = @gateway.purchase(1100, @credit_card, @options)
assert_failure response
- assert_equal "DECLINE", response.message
+ assert_equal 'DECLINE', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
end
def test_avs_and_cvv_results
@@ -90,18 +94,35 @@ def test_avs_and_cvv_results
assert_success response
assert_equal(
{
- "code" => "Y",
- "postal_match" => "Y",
- "street_match" => "Y",
- "message" => "Street address and 5-digit postal code match."
+ 'code' => 'Y',
+ 'postal_match' => 'Y',
+ 'street_match' => 'Y',
+ 'message' => 'Street address and 5-digit postal code match.'
},
response.avs_result
)
- assert_equal({"code"=>"M", "message"=>"Match"}, response.cvv_result)
+ assert_equal({'code'=>'M', 'message'=>'CVV matches'}, response.cvv_result)
+ end
+
+ def test_avs_and_cvv_results_with_track_data
+ @credit_card.track_data = @track_1_data
+ response = @gateway.authorize(333, @credit_card, @options_with_billing)
+
+ assert_success response
+ assert_equal(
+ {
+ 'code' => nil,
+ 'postal_match' => nil,
+ 'street_match' => nil,
+ 'message' => nil
+ },
+ response.avs_result
+ )
+ assert_equal({'code'=>'P', 'message'=>'CVV not processed'}, response.cvv_result)
end
def test_partial_capture
- visa_partial_card = credit_card("4005550000000480")
+ visa_partial_card = credit_card('4005550000000480')
response = @gateway.authorize(2354, visa_partial_card, @options)
@@ -119,11 +140,11 @@ def test_authorize_with_bad_expiration_date
@credit_card.year = 2001
response = @gateway.authorize(575, @credit_card, @options_with_billing)
assert_failure response
- assert_equal "INVLD EXP DATE", response.message
+ assert_equal 'INVLD EXP DATE', response.message
end
def test_mastercard_authorize_and_capture_with_refund
- mc = credit_card("5499990123456781", :brand => "master")
+ mc = credit_card('5499990123456781', :brand => 'master')
response = @gateway.authorize(200, mc, @options)
assert_success response
@@ -140,7 +161,7 @@ def test_mastercard_authorize_and_capture_with_refund
end
def test_amex_authorize_and_capture_with_refund
- amex = credit_card("373953244361001", :brand => "american_express", :verification_value => "1234")
+ amex = credit_card('373953244361001', :brand => 'american_express', :verification_value => '1234')
response = @gateway.authorize(201, amex, @options)
assert_success response
@@ -156,7 +177,7 @@ def test_amex_authorize_and_capture_with_refund
end
def test_discover_authorize_and_capture
- discover = credit_card("6011000997235373", :brand => "discover")
+ discover = credit_card('6011000997235373', :brand => 'discover')
response = @gateway.authorize(225, discover, @options_with_billing)
assert_success response
@@ -189,4 +210,46 @@ def test_authorize_and_capture_without_tokenization
assert_success capture
assert_equal '1.00', capture.params['authorize']
end
+
+ def test_successful_authorize_and_capture_with_track_1_data
+ @credit_card.track_data = @track_1_data
+ response = @gateway.authorize(100, @credit_card, @options)
+ assert_success response
+ assert_equal '1.00', response.params['authorize']
+
+ capture = @gateway.capture(nil, response.authorization)
+ assert_success capture
+ assert_equal '1.00', capture.params['authorize']
+ end
+
+ def test_successful_authorize_and_capture_with_track_2_data
+ @credit_card.track_data = @track_2_data
+ response = @gateway.authorize(100, @credit_card, @options)
+ assert_success response
+ assert_equal '1.00', response.params['authorize']
+
+ capture = @gateway.capture(nil, response.authorization)
+ assert_success capture
+ assert_equal '1.00', capture.params['authorize']
+ end
+
+ def test_authorize_and_void
+ response = @gateway.authorize(100, @credit_card, @options)
+ assert_success response
+ assert_equal '1.00', response.params['authorize']
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_metrics_global_test.rb b/test/remote/gateways/remote_metrics_global_test.rb
index 4748cdc23c3..3669fd41a11 100644
--- a/test/remote/gateways/remote_metrics_global_test.rb
+++ b/test/remote/gateways/remote_metrics_global_test.rb
@@ -3,7 +3,7 @@
class MetricsGlobalTest < Test::Unit::TestCase
def setup
Base.mode = :test
-
+
@gateway = MetricsGlobalGateway.new(fixtures(:metrics_global))
@amount = 100
@credit_card = credit_card('4111111111111111', :verification_value => '999')
@@ -13,7 +13,7 @@ def setup
:description => 'Store purchase'
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -21,7 +21,7 @@ def test_successful_purchase
assert_equal 'This transaction has been approved', response.message
assert response.authorization
end
-
+
def test_declined_authorization
@amount = 10
assert response = @gateway.purchase(@amount, @credit_card, @options)
@@ -29,71 +29,71 @@ def test_declined_authorization
assert response.test?
assert_equal 'This transaction has been declined', response.message
end
-
+
def test_successful_authorization
assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal 'This transaction has been approved', response.message
assert response.authorization
end
-
+
def test_authorization_and_capture
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
-
+
assert capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
assert_equal 'This transaction has been approved', capture.message
end
-
+
def test_authorization_and_void
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
-
+
assert void = @gateway.void(authorization.authorization)
assert_success void
assert_equal 'This transaction has been approved', void.message
end
-
+
def test_bad_login
gateway = MetricsGlobalGateway.new(
:login => 'X',
:password => 'Y'
)
-
+
assert response = gateway.purchase(@amount, @credit_card)
-
+
assert_equal Response, response.class
- assert_equal ["avs_result_code",
- "card_code",
- "response_code",
- "response_reason_code",
- "response_reason_text",
- "transaction_id"], response.params.keys.sort
+ assert_equal ['avs_result_code',
+ 'card_code',
+ 'response_code',
+ 'response_reason_code',
+ 'response_reason_text',
+ 'transaction_id'], response.params.keys.sort
assert_match(/Authentication Failed/, response.message)
-
+
assert_equal false, response.success?
end
-
+
def test_using_test_request
gateway = MetricsGlobalGateway.new(
:login => 'X',
:password => 'Y'
)
-
+
assert response = gateway.purchase(@amount, @credit_card)
-
+
assert_equal Response, response.class
- assert_equal ["avs_result_code",
- "card_code",
- "response_code",
- "response_reason_code",
- "response_reason_text",
- "transaction_id"], response.params.keys.sort
-
+ assert_equal ['avs_result_code',
+ 'card_code',
+ 'response_code',
+ 'response_reason_code',
+ 'response_reason_text',
+ 'transaction_id'], response.params.keys.sort
+
assert_match(/Authentication Failed/, response.message)
-
- assert_equal false, response.success?
+
+ assert_equal false, response.success?
end
end
diff --git a/test/remote/gateways/remote_micropayment_test.rb b/test/remote/gateways/remote_micropayment_test.rb
new file mode 100644
index 00000000000..fb4589e7d71
--- /dev/null
+++ b/test/remote/gateways/remote_micropayment_test.rb
@@ -0,0 +1,151 @@
+require 'test_helper'
+
+class RemoteMicropaymentTest < Test::Unit::TestCase
+ def setup
+ @gateway = MicropaymentGateway.new(fixtures(:micropayment))
+
+ @amount = 250
+ @credit_card = credit_card('4111111111111111', verification_value: '666')
+ @declined_card = credit_card('4111111111111111')
+
+ @options = {
+ order_id: generate_unique_id,
+ description: 'Eggcellent',
+ billing_address: address
+ }
+ end
+
+ def test_invalid_login
+ gateway = MicropaymentGateway.new(access_key: 'invalid', api_key: 'invalid')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authorization failed - Reason: api accesskey wrong', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'AS stellt falsches Routing fest', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\w+\|.+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_recurring
+ @credit_card.verification_value = ''
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(recurring: true))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\w+\|.+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_partial_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ capture = @gateway.capture(100, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'AS stellt falsches Routing fest', response.message
+ assert_equal 'ipg92', response.params['transactionResultCode']
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '1|2')
+ assert_failure response
+ assert_equal '"sessionId" with the value "1" does not exist', response.message
+ assert_equal '3110', response.params['error']
+ end
+
+ def test_successful_void_for_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_successful_authorize_and_capture_and_refund
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(recurring: false))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\w+\|.+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+
+ refund = @gateway.refund(@amount, capture.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal '"transactionId" is empty', response.message
+ assert_equal '3101', response.params['error']
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ assert_equal '"transactionId" is empty', response.message
+ assert_equal '3101', response.params['error']
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'Succeeded', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'AS stellt falsches Routing fest', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:access_key], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_migs_test.rb b/test/remote/gateways/remote_migs_test.rb
index adbf66b1f38..69ea544201c 100644
--- a/test/remote/gateways/remote_migs_test.rb
+++ b/test/remote/gateways/remote_migs_test.rb
@@ -2,20 +2,35 @@
require 'net/http'
class RemoteMigsTest < Test::Unit::TestCase
+ include ActiveMerchant::NetworkConnectionRetries
+ include ActiveMerchant::PostsData
+
def setup
- @gateway = MigsGateway.new(fixtures(:migs_purchase))
- @capture_gateway = MigsGateway.new(fixtures(:migs_capture))
+ @gateway = MigsGateway.new(fixtures(:migs))
@amount = 100
@declined_amount = 105
- @visa = credit_card('4005550000000001', :month => 5, :year => 2017, :brand => 'visa')
- @master = credit_card('5123456789012346', :month => 5, :year => 2017, :brand => 'master')
- @amex = credit_card('371449635311004', :month => 5, :year => 2017, :brand => 'american_express')
- @diners = credit_card('30123456789019', :month => 5, :year => 2017, :brand => 'diners_club')
+ @visa = credit_card('4987654321098769', :month => 5, :year => 2021, :brand => 'visa')
+ @master = credit_card('5123456789012346', :month => 5, :year => 2021, :brand => 'master')
+ @amex = credit_card('371449635311004', :month => 5, :year => 2021, :brand => 'american_express')
+ @diners = credit_card('30123456789019', :month => 5, :year => 2021, :brand => 'diners_club')
@credit_card = @visa
+ @valid_tx_source = 'MOTO'
+ @invalid_tx_source = 'penguin'
+
@options = {
- :order_id => '1'
+ :order_id => '1',
+ :currency => 'SAR'
+ }
+
+ @three_ds_options = {
+ :VerType => '3DS',
+ :VerToken => 'AAACAFBEUBgoAhEAIURQAAAAAAA=',
+ '3DSXID' => 'NWJlZDJmYzkyMTU1NGEwNzk1YjA=',
+ '3DSECI' => '02',
+ '3DSenrolled' => 'Y',
+ '3DSstatus' => 'A'
}
end
@@ -23,22 +38,22 @@ def test_server_purchase_url
options = {
:order_id => 1,
:unique_id => 9,
- :return_url => 'http://localhost:8080/payments/return'
+ :return_url => 'http://localhost:8080/payments/return',
+ :currency => 'SAR'
}
choice_url = @gateway.purchase_offsite_url(@amount, options)
- assert_response_contains 'Pay securely by clicking on the card logo below', choice_url
+
+ assert_response_match(/Pay securely .* by clicking on the card logo below/, choice_url)
responses = {
- 'visa' => 'You have chosen VISA',
- 'master' => 'You have chosen MasterCard',
- 'diners_club' => 'You have chosen Diners Club',
- 'american_express' => 'You have chosen American Express'
+ 'visa' => /You have chosen .*VISA.*/,
+ 'master' => /You have chosen .*MasterCard.*/
}
responses.each_pair do |card_type, response_text|
- url = @capture_gateway.purchase_offsite_url(@amount, options.merge(:card_type => card_type))
- assert_response_contains response_text, url
+ url = @gateway.purchase_offsite_url(@amount, options.merge(:card_type => card_type))
+ assert_response_match response_text, url
end
end
@@ -55,25 +70,83 @@ def test_unsuccessful_purchase
end
def test_authorize_and_capture
- assert auth = @capture_gateway.authorize(@amount, @credit_card, @options)
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal 'Approved', auth.message
- assert capture = @capture_gateway.capture(@amount, auth.authorization, @options)
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
assert_success capture
end
+ def test_authorize_and_void
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_verify
+ assert verify = @gateway.verify(@credit_card, @options)
+ assert_success verify
+ assert_equal 'Approved', verify.message
+ end
+
def test_failed_authorize
- assert response = @capture_gateway.authorize(@declined_amount, @credit_card, @options)
+ assert response = @gateway.authorize(@declined_amount, @credit_card, @options)
assert_failure response
assert_equal 'Declined', response.message
end
def test_refund
- assert payment_response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success payment_response
- assert response = @gateway.refund(@amount, payment_response.authorization, @options)
- assert_success response
- assert_equal 'Approved', response.message
+ # skip "Refunds are not working in the testing envirnment"
+ # assert payment_response = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_success payment_response
+ # assert response = @gateway.refund(@amount, payment_response.authorization, @options)
+ # refute_success response
+ # assert_equal 'Approved', response.message
+ end
+
+ def test_purchase_passes_tx_source
+ # returns a successful response when a valid tx_source parameter is sent
+ assert good_response = @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @valid_tx_source))
+ assert_success good_response
+ assert_equal 'Approved', good_response.message
+
+ # returns a failed response when an invalid tx_source parameter is sent
+ assert bad_response = @gateway.purchase(@amount, @credit_card, @options.merge(tx_source: @invalid_tx_source))
+ assert_failure bad_response
+ end
+
+ def test_capture_passes_tx_source
+ # authorize the credit card in order to then run capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ # returns a successful response when a valid tx_source paramater is sent
+ assert good_response = @gateway.capture(@amount, auth.authorization, @options.merge(tx_source: @valid_tx_source))
+ assert_success good_response
+
+ # returns a failed response when an invalid tx_source parameter is sent
+ assert bad_response = @gateway.capture(@amount, auth.authorization, @options.merge(tx_source: @invalid_tx_source))
+ assert_failure bad_response
+ end
+
+ def test_void_passes_tx_source
+ # authorize the credit card in order to then run capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ # returns a successful response when a valid tx_source paramater is sent
+ assert good_response = @gateway.void(auth.authorization, @options.merge(tx_source: @valid_tx_source))
+ assert_success good_response
+ assert_equal 'Approved', good_response.message
+
+ # returns a failed response when an invalid tx_source parameter is sent
+ assert bad_response = @gateway.void(auth.authorization, @options.merge(tx_source: @invalid_tx_source))
+ assert_failure bad_response
end
def test_status
@@ -90,21 +163,61 @@ def test_invalid_login
assert_equal 'Required field vpc_Merchant was not present in the request', response.message
end
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_transcript_scrubbing_of_advanced_password
+ gateway = MigsGateway.new(fixtures(:migs).merge(advanced_login: 'advlogin', advanced_password: 'advpass'))
+ purchase = gateway.purchase(@amount, @credit_card, @options)
+
+ transcript = capture_transcript(@gateway) do
+ gateway.refund(@amount, purchase.authorization, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:advanced_password], transcript)
+ end
+
+ def test_transcript_scrubbing_of_3ds_cavv_and_xid_values
+ opts = @options.merge(@three_ds_options)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, opts)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(opts[:VerToken], transcript)
+ assert_scrubbed(opts['3DSXID'], transcript)
+ end
+
private
- include ActiveMerchant::PostsData
- def assert_response_contains(text, url)
+ def assert_response_match(regexp, url)
response = https_response(url)
- assert response.body.include?(text)
+ assert_match regexp, response.body
end
def https_response(url, cookie = nil)
- headers = cookie ? {'Cookie' => cookie} : {}
- response = raw_ssl_request(:get, url, nil, headers)
- if response.is_a?(Net::HTTPRedirection)
- new_cookie = [cookie, response['Set-Cookie']].compact.join(';')
- response = https_response(response['Location'], new_cookie)
+ retry_exceptions do
+ headers = cookie ? {'Cookie' => cookie} : {}
+ response = raw_ssl_request(:get, url, nil, headers)
+ if response.is_a?(Net::HTTPRedirection)
+ new_cookie = [cookie, response['Set-Cookie']].compact.join(';')
+ response = https_response(response['Location'], new_cookie)
+ end
+ response
end
- response
end
end
diff --git a/test/remote/gateways/remote_modern_payments_cim_test.rb b/test/remote/gateways/remote_modern_payments_cim_test.rb
index efd3bec9c6f..8c310d0fe17 100644
--- a/test/remote/gateways/remote_modern_payments_cim_test.rb
+++ b/test/remote/gateways/remote_modern_payments_cim_test.rb
@@ -1,47 +1,46 @@
require 'test_helper'
class RemoteModernPaymentsCimTest < Test::Unit::TestCase
-
def setup
@gateway = ModernPaymentsCimGateway.new(fixtures(:modern_payments))
-
+
@amount = 100
@credit_card = credit_card('4111111111111111')
@declined_card = credit_card('4000000000000000')
-
- @options = {
+
+ @options = {
:billing_address => address,
:customer => 'JIMSMITH2000'
}
end
-
+
def test_successful_create_customer
response = @gateway.create_customer(@options)
assert_success response
- assert !response.params["create_customer_result"].blank?
+ assert !response.params['create_customer_result'].blank?
end
-
+
def test_successful_modify_customer_credit_card
customer = @gateway.create_customer(@options)
assert_success customer
-
- customer_id = customer.params["create_customer_result"]
-
+
+ customer_id = customer.params['create_customer_result']
+
credit_card = @gateway.modify_customer_credit_card(customer_id, @credit_card)
assert_success credit_card
- assert !credit_card.params["modify_customer_credit_card_result"].blank?
+ assert !credit_card.params['modify_customer_credit_card_result'].blank?
end
-
+
def test_succsessful_authorize_credit_card_payment
customer = @gateway.create_customer(@options)
assert_success customer
-
- customer_id = customer.params["create_customer_result"]
-
+
+ customer_id = customer.params['create_customer_result']
+
credit_card = @gateway.modify_customer_credit_card(customer_id, @credit_card)
assert_success credit_card
-
+
payment = @gateway.authorize_credit_card_payment(customer_id, @amount)
assert_success payment
end
diff --git a/test/remote/gateways/remote_modern_payments_test.rb b/test/remote/gateways/remote_modern_payments_test.rb
index 0f13927ecdf..e0fff6eeb95 100644
--- a/test/remote/gateways/remote_modern_payments_test.rb
+++ b/test/remote/gateways/remote_modern_payments_test.rb
@@ -4,54 +4,42 @@ class RemoteModernPaymentTest < Test::Unit::TestCase
def setup
@gateway = ModernPaymentsGateway.new(fixtures(:modern_payments))
-
+
@amount = 100
@credit_card = credit_card('4111111111111111')
@declined_card = credit_card('4000000000000000')
-
- @options = {
+
+ @options = {
:order_id => '1',
:billing_address => address,
:description => 'Store Purchase'
}
-
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
-
+
# Test mode seems to not return "approved = true"
assert_failure response
- assert_match /RESPONSECODE=A/, response.params["auth_string"]
+ assert_match %r{RESPONSECODE=A}, response.params['auth_string']
assert_equal ModernPaymentsCimGateway::FAILURE_MESSAGE, response.message
end
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_match /RESPONSECODE=D/, response.params["auth_string"]
+ assert_match %r{RESPONSECODE=D}, response.params['auth_string']
assert_equal ModernPaymentsCimGateway::FAILURE_MESSAGE, response.message
end
- def test_invalid_login
- gateway = ModernPaymentsGateway.new(
- :login => '5000',
- :password => 'password'
- )
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_equal ModernPaymentsCimGateway::FAILURE_MESSAGE, response.message
- end
-
def test_invalid_login
gateway = ModernPaymentsGateway.new(
:login => '',
:password => ''
)
-
+
assert_raises(ActiveMerchant::ResponseError) do
gateway.purchase(@amount, @credit_card, @options)
end
end
-
end
diff --git a/test/remote/gateways/remote_monei_test.rb b/test/remote/gateways/remote_monei_test.rb
new file mode 100755
index 00000000000..984187d4dee
--- /dev/null
+++ b/test/remote/gateways/remote_monei_test.rb
@@ -0,0 +1,166 @@
+require 'test_helper'
+
+class RemoteMoneiTest < Test::Unit::TestCase
+ def setup
+ @gateway = MoneiGateway.new(
+ fixtures(:monei)
+ )
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('5453010000059675')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message
+ end
+
+ def test_successful_purchase_with_3ds
+ options = @options.merge!({
+ three_d_secure: {
+ eci: '05',
+ cavv: 'AAACAgSRBklmQCFgMpEGAAAAAAA=',
+ xid: 'CAACCVVUlwCXUyhQNlSXAAAAAAA='
+ }
+ })
+ response = @gateway.purchase(@amount, @credit_card, options)
+
+ assert_success response
+ assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'invalid cc number/brand combination', response.message
+ end
+
+ def test_failed_purchase_with_3ds
+ options = @options.merge!({
+ three_d_secure: {
+ eci: '05',
+ cavv: 'INVALID_Verification_ID',
+ xid: 'CAACCVVUlwCXUyhQNlSXAAAAAAA='
+ }
+ })
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_failure response
+ assert_equal 'Invalid 3DSecure Verification_ID. Must have Base64 encoding a Length of 28 digits', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_multi_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_failure capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_multi_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_failure refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Request successfully processed in \'Merchant in Connector Test Mode\'', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+
+ assert_equal 'invalid cc number/brand combination', response.message
+ end
+
+ def test_invalid_login
+ gateway = MoneiGateway.new(
+ :sender_id => 'mother',
+ :channel_id => 'there is no other',
+ :login => 'like mother',
+ :pwd => 'so treat Her right'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+end
diff --git a/test/remote/gateways/remote_moneris_test.rb b/test/remote/gateways/remote_moneris_test.rb
index b41ed619018..161f17f6807 100644
--- a/test/remote/gateways/remote_moneris_test.rb
+++ b/test/remote/gateways/remote_moneris_test.rb
@@ -8,8 +8,9 @@ def setup
@amount = 100
@credit_card = credit_card('4242424242424242')
@options = {
- :order_id => generate_unique_id,
- :customer => generate_unique_id
+ :order_id => generate_unique_id,
+ :customer => generate_unique_id,
+ :billing_address => address
}
end
@@ -20,6 +21,87 @@ def test_successful_purchase
assert_false response.authorization.blank?
end
+ def test_successful_first_purchase_with_credential_on_file
+ gateway = MonerisGateway.new(fixtures(:moneris))
+ assert response = gateway.purchase(@amount, @credit_card, @options.merge(issuer_id: '', payment_indicator: 'C', payment_information: '0'))
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ assert_not_empty response.params['issuer_id']
+ end
+
+ def test_successful_purchase_with_cof_enabled_and_no_cof_options
+ gateway = MonerisGateway.new(fixtures(:moneris))
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_non_cof_purchase_with_cof_enabled_and_only_issuer_id_sent
+ gateway = MonerisGateway.new(fixtures(:moneris))
+ assert response = gateway.purchase(@amount, @credit_card, @options.merge(issuer_id: ''))
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ assert_nil response.params['issuer_id']
+ end
+
+ def test_successful_subsequent_purchase_with_credential_on_file
+ gateway = MonerisGateway.new(fixtures(:moneris))
+ assert response = gateway.authorize(
+ @amount,
+ @credit_card,
+ @options.merge(
+ issuer_id: '',
+ payment_indicator: 'C',
+ payment_information: '0'
+ )
+ )
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+
+ assert response2 = gateway.purchase(
+ @amount,
+ @credit_card,
+ @options.merge(
+ order_id: response.authorization,
+ issuer_id: response.params['issuer_id'],
+ payment_indicator: 'U',
+ payment_information: '2'
+ )
+ )
+ assert_success response2
+ assert_equal 'Approved', response2.message
+ assert_false response2.authorization.blank?
+ end
+
+ def test_successful_purchase_with_network_tokenization
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: nil
+ )
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_network_tokenization_apple_pay_source
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: nil,
+ source: :apple_pay
+ )
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
def test_successful_authorization
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
@@ -40,6 +122,18 @@ def test_successful_authorization_and_capture
assert_success response
end
+ def test_successful_authorization_and_capture_and_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+
+ response = @gateway.capture(@amount, response.authorization)
+ assert_success response
+
+ void = @gateway.void(response.authorization, :purchasecorrection => true)
+ assert_success void
+ end
+
def test_successful_authorization_and_void
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
@@ -49,6 +143,34 @@ def test_successful_authorization_and_void
assert_success void
end
+ def test_successful_authorization_with_network_tokenization
+ @credit_card = network_tokenization_credit_card(
+ '4242424242424242',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: nil
+ )
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_and_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ void = @gateway.void(purchase.authorization, :purchasecorrection => true)
+ assert_success void
+ end
+
+ def test_failed_purchase_and_void
+ purchase = @gateway.purchase(101, @credit_card, @options)
+ assert_failure purchase
+
+ void = @gateway.void(purchase.authorization)
+ assert_failure void
+ end
+
def test_successful_purchase_and_refund
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -63,35 +185,41 @@ def test_failed_purchase_from_error
assert_equal 'Declined', response.message
end
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'Approved', response.message
+ end
+
def test_successful_store
assert response = @gateway.store(@credit_card)
assert_success response
- assert_equal "Successfully registered cc details", response.message
- assert response.params["data_key"].present?
- @data_key = response.params["data_key"]
+ assert_equal 'Successfully registered cc details', response.message
+ assert response.params['data_key'].present?
+ @data_key = response.params['data_key']
end
def test_successful_unstore
test_successful_store
assert response = @gateway.unstore(@data_key)
assert_success response
- assert_equal "Successfully deleted cc details", response.message
- assert response.params["data_key"].present?
+ assert_equal 'Successfully deleted cc details', response.message
+ assert response.params['data_key'].present?
end
def test_update
test_successful_store
assert response = @gateway.update(@data_key, @credit_card)
assert_success response
- assert_equal "Successfully updated cc details", response.message
- assert response.params["data_key"].present?
+ assert_equal 'Successfully updated cc details', response.message
+ assert response.params['data_key'].present?
end
def test_successful_purchase_with_vault
test_successful_store
assert response = @gateway.purchase(@amount, @data_key, @options)
assert_success response
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_false response.authorization.blank?
end
@@ -124,14 +252,54 @@ def test_cvv_match_when_enabled
gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true))
assert response = gateway.purchase(1039, @credit_card, @options)
assert_success response
- assert_equal({'code' => 'M', 'message' => 'Match'}, response.cvv_result)
+ assert_equal({'code' => 'M', 'message' => 'CVV matches'}, response.cvv_result)
end
- def test_cvv_no_match_when_enabled
- gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true))
- assert response = gateway.purchase(1053, @credit_card, @options)
+ def test_avs_result_valid_when_enabled
+ gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true))
+
+ assert response = gateway.purchase(1010, @credit_card, @options)
+ assert_success response
+ assert_equal(response.avs_result, {
+ 'code' => 'A',
+ 'message' => 'Street address matches, but postal code does not match.',
+ 'street_match' => 'Y',
+ 'postal_match' => 'N'
+ })
+ end
+
+ def test_avs_result_nil_when_address_absent
+ gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true))
+
+ assert response = gateway.purchase(1010, @credit_card, @options.tap { |x| x.delete(:billing_address) })
assert_success response
- assert_equal({'code' => 'N', 'message' => 'No Match'}, response.cvv_result)
+ assert_equal(response.avs_result, {
+ 'code' => nil,
+ 'message' => nil,
+ 'street_match' => nil,
+ 'postal_match' => nil
+ })
end
+ def test_avs_result_nil_when_efraud_disabled
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal(response.avs_result, {
+ 'code' => nil,
+ 'message' => nil,
+ 'street_match' => nil,
+ 'postal_match' => nil
+ })
+ end
+
+ def test_purchase_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_moneris_us_test.rb b/test/remote/gateways/remote_moneris_us_test.rb
index 6479141661f..dd5c30614f3 100644
--- a/test/remote/gateways/remote_moneris_us_test.rb
+++ b/test/remote/gateways/remote_moneris_us_test.rb
@@ -7,26 +7,59 @@ def setup
@gateway = MonerisUsGateway.new(fixtures(:moneris_us))
@amount = 100
@credit_card = credit_card('4242424242424242')
- @options = {
+ @options = {
:order_id => generate_unique_id,
:billing_address => address,
:description => 'Store Purchase'
}
+ @check = check({
+ routing_number: '011000015',
+ account_number: '1234455',
+ number: 123
+ })
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Approved', response.message
assert_false response.authorization.blank?
end
-
+
+ def test_successful_echeck_purchase
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Registered', response.message
+ assert response.authorization
+ end
+
+ def test_failed_echeck_purchase
+ response = @gateway.purchase(105, check(routing_number: 5), @options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'Unspecified error', response.message
+ end
+
def test_successful_authorization
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_false response.authorization.blank?
end
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert response.authorization
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(credit_card(nil), @options)
+ assert_failure response
+ assert_match %r{Invalid pan parameter}, response.message
+ end
+
def test_failed_authorization
response = @gateway.authorize(105, @credit_card, @options)
assert_failure response
@@ -40,40 +73,40 @@ def test_successful_authorization_and_capture
response = @gateway.capture(@amount, response.authorization)
assert_success response
end
-
+
def test_successful_authorization_and_void
response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert response.authorization
-
+
# Moneris cannot void a preauthorization
# You must capture the auth transaction with an amount of $0.00
void = @gateway.capture(0, response.authorization)
assert_success void
end
-
+
def test_successful_purchase_and_void
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
-
+
void = @gateway.void(purchase.authorization)
assert_success void
end
-
+
def test_failed_purchase_and_void
purchase = @gateway.purchase(101, @credit_card, @options)
assert_failure purchase
-
+
void = @gateway.void(purchase.authorization)
assert_failure void
end
-
- def test_successful_purchase_and_credit
+
+ def test_successful_purchase_and_refund
purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
-
- credit = @gateway.credit(@amount, purchase.authorization)
- assert_success credit
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
end
def test_failed_purchase_from_error
@@ -81,4 +114,148 @@ def test_failed_purchase_from_error
assert_failure response
assert_equal 'Declined', response.message
end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+ assert_equal 'Successfully registered cc details', response.message
+ assert response.params['data_key'].present?
+ @data_key = response.params['data_key']
+ end
+
+ def test_successful_echeck_store
+ assert response = @gateway.store(@check)
+ assert_success response
+ assert_equal 'Successfully registered ach details', response.message
+ assert response.params['data_key'].present?
+ @data_key_echeck = response.params['data_key']
+ end
+
+ def test_successful_unstore
+ test_successful_store
+ assert response = @gateway.unstore(@data_key)
+ assert_success response
+ assert_equal 'Successfully deleted cc details', response.message
+ assert response.params['data_key'].present?
+ end
+
+ def test_successful_echeck_unstore
+ test_successful_echeck_store
+ assert response = @gateway.unstore(@data_key_echeck)
+ assert_success response
+ assert_equal 'Successfully deleted ach details', response.message
+ assert response.params['data_key'].present?
+ end
+
+ def test_update
+ test_successful_store
+ assert response = @gateway.update(@data_key, @credit_card)
+ assert_success response
+ assert_equal 'Successfully updated cc details', response.message
+ assert response.params['data_key'].present?
+ end
+
+ def test_echeck_update
+ test_successful_echeck_store
+ assert response = @gateway.update(@data_key_echeck, @check)
+ assert_success response
+ assert_equal 'Successfully updated ach details', response.message
+ assert response.params['data_key'].present?
+ end
+
+ def test_successful_purchase_with_vault
+ test_successful_store
+ assert response = @gateway.purchase(@amount, @data_key, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_authorization_with_vault
+ test_successful_store
+ assert response = @gateway.authorize(@amount, @data_key, @options)
+ assert_success response
+ assert_false response.authorization.blank?
+ end
+
+ def test_failed_authorization_with_vault
+ test_successful_store
+ response = @gateway.authorize(105, @data_key, @options)
+ assert_failure response
+ end
+
+ def test_cvv_match_when_not_enabled
+ assert response = @gateway.purchase(1039, @credit_card, @options)
+ assert_success response
+ assert_equal({'code' => nil, 'message' => nil}, response.cvv_result)
+ end
+
+ def test_cvv_no_match_when_not_enabled
+ assert response = @gateway.purchase(1053, @credit_card, @options)
+ assert_success response
+ assert_equal({'code' => nil, 'message' => nil}, response.cvv_result)
+ end
+
+ def test_cvv_match_when_enabled
+ gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true))
+ assert response = gateway.purchase(1039, @credit_card, @options)
+ assert_success response
+ assert_equal({'code' => 'M', 'message' => 'CVV matches'}, response.cvv_result)
+ end
+
+ def test_cvv_no_match_when_enabled
+ gateway = MonerisGateway.new(fixtures(:moneris).merge(cvv_enabled: true))
+ assert response = gateway.purchase(1053, @credit_card, @options)
+ assert_success response
+ assert_equal({'code' => 'N', 'message' => 'CVV does not match'}, response.cvv_result)
+ end
+
+ def test_avs_result_valid_when_enabled
+ gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true))
+
+ assert response = gateway.purchase(1010, @credit_card, @options)
+ assert_success response
+ assert_equal(response.avs_result, {
+ 'code' => 'A',
+ 'message' => 'Street address matches, but postal code does not match.',
+ 'street_match' => 'Y',
+ 'postal_match' => 'N'
+ })
+ end
+
+ def test_avs_result_nil_when_address_absent
+ gateway = MonerisGateway.new(fixtures(:moneris).merge(avs_enabled: true))
+
+ assert response = gateway.purchase(1010, @credit_card, @options.tap { |x| x.delete(:billing_address) })
+ assert_success response
+ assert_equal(response.avs_result, {
+ 'code' => nil,
+ 'message' => nil,
+ 'street_match' => nil,
+ 'postal_match' => nil
+ })
+ end
+
+ def test_avs_result_nil_when_efraud_disabled
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal(response.avs_result, {
+ 'code' => nil,
+ 'message' => nil,
+ 'street_match' => nil,
+ 'postal_match' => nil
+ })
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
end
diff --git a/test/remote/gateways/remote_money_movers_test.rb b/test/remote/gateways/remote_money_movers_test.rb
new file mode 100644
index 00000000000..64165cb50d8
--- /dev/null
+++ b/test/remote/gateways/remote_money_movers_test.rb
@@ -0,0 +1,82 @@
+require 'test_helper'
+
+class RemoteMoneyMoversTest < Test::Unit::TestCase
+ def setup
+ @gateway = MoneyMoversGateway.new(fixtures(:money_movers))
+
+ @amount = 100
+ @declined_amount = 99
+
+ @credit_card = credit_card('4111111111111111')
+
+ @options = {
+ :order_id => generate_unique_id,
+ :billing_address => address,
+ :description => 'Active Merchant Remote Test Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Transaction Declined', response.message
+ end
+
+ def test_successful_authorization
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Error in transaction data or system error', response.message
+ end
+
+ def test_purchase_and_refund
+ assert auth = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Transaction Approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.refund(@amount, auth.authorization)
+ assert_equal 'Transaction Approved', capture.message
+ assert_success capture
+ end
+
+ def test_authorize_and_void
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Transaction Approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.void(auth.authorization)
+ assert_equal 'Transaction Approved', capture.message
+ assert_success capture
+ end
+
+ def test_authorize_and_capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Transaction Approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_equal 'Transaction Approved', capture.message
+ assert_success capture
+ end
+
+ def test_invalid_login
+ gateway = MoneyMoversGateway.new(
+ :login => '',
+ :password => ''
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Error in transaction data or system error', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_mundipagg_test.rb b/test/remote/gateways/remote_mundipagg_test.rb
new file mode 100644
index 00000000000..e1c16f2cc67
--- /dev/null
+++ b/test/remote/gateways/remote_mundipagg_test.rb
@@ -0,0 +1,297 @@
+require 'test_helper'
+
+class RemoteMundipaggTest < Test::Unit::TestCase
+ def setup
+ @gateway = MundipaggGateway.new(fixtures(:mundipagg))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('4000300011112220')
+ @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo')
+
+ # Mundipagg only allows certain card numbers for success and failure scenarios.
+ # As such, we cannot use card numbers with BINs belonging to VR or Alelo.
+ # See https://docs.mundipagg.com/docs/simulador-de-voucher.
+ @vr_voucher = credit_card('4000000000000010', brand: 'vr')
+ @alelo_voucher = credit_card('4000000000000010', brand: 'alelo')
+ @declined_alelo_voucher = credit_card('4000000000000028', brand: 'alelo')
+
+ @options = {
+ gateway_affiliation_id: fixtures(:mundipagg)[:gateway_affiliation_id],
+ billing_address: address({neighborhood: 'Sesame Street'}),
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ test_successful_purchase_with(@credit_card)
+ end
+
+ def test_successful_purchase_with_alelo_card
+ test_successful_purchase_with(@alelo_voucher)
+ end
+
+ def test_successful_purchase_no_address
+ @options.delete(:billing_address)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = @options.update({
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ shipping_address: address
+ })
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_sodexo_voucher
+ @options.update(holder_document: '93095135270')
+ response = @gateway.purchase(@amount, @sodexo_voucher, @options)
+ assert_success response
+ assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message
+ end
+
+ def test_successful_purchase_with_vr_voucher
+ @options.update(holder_document: '93095135270')
+ response = @gateway.purchase(@amount, @vr_voucher, @options)
+ assert_success response
+ assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message
+ end
+
+ def test_failed_purchase
+ test_failed_purchase_with(@declined_card)
+ end
+
+ def test_failed_purchase_with_alelo_card
+ test_failed_purchase_with(@declined_alelo_voucher)
+ end
+
+ def test_successful_authorize_and_capture
+ test_successful_authorize_and_capture_with(@credit_card)
+ end
+
+ def test_successful_authorize_and_capture_with_alelo_card
+ test_successful_authorize_and_capture_with(@alelo_voucher)
+ end
+
+ def test_failed_authorize
+ test_failed_authorize_with(@declined_card)
+ end
+
+ def test_failed_authorize_with_alelo_card
+ test_failed_authorize_with(@declined_alelo_voucher)
+ end
+
+ def test_partial_capture
+ test_partial_capture_with(@credit_card)
+ end
+
+ def test_partial_capture_with_alelo_card
+ test_partial_capture_with(@alelo_voucher)
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'abc')
+ assert_failure response
+ assert_equal 'The requested resource does not exist; Charge not found.', response.message
+ end
+
+ def test_successful_refund
+ test_successful_refund_with(@credit_card)
+ end
+
+ def test_successful_refund_with_alelo_card
+ test_successful_refund_with(@alelo_voucher)
+ end
+
+ def test_partial_refund
+ test_partial_refund_with(@credit_card)
+ end
+
+ def test_partial_refund_with_alelo_card
+ test_partial_refund_with(@alelo_voucher)
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, 'abc')
+ assert_failure response
+ assert_equal 'The requested resource does not exist; Charge not found.', response.message
+ end
+
+ def test_successful_void
+ test_successful_void_with(@credit_card)
+ end
+
+ def test_successful_void_with_alelo_card
+ test_successful_void_with(@alelo_voucher)
+ end
+
+ def test_successful_void_with_sodexo_voucher
+ @options.update(holder_document: '93095135270')
+ auth = @gateway.purchase(@amount, @sodexo_voucher, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_void_with_vr_voucher
+ @options.update(holder_document: '93095135270')
+ auth = @gateway.purchase(@amount, @vr_voucher, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_refund_with_sodexo_voucher
+ @options.update(holder_document: '93095135270')
+ auth = @gateway.purchase(@amount, @sodexo_voucher, @options)
+ assert_success auth
+
+ assert void = @gateway.refund(1, auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_refund_with_vr_voucher
+ @options.update(holder_document: '93095135270')
+ auth = @gateway.purchase(@amount, @vr_voucher, @options)
+ assert_success auth
+
+ assert void = @gateway.refund(1, auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('abc')
+ assert_failure response
+ assert_equal 'The requested resource does not exist; Charge not found.', response.message
+ end
+
+ def test_successful_verify
+ test_successful_verify_with(@credit_card)
+ end
+
+ def test_successful_verify_with_alelo_card
+ test_successful_verify_with(@alelo_voucher)
+ end
+
+ def test_successful_store_and_purchase
+ test_successful_store_and_purchase_with(@credit_card)
+ end
+
+ def test_successful_store_and_purchase_with_alelo_card
+ test_successful_store_and_purchase_with(@alelo_voucher)
+ end
+
+ def test_invalid_login
+ gateway = MundipaggGateway.new(api_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid API key; Authorization has been denied for this request.}, response.message
+ end
+
+ def test_gateway_id_fallback
+ gateway = MundipaggGateway.new(api_key: fixtures(:mundipagg)[:api_key], gateway_id: fixtures(:mundipagg)[:gateway_id])
+ options = {
+ billing_address: address({neighborhood: 'Sesame Street'}),
+ description: 'Store Purchase'
+ }
+ response = gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
+
+ private
+
+ def test_successful_purchase_with(card)
+ response = @gateway.purchase(@amount, card, @options)
+ assert_success response
+ assert_equal 'Simulator|Transação de simulação autorizada com sucesso', response.message
+ end
+
+ def test_failed_purchase_with(card)
+ response = @gateway.purchase(105200, card, @options)
+ assert_failure response
+ assert_equal 'Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.', response.message
+ end
+
+ def test_successful_authorize_and_capture_with(card)
+ auth = @gateway.authorize(@amount, card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Simulator|Transação de simulação capturada com sucesso', capture.message
+ end
+
+ def test_failed_authorize_with(card)
+ response = @gateway.authorize(105200, card, @options)
+ assert_failure response
+ assert_equal 'Simulator|Transação de simulada negada por falta de crédito, utilizado para realizar simulação de autorização parcial.', response.message
+ end
+
+ def test_partial_capture_with(card)
+ auth = @gateway.authorize(@amount, card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_refund_with(card)
+ purchase = @gateway.purchase(@amount, card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund_with(card)
+ purchase = @gateway.purchase(@amount, card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount - 1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_successful_void_with(card)
+ auth = @gateway.authorize(@amount, card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_verify_with(card)
+ response = @gateway.verify(card, @options)
+ assert_success response
+ assert_match %r{Simulator|Transação de simulação autorizada com sucesso}, response.message
+ end
+
+ def test_successful_store_and_purchase_with(card)
+ store = @gateway.store(card, @options)
+ assert_success store
+
+ assert purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+ assert_equal 'Simulator|Transação de simulação autorizada com sucesso', purchase.message
+ end
+end
diff --git a/test/remote/gateways/remote_nab_transact_test.rb b/test/remote/gateways/remote_nab_transact_test.rb
index 67d1f1cd3f0..55289ec9ff0 100644
--- a/test/remote/gateways/remote_nab_transact_test.rb
+++ b/test/remote/gateways/remote_nab_transact_test.rb
@@ -4,7 +4,7 @@ class RemoteNabTransactTest < Test::Unit::TestCase
def setup
@gateway = NabTransactGateway.new(fixtures(:nab_transact))
- @card_acceptor_gateway = NabTransactGateway.new(fixtures(:nab_transact_card_acceptor))
+ @privileged_gateway = NabTransactGateway.new(fixtures(:nab_transact_privileged))
@amount = 200
@credit_card = credit_card('4444333322221111')
@@ -31,16 +31,16 @@ def test_successful_purchase
end
def test_unsuccessful_purchase_insufficient_funds
- #Any total not ending in 00/08/11/16
- failing_amount = 151 #Specifically tests 'Insufficient Funds'
+ # Any total not ending in 00/08/11/16
+ failing_amount = 151 # Specifically tests 'Insufficient Funds'
assert response = @gateway.purchase(failing_amount, @credit_card, @options)
assert_failure response
assert_equal 'Insufficient Funds', response.message
end
def test_unsuccessful_purchase_do_not_honour
- #Any total not ending in 00/08/11/16
- failing_amount = 105 #Specifically tests 'do not honour'
+ # Any total not ending in 00/08/11/16
+ failing_amount = 105 # Specifically tests 'do not honour'
assert response = @gateway.purchase(failing_amount, @credit_card, @options)
assert_failure response
assert_equal 'Do Not Honour', response.message
@@ -73,7 +73,7 @@ def test_successful_purchase_with_card_acceptor
assert_failure response
assert_equal 'Permission denied', response.message
- assert response = @card_acceptor_gateway.purchase(@amount, @credit_card, options)
+ assert response = @privileged_gateway.purchase(@amount, @credit_card, options)
assert_success response
assert_equal 'Approved', response.message
end
@@ -138,13 +138,13 @@ def test_authorize_and_capture_with_card_acceptor
assert_failure response
assert_equal 'Permission denied', response.message
- assert response = @card_acceptor_gateway.authorize(@amount, @credit_card, options)
+ assert response = @privileged_gateway.authorize(@amount, @credit_card, options)
assert_success response
assert_equal 'Approved', response.message
authorization = response.authorization
- assert response = @card_acceptor_gateway.capture(@amount, authorization)
+ assert response = @privileged_gateway.capture(@amount, authorization)
assert_success response
assert_equal 'Approved', response.message
end
@@ -159,13 +159,25 @@ def test_successful_refund
assert_equal 'Approved', response.message
end
+ # You need to speak to NAB Transact to have this feature enabled on
+ # your account otherwise you will receive a "Permission denied" error
+ def test_credit
+ assert response = @gateway.credit(@amount, @credit_card, {:order_id => '1'})
+ assert_failure response
+ assert_equal 'Permission denied', response.message
+
+ assert response = @privileged_gateway.credit(@amount, @credit_card, {:order_id => '1'})
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
def test_failed_refund
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
authorization = response.authorization
assert response = @gateway.refund(@amount+1, authorization)
assert_failure response
- assert_equal 'Only $2.0 available for refund', response.message
+ assert_equal 'Only 2.00 AUD available for refund', response.message
end
def test_invalid_login
@@ -179,17 +191,13 @@ def test_invalid_login
end
def test_successful_store
- @gateway.unstore(1234)
-
- assert response = @gateway.store(@credit_card, {:billing_id => 1234, :amount => 150})
+ assert response = @gateway.store(@credit_card, @options)
assert_success response
assert_equal 'Successful', response.message
end
def test_unsuccessful_store
- @gateway.unstore(1235)
-
- assert response = @gateway.store(@declined_card, {:billing_id => 1235, :amount => 150})
+ assert response = @gateway.store(@declined_card)
assert_failure response
assert_equal 'Invalid Credit Card Number', response.message
end
@@ -197,41 +205,32 @@ def test_unsuccessful_store
def test_duplicate_store
@gateway.unstore(1236)
- assert response = @gateway.store(@credit_card, {:billing_id => 1236, :amount => 150})
+ assert response = @gateway.store(@credit_card, {:billing_id => 1236})
assert_success response
assert_equal 'Successful', response.message
- assert response = @gateway.store(@credit_card, {:billing_id => 1236, :amount => 150})
+ assert response = @gateway.store(@credit_card, {:billing_id => 1236})
assert_failure response
assert_equal 'Duplicate CRN Found', response.message
end
def test_unstore
- gateway_id = '1234'
- @gateway.unstore(gateway_id)
-
- assert response = @gateway.store(@credit_card, {:billing_id => gateway_id, :amount => 150})
+ assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'Successful', response.message
- assert gateway_id = response.params["crn"]
- assert unstore_response = @gateway.unstore(gateway_id)
+ assert card_id = response.authorization
+ assert unstore_response = @gateway.unstore(card_id)
assert_success unstore_response
+ assert_equal 'Successful', unstore_response.message
end
- def test_successful_trigger_purchase
- gateway_id = '1234'
- trigger_amount = 12000
- @gateway.unstore(gateway_id)
-
- assert response = @gateway.store(@credit_card, {:billing_id => gateway_id, :amount => 150})
+ def test_successful_purchase_using_stored_card
+ assert response = @gateway.store(@credit_card)
assert_success response
assert_equal 'Successful', response.message
- purchase_response = @gateway.purchase(trigger_amount, gateway_id)
-
- assert gateway_id = purchase_response.params["crn"]
- assert trigger_amount = purchase_response.params["amount"]
+ purchase_response = @gateway.purchase(12000, response.authorization)
assert_success purchase_response
assert_equal 'Approved', purchase_response.message
end
@@ -247,9 +246,19 @@ def test_failure_trigger_purchase
purchase_response = @gateway.purchase(trigger_amount, gateway_id)
- assert gateway_id = purchase_response.params["crn"]
+ assert purchase_response.params['crn']
assert_failure purchase_response
assert_equal 'Invalid Amount', purchase_response.message
end
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
end
diff --git a/test/remote/gateways/remote_ncr_secure_pay_test.rb b/test/remote/gateways/remote_ncr_secure_pay_test.rb
new file mode 100644
index 00000000000..239063f06e8
--- /dev/null
+++ b/test/remote/gateways/remote_ncr_secure_pay_test.rb
@@ -0,0 +1,122 @@
+require 'test_helper'
+
+class RemoteMonetraTest < Test::Unit::TestCase
+ def setup
+ @gateway = NcrSecurePayGateway.new(fixtures(:ncr_secure_pay))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111')
+ @bad_credit_card = credit_card('1234567890123456')
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(601, @credit_card, @options)
+ assert_failure response
+ assert_equal 'DECLINE', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'SUCCESS', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(601, @credit_card, @options)
+ assert_failure response
+ assert_equal 'DECLINE', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'invalid')
+ assert_failure response
+ assert_equal 'This transaction requires an apprcode ttid or unique ptrannum', response.message
+ end
+
+ # Unable to test this case since have to wait for original tx to settle
+ # for refund
+ def test_successful_refund
+ end
+
+ # Unable to test this case since have to wait for original tx to settle
+ # for refund
+ def test_partial_refund
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_failure refund
+ assert_equal 'USE VOID OR REVERSAL TO REFUND UNSETTLED TRANSACTIONS', refund.message
+ end
+
+ def test_successful_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ assert_equal 'SUCCESS', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('invalid')
+ assert_failure response
+ assert_equal 'Must specify ttid or ptrannum', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'APPROVED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@bad_credit_card, @options)
+ assert_failure response
+ assert_match 'UNSUPPORTED CARD TYPE', response.message
+ end
+
+ def test_invalid_login
+ gateway = NcrSecurePayGateway.new(username: '', password: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'AUTHENTICATION FAILED', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_net_registry_test.rb b/test/remote/gateways/remote_net_registry_test.rb
index 6525b4fdd3a..aeadc59b83d 100644
--- a/test/remote/gateways/remote_net_registry_test.rb
+++ b/test/remote/gateways/remote_net_registry_test.rb
@@ -27,7 +27,7 @@ def test_successful_purchase_and_credit
assert_success response
assert_match(/\A\d{16}\z/, response.authorization)
- assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do
+ assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do
response = @gateway.credit(@amount, response.authorization)
assert_equal 'approved', response.params['status']
assert_success response
@@ -58,8 +58,8 @@ def test_successful_authorization_and_capture
assert_match(/\A\d{6}\z/, response.authorization)
response = @gateway.capture(@amount,
- response.authorization,
- :credit_card => @valid_creditcard)
+ response.authorization,
+ :credit_card => @valid_creditcard)
assert_success response
assert_equal 'approved', response.params['status']
end
diff --git a/test/remote/gateways/remote_netaxept_test.rb b/test/remote/gateways/remote_netaxept_test.rb
index a7ea2c36715..0ae763f5876 100644
--- a/test/remote/gateways/remote_netaxept_test.rb
+++ b/test/remote/gateways/remote_netaxept_test.rb
@@ -20,24 +20,24 @@ def test_successful_purchase
end
def test_failed_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
- assert_failure response
- assert_match(/failure/i, response.message)
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/failure/i, response.message)
end
def test_authorize_and_capture
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
- assert_success auth
- assert_equal 'OK', auth.message
- assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization)
- assert_success capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'OK', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
end
def test_failed_capture
- assert response = @gateway.capture(@amount, '')
- assert_failure response
- assert_equal "Unable to find transaction", response.message
+ assert response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Unable to find transaction', response.message
end
def test_successful_refund
@@ -49,12 +49,12 @@ def test_successful_refund
end
def test_failed_refund
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
- response = @gateway.refund(@amount+100, response.authorization)
- assert_failure response
- assert_equal "Unable to credit more than captured amount", response.message
+ response = @gateway.refund(@amount+100, response.authorization)
+ assert_failure response
+ assert_equal 'Unable to credit more than captured amount', response.message
end
def test_successful_void
@@ -66,12 +66,18 @@ def test_successful_void
end
def test_failed_void
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ response = @gateway.void(response.authorization)
+ assert_failure response
+ assert_equal 'Unable to annul, wrong state', response.message
+ end
- response = @gateway.void(response.authorization)
- assert_failure response
- assert_equal "Unable to annul, wrong state", response.message
+ def test_error_in_transaction_setup
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'BOGG'))
+ assert_failure response
+ assert_match(/currency code/, response.message)
end
def test_successful_amex_purchase
@@ -88,53 +94,33 @@ def test_successful_master_purchase
assert_equal 'OK', response.message
end
- def test_error_in_transaction_setup
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'BOGG'))
- assert_failure response
- assert_match(/currency code/, response.message)
- end
-
- def test_successful_amex_purchase
- credit_card = credit_card('378282246310005', :brand => 'american_express')
- assert response = @gateway.purchase(@amount, credit_card, @options)
- assert_success response
- assert_equal 'OK', response.message
- end
-
- def test_successful_master_purchase
- credit_card = credit_card('5413000000000000', :brand => 'master')
- assert response = @gateway.purchase(@amount, credit_card, @options)
- assert_success response
- assert_equal 'OK', response.message
- end
-
def test_error_in_payment_details
- assert response = @gateway.purchase(@amount, credit_card(''), @options)
- assert_failure response
- assert_equal "Cardnumber:Required", response.message
+ assert response = @gateway.purchase(@amount, credit_card(''), @options)
+ assert_failure response
+ assert_equal 'Cardnumber:Required', response.message
end
def test_amount_is_not_required_again_when_capturing_authorization
- assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_success response
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
- assert response = @gateway.capture(nil, response.authorization)
- assert_equal "OK", response.message
+ assert response = @gateway.capture(nil, response.authorization)
+ assert_equal 'OK', response.message
end
def test_query_fails
- query = @gateway.send(:query_transaction, "bogus", @options)
+ query = @gateway.send(:query_transaction, 'bogus', @options)
assert_failure query
assert_match(/unable to find/i, query.message)
end
def test_invalid_login
- gateway = NetaxeptGateway.new(
- :login => '',
- :password => ''
- )
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert_match(/Unable to authenticate merchant/, response.message)
+ gateway = NetaxeptGateway.new(
+ :login => '',
+ :password => ''
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match(/Unable to authenticate merchant/, response.message)
end
end
diff --git a/test/remote/gateways/remote_netbanx_test.rb b/test/remote/gateways/remote_netbanx_test.rb
new file mode 100644
index 00000000000..19b4faddd5c
--- /dev/null
+++ b/test/remote/gateways/remote_netbanx_test.rb
@@ -0,0 +1,205 @@
+require 'test_helper'
+
+class RemoteNetbanxTest < Test::Unit::TestCase
+ def setup
+ @gateway = NetbanxGateway.new(fixtures(:netbanx))
+ @amount = 100
+ @credit_card = credit_card('4530910000012345')
+ @declined_amount = 11
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase',
+ currency: 'CAD'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+ assert_equal response.authorization, response.params['id']
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: SecureRandom.uuid,
+ ip: '127.0.0.1',
+ billing_address: address,
+ email: 'joe@example.com'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_equal 'OK', response.message
+ assert_equal response.authorization, response.params['id']
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'The card has been declined due to insufficient funds.', response.message
+ end
+
+ def test_successful_authorize
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'The card has been declined due to insufficient funds.', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'OK', capture.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, SecureRandom.uuid)
+ assert_failure response
+ assert_equal 'The authorization ID included in this settlement request could not be found.', response.message
+ end
+
+ # def test_successful_refund
+ # # Unfortunately when testing a refund, you need to wait until the transaction
+ # # if batch settled by the test system, this can take up to 2h.
+ # # This is the reason why these tests are commented out. You can run them
+ # # manually once you have batched/completed transactions.
+ # #
+ # # Otherwise you will get an error like:
+ # # You tried a credit transaction for a settlement that has not been batched,
+ # # so there is no balance available to be credited. A settlement is typically
+ # # in a pending state until midnight of the day that it is requested, at
+ # # which point it is batched. You cannot credit that settlement until it has
+ # # been batched. Verify the credit card for which you are attempting the
+ # # credit, and retry the transaction. Otherwise, wait until the settlement
+ # # has been batched and retry the transaction.
+
+ # auth = @gateway.authorize(@amount, @credit_card, @options)
+ # assert_success auth
+
+ # assert capture = @gateway.capture(@amount, auth.authorization)
+ # assert_success capture
+
+ # # replace this with the transaction that you can verify via the back-office
+ # # or API that it's in `completed` state. And use this in the refund
+ # # call below
+ # # authorization = "fd5d6776-29b7-4108-b98a-7a4603db9ff0"
+
+ # assert refund = @gateway.refund(@amount, authorization)
+ # assert_success refund
+ # assert_equal 'OK', refund.message
+ # end
+
+ # def test_partial_refund
+ # # Read comment in `test_successful_refund` method.
+ # auth = @gateway.authorize(@amount, @credit_card, @options)
+ # assert_success auth
+
+ # assert capture = @gateway.capture(@amount, auth.authorization)
+ # assert_success capture
+
+ # # replace this with the transaction that you can verify via the back-office
+ # # or API that it's in `completed` state. And use this in the refund
+ # # call below
+ # # authorization = "REPLACE-ME"
+
+ # assert refund = @gateway.refund(@amount-1, capture.authorization)
+ # assert_success refund
+ # assert_equal 'OK', refund.message
+ # end
+
+ def test_failed_refund
+ # Read comment in `test_successful_refund` method.
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+
+ # the following shall fail if you run it immediately after the capture
+ # as noted in the comment from `test_successful_refund`
+ assert refund = @gateway.refund(@amount, capture.authorization)
+ assert_failure refund
+ assert_equal 'The settlement you are attempting to refund has not been batched yet. There are no settled funds available to refund.', refund.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ assert_equal 'OK', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void(SecureRandom.uuid)
+ assert_failure response
+ assert_equal 'The confirmation number included in this request could not be found.', response.message
+ end
+
+ def test_invalid_login
+ gateway = NetbanxGateway.new(api_key: 'foobar', account_number: '12345')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Login}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(Base64.strict_encode64(@gateway.options[:api_key]).strip, transcript)
+ end
+
+ def test_successful_store
+ merchant_customer_id = SecureRandom.hex
+ assert response = @gateway.store(@credit_card, locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com')
+ assert_success response
+ assert_equal merchant_customer_id, response.params['merchantCustomerId']
+ first_card = response.params['cards'].first
+ assert_equal @credit_card.last_digits, first_card['lastDigits']
+ end
+
+ def test_successful_unstore
+ merchant_customer_id = SecureRandom.hex
+ assert response = @gateway.store(@credit_card, locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com')
+ assert_success response
+ assert_equal merchant_customer_id, response.params['merchantCustomerId']
+ first_card = response.params['cards'].first
+ assert_equal @credit_card.last_digits, first_card['lastDigits']
+ identification = "#{response.params['id']}|#{first_card['id']}"
+ assert unstore_card = @gateway.unstore(identification)
+ assert_success unstore_card
+ assert unstore_profile = @gateway.unstore(response.params['id'])
+ assert_success unstore_profile
+ end
+
+ def test_successful_purchase_using_stored_card
+ merchant_customer_id = SecureRandom.hex
+ assert store = @gateway.store(@credit_card, @options.merge({locale: 'en_GB', merchant_customer_id: merchant_customer_id, email: 'email@example.com'}))
+ assert_success store
+
+ assert response = @gateway.purchase(@amount, store.authorization.split('|').last)
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_netbilling_test.rb b/test/remote/gateways/remote_netbilling_test.rb
index 9eaeadd651b..7004e213627 100644
--- a/test/remote/gateways/remote_netbilling_test.rb
+++ b/test/remote/gateways/remote_netbilling_test.rb
@@ -16,7 +16,8 @@ def setup
@options = {
:billing_address => @address,
- :description => 'Internet purchase'
+ :description => 'Internet purchase',
+ :order_id => 987654321
}
@amount = 100
@@ -30,6 +31,39 @@ def test_successful_purchase
assert response.test?
end
+ def test_successful_repeat_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal NetbillingGateway::SUCCESS_MESSAGE, response.message
+ assert response.test?
+
+ transaction_id = response.authorization
+ assert response = @gateway.purchase(@amount, transaction_id, @options)
+ assert_false response.authorization.blank?
+ assert_equal NetbillingGateway::SUCCESS_MESSAGE, response.message
+ assert response.test?
+ end
+
+ def test_unsuccessful_repeat_purchase
+ assert response = @gateway.purchase(@amount, '1111', @options)
+ assert_failure response
+ assert_match(/no record found/i, response.message)
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_false response.authorization.blank?
+ assert_equal NetbillingGateway::SUCCESS_MESSAGE, response.message
+ end
+
+ def test_unsuccessful_store
+ assert response = @gateway.store(credit_card('123'), @options)
+ assert_failure response
+ assert_match(/invalid credit card number/i, response.message)
+ end
+
def test_unsuccessful_purchase
@credit_card.year = '2006'
assert response = @gateway.purchase(@amount, @credit_card, @options)
@@ -91,4 +125,14 @@ def test_successful_void
assert_failure void_response
assert_match(/error/i, void_response.message)
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
end
diff --git a/test/remote/gateways/remote_netpay_test.rb b/test/remote/gateways/remote_netpay_test.rb
index 754d11d0086..cb87c5ca3f7 100644
--- a/test/remote/gateways/remote_netpay_test.rb
+++ b/test/remote/gateways/remote_netpay_test.rb
@@ -60,7 +60,7 @@ def test_unsuccessful_authorize
opts[:mode] = 'D'
assert response = @gateway.authorize(@amount, @declined_card, opts)
assert_failure response
- assert_match /Declinada/, response.message
+ assert_match %r{Declinada}, response.message
end
def test_successful_authorize_and_capture
diff --git a/test/remote/gateways/remote_transnational_test.rb b/test/remote/gateways/remote_network_merchants_test.rb
similarity index 61%
rename from test/remote/gateways/remote_transnational_test.rb
rename to test/remote/gateways/remote_network_merchants_test.rb
index 57d058a87a9..290a0cad4c6 100644
--- a/test/remote/gateways/remote_transnational_test.rb
+++ b/test/remote/gateways/remote_network_merchants_test.rb
@@ -1,12 +1,13 @@
require 'test_helper'
-class RemoteTransnationalTest < Test::Unit::TestCase
+class RemoteNetworkMerchantsTest < Test::Unit::TestCase
def setup
- @gateway = TransnationalGateway.new(fixtures(:transnational))
+ @gateway = NetworkMerchantsGateway.new(fixtures(:network_merchants))
@amount = 100
@decline_amount = 1
@credit_card = credit_card('4111111111111111')
+ @credit_card_with_track_data = credit_card_with_track_data('4111111111111111')
@check = check
@options = {
@@ -22,6 +23,18 @@ def test_successful_purchase
assert_equal 'SUCCESS', response.message
end
+ def test_successful_purchase_with_track_data
+ assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_with_non_default_currency
+ @options.update(currency: 'EUR')
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ end
+
def test_successful_check_purchase
assert response = @gateway.purchase(@amount, @check, @options)
assert_success response
@@ -34,6 +47,12 @@ def test_unsuccessful_purchase
assert_equal 'DECLINE', response.message
end
+ def test_unsuccessful_purchase_with_track_data
+ assert response = @gateway.purchase(@decline_amount, @credit_card_with_track_data, @options)
+ assert_failure response
+ assert_equal 'DECLINE', response.message
+ end
+
def test_purchase_and_store
assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:store => true))
assert_success response
@@ -64,7 +83,7 @@ def test_void
assert response = @gateway.void(purchase.authorization)
assert_success response
- assert_equal "Transaction Void Successful", response.message
+ assert_equal 'Transaction Void Successful', response.message
end
def test_refund
@@ -74,7 +93,18 @@ def test_refund
assert response = @gateway.refund(50, purchase.authorization)
assert_success response
- assert_equal "SUCCESS", response.message
+ assert_equal 'SUCCESS', response.message
+ assert response.authorization
+ end
+
+ def test_refund_with_track_data
+ assert purchase = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_success purchase
+ assert purchase.authorization
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
assert response.authorization
end
@@ -93,11 +123,11 @@ def test_store_check
end
def test_store_failure
- @credit_card.number = "123"
- assert store = @gateway.store(@creditcard, @options)
+ @credit_card.number = '123'
+ assert store = @gateway.store(@credit_card, @options)
assert_failure store
- assert store.message.include?('Billing Information missing')
- assert_equal '', store.params['customer_vault_id']
+ assert store.message.include?('Invalid Credit Card Number')
+ assert store.params['customer_vault_id'].blank?
assert_nil store.authorization
end
@@ -108,7 +138,7 @@ def test_unstore
assert unstore = @gateway.unstore(store.params['customer_vault_id'])
assert_success unstore
- assert_equal "Customer Deleted", unstore.message
+ assert_equal 'Customer Deleted', unstore.message
end
def test_purchase_on_stored_card
@@ -118,11 +148,11 @@ def test_purchase_on_stored_card
assert purchase = @gateway.purchase(@amount, store.params['customer_vault_id'], @options)
assert_success purchase
- assert_equal "SUCCESS", purchase.message
+ assert_equal 'SUCCESS', purchase.message
end
def test_invalid_login
- gateway = TransnationalGateway.new(
+ gateway = NetworkMerchantsGateway.new(
:login => '',
:password => ''
)
@@ -130,4 +160,23 @@ def test_invalid_login
assert_failure response
assert_equal 'Invalid Username', response.message
end
+
+ def test_successful_purchase_without_state
+ @options[:billing_address] = {
+ :name => 'Jim Smith',
+ :address1 => 'Gullhauggrenda 30',
+ :address2 => 'Apt 1',
+ :company => 'Widgets Inc',
+ :city => 'Baerums Verk',
+ :state => nil,
+ :zip => '1354',
+ :country => 'NO',
+ :phone => '(555)555-5555',
+ :fax => '(555)555-6666'
+ }
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
end
diff --git a/test/remote/gateways/remote_nmi_test.rb b/test/remote/gateways/remote_nmi_test.rb
index e96b9b79fe9..4c4784b216a 100644
--- a/test/remote/gateways/remote_nmi_test.rb
+++ b/test/remote/gateways/remote_nmi_test.rb
@@ -3,45 +3,135 @@
class RemoteNmiTest < Test::Unit::TestCase
def setup
@gateway = NmiGateway.new(fixtures(:nmi))
- @amount = 100
- @credit_card = credit_card('4000100011112224')
+ @amount = Random.rand(100...1000)
+ @credit_card = credit_card('4111111111111111', verification_value: 917)
+ @check = check(
+ :routing_number => '123123123',
+ :account_number => '123123123'
+ )
+ @apple_pay_card = network_tokenization_credit_card('4111111111111111',
+ :payment_cryptogram => 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ :month => '01',
+ :year => '2024',
+ :source => :apple_pay,
+ :eci => '5',
+ :transaction_id => '123456789'
+ )
@options = {
:order_id => generate_unique_id,
:billing_address => address,
:description => 'Store purchase'
}
+ @level3_options = {
+ tax: 5.25, shipping: 10.51, ponumber: 1002
+ }
+ end
+
+ def test_invalid_login
+ @gateway = NmiGateway.new(login: 'invalid', password: 'no')
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication Failed', response.message
end
def test_successful_purchase
+ options = @options.merge(@level3_options)
+
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
+ def test_successful_purchase_sans_cvv
+ @credit_card.verification_value = nil
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert response.test?
- assert_equal 'This transaction has been approved', response.message
+ assert_equal 'Succeeded', response.message
assert response.authorization
end
- def test_forced_test_mode_purchase
- gateway = NmiGateway.new(fixtures(:nmi).update(:test => true))
- assert response = gateway.purchase(@amount, @credit_card, @options)
+ def test_failed_purchase
+ assert response = @gateway.purchase(99, @credit_card, @options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'DECLINE', response.message
+ end
+
+ def test_successful_purchase_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
+ def test_failed_purchase_with_echeck
+ assert response = @gateway.purchase(99, @check, @options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'FAILED', response.message
+ end
+
+ def test_successful_purchase_with_apple_pay_card
+ assert @gateway.supports_network_tokenization?
+ assert response = @gateway.purchase(@amount, @apple_pay_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
+ def test_failed_purchase_with_apple_pay_card
+ assert response = @gateway.purchase(99, @apple_pay_card, @options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'DECLINE', response.message
+ end
+
+ def test_successful_purchase_with_additional_options
+ options = @options.merge({
+ customer_id: '234',
+ vendor_id: '456',
+ recurring: true,
+ })
+ assert response = @gateway.purchase(@amount, @credit_card, options)
assert_success response
assert response.test?
+ assert_equal 'Succeeded', response.message
assert response.authorization
end
def test_successful_authorization
- assert response = @gateway.authorize(@amount, @credit_card, @options)
+ options = @options.merge(@level3_options)
+
+ assert response = @gateway.authorize(@amount, @credit_card, options)
assert_success response
- assert_equal 'This transaction has been approved', response.message
+ assert_equal 'Succeeded', response.message
assert response.authorization
end
+ def test_failed_authorization
+ assert response = @gateway.authorize(99, @credit_card, @options)
+ assert_failure response
+ assert response.test?
+ assert_equal 'DECLINE', response.message
+ end
+
def test_authorization_and_capture
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
assert capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
- assert_equal 'This transaction has been approved', capture.message
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_capture
+ assert capture = @gateway.capture(@amount, 'badauth')
+ assert_failure capture
end
def test_authorization_and_void
@@ -50,28 +140,265 @@ def test_authorization_and_void
assert void = @gateway.void(authorization.authorization)
assert_success void
- assert_equal 'This transaction has been approved', void.message
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ assert void = @gateway.void('badauth')
+ assert_failure void
end
- def test_refund
+ def test_successful_void_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+
+ assert response = @gateway.void(response.authorization)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_refund
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert response = @gateway.refund(@amount, response.authorization, :card_number => @credit_card.number)
+ assert response = @gateway.refund(@amount, response.authorization)
assert_success response
- assert_equal 'This transaction has been approved', response.message
+ assert_equal 'Succeeded', response.message
end
- def test_bad_login
- gateway = NmiGateway.new(
- :login => 'X',
- :password => 'Y'
- )
+ def test_failed_refund
+ assert response = @gateway.refund(@amount, 'badauth')
+ assert_failure response
+ end
+
+ def test_successful_refund_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+
+ assert response = @gateway.refund(@amount, response.authorization)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_credit
+ options = @options.merge(@level3_options)
+
+ response = @gateway.credit(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_credit
+ card = credit_card(year: 2010)
+ response = @gateway.credit(@amount, card, @options)
+ assert_failure response
+ end
+
+ def test_successful_verify
+ options = @options.merge(@level3_options)
+
+ response = @gateway.verify(@credit_card, options)
+ assert_success response
+ assert_match 'Succeeded', response.message
+ end
- assert response = gateway.purchase(@amount, @credit_card)
- assert_equal Response, response.class
- assert_match(/Authentication Failed/, response.message)
- assert_equal false, response.success?
+ def test_failed_verify
+ card = credit_card(year: 2010)
+ response = @gateway.verify(card, @options)
+ assert_failure response
+ assert_match 'Invalid Credit Card', response.message
end
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization.include?(response.params['customer_vault_id'])
+ end
+
+ def test_failed_store
+ card = credit_card(year: 2010)
+ response = @gateway.store(card, @options)
+ assert_failure response
+ end
+
+ def test_successful_store_with_echeck
+ response = @gateway.store(@check, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization.include?(response.params['customer_vault_id'])
+ end
+
+ def test_successful_store_and_purchase
+ vault_id = @gateway.store(@credit_card, @options).authorization
+ purchase = @gateway.purchase(@amount, vault_id, @options)
+ assert_success purchase
+ assert_equal 'Succeeded', purchase.message
+ end
+
+ def test_successful_store_and_auth
+ vault_id = @gateway.store(@credit_card, @options).authorization
+ auth = @gateway.authorize(@amount, vault_id, @options)
+ assert_success auth
+ assert_equal 'Succeeded', auth.message
+ end
+
+ def test_successful_store_and_credit
+ vault_id = @gateway.store(@credit_card, @options).authorization
+ credit = @gateway.credit(@amount, vault_id, @options)
+ assert_success credit
+ assert_equal 'Succeeded', credit.message
+ end
+
+ def test_merchant_defined_fields
+ (1..20).each { |e| @options["merchant_defined_field_#{e}".to_sym] = "value #{e}" }
+ assert_success @gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = NmiGateway.new(login: 'unknown', password: 'unknown')
+ assert !gateway.verify_credentials
+ gateway = NmiGateway.new(login: fixtures(:nmi)[:login], password: 'unknown')
+ assert !gateway.verify_credentials
+ end
+
+ def test_purchase_using_stored_credential_recurring_cit
+ initial_options = stored_credential_options(:cardholder, :recurring, :initial)
+ assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
+ assert_success purchase
+ assert network_transaction_id = purchase.params['transactionid']
+
+ used_options = stored_credential_options(:recurring, :cardholder, id: network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_recurring_mit
+ initial_options = stored_credential_options(:merchant, :recurring, :initial)
+ assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
+ assert_success purchase
+ assert network_transaction_id = purchase.params['transactionid']
+
+ used_options = stored_credential_options(:merchant, :recurring, id: network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_installment_cit
+ initial_options = stored_credential_options(:cardholder, :installment, :initial)
+ assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
+ assert_success purchase
+ assert network_transaction_id = purchase.params['transactionid']
+
+ used_options = stored_credential_options(:cardholder, :installment, id: network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_installment_mit
+ initial_options = stored_credential_options(:merchant, :installment, :initial)
+ assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
+ assert_success purchase
+ assert network_transaction_id = purchase.params['transactionid']
+
+ used_options = stored_credential_options(:merchant, :installment, id: network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_unscheduled_cit
+ initial_options = stored_credential_options(:cardholder, :unscheduled, :initial)
+ assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
+ assert_success purchase
+ assert network_transaction_id = purchase.params['transactionid']
+
+ used_options = stored_credential_options(:cardholder, :unscheduled, id: network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
+ assert_success purchase
+ end
+
+ def test_purchase_using_stored_credential_unscheduled_mit
+ initial_options = stored_credential_options(:merchant, :unscheduled, :initial)
+ assert purchase = @gateway.purchase(@amount, @credit_card, initial_options)
+ assert_success purchase
+ assert network_transaction_id = purchase.params['transactionid']
+
+ used_options = stored_credential_options(:merchant, :unscheduled, id: network_transaction_id)
+ assert purchase = @gateway.purchase(@amount, @credit_card, used_options)
+ assert_success purchase
+ end
+
+ def test_authorize_and_capture_with_stored_credential
+ initial_options = stored_credential_options(:cardholder, :recurring, :initial)
+ assert authorization = @gateway.authorize(@amount, @credit_card, initial_options)
+ assert_success authorization
+ assert network_transaction_id = authorization.params['transactionid']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+
+ used_options = stored_credential_options(:cardholder, :recurring, id: network_transaction_id)
+ assert authorization = @gateway.authorize(@amount, @credit_card, used_options)
+ assert_success authorization
+ assert @gateway.capture(@amount, authorization.authorization)
+ end
+
+ def test_card_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_cvv_scrubbed(clean_transcript)
+ assert_password_scrubbed(clean_transcript)
+ end
+
+ def test_check_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, clean_transcript)
+ assert_scrubbed(@check.routing_number, clean_transcript)
+ assert_password_scrubbed(clean_transcript)
+ end
+
+ def test_network_tokenization_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @apple_pay_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@apple_pay_card.number, clean_transcript)
+ assert_scrubbed(@apple_pay_card.payment_cryptogram, clean_transcript)
+ assert_password_scrubbed(clean_transcript)
+ end
+
+ private
+
+ # "password=password is filtered, but can't be tested via normal
+ # `assert_scrubbed` b/c of key match"
+ def assert_password_scrubbed(transcript)
+ assert_match(/password=\[FILTERED\]/, transcript)
+ end
+
+ # Because the cvv is a simple three digit number, sometimes there are random
+ # failures using `assert_scrubbed` because of natural collisions with a
+ # substring within orderid in transcript; e.g.
+ #
+ # Expected the value to be scrubbed out of the transcript.
+ # 917/> was expected to not match
+ # <"opening connection to secure.nmi.com:443...\nopened\nstarting SSL for secure.nmi.com:443...\nSSL established\n<- \"POST /api/transact.php HTTP/1.1\\r\\nContent-Type: application/x-www-form-urlencoded;charset=UTF-8\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nHost: secure.nmi.com\\r\\nContent-Length: 394\\r\\n\\r\\n\"\n<- \"amount=7.96&orderid=9bb4c3bf6fbb26b91796ae9442cb1941&orderdescription=Store+purchase¤cy=USD&payment=creditcard&firstname=Longbob&lastname=Longsen&ccnumber=[FILTERED]&cvv=[FILTERED]&ccexp=0920&email=&ipaddress=&customer_id=&company=Widgets+Inc&address1=456+My+Street&address2=Apt+1&city=Ottawa&state=ON&country=CA&zip=K1C2N6&phone=%28555%29555-5555&type=sale&username=demo&password=[FILTERED]\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Wed, 12 Jun 2019 21:10:29 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Content-Length: 169\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: text/html; charset=UTF-8\\r\\n\"\n-> \"\\r\\n\"\nreading 169 bytes...\n-> \"response=1&responsetext=SUCCESS&authcode=123456&transactionid=4743046890&avsresponse=N&cvvresponse=N&orderid=9bb4c3bf6fbb26b91796ae9442cb1941&type=sale&response_code=100\"\nread 169 bytes\nConn close\n">.
+ def assert_cvv_scrubbed(transcript)
+ assert_match(/cvv=\[FILTERED\]/, transcript)
+ end
+
+ def stored_credential_options(*args, id: nil)
+ @options.merge(order_id: generate_unique_id,
+ stored_credential: stored_credential(*args, id: id))
+ end
end
diff --git a/test/remote/gateways/remote_ogone_test.rb b/test/remote/gateways/remote_ogone_test.rb
index 8003596642c..3d2b878db33 100644
--- a/test/remote/gateways/remote_ogone_test.rb
+++ b/test/remote/gateways/remote_ogone_test.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+
require 'test_helper'
class RemoteOgoneTest < Test::Unit::TestCase
@@ -7,13 +8,15 @@ def setup
@gateway = OgoneGateway.new(fixtures(:ogone))
@amount = 100
@credit_card = credit_card('4000100011112224')
+ @mastercard = credit_card('5399999999999999', :brand => 'mastercard')
@declined_card = credit_card('1111111111111111')
@credit_card_d3d = credit_card('4000000000000002', :verification_value => '111')
@options = {
:order_id => generate_unique_id[0...30],
:billing_address => address,
:description => 'Store Purchase',
- :currency => fixtures(:ogone)[:currency] || 'EUR'
+ :currency => fixtures(:ogone)[:currency] || 'EUR',
+ :origin => 'STORE'
}
end
@@ -21,40 +24,40 @@ def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
- assert_equal '7', response.params['ECI']
- assert_equal @options[:currency], response.params["currency"]
assert_equal @options[:order_id], response.order_id
end
def test_successful_purchase_with_utf8_encoding_1
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "Rémy", :last_name => "Fröåïør"), @options)
+ assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'Rémy', :last_name => 'Fröåïør'), @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
def test_successful_purchase_with_utf8_encoding_2
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => "ワタシ", :last_name => "ёжзийклмнопрсуфхцч"), @options)
+ assert response = @gateway.purchase(@amount, credit_card('4000100011112224', :first_name => 'ワタシ', :last_name => 'ёжзийклмнопрсуфхцч'), @options)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
end
+ # This test is commented out since it is mutually exclusive with the other signature tests.
# NOTE: You have to set the "Hash algorithm" to "SHA-1" in the "Technical information"->"Global security parameters"
# section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test
- def test_successful_purchase_with_signature_encryptor_to_sha1
- gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha1'))
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
- end
-
+ # def test_successful_purchase_with_signature_encryptor_to_sha1
+ # gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha1'))
+ # assert response = gateway.purchase(@amount, @credit_card, @options)
+ # assert_success response
+ # assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
+ # end
+
+ # This test is commented out since it is mutually exclusive with the other signature tests.
# NOTE: You have to set the "Hash algorithm" to "SHA-256" in the "Technical information"->"Global security parameters"
# section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test
- def test_successful_purchase_with_signature_encryptor_to_sha256
- gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha256'))
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
- end
+ # def test_successful_purchase_with_signature_encryptor_to_sha256
+ # gateway = OgoneGateway.new(fixtures(:ogone).merge(:signature_encryptor => 'sha256'))
+ # assert response = gateway.purchase(@amount, @credit_card, @options)
+ # assert_success response
+ # assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
+ # end
# NOTE: You have to set the "Hash algorithm" to "SHA-512" in the "Technical information"->"Global security parameters"
# section of your account admin on https://secure.ogone.com/ncol/test/frame_ogone.asp before running this test
@@ -69,9 +72,9 @@ def test_successful_purchase_with_signature_encryptor_to_sha512
def test_successful_purchase_with_3d_secure
assert response = @gateway.purchase(@amount, @credit_card_d3d, @options.merge(:d3d => true))
assert_success response
- assert_equal '46', response.params["STATUS"]
+ assert_equal '46', response.params['STATUS']
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
- assert response.params["HTML_ANSWER"]
+ assert response.params['HTML_ANSWER']
end
def test_successful_with_non_numeric_order_id
@@ -92,7 +95,6 @@ def test_successful_purchase_with_custom_eci
assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:eci => 4))
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
- assert_equal '4', response.params['ECI']
end
# NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies"
@@ -102,7 +104,6 @@ def test_successful_purchase_with_custom_currency_at_the_gateway_level
assert response = gateway.purchase(@amount, @credit_card)
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
- assert_equal "USD", response.params["currency"]
end
# NOTE: You have to allow USD as a supported currency in the "Account"->"Currencies"
@@ -112,7 +113,6 @@ def test_successful_purchase_with_custom_currency
assert response = gateway.purchase(@amount, @credit_card, @options.merge(:currency => 'USD'))
assert_success response
assert_equal OgoneGateway::SUCCESS_MESSAGE, response.message
- assert_equal "USD", response.params["currency"]
end
def test_unsuccessful_purchase
@@ -121,11 +121,16 @@ def test_unsuccessful_purchase
assert_equal 'No brand', response.message
end
+ def test_successful_authorize_with_mastercard
+ assert auth = @gateway.authorize(@amount, @mastercard, @options)
+ assert_success auth
+ assert_equal BarclaysEpdqExtraPlusGateway::SUCCESS_MESSAGE, auth.message
+ end
+
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
- assert_equal '7', auth.params['ECI']
assert auth.authorization
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
@@ -135,7 +140,6 @@ def test_authorize_and_capture_with_custom_eci
assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(:eci => 4))
assert_success auth
assert_equal OgoneGateway::SUCCESS_MESSAGE, auth.message
- assert_equal '4', auth.params['ECI']
assert auth.authorization
assert capture = @gateway.capture(@amount, auth.authorization, @options)
assert_success capture
@@ -163,68 +167,89 @@ def test_successful_store
assert_success purchase
end
- def test_successful_store_generated_alias
- assert response = @gateway.store(@credit_card)
+ def test_successful_store_with_store_amount_at_the_gateway_level
+ gateway = OgoneGateway.new(fixtures(:ogone).merge(:store_amount => 100))
+ assert response = gateway.store(@credit_card, :billing_id => 'test_alias')
assert_success response
- assert purchase = @gateway.purchase(@amount, response.billing_id)
+ assert purchase = gateway.purchase(@amount, 'test_alias')
assert_success purchase
end
- def test_successful_store
- assert response = @gateway.store(@credit_card, :billing_id => 'test_alias')
+ def test_successful_store_generated_alias
+ assert response = @gateway.store(@credit_card)
assert_success response
- assert purchase = @gateway.purchase(@amount, 'test_alias')
+ assert purchase = @gateway.purchase(@amount, response.billing_id)
assert_success purchase
end
- def test_successful_referenced_credit
+ def test_successful_refund
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
- assert credit = @gateway.credit(@amount, purchase.authorization, @options)
- assert_success credit
- assert credit.authorization
- assert_equal OgoneGateway::SUCCESS_MESSAGE, credit.message
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert refund.authorization
+ assert_equal OgoneGateway::SUCCESS_MESSAGE, refund.message
end
- def test_unsuccessful_referenced_credit
+ def test_unsuccessful_refund
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
- assert credit = @gateway.credit(@amount+1, purchase.authorization, @options) # too much refund requested
- assert_failure credit
- assert credit.authorization
- assert_equal 'Overflow in refunds requests', credit.message
+ assert refund = @gateway.refund(@amount+1, purchase.authorization, @options) # too much refund requested
+ assert_failure refund
+ assert refund.authorization
+ assert_equal 'Overflow in refunds requests', refund.message
end
- def test_successful_unreferenced_credit
+ def test_successful_credit
assert credit = @gateway.credit(@amount, @credit_card, @options)
assert_success credit
assert credit.authorization
assert_equal OgoneGateway::SUCCESS_MESSAGE, credit.message
end
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'The transaction was successful', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'No brand', response.message
+ end
+
def test_reference_transactions
# Setting an alias
- assert response = @gateway.purchase(@amount, credit_card('4000100011112224'), @options.merge(:billing_id => "awesomeman", :order_id=>Time.now.to_i.to_s+"1"))
+ assert response = @gateway.purchase(@amount, credit_card('4000100011112224'), @options.merge(:billing_id => 'awesomeman', :order_id=>Time.now.to_i.to_s+'1'))
assert_success response
- assert_equal '7', response.params['ECI']
# Updating an alias
- assert response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(:billing_id => "awesomeman", :order_id=>Time.now.to_i.to_s+"2"))
+ assert response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(:billing_id => 'awesomeman', :order_id=>Time.now.to_i.to_s+'2'))
assert_success response
- assert_equal '7', response.params['ECI']
# Using an alias (i.e. don't provide the credit card)
- assert response = @gateway.purchase(@amount, "awesomeman", @options.merge(:order_id => Time.now.to_i.to_s + "3"))
+ assert response = @gateway.purchase(@amount, 'awesomeman', @options.merge(:order_id => Time.now.to_i.to_s + '3'))
assert_success response
- assert_equal '9', response.params['ECI']
end
def test_invalid_login
gateway = OgoneGateway.new(
- :login => '',
- :user => '',
- :password => ''
+ login: 'login',
+ user: 'user',
+ password: 'password',
+ signature: 'signature'
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal 'Some of the data entered is incorrect. please retry.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
end
end
diff --git a/test/remote/gateways/remote_omise_test.rb b/test/remote/gateways/remote_omise_test.rb
new file mode 100644
index 00000000000..4b5be3064b0
--- /dev/null
+++ b/test/remote/gateways/remote_omise_test.rb
@@ -0,0 +1,103 @@
+require 'test_helper'
+
+class RemoteOmiseTest < Test::Unit::TestCase
+ def setup
+ @gateway = OmiseGateway.new(fixtures(:omise))
+ @amount = 8888
+ @credit_card = credit_card('4242424242424242')
+ @declined_card = credit_card('4255555555555555')
+ @invalid_cvc = credit_card('4111111111160001', {verification_value: ''})
+ @options = {
+ description: 'Active Merchant',
+ email: 'active.merchant@testing.test',
+ currency: 'thb'
+ }
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:public_key], transcript)
+ end
+
+ def test_missing_secret_key
+ assert_raise ArgumentError do
+ OmiseGateway.new()
+ end
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ assert_equal response.params['amount'], @amount
+ assert response.params['paid'], 'paid should be true'
+ assert response.params['authorized'], 'authorized should be true'
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @invalid_cvc)
+ assert_failure response
+ assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code
+ end
+
+ def test_successful_purchase_after_store
+ response = @gateway.store(@credit_card)
+ response = @gateway.purchase(@amount, nil, { customer_id: response.authorization })
+ assert_success response
+ assert_equal response.params['amount'], @amount
+ end
+
+ def test_failed_purchase_with_token
+ response = @gateway.purchase(@amount, nil, {token_id: 'tokn_invalid_12345'})
+ assert_failure response
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert response.params['id'].match(/cust_test_[1-9a-z]+/)
+ end
+
+ def test_failed_store
+ response = @gateway.store(@declined_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_unstore
+ response = @gateway.store(@credit_card, @options)
+ customer = @gateway.unstore(response.params['id'])
+ assert customer.params['deleted']
+ end
+
+ def test_authorize
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+ assert_equal authorize.params['amount'], @amount
+ assert !authorize.params['paid'], 'paid should be false'
+ assert authorize.params['authorized'], 'authorized should be true'
+ end
+
+ def test_authorize_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ capture = @gateway.capture(@amount, authorize.authorization, @options)
+ assert_success capture
+ assert capture.params['paid'], 'paid should be true'
+ assert capture.params['authorized'], 'authorized should be true'
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert_equal purchase.params['amount'], @amount
+ response = @gateway.refund(@amount-1000, purchase.authorization)
+ assert_success response
+ assert_equal @amount-1000, response.params['amount']
+ end
+
+end
diff --git a/test/remote/gateways/remote_openpay_test.rb b/test/remote/gateways/remote_openpay_test.rb
new file mode 100644
index 00000000000..079b4d09279
--- /dev/null
+++ b/test/remote/gateways/remote_openpay_test.rb
@@ -0,0 +1,221 @@
+require 'test_helper'
+
+class RemoteOpenpayTest < Test::Unit::TestCase
+ def setup
+ @gateway = OpenpayGateway.new(fixtures(:openpay))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111')
+ @store_card = credit_card('5105105105105100')
+ @declined_card = credit_card('4222222222222220')
+
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_nil response.message
+ end
+
+ def test_successful_purchase_with_email
+ @options[:email] = '%d@example.org' % Time.now
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_nil response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'The card was declined', response.message
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_nil response.message
+
+ assert response = @gateway.refund(@amount, response.authorization, @options)
+ assert_success response
+ assert_nil response.message
+ assert response.params['refund']
+ assert_equal 'completed', response.params['status']
+ assert_equal 'completed', response.params['refund']['status']
+ end
+
+ def test_unsuccessful_refund
+ assert response = @gateway.refund(@amount, '1', @options)
+ assert_failure response
+ assert_not_nil response.message
+ end
+
+ def test_successful_authorize
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_nil response.message
+ end
+
+ def test_successful_authorize_with_email
+ @options[:email] = '%d@example.org' % Time.now
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_nil response.message
+ end
+
+ def test_unsuccessful_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'The card was declined', response.message
+ end
+
+ def test_successful_capture
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_nil response.message
+
+ assert response = @gateway.capture(@amount, response.authorization, @options)
+ assert_success response
+ assert_nil response.message
+ end
+
+ def test_unsuccessful_capture
+ assert response = @gateway.capture(@amount, '1')
+ assert_failure response
+ assert_equal 'The requested resource doesn\'t exist', response.message
+ end
+
+ def test_successful_void
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_nil response.message
+
+ assert response = @gateway.void(response.authorization, @options)
+ assert_success response
+ assert_equal 'cancelled', response.params['status']
+ end
+
+ def test_successful_purchase_with_card_stored
+ @options[:email] = '%d@example.org' % Time.now
+ @options[:name] = 'Customer name'
+ response_store = @gateway.store(@store_card, @options)
+ assert_success response_store
+ assert_instance_of MultiResponse, response_store
+
+ customer_stored = response_store.responses[0]
+ card_stored = response_store.responses[1]
+ assert response = @gateway.purchase(@amount, card_stored.authorization, @options)
+ assert_success response
+ assert_nil response.message
+
+ assert_success @gateway.unstore(customer_stored.authorization, card_stored.authorization)
+ assert_success @gateway.unstore(customer_stored.authorization)
+ end
+
+ def test_successful_purchase_with_device_session_id
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(device_session_id: 'weur2ty732yu2y47824u23yu4i'))
+ assert_success response
+ end
+
+ def test_successful_purchase_with_card_points
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(use_card_points: 'NONE'))
+ assert_success response
+ end
+
+ def test_failed_purchase_with_card_points
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(use_card_points: 'MIXED'))
+ assert_failure response
+ assert_match %r{cardNumber not allowed for Card points}, response.message
+ end
+
+ def test_successful_purchase_with_installments
+ assert response = @gateway.purchase(@amount * 300, @store_card, @options.merge(payments: '3'))
+ assert_success response
+ end
+
+ def test_successful_store
+ new_email_address = '%d@example.org' % Time.now
+ assert response = @gateway.store(@credit_card, name: 'Test User', email: new_email_address)
+ assert_success response
+ assert_instance_of MultiResponse, response
+ assert response.authorization
+
+ @options[:customer] = response.authorization
+ assert second_card = @gateway.store(@store_card, @options)
+ assert_success second_card
+ assert_instance_of Response, second_card
+ assert second_card.authorization
+
+ customer_stored = response.responses[0]
+ first_card = response.responses[1]
+
+ assert_success @gateway.unstore(customer_stored.authorization, first_card.authorization)
+ assert_success @gateway.unstore(customer_stored.authorization, second_card.authorization)
+ assert_success @gateway.unstore(customer_stored.authorization)
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_unsuccessful_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match(/The card was declined/, response.message)
+ end
+
+ def test_invalid_login
+ gateway = OpenpayGateway.new(
+ key: '123456789',
+ merchant_id: 'mwfxtxcoom7dh47pcds1',
+ production: true
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'The api key or merchant id are invalid', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_nil_cvv_scrubbing
+ @credit_card.verification_value = nil
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('\"cvv2\":[BLANK]'), true
+ end
+
+ def test_empty_string_cvv_scrubbing
+ @credit_card.verification_value = ''
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('\"cvv2\":\"[BLANK]'), true
+ end
+
+ def test_whitespace_string_cvv_scrubbing
+ @credit_card.verification_value = ' '
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('\"cvv2\":\"[BLANK]'), true
+ end
+end
diff --git a/test/remote/gateways/remote_opp_test.rb b/test/remote/gateways/remote_opp_test.rb
new file mode 100644
index 00000000000..652059e87ad
--- /dev/null
+++ b/test/remote/gateways/remote_opp_test.rb
@@ -0,0 +1,216 @@
+require 'test_helper'
+
+class RemoteOppTest < Test::Unit::TestCase
+
+ def setup
+ @gateway = OppGateway.new(fixtures(:opp))
+ @amount = 100
+
+ @valid_card = credit_card('4200000000000000', month: 05, year: 2018)
+ @invalid_card = credit_card('4444444444444444', month: 05, year: 2018)
+ @amex_card = credit_card('377777777777770 ', month: 05, year: 2018, brand: 'amex', verification_value: '1234')
+
+ request_type = 'complete' # 'minimal' || 'complete'
+ time = Time.now.to_i
+ ip = '101.102.103.104'
+ @complete_request_options = {
+ order_id: "Order #{time}",
+ merchant_transaction_id: "active_merchant_test_complete #{time}",
+ address: address,
+ description: 'Store Purchase - Books',
+ # riskWorkflow: true,
+ # testMode: 'EXTERNAL' # or 'INTERNAL', valid only for test system
+
+ billing_address: {
+ address1: '123 Test Street',
+ city: 'Test',
+ state: 'TE',
+ zip: 'AB12CD',
+ country: 'GB',
+ },
+ shipping_address: {
+ name: 'Muton DeMicelis',
+ address1: 'My Street On Upiter, Apt 3.14/2.78',
+ city: 'Munich',
+ state: 'Bov',
+ zip: '81675',
+ country: 'DE',
+ },
+ customer: {
+ merchant_customer_id: 'your merchant/customer id',
+ givenName: 'Jane',
+ surname: 'Jones',
+ birthDate: '1965-05-01',
+ phone: '(?!?)555-5555',
+ mobile: '(?!?)234-23423',
+ email: 'jane@jones.com',
+ company_name: 'JJ Ltd.',
+ identification_doctype: 'PASSPORT',
+ identification_docid: 'FakeID2342431234123',
+ ip: ip,
+ },
+ }
+
+ @minimal_request_options = {
+ order_id: "Order #{time}",
+ description: 'Store Purchase - Books',
+ }
+
+ @complete_request_options['customParameters[SHOPPER_test124TestName009]'] = 'customParameters_test'
+ @complete_request_options['customParameters[SHOPPER_otherCustomerParameter]'] = 'otherCustomerParameter_test'
+
+ @test_success_id = '8a82944a4e008ca9014e1273e0696122'
+ @test_failure_id = '8a8294494e0078a6014e12b371fb6a8e'
+ @test_wrong_reference_id = '8a8444494a0033a6014e12b371fb6a1e'
+
+ @options = @minimal_request_options if request_type == 'minimal'
+ @options = @complete_request_options if request_type == 'complete'
+ end
+
+ # ****************************************** SUCCESSFUL TESTS ******************************************
+ def test_successful_purchase
+ @options[:description] = __method__
+
+ response = @gateway.purchase(@amount, @valid_card, @options)
+ assert_success response, 'Failed purchase'
+ assert_match %r{Request successfully processed}, response.message
+
+ assert response.test?
+ end
+
+ def test_successful_purchase_sans_options
+ response = @gateway.purchase(@amount, @valid_card)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+
+ assert response.test?
+ end
+
+ def test_successful_authorize
+ @options[:description] = __method__
+
+ response = @gateway.authorize(@amount, @valid_card, @options)
+ assert_success response, 'Authorization Failed'
+ assert_match %r{Request successfully processed}, response.message
+
+ assert response.test?
+ end
+
+ def test_successful_capture
+ @options[:description] = __method__
+ auth = @gateway.authorize(@amount, @valid_card, @options)
+ assert_success auth, 'Authorization Failed'
+ assert auth.test?
+
+ capt = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capt, 'Capture failed'
+ assert_match %r{Request successfully processed}, capt.message
+
+ assert capt.test?
+ end
+
+ def test_successful_refund
+ @options[:description] = __method__
+ purchase = @gateway.purchase(@amount, @valid_card, @options)
+ assert_success purchase, 'Purchase failed'
+ assert purchase.test?
+
+ refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund, 'Refund failed'
+ assert_match %r{Request successfully processed}, refund.message
+
+ assert refund.test?
+ end
+
+ def test_successful_void
+ @options[:description] = __method__
+ purchase = @gateway.purchase(@amount, @valid_card, @options)
+ assert_success purchase, 'Purchase failed'
+ assert purchase.test?
+
+ void = @gateway.void(purchase.authorization, @options)
+ assert_success void, 'Void failed'
+ assert_match %r{Request successfully processed}, void.message
+
+ assert void.test?
+ end
+
+ def test_successful_partial_capture
+ @options[:description] = __method__
+ auth = @gateway.authorize(@amount, @valid_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ assert_match %r{Request successfully processed}, capture.message
+ end
+
+ def test_successful_partial_refund
+ @options[:description] = __method__
+ purchase = @gateway.purchase(@amount, @valid_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ assert_match %r{Request successfully processed}, refund.message
+ end
+
+ def test_successful_verify
+ @options[:description] = __method__
+ response = @gateway.verify(@valid_card, @options)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ # ****************************************** FAILURE TESTS ******************************************
+
+ def test_failed_purchase
+ @options[:description] = __method__
+ response = @gateway.purchase(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_match %r{invalid creditcard}, response.message
+ end
+
+ def test_failed_authorize
+ @options[:description] = __method__
+ response = @gateway.authorize(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_match %r{invalid creditcard}, response.message
+ end
+
+ def test_failed_capture
+ @options[:description] = __method__
+ response = @gateway.capture(@amount, @test_wrong_reference_id)
+ assert_failure response
+ assert_match %r{capture needs at least one successful transaction}, response.message
+ end
+
+ def test_failed_refund
+ @options[:description] = __method__
+ response = @gateway.refund(@amount, @test_wrong_reference_id)
+ assert_failure response
+ assert_match %r{Invalid payment data}, response.message
+ end
+
+ def test_failed_void
+ @options[:description] = __method__
+ response = @gateway.void(@test_wrong_reference_id, @options)
+ assert_failure response
+ assert_match %r{reversal needs at least one successful transaction}, response.message
+ end
+
+ # ************************************** TRANSCRIPT SCRUB ******************************************
+
+ def test_transcript_scrubbing
+ assert @gateway.supports_scrubbing?
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @valid_card)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@valid_card.number, transcript)
+ assert_scrubbed(@valid_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_optimal_payment_test.rb b/test/remote/gateways/remote_optimal_payment_test.rb
index 7aad015a162..6061a306300 100644
--- a/test/remote/gateways/remote_optimal_payment_test.rb
+++ b/test/remote/gateways/remote_optimal_payment_test.rb
@@ -12,7 +12,8 @@ def setup
:order_id => '1',
:billing_address => address,
:description => 'Basic Subscription',
- :email => 'email@example.com'
+ :email => 'email@example.com',
+ :ip => '1.2.3.4'
}
end
@@ -23,47 +24,39 @@ def test_successful_purchase
end
def test_unsuccessful_purchase_with_shipping_address
- @options.merge!(:shipping_address => address)
+ @options[:shipping_address] = address
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'no_error', response.message
end
def test_successful_great_britain
- @options[:billing_address][:country] = "GB"
- @options[:billing_address][:state] = "North West England"
+ @options[:billing_address][:country] = 'GB'
+ @options[:billing_address][:state] = 'North West England'
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'no_error', response.message
end
- def test_minimal_successful_purchase
- options = {
- :order_id => '1',
- :description => 'Basic Subscription',
- :billing_address => {
- :zip => 'K1C2N6',
- }
- }
- credit_card = CreditCard.new(
- :number => '4242424242424242',
- :month => 9,
- :year => Time.now.year + 1,
- :first_name => 'Longbob',
- :last_name => 'Longsen',
- :brand => 'visa'
- )
- assert response = @gateway.purchase(@amount, credit_card, options)
- assert_success response
- assert_equal 'no_error', response.message
- end
-
def test_unsuccessful_purchase
assert response = @gateway.purchase(@declined_amount, @credit_card, @options)
assert_failure response
assert_equal 'auth declined', response.message
end
+ def test_purchase_with_no_cvv
+ @credit_card.verification_value = ''
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'no_error', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'no_error', response.message
+ end
+
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
@@ -148,12 +141,24 @@ def test_overloaded_stored_data_authorize_and_capture
def test_invalid_login
gateway = OptimalPaymentGateway.new(
- :account => '1',
- :login => 'bad',
+ :account_number => '1',
+ :store_id => 'bad',
:password => 'bad'
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal 'invalid credentials', response.message
+ assert_equal 'invalid merchant account', response.message
+ end
+
+ # Password assertion hard-coded due to the value being the same as the login, which would cause a false-positive
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed('%3CstorePwd%3Etest%3C/storePwd%3E', transcript)
end
end
diff --git a/test/remote/gateways/remote_orbital_test.rb b/test/remote/gateways/remote_orbital_test.rb
index 84b22a10e48..92e7c3a7d9b 100644
--- a/test/remote/gateways/remote_orbital_test.rb
+++ b/test/remote/gateways/remote_orbital_test.rb
@@ -1,37 +1,55 @@
-require "test_helper.rb"
+require 'test_helper.rb'
class RemoteOrbitalGatewayTest < Test::Unit::TestCase
def setup
Base.mode = :test
- @gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital))
+ @gateway = ActiveMerchant::Billing::OrbitalGateway.new(fixtures(:orbital_gateway))
@amount = 100
- @credit_card = credit_card('4111111111111111')
+ @credit_card = credit_card('4112344112344113')
@declined_card = credit_card('4000300011112220')
@options = {
:order_id => generate_unique_id,
:address => address,
+ :merchant_id => 'merchant1234'
}
@cards = {
- :visa => "4788250000028291",
- :mc => "5454545454545454",
- :amex => "371449635398431",
- :ds => "6011000995500000",
- :diners => "36438999960016",
- :jcb => "3566002020140006"}
+ :visa => '4788250000028291',
+ :mc => '5454545454545454',
+ :amex => '371449635398431',
+ :ds => '6011000995500000',
+ :diners => '36438999960016',
+ :jcb => '3566002020140006'}
+
+ @level_2_options = {
+ tax_indicator: '1',
+ tax: '75',
+ advice_addendum_1: 'taa1 - test',
+ advice_addendum_2: 'taa2 - test',
+ advice_addendum_3: 'taa3 - test',
+ advice_addendum_4: 'taa4 - test',
+ purchase_order: '123abc',
+ name: address[:name],
+ address1: address[:address1],
+ address2: address[:address2],
+ city: address[:city],
+ state: address[:state],
+ zip: address[:zip],
+ }
@test_suite = [
- {:card => :visa, :AVSzip => 11111, :CVD => 111, :amount => 3000},
- {:card => :visa, :AVSzip => 33333, :CVD => nil, :amount => 3801},
- {:card => :mc, :AVSzip => 44444, :CVD => nil, :amount => 4100},
- {:card => :mc, :AVSzip => 88888, :CVD => 666, :amount => 1102},
- {:card => :amex, :AVSzip => 55555, :CVD => nil, :amount => 105500},
- {:card => :amex, :AVSzip => 66666, :CVD => 2222, :amount => 7500},
- {:card => :ds, :AVSzip => 77777, :CVD => nil, :amount => 1000},
- {:card => :ds, :AVSzip => 88888, :CVD => 444, :amount => 6303},
- {:card => :jcb, :AVSzip => 33333, :CVD => nil, :amount => 2900}]
+ {:card => :visa, :AVSzip => 11111, :CVD => 111, :amount => 3000},
+ {:card => :visa, :AVSzip => 33333, :CVD => nil, :amount => 3801},
+ {:card => :mc, :AVSzip => 44444, :CVD => nil, :amount => 4100},
+ {:card => :mc, :AVSzip => 88888, :CVD => 666, :amount => 1102},
+ {:card => :amex, :AVSzip => 55555, :CVD => nil, :amount => 105500},
+ {:card => :amex, :AVSzip => 66666, :CVD => 2222, :amount => 7500},
+ {:card => :ds, :AVSzip => 77777, :CVD => nil, :amount => 1000},
+ {:card => :ds, :AVSzip => 88888, :CVD => 444, :amount => 6303},
+ {:card => :jcb, :AVSzip => 33333, :CVD => nil, :amount => 2900}
+ ]
end
def test_successful_purchase
@@ -40,11 +58,255 @@ def test_successful_purchase
assert_equal 'Approved', response.message
end
+ def test_successful_purchase_with_soft_descriptor_hash
+ assert response = @gateway.purchase(
+ @amount, @credit_card, @options.merge(
+ soft_descriptors: {
+ merchant_name: 'Merch',
+ product_description: 'Description',
+ merchant_email: 'email@example',
+ }
+ )
+ )
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_level_2_data
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(level_2_data: @level_2_options))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_visa_network_tokenization_credit_card_with_eci
+ network_card = network_tokenization_credit_card('4788250000028291',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: '111',
+ brand: 'visa',
+ eci: '5'
+ )
+ assert response = @gateway.purchase(3000, network_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_master_card_network_tokenization_credit_card
+ network_card = network_tokenization_credit_card('4788250000028291',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: '111',
+ brand: 'master'
+ )
+ assert response = @gateway.purchase(3000, network_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_american_express_network_tokenization_credit_card
+ network_card = network_tokenization_credit_card('4788250000028291',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: '111',
+ brand: 'american_express'
+ )
+ assert response = @gateway.purchase(3000, network_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_discover_network_tokenization_credit_card
+ network_card = network_tokenization_credit_card('4788250000028291',
+ payment_cryptogram: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ transaction_id: 'BwABB4JRdgAAAAAAiFF2AAAAAAA=',
+ verification_value: '111',
+ brand: 'discover'
+ )
+ assert response = @gateway.purchase(3000, network_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ [
+ {
+ card: {
+ number: '4112344112344113',
+ verification_value: '411',
+ brand: 'visa',
+ },
+ three_d_secure: {
+ eci: '5',
+ cavv: 'AAABAIcJIoQDIzAgVAkiAAAAAAA=',
+ xid: 'AAABAIcJIoQDIzAgVAkiAAAAAAA=',
+ },
+ address: {
+ address1: '55 Forever Ave',
+ address2: '',
+ city: 'Concord',
+ state: 'NH',
+ zip: '03301',
+ country: 'US',
+ },
+ },
+ {
+ card: {
+ number: '5112345112345114',
+ verification_value: '823',
+ brand: 'master',
+ },
+ three_d_secure: {
+ eci: '6',
+ cavv: 'Asju1ljfl86bAAAAAACm9zU6aqY=',
+ xid: 'Asju1ljfl86bAAAAAACm9zU6aqY=',
+ },
+ address: {
+ address1: 'Byway Street',
+ address2: '',
+ city: 'Portsmouth',
+ state: 'MA',
+ zip: '',
+ country: 'US',
+ },
+ },
+ {
+ card: {
+ number: '371144371144376',
+ verification_value: '1234',
+ brand: 'american_express',
+ },
+ three_d_secure: {
+ eci: '5',
+ cavv: 'AAABBWcSNIdjeUZThmNHAAAAAAA=',
+ xid: 'AAABBWcSNIdjeUZThmNHAAAAAAA=',
+ },
+ address: {
+ address1: '4 Northeastern Blvd',
+ address2: '',
+ city: 'Salem',
+ state: 'NH',
+ zip: '03105',
+ country: 'US',
+ },
+ }
+ ].each do |fixture|
+ define_method("test_successful_#{fixture[:card][:brand]}_authorization_with_3ds") do
+ cc = credit_card(fixture[:card][:number], {
+ verification_value: fixture[:card][:verification_value],
+ brand: fixture[:card][:brand]
+ })
+ assert response = @gateway.authorize(100, cc, @options.merge(
+ order_id: '2',
+ currency: 'USD',
+ three_d_secure: fixture[:three_d_secure],
+ address: fixture[:address]
+ ))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+
+ define_method("test_successful_#{fixture[:card][:brand]}_purchase_with_3ds") do
+ cc = credit_card(fixture[:card][:number], {
+ verification_value: fixture[:card][:verification_value],
+ brand: fixture[:card][:brand]
+ })
+ assert response = @gateway.purchase(100, cc, @options.merge(
+ order_id: '2',
+ currency: 'USD',
+ three_d_secure: fixture[:three_d_secure],
+ address: fixture[:address]
+ ))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert_false response.authorization.blank?
+ end
+ end
+
+ def test_successful_purchase_with_mit_stored_credentials
+ mit_stored_credentials = {
+ mit_msg_type: 'MUSE',
+ mit_stored_credential_ind: 'Y',
+ mit_submitted_transaction_id: 'abcdefg12345678'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(mit_stored_credentials))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_cit_stored_credentials
+ cit_options = {
+ mit_msg_type: 'CUSE',
+ mit_stored_credential_ind: 'Y'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(cit_options))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_normalized_mit_stored_credentials
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: 'abcdefg12345678'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_normalized_cit_stored_credentials
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: true,
+ initiator: 'customer',
+ reason_type: 'unscheduled'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_successful_purchase_with_overridden_normalized_stored_credentials
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'merchant',
+ reason_type: 'unscheduled',
+ network_transaction_id: 'abcdefg12345678'
+ },
+ mit_msg_type: 'MRSB'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential))
+
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
# Amounts of x.01 will fail
def test_unsuccessful_purchase
assert response = @gateway.purchase(101, @declined_card, @options)
assert_failure response
- assert_equal 'AUTH DECLINED 12001', response.message
+ assert_equal 'Invalid CC Number', response.message
end
def test_authorize_and_capture
@@ -57,6 +319,15 @@ def test_authorize_and_capture
assert_success capture
end
+ def test_successful_authorize_and_capture_with_level_2_data
+ auth = @gateway.authorize(@amount, @credit_card, @options.merge(level_2_data: @level_2_options))
+ assert_success auth
+ assert_equal 'Approved', auth.message
+
+ capture = @gateway.capture(@amount, auth.authorization, @options.merge(level_2_data: @level_2_options))
+ assert_success capture
+ end
+
def test_authorize_and_void
assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(:order_id => '2'))
assert_success auth
@@ -75,21 +346,21 @@ def test_refund
assert_success refund
end
+ def test_successful_refund_with_level_2_data
+ amount = @amount
+ assert response = @gateway.purchase(amount, @credit_card, @options.merge(level_2_data: @level_2_options))
+ assert_success response
+ assert response.authorization
+ assert refund = @gateway.refund(amount, response.authorization, @options.merge(level_2_data: @level_2_options))
+ assert_success refund
+ end
+
def test_failed_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
assert_equal 'Bad data error', response.message
end
- def test_successful_purchase_with_money
- response = nil
- silence_warnings do
- assert response = @gateway.purchase(Money.new(100), @credit_card, @options)
- end
- assert_success response
- assert_equal 'Approved', response.message
- end
-
# == Certification Tests
# ==== Section A
@@ -97,7 +368,7 @@ def test_auth_only_transactions
for suite in @test_suite do
amount = suite[:amount]
card = credit_card(@cards[suite[:card]], :verification_value => suite[:CVD])
- @options[:address].merge!(:zip => suite[:AVSzip])
+ @options[:address][:zip] = suite[:AVSzip]
assert response = @gateway.authorize(amount, card, @options)
assert_kind_of Response, response
@@ -115,7 +386,7 @@ def test_auth_capture_transactions
for suite in @test_suite do
amount = suite[:amount]
card = credit_card(@cards[suite[:card]], :verification_value => suite[:CVD])
- options = @options; options[:address].merge!(:zip => suite[:AVSzip])
+ options = @options; options[:address][:zip] = suite[:AVSzip]
assert response = @gateway.purchase(amount, card, options)
assert_kind_of Response, response
@@ -130,7 +401,7 @@ def test_auth_capture_transactions
# ==== Section C
def test_mark_for_capture_transactions
- [[:visa, 3000],[:mc, 4100],[:amex, 105500],[:ds, 1000],[:jcb, 2900]].each do |suite|
+ [[:visa, 3000], [:mc, 4100], [:amex, 105500], [:ds, 1000], [:jcb, 2900]].each do |suite|
amount = suite[1]
card = credit_card(@cards[suite[0]])
assert auth_response = @gateway.authorize(amount, card, @options)
@@ -146,7 +417,7 @@ def test_mark_for_capture_transactions
# ==== Section D
def test_refund_transactions
- [[:visa, 1200],[:mc, 1100],[:amex, 105500],[:ds, 1000],[:jcb, 2900]].each do |suite|
+ [[:visa, 1200], [:mc, 1100], [:amex, 105500], [:ds, 1000], [:jcb, 2900]].each do |suite|
amount = suite[1]
card = credit_card(@cards[suite[0]])
assert purchase_response = @gateway.purchase(amount, card, @options)
@@ -172,4 +443,42 @@ def test_void_transactions
# puts
end
end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid CC Number', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:merchant_id], transcript)
+ end
+
+ def test_transcript_scrubbing_profile
+ transcript = capture_transcript(@gateway) do
+ @gateway.add_customer_profile(@credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:merchant_id], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_pac_net_raven_test.rb b/test/remote/gateways/remote_pac_net_raven_test.rb
new file mode 100644
index 00000000000..a1473031fbc
--- /dev/null
+++ b/test/remote/gateways/remote_pac_net_raven_test.rb
@@ -0,0 +1,207 @@
+require 'test_helper'
+
+class RemotePacNetRavenGatewayTest < Test::Unit::TestCase
+ def setup
+ @gateway = PacNetRavenGateway.new(fixtures(:raven_pac_net))
+
+ @amount = 100
+ @credit_card = credit_card('4000000000000028')
+ @declined_card = credit_card('5100000000000040')
+
+ @options = {
+ billing_address: address
+ }
+ end
+
+ def test_successful_purchase
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert purchase.params['ApprovalCode']
+ assert purchase.params['TrackingNumber']
+ assert_nil purchase.params['ErrorCode']
+ assert_equal 'Approved', purchase.params['Status']
+ assert_equal 'ok', purchase.params['RequestResult']
+ assert_nil purchase.params['Message']
+ assert_equal 'This transaction has been approved', purchase.message
+ end
+
+ def test_invalid_credit_card_number_purchese
+ @credit_card = credit_card('0000')
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure purchase
+ assert_nil purchase.params['ApprovalCode']
+ assert purchase.params['TrackingNumber']
+ assert_equal 'invalid:cardNumber', purchase.params['ErrorCode']
+ assert_equal 'Invalid:CardNumber', purchase.params['Status']
+ assert_equal 'ok', purchase.params['RequestResult']
+ assert_equal 'Error processing transaction because CardNumber "0000" is not between 12 and 19 in length.', purchase.params['Message']
+ end
+
+ def test_expired_credit_card_purchese
+ @credit_card.month = 9
+ @credit_card.year = 2012
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure purchase
+ assert_nil purchase.params['ApprovalCode']
+ assert purchase.params['TrackingNumber']
+ assert_equal 'invalid:CustomerCardExpiryDate', purchase.params['ErrorCode']
+ assert_equal 'Invalid:CustomerCardExpiryDate', purchase.params['Status']
+ assert_equal 'ok', purchase.params['RequestResult']
+ assert_equal 'Invalid because the card expiry date (mmyy) "0912" is not a date in the future', purchase.params['Message']
+ end
+
+ def test_declined_purchase
+ assert purchase = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure purchase
+ assert_equal 'This transaction has been declined', purchase.message
+ end
+
+ def test_successful_authorization
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert auth.params['ApprovalCode']
+ assert auth.params['TrackingNumber']
+ assert_nil auth.params['ErrorCode']
+ assert_nil auth.params['Message']
+ assert_equal 'Approved', auth.params['Status']
+ assert_equal 'ok', auth.params['RequestResult']
+ assert_equal 'This transaction has been approved', auth.message
+ end
+
+ def test_invalid_credit_card_number_authorization
+ @credit_card = credit_card('0000')
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure auth
+ assert_nil auth.params['ApprovalCode']
+ assert auth.params['TrackingNumber']
+ assert_equal 'invalid:cardNumber', auth.params['ErrorCode']
+ assert_equal 'Invalid:CardNumber', auth.params['Status']
+ assert_equal 'ok', auth.params['RequestResult']
+ assert_equal 'Error processing transaction because CardNumber "0000" is not between 12 and 19 in length.', auth.params['Message']
+ end
+
+ def test_expired_credit_card_authorization
+ @credit_card.month = 9
+ @credit_card.year = 2012
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure auth
+ assert_nil auth.params['ApprovalCode']
+ assert auth.params['TrackingNumber']
+ assert_equal 'invalid:CustomerCardExpiryDate', auth.params['ErrorCode']
+ assert_equal 'Invalid:CustomerCardExpiryDate', auth.params['Status']
+ assert_equal 'ok', auth.params['RequestResult']
+ assert_equal 'Invalid because the card expiry date (mmyy) "0912" is not a date in the future', auth.params['Message']
+ end
+
+ def test_declined_authorization
+ assert auth = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure auth
+ assert_equal 'This transaction has been declined', auth.message
+ end
+
+ def test_successful_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert refund.params['ApprovalCode']
+ assert refund.params['TrackingNumber']
+ assert_nil refund.params['ErrorCode']
+ assert_equal 'Approved', refund.params['Status']
+ assert_equal 'ok', refund.params['RequestResult']
+ assert_nil refund.params['Message']
+ assert_equal 'This transaction has been approved', refund.message
+ end
+
+ def test_amount_greater_than_original_amount_refund
+ assert purchase = @gateway.purchase(100, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(200, purchase.authorization)
+ assert_failure refund
+ assert_nil refund.params['ApprovalCode']
+ assert refund.params['TrackingNumber']
+ assert_equal 'invalid:RefundAmountGreaterThanOriginalAmount', refund.params['ErrorCode']
+ assert_equal 'Invalid:RefundAmountGreaterThanOriginalAmount', refund.params['Status']
+ assert_equal 'ok', refund.params['RequestResult']
+ assert_equal 'Invalid because the payment amount cannot be greater than the original charge.', refund.params['Message']
+ assert_equal 'Invalid because the payment amount cannot be greater than the original charge.', refund.message
+ end
+
+ def test_purchase_and_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ assert void.params['ApprovalCode']
+ assert void.params['TrackingNumber']
+ assert_nil void.params['ErrorCode']
+ assert_equal 'ok', void.params['RequestResult']
+ assert_nil void.params['Message']
+ assert_equal 'Voided', void.params['Status']
+ assert_equal 'This transaction has been voided', void.message
+ end
+
+ def test_authorize_and_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert void.params['ApprovalCode']
+ assert void.params['TrackingNumber']
+ assert_nil void.params['ErrorCode']
+ assert_equal 'ok', void.params['RequestResult']
+ assert_nil void.params['Message']
+ assert_equal 'Voided', void.params['Status']
+ assert_equal 'This transaction has been voided', void.message
+ end
+
+ def test_authorize_capture_and_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert void = @gateway.void(capture.authorization)
+ assert_success void
+ assert void.params['ApprovalCode']
+ assert void.params['TrackingNumber']
+ assert_nil void.params['ErrorCode']
+ assert_equal 'ok', void.params['RequestResult']
+ assert_nil void.params['Message']
+ assert_equal 'Voided', void.params['Status']
+ assert_equal 'This transaction has been voided', void.message
+ end
+
+ def test_successful_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert capture.params['ApprovalCode']
+ assert capture.params['TrackingNumber']
+ assert_nil capture.params['ErrorCode']
+ assert_equal 'Approved', capture.params['Status']
+ assert_equal 'ok', capture.params['RequestResult']
+ assert_nil capture.params['Message']
+ assert_equal 'This transaction has been approved', capture.message
+ end
+
+ def test_invalid_preauth_number_capture
+ assert capture = @gateway.capture(@amount, '')
+ assert_failure capture
+ assert_nil capture.params['ApprovalCode']
+ assert capture.params['TrackingNumber']
+ assert_equal 'rejected:UnknownPreauthNumber', capture.params['ErrorCode']
+ assert_equal 'Rejected:UnknownPreauthNumber', capture.params['Status']
+ assert_equal 'ok', capture.params['RequestResult']
+ assert_equal 'Invalid because the preauthorization # does not exist', capture.params['Message']
+ assert_equal 'Invalid because the preauthorization # does not exist', capture.message
+ end
+
+ def test_insufficient_preauth_amount_capture
+ auth = @gateway.authorize(100, @credit_card, @options)
+ assert capture = @gateway.capture(200, auth.authorization)
+ assert_failure capture
+ assert_nil capture.params['ApprovalCode']
+ assert capture.params['TrackingNumber']
+ assert_equal 'rejected:PreauthAmountInsufficient', capture.params['ErrorCode']
+ assert_equal 'Rejected:PreauthAmountInsufficient', capture.params['Status']
+ assert_equal 'ok', capture.params['RequestResult']
+ assert_equal 'Invalid because the preauthorization amount 100 is not identical to the amount to be settled', capture.params['Message']
+ assert_equal 'Invalid because the preauthorization amount 100 is not identical to the amount to be settled', capture.message
+ end
+end
diff --git a/test/remote/gateways/remote_pagarme_test.rb b/test/remote/gateways/remote_pagarme_test.rb
new file mode 100644
index 00000000000..868ff0a48c3
--- /dev/null
+++ b/test/remote/gateways/remote_pagarme_test.rb
@@ -0,0 +1,157 @@
+require 'test_helper'
+
+class RemotePagarmeTest < Test::Unit::TestCase
+ def setup
+ @gateway = PagarmeGateway.new(fixtures(:pagarme))
+
+ @amount = 1000
+
+ @credit_card = credit_card('4242424242424242', {
+ first_name: 'Richard',
+ last_name: 'Deschamps'
+ })
+
+ @declined_card = credit_card('4242424242424242', {
+ first_name: 'Richard',
+ last_name: 'Deschamps',
+ :verification_value => '688'
+ })
+
+ @options = {
+ billing_address: address(),
+ description: 'ActiveMerchant Teste de Compra'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transação aprovada', response.message
+
+ # Assert metadata
+ assert_equal response.params['metadata']['description'], @options[:description]
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ customer: 'Richard Deschamps',
+ invoice: '1',
+ merchant: 'Richard\'s',
+ description: 'ActiveMerchant Teste de Compra',
+ email: 'suporte@pagar.me',
+ billing_address: address()
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Transação aprovada', response.message
+
+ # Assert metadata
+ assert_equal response.params['metadata']['order_id'], options[:order_id]
+ assert_equal response.params['metadata']['ip'], options[:ip]
+ assert_equal response.params['metadata']['customer'], options[:customer]
+ assert_equal response.params['metadata']['invoice'], options[:invoice]
+ assert_equal response.params['metadata']['merchant'], options[:merchant]
+ assert_equal response.params['metadata']['description'], options[:description]
+ assert_equal response.params['metadata']['email'], options[:email]
+ end
+
+ def test_successful_purchase_without_options
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Transação aprovada', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transação recusada', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert_equal 'Transação autorizada', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Transação aprovada', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transação recusada', response.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, nil)
+ assert_failure response
+ assert_equal 'Não é possível capturar uma transação sem uma prévia autorização.', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Transação estornada', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, nil)
+ assert_failure response
+ assert_equal 'Não é possível estornar uma transação sem uma prévia captura.', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Transação estornada', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void(nil)
+ assert_failure response
+ assert_equal 'Não é possível estornar uma transação autorizada sem uma prévia autorização.', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Transação autorizada', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Transação recusada', response.message
+ end
+
+ def test_invalid_login
+ gateway = PagarmeGateway.new(api_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{401 Authorization Required}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_pago_facil_test.rb b/test/remote/gateways/remote_pago_facil_test.rb
new file mode 100644
index 00000000000..254ebd33dec
--- /dev/null
+++ b/test/remote/gateways/remote_pago_facil_test.rb
@@ -0,0 +1,97 @@
+require 'test_helper'
+
+class RemotePagoFacilTest < Test::Unit::TestCase
+ def setup
+ @gateway = PagoFacilGateway.new(fixtures(:pago_facil))
+
+ @amount = 100
+
+ @credit_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '4111111111111111',
+ verification_value: '123',
+ first_name: 'Juan',
+ last_name: 'Reyes Garza',
+ month: 9,
+ year: Time.now.year + 1
+ )
+
+ @declined_card = ActiveMerchant::Billing::CreditCard.new(
+ number: '1111111111111111',
+ verification_value: '123',
+ first_name: 'Juan',
+ last_name: 'Reyes Garza',
+ month: 9,
+ year: Time.now.year + 1
+ )
+
+ @options = {
+ order_id: '1',
+ billing_address: {
+ address1: 'Anatole France 311',
+ address2: 'Polanco',
+ city: 'Miguel Hidalgo',
+ state: 'Distrito Federal',
+ country: 'Mexico',
+ zip: '11560',
+ phone: '5550220910'
+ },
+ email: 'comprador@correo.com',
+ cellphone: '5550123456'
+ }
+ end
+
+ def test_successful_purchase
+ response = successful_response_to do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+
+ assert response.authorization
+ assert_equal 'Transaction has been successful!-Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Errores en los datos de entrada Validaciones', response.message
+ end
+
+ def test_invalid_login
+ gateway = PagoFacilGateway.new(
+ branch_id: '',
+ merchant_id: '',
+ service_id: 3
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase_usd
+ options = @options.merge(currency: 'USD')
+ response = successful_response_to do
+ @gateway.purchase(@amount, @credit_card, options)
+ end
+
+ assert_equal 'USD', response.params['dataVal']['divisa']
+ assert response.authorization
+ assert_equal 'Transaction has been successful!-Approved', response.message
+ end
+
+ # Even when all the parameters are correct the PagoFacil's test service will
+ # respond randomly (can be approved or declined). When for this reason the
+ # service returns a "declined" response, the response should have the error
+ # message 'Declined_(General)'
+ def successful_response_to
+ attempts = 0
+ loop do
+ random_response = yield
+ if random_response.success?
+ return random_response
+ elsif(attempts > 2)
+ raise 'Unable to get a successful response'
+ else
+ assert_equal 'Declined_(General).', random_response.params.fetch('error')
+ attempts += 1
+ end
+ end
+ end
+end
diff --git a/test/remote/gateways/remote_pay_conex_test.rb b/test/remote/gateways/remote_pay_conex_test.rb
new file mode 100644
index 00000000000..22ed010bcbd
--- /dev/null
+++ b/test/remote/gateways/remote_pay_conex_test.rb
@@ -0,0 +1,202 @@
+require 'test_helper'
+
+class RemotePayConexTest < Test::Unit::TestCase
+ def setup
+ @gateway = PayConexGateway.new(fixtures(:pay_conex))
+ @credit_card = credit_card('4000100011112224')
+ @check = check
+
+ @amount = 100
+ @failed_amount = 101
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase',
+ email: 'joe@example.com'
+ }
+ end
+
+ def test_transcript_scrubbing
+ @credit_card.verification_value = '447'
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:api_accesskey], transcript)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@failed_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'DECLINED', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'APPROVED', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'CAPTURED', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@failed_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'DECLINED', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ assert_equal 'CAPTURED', capture.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'UnknownAuth')
+ assert_failure response
+ assert_equal 'Invalid token_id', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'VOID', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ assert_equal 'REFUND', refund.message
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ response = @gateway.refund(@amount + 400, purchase.authorization)
+ assert_failure response
+ assert_equal 'INVALID REFUND AMOUNT', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'APPROVED', void.message
+ end
+
+ def test_failed_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ response = @gateway.void(auth.authorization)
+ assert_success response
+
+ response = @gateway.void(auth.authorization)
+ assert_failure response
+ assert_equal 'TRANSACTION ID ALREADY REVERSED', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(credit_card('BogusCard'), @options)
+ assert_failure response
+ assert_equal 'INVALID CARD NUMBER', response.message
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+ assert response.authorization
+ assert_equal '2224', response.params['last4']
+ end
+
+ def test_failed_store
+ assert response = @gateway.store(credit_card('141241'))
+ assert_failure response
+ assert_equal 'CARD DATA UNREADABLE', response.message
+ end
+
+ def test_purchase_using_stored_card
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'CREDIT', response.message
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, credit_card('12321'), @options)
+ assert_failure response
+ assert_equal 'CARD DATA UNREADABLE', response.message
+ end
+
+ def test_successful_card_present_purchase
+ response = @gateway.purchase(@amount, credit_card_with_track_data('4000100011112224'), @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ end
+
+ def test_failed_card_present_purchase
+ card = CreditCard.new(track_data: '%B37826310005^LOB^17001130504392?')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_equal 'CARD DATA UNREADABLE', response.message
+ end
+
+ def test_successful_echeck_purchase
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'PENDING', response.message
+ assert response.authorization
+ end
+
+ def test_failed_echeck_purchase
+ response = @gateway.purchase(@amount, check(routing_number: '23433'), @options)
+ assert_failure response
+ assert_equal 'Invalid bank_routing_number', response.message
+ end
+
+ def test_invalid_login
+ gateway = PayConexGateway.new(account_id: 'Unknown', api_accesskey: 'Incorrect')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Invalid account_id', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_pay_gate_xml_test.rb b/test/remote/gateways/remote_pay_gate_xml_test.rb
index 3be6b3cb48d..b16cedb0678 100644
--- a/test/remote/gateways/remote_pay_gate_xml_test.rb
+++ b/test/remote/gateways/remote_pay_gate_xml_test.rb
@@ -11,6 +11,8 @@ def setup
@options = {
:order_id => generate_unique_id,
:billing_address => address,
+ :email => 'john.doe@example.com',
+ :ip => '127.0.0.1',
:description => 'Store Purchase',
}
end
@@ -24,7 +26,7 @@ def test_successful_purchase
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal "Declined", response.message
+ assert_equal 'Declined', response.message
end
def test_authorize_and_capture
@@ -45,11 +47,20 @@ def test_failed_capture
def test_invalid_login
gateway = PayGateXmlGateway.new(
- :login => '',
- :password => ''
- )
+ :login => '',
+ :password => ''
+ )
assert response = gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Incorrect Credentials Supplied', response.message
end
+
+ def test_successful_purchase_and_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ credit = @gateway.refund(@amount, purchase.authorization, :note => 'Sorry')
+ assert_success credit
+ assert credit.test?
+ end
end
diff --git a/test/remote/gateways/remote_pay_hub_test.rb b/test/remote/gateways/remote_pay_hub_test.rb
new file mode 100644
index 00000000000..e57fe048e4a
--- /dev/null
+++ b/test/remote/gateways/remote_pay_hub_test.rb
@@ -0,0 +1,82 @@
+require 'test_helper'
+
+class RemotePayHubTest < Test::Unit::TestCase
+ def setup
+ @gateway = PayHubGateway.new(fixtures(:pay_hub))
+ @amount = 100
+ @credit_card = credit_card('5466410004374507', verification_value: '998')
+ @invalid_card = credit_card('371449635398431', verification_value: '9997')
+ @options = {
+ :first_name => 'Garrya',
+ :last_name => 'Barrya',
+ :email => 'payhubtest@mailinator.com',
+ :address => {
+ :address1 => '123a ahappy St.',
+ :city => 'Happya City',
+ :state => 'CA',
+ :zip => '94901'
+ }
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_unsuccessful_purchase
+ amount = 20
+ response = @gateway.purchase(amount, @invalid_card, @options)
+ assert_failure response
+ assert_equal 'DECLINE', response.message
+ end
+
+ def test_successful_auth
+ response = @gateway.authorize(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_unsuccessful_auth
+ response = @gateway.authorize(20, @invalid_card, @options)
+ assert_failure response
+ assert_equal 'DECLINE', response.message
+ end
+
+ def test_unsuccessful_capture
+ assert_failure @gateway.capture(@amount, 'bogus')
+ end
+
+ def test_partial_capture
+ auth_response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth_response
+
+ response = @gateway.capture(10, auth_response.authorization)
+ assert_success response
+ assert_equal 'TRANSACTION CAPTURED SUCCESSFULLY', response.message
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+
+ response = @gateway.refund(nil, response.authorization)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_unsuccessful_refund
+ assert_failure @gateway.refund(@amount, 'bogus')
+ end
+
+ def test_successful_verify
+ assert_success @gateway.verify(@credit_card)
+ end
+
+ def test_failed_verify
+ assert_failure @gateway.verify(credit_card('4111111111111111'))
+ end
+end
diff --git a/test/remote/gateways/remote_pay_junction_test.rb b/test/remote/gateways/remote_pay_junction_test.rb
index db8724e1811..1db822d8bb1 100644
--- a/test/remote/gateways/remote_pay_junction_test.rb
+++ b/test/remote/gateways/remote_pay_junction_test.rb
@@ -37,8 +37,8 @@ def setup
def test_successful_purchase
assert response = @gateway.purchase(AMOUNT, @credit_card, @options)
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
- assert_equal 'capture', response.params["posture"], 'Should be captured funds'
- assert_equal 'charge', response.params["transaction_action"]
+ assert_equal 'capture', response.params['posture'], 'Should be captured funds'
+ assert_equal 'charge', response.params['transaction_action']
assert_success response
assert response.test?
end
@@ -48,18 +48,18 @@ def test_successful_purchase_with_cvv
assert response = @gateway.purchase(AMOUNT, @credit_card, @options)
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
- assert_equal 'capture', response.params["posture"], 'Should be captured funds'
- assert_equal 'charge', response.params["transaction_action"]
+ assert_equal 'capture', response.params['posture'], 'Should be captured funds'
+ assert_equal 'charge', response.params['transaction_action']
assert_success response
end
def test_successful_authorize
- assert response = @gateway.authorize( AMOUNT, @credit_card, @options)
+ assert response = @gateway.authorize(AMOUNT, @credit_card, @options)
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
- assert_equal 'hold', response.params["posture"], 'Should be a held charge'
- assert_equal 'charge', response.params["transaction_action"]
+ assert_equal 'hold', response.params['posture'], 'Should be a held charge'
+ assert_equal 'charge', response.params['transaction_action']
assert_success response
end
@@ -70,9 +70,9 @@ def test_successful_capture
response = @gateway.capture(AMOUNT, auth.authorization, @options)
assert_success response
- assert_equal 'capture', response.params["posture"], 'Should be a capture'
+ assert_equal 'capture', response.params['posture'], 'Should be a capture'
assert_equal auth.authorization, response.authorization,
- "Should maintain transaction ID across request"
+ 'Should maintain transaction ID across request'
end
def test_successful_credit
@@ -80,7 +80,7 @@ def test_successful_credit
assert_success purchase
assert response = @gateway.credit(success_price, purchase.authorization)
- assert_equal 'refund', response.params["transaction_action"]
+ assert_equal 'refund', response.params['transaction_action']
assert_success response
end
@@ -92,39 +92,39 @@ def test_successful_void
assert response = @gateway.void(purchase.authorization, :order_id => order_id)
assert_success response
- assert_equal 'void', response.params["posture"], 'Should be a capture'
+ assert_equal 'void', response.params['posture'], 'Should be a capture'
assert_equal purchase.authorization, response.authorization,
- "Should maintain transaction ID across request"
+ 'Should maintain transaction ID across request'
end
def test_successful_instant_purchase
- # this takes advatange of the PayJunction feature where another
+ # this takes advantage of the PayJunction feature where another
# transaction can be executed if you have the transaction ID of a
# previous successful transaction.
- purchase = @gateway.purchase( AMOUNT, @credit_card, @options)
+ purchase = @gateway.purchase(AMOUNT, @credit_card, @options)
assert_success purchase
assert response = @gateway.purchase(AMOUNT, purchase.authorization, :order_id => generate_unique_id)
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
- assert_equal 'capture', response.params["posture"], 'Should be captured funds'
- assert_equal 'charge', response.params["transaction_action"]
+ assert_equal 'capture', response.params['posture'], 'Should be captured funds'
+ assert_equal 'charge', response.params['transaction_action']
assert_not_equal purchase.authorization, response.authorization,
- 'Should have recieved new transaction ID'
+ 'Should have recieved new transaction ID'
assert_success response
end
def test_successful_recurring
assert response = @gateway.recurring(AMOUNT, @credit_card,
- :periodicity => :monthly,
- :payments => 12,
- :order_id => generate_unique_id[0..15]
- )
+ :periodicity => :monthly,
+ :payments => 12,
+ :order_id => generate_unique_id[0..15]
+ )
assert_equal PayJunctionGateway::SUCCESS_MESSAGE, response.message
- assert_equal 'charge', response.params["transaction_action"]
+ assert_equal 'charge', response.params['transaction_action']
assert_success response
end
@@ -132,11 +132,12 @@ def test_should_send_invoice
response = @gateway.purchase(AMOUNT, @credit_card, @options)
assert_success response
- assert_equal @options[:order_id], response.params["invoice_number"], 'Should have set invoice'
+ assert_equal @options[:order_id], response.params['invoice_number'], 'Should have set invoice'
end
private
+
def success_price
- 200 + rand(200)
+ rand(200..399)
end
end
diff --git a/test/remote/gateways/remote_pay_junction_v2_test.rb b/test/remote/gateways/remote_pay_junction_v2_test.rb
new file mode 100644
index 00000000000..62954ff4496
--- /dev/null
+++ b/test/remote/gateways/remote_pay_junction_v2_test.rb
@@ -0,0 +1,170 @@
+require 'test_helper'
+
+class RemotePayJunctionV2Test < Test::Unit::TestCase
+ def setup
+ @gateway = PayJunctionV2Gateway.new(fixtures(:pay_junction_v2))
+
+ @amount = 99
+ @credit_card = credit_card('4444333322221111', month: 01, year: 2020, verification_value: 999)
+ @options = {
+ order_id: generate_unique_id
+ }
+ end
+
+ def test_invalid_login
+ gateway = PayJunctionV2Gateway.new(api_login: '', api_password: '', api_key: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid application key}, response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_sans_options
+ amount = SecureRandom.random_number(100) + 100
+ response = @gateway.purchase(amount, @credit_card)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_purchase
+ amount = 5
+ response = @gateway.purchase(amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined (do not honor)', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_failed_authorize
+ amount = 10
+ response = @gateway.authorize(amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Declined (restricted)', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ assert_equal sprintf('%.2f', (@amount-1).to_f / 100), capture.params['amountTotal']
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'invalid_authorization')
+ assert_failure response
+ assert_equal '404 Not Found|', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-50, purchase.authorization)
+ assert_success refund
+ assert_equal 'Approved', refund.message
+ assert_equal sprintf('%.2f', (@amount-50).to_f / 100), refund.params['amountTotal']
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '0000')
+ assert_failure response
+ assert_match %r{Transaction Id 0 does not exist}, response.message
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_credit
+ amount = 0
+ response = @gateway.credit(amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Amount Base must be greater than 0.|', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Approved', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('invalid_authorization')
+ assert_failure response
+ assert_equal '404 Not Found|', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_verify
+ credit_card = credit_card('444433332222111')
+ response = @gateway.verify(credit_card, @options)
+ assert_failure response
+ assert_match %r{Card Number is not a valid card number}, response.message
+ end
+
+ def test_successful_store_and_purchase
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert response.authorization
+ assert_equal 'Approved', response.message
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_store
+ credit_card = credit_card('444433332222111')
+ response = @gateway.store(credit_card, @options)
+ assert_failure response
+ assert_match %r{Card Number is not a valid card number}, response.message
+ end
+
+ def test_transcript_scrubbing
+ assert @gateway.supports_scrubbing?
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:api_key], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_pay_secure_test.rb b/test/remote/gateways/remote_pay_secure_test.rb
index 8dd8837adc1..ee153752bb9 100644
--- a/test/remote/gateways/remote_pay_secure_test.rb
+++ b/test/remote/gateways/remote_pay_secure_test.rb
@@ -4,15 +4,15 @@ class RemotePaySecureTest < Test::Unit::TestCase
def setup
@gateway = PaySecureGateway.new(fixtures(:pay_secure))
-
+
@credit_card = credit_card('4000100011112224')
- @options = {
+ @options = {
:billing_address => address,
:order_id => generate_unique_id
}
@amount = 100
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -26,7 +26,7 @@ def test_unsuccessful_purchase
assert_equal 'Declined, card expired', response.message
assert_failure response
end
-
+
def test_invalid_login
gateway = PaySecureGateway.new(
:login => '',
diff --git a/test/remote/gateways/remote_paybox_direct_test.rb b/test/remote/gateways/remote_paybox_direct_test.rb
index e1ccf73ad21..25c03d67804 100644
--- a/test/remote/gateways/remote_paybox_direct_test.rb
+++ b/test/remote/gateways/remote_paybox_direct_test.rb
@@ -3,22 +3,21 @@
require 'test_helper'
class RemotePayboxDirectTest < Test::Unit::TestCase
-
def setup
@gateway = PayboxDirectGateway.new(fixtures(:paybox_direct))
-
+
@amount = 100
@credit_card = credit_card('1111222233334444')
@declined_card = credit_card('1111222233334445')
-
- @options = {
+
+ @options = {
:order_id => '1',
:billing_address => address,
:description => 'Store Purchase'
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
@@ -28,7 +27,7 @@ def test_successful_purchase
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal "PAYBOX : Numéro de porteur invalide", response.message
+ assert_equal "PAYBOX : Num\xE9ro de porteur invalide".force_encoding('ASCII-8BIT'), response.message
end
def test_authorize_and_capture
@@ -40,7 +39,7 @@ def test_authorize_and_capture
assert capture = @gateway.capture(amount, auth.authorization, :order_id => '1')
assert_success capture
end
-
+
def test_purchase_and_void
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -55,9 +54,9 @@ def test_purchase_and_void
def test_failed_capture
assert response = @gateway.capture(@amount, '', :order_id => '1')
assert_failure response
- assert_equal "Mandatory values missing keyword:13 Type:1", response.message
+ assert_equal 'Invalid data', response.message
end
-
+
def test_purchase_and_partial_credit
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
@@ -67,15 +66,47 @@ def test_purchase_and_partial_credit
assert_equal 'The transaction was approved', credit.message
assert_success credit
end
-
+
+ def test_successful_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, order_id: '1')
+ assert_success refund
+ end
+
+ def test_partial_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount/2, purchase.authorization, order_id: '1')
+ assert_success refund
+ end
+
+ def test_failed_refund
+ refund = @gateway.refund(@amount, '', order_id: '2')
+ assert_failure refund
+ assert_equal 'Invalid data', refund.message
+ end
def test_invalid_login
gateway = PayboxDirectGateway.new(
- :login => '199988899',
- :password => '1999888F'
+ login: '199988899',
+ password: '1999888F',
+ rang: 100
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Non autorise', response.message
+ end
+
+ def test_invalid_login_without_rang
+ gateway = PayboxDirectGateway.new(
+ login: '199988899',
+ password: '1999888F'
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "PAYBOX : Accès refusé ou site/rang/clé invalide", response.message
+ assert_equal 'Non autorise', response.message
end
end
diff --git a/test/remote/gateways/remote_payeezy_test.rb b/test/remote/gateways/remote_payeezy_test.rb
new file mode 100644
index 00000000000..789e500a456
--- /dev/null
+++ b/test/remote/gateways/remote_payeezy_test.rb
@@ -0,0 +1,330 @@
+require 'test_helper'
+
+class RemotePayeezyTest < Test::Unit::TestCase
+ def setup
+ @gateway = PayeezyGateway.new(fixtures(:payeezy))
+ @credit_card = credit_card
+ @bad_credit_card = credit_card('4111111111111113')
+ @check = check
+ @amount = 100
+ @reversal_id = "REV-#{SecureRandom.random_number(1000000)}"
+ @options = {
+ :billing_address => address,
+ :merchant_ref => 'Store Purchase',
+ :ta_token => 'NOIW'
+ }
+ @options_mdd = {
+ soft_descriptors: {
+ dba_name: 'Caddyshack',
+ street: '1234 Any Street',
+ city: 'Durham',
+ region: 'North Carolina',
+ mid: 'mid_1234',
+ mcc: 'mcc_5678',
+ postal_code: '27701',
+ country_code: 'US',
+ merchant_contact_info: '8885551212'
+ }
+ }
+ @options_stored_credentials = {
+ cardbrand_original_transaction_id: 'abc123',
+ sequence: 'FIRST',
+ is_scheduled: true,
+ initiator: 'MERCHANT',
+ auth_type_override: 'A'
+ }
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Token successfully created.', response.message
+ assert response.authorization
+ end
+
+ def test_successful_store_and_purchase
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert purchase = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success purchase
+ end
+
+ def test_unsuccessful_store
+ assert response = @gateway.store(@bad_credit_card, @options)
+ assert_failure response
+ assert_equal 'The credit card number check failed', response.message
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_echeck
+ options = @options.merge({customer_id_type: '1', customer_id_number: '1', client_email: 'test@example.com'})
+ assert response = @gateway.purchase(@amount, @check, options)
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_soft_descriptors
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@options_mdd))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_stored_credentials
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(@options_stored_credentials))
+ assert_match(/Transaction Normal/, response.message)
+ assert_success response
+ end
+
+ def test_failed_purchase
+ @amount = 501300
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction not approved/, response.message)
+ assert_failure response
+ end
+
+ def test_authorize_and_capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_successful_store_and_auth_and_capture
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+
+ assert auth = @gateway.authorize(@amount, response.authorization, @options)
+ assert_success auth
+ assert auth.authorization
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ @amount = 501300
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure auth
+ assert auth.authorization
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '1|1')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ end
+
+ def test_successful_refund_with_echeck
+ assert purchase = @gateway.purchase(@amount, @check, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ end
+
+ def test_successful_refund_with_stored_card
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+
+ assert purchase = @gateway.purchase(@amount, response.authorization, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, purchase.authorization)
+ assert_success response
+ assert_match(/Transaction Normal/, response.message)
+ assert response.authorization
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Transaction Normal/, purchase.message)
+ assert_success purchase
+
+ assert response = @gateway.refund(50, 'bad-authorization')
+ assert_failure response
+ assert_match(/The transaction tag is not provided/, response.message)
+ assert response.authorization
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Transaction Normal - Approved', void.message
+ end
+
+ def test_successful_auth_void_with_reversal_id
+ auth = @gateway.authorize(@amount, @credit_card, @options.merge(reversal_id: @reversal_id))
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization, reversal_id: @reversal_id)
+ assert_success void
+ assert_equal 'Transaction Normal - Approved', void.message
+ end
+
+ def test_successful_void_purchase_with_reversal_id
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(reversal_id: @reversal_id))
+ assert_success response
+
+ assert void = @gateway.void(response.authorization, reversal_id: @reversal_id)
+ assert_success void
+ assert_equal 'Transaction Normal - Approved', void.message
+ end
+
+ def test_successful_void_with_stored_card_and_reversal_id
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+
+ auth = @gateway.authorize(@amount, response.authorization, @options.merge(reversal_id: @reversal_id))
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization, reversal_id: @reversal_id)
+ assert_success void
+ assert_equal 'Transaction Normal - Approved', void.message
+ end
+
+ def test_successful_void_with_stored_card
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+
+ auth = @gateway.authorize(@amount, response.authorization, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Transaction Normal - Approved', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'The transaction id is not provided', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Transaction Normal - Approved}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@bad_credit_card, @options)
+ assert_failure response
+ assert_match %r{The credit card number check failed}, response.message
+ end
+
+ def test_bad_creditcard_number
+ assert response = @gateway.purchase(@amount, @bad_credit_card, @options)
+ assert_failure response
+ assert_equal response.error_code, 'invalid_card_number'
+ end
+
+ def test_invalid_login
+ gateway = PayeezyGateway.new(apikey: 'NotARealUser', apisecret: 'NotARealPassword', token: 'token')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_match %r{Invalid Api Key}, response.message
+ assert_failure response
+ end
+
+ def test_response_contains_cvv_and_avs_results
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'M', response.cvv_result['code']
+ assert_equal '4', response.avs_result['code']
+ end
+
+ def test_trans_error
+ # ask for error 42 (unable to send trans) as the cents bit...
+ @amount = 500042
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_match(/Server Error/, response.message) # 42 is 'unable to send trans'
+ assert_failure response
+ assert_equal '500', response.error_code
+ end
+
+ def test_transcript_scrubbing_store
+ transcript = capture_transcript(@gateway) do
+ @gateway.store(@credit_card, @options)
+ end
+
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:token], transcript)
+ assert_scrubbed(@gateway.options[:apikey], transcript)
+ end
+
+ def test_transcript_scrubbing_store_with_missing_ta_token
+ transcript = capture_transcript(@gateway) do
+ @options.delete(:ta_token)
+ @gateway.store(@credit_card, @options)
+ end
+
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:token], transcript)
+ assert_scrubbed(@gateway.options[:apikey], transcript)
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:token], transcript)
+ end
+
+ def test_transcript_scrubbing_echeck
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@gateway.options[:token], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_payex_test.rb b/test/remote/gateways/remote_payex_test.rb
new file mode 100644
index 00000000000..fd37ddb6164
--- /dev/null
+++ b/test/remote/gateways/remote_payex_test.rb
@@ -0,0 +1,119 @@
+require 'test_helper'
+
+class RemotePayexTest < Test::Unit::TestCase
+
+ def setup
+ @gateway = PayexGateway.new(fixtures(:payex))
+
+ @amount = 1000
+ @credit_card = credit_card('4581090329655682')
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ :order_id => '1234',
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ # we can't test for a message since the messages vary so much
+ assert_not_equal 'OK', response.message
+ end
+
+ def test_authorize_and_capture
+ amount = @amount
+ assert response = @gateway.authorize(amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+
+ assert response.authorization
+ assert response = @gateway.capture(amount, response.authorization)
+ assert_success response
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '1')
+ assert_failure response
+ assert_not_equal 'OK', response.message
+ assert_not_equal 'RecordNotFound', response.params[:status_errorcode]
+ end
+
+ def test_authorization_and_void
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ assert response = @gateway.void(response.authorization)
+ assert_success response
+ end
+
+ def test_unsuccessful_void
+ assert response = @gateway.void('1')
+ assert_failure response
+ assert_not_equal 'OK', response.message
+ assert_match %r{1}, response.message
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+ assert response = @gateway.refund(@amount - 200, response.authorization, order_id: '123')
+ assert_success response
+ end
+
+ def test_unsuccessful_refund
+ assert response = @gateway.refund(@amount, '1', order_id: '123')
+ assert_failure response
+ assert_not_equal 'OK', response.message
+ assert_match %r{1}, response.message
+ end
+
+ def test_successful_store_and_purchase
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+
+ assert response = @gateway.purchase(@amount, response.authorization, @options.merge({order_id: '5678'}))
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+
+ def test_successful_store_and_authorize_and_capture
+ assert response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+
+ assert response = @gateway.authorize(@amount, response.authorization, @options.merge({order_id: '5678'}))
+ assert_success response
+ assert_equal 'OK', response.message
+ assert response.authorization
+
+ assert response = @gateway.capture(@amount, response.authorization)
+ assert_success response
+ end
+
+ def test_successful_unstore
+ assert response = @gateway.store(@credit_card, @options)
+ assert_equal 'OK', response.message
+ assert response = @gateway.unstore(response.authorization)
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+
+ def test_invalid_login
+ gateway = PayexGateway.new(
+ :account => '1',
+ :encryption_key => '1'
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_not_equal 'OK', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_payflow_express_test.rb b/test/remote/gateways/remote_payflow_express_test.rb
index 19eac916479..827907d02d9 100644
--- a/test/remote/gateways/remote_payflow_express_test.rb
+++ b/test/remote/gateways/remote_payflow_express_test.rb
@@ -2,23 +2,25 @@
class RemotePayflowExpressTest < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
-
+ Base.mode = :test
+
@gateway = PayflowExpressGateway.new(fixtures(:payflow))
- @options = { :billing_address => {
- :name => 'Cody Fauser',
- :address1 => '1234 Shady Brook Lane',
- :city => 'Ottawa',
- :state => 'ON',
- :country => 'CA',
- :zip => '90210',
- :phone => '555-555-5555'
- },
- :email => 'cody@example.com'
- }
+ @options = {
+ :billing_address =>
+ {
+ :name => 'Cody Fauser',
+ :address1 => '1234 Shady Brook Lane',
+ :city => 'Ottawa',
+ :state => 'ON',
+ :country => 'CA',
+ :zip => '90210',
+ :phone => '555-555-5555'
+ },
+ :email => 'cody@example.com'
+ }
end
-
+
# Only works with a Payflow 2.0 account or by requesting the addition
# of Express checkout to an existing Payflow Pro account. This can be done
# by contacting Payflow sales. The PayPal account used must be a business
@@ -35,7 +37,7 @@ def test_set_express_authorization
assert response.test?
assert !response.params['token'].blank?
end
-
+
def test_set_express_purchase
@options.update(
:return_url => 'http://example.com',
@@ -50,66 +52,81 @@ def test_set_express_purchase
def test_setup_authorization_discount_taxes_included_free_shipping
amount = 2518
- options = {:ip=>"127.0.0.1",
- :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1",
- :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1",
- :customer=>"test6@test.com",
- :email=>"test6@test.com",
- :order_id=>"#1092",
- :currency=>"USD",
+ options = {
+ :ip=>'127.0.0.1',
+ :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1',
+ :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1',
+ :customer=>'test6@test.com',
+ :email=>'test6@test.com',
+ :order_id=>'#1092',
+ :currency=>'USD',
:subtotal=>2798,
:items => [
- {:name => "test4",
- :description => "test4",
- :quantity=>2 ,
- :amount=> 1399 ,
- :url=>"http://localhost:3000/products/test4"}],
+ {
+ :name => 'test4',
+ :description => 'test4',
+ :quantity=>2,
+ :amount=> 1399,
+ :url=>'http://localhost:3000/products/test4'
+ }
+ ],
:discount=>280,
- :no_shipping=>true}
+ :no_shipping=>true
+ }
response = @gateway.setup_authorization(amount, options)
assert response.success?, response.message
end
def test_setup_authorization_with_discount_taxes_additional
amount = 2518
- options = {:ip=>"127.0.0.1",
- :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1",
- :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1",
- :customer=>"test6@test.com",
- :email=>"test6@test.com",
- :order_id=>"#1092",
- :currency=>"USD",
+ options = {
+ :ip=>'127.0.0.1',
+ :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1',
+ :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1',
+ :customer=>'test6@test.com',
+ :email=>'test6@test.com',
+ :order_id=>'#1092',
+ :currency=>'USD',
:subtotal=>2798,
:items => [
- {:name => "test4",
- :description => "test4",
- :quantity=>2 ,
- :amount=> 1399 ,
- :url=>"http://localhost:3000/products/test4"}],
+ {
+ :name => 'test4',
+ :description => 'test4',
+ :quantity=>2,
+ :amount=> 1399,
+ :url=>'http://localhost:3000/products/test4'
+ }
+ ],
:discount=>280,
- :no_shipping=>true}
+ :no_shipping=>true
+ }
response = @gateway.setup_authorization(amount, options)
assert response.success?, response.message
end
def test_setup_authorization_with_discount_taxes_and_shipping_addtiional
amount = 2518
- options = {:ip=>"127.0.0.1",
- :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1",
- :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1",
- :customer=>"test6@test.com",
- :email=>"test6@test.com",
- :order_id=>"#1092",
- :currency=>"USD",
+ options = {
+ :ip=>'127.0.0.1',
+ :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1',
+ :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1',
+ :customer=>'test6@test.com',
+ :email=>'test6@test.com',
+ :order_id=>'#1092',
+ :currency=>'USD',
:subtotal=>2798,
:items => [
- {:name => "test4",
- :description => "test4",
- :quantity=>2 ,
- :amount=> 1399 ,
- :url=>"http://localhost:3000/products/test4"}],
+ {
+ :name => 'test4',
+ :description => 'test4',
+ :quantity=>2,
+ :amount=> 1399,
+ :url=>'http://localhost:3000/products/test4'
+ }
+ ],
:discount=>280,
- :no_shipping=>false}
+ :no_shipping=>false
+ }
response = @gateway.setup_authorization(amount, options)
assert response.success?, response.message
end
@@ -123,69 +140,81 @@ def setup
def test_setup_authorization_discount_taxes_included_free_shipping
amount = 2518
- options = {:ip=>"127.0.0.1",
- :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1",
- :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1",
- :customer=>"test6@test.com",
- :email=>"test6@test.com",
- :order_id=>"#1092",
- :currency=>"GBP",
+ options = {
+ :ip=>'127.0.0.1',
+ :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1',
+ :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1',
+ :customer=>'test6@test.com',
+ :email=>'test6@test.com',
+ :order_id=>'#1092',
+ :currency=>'GBP',
:subtotal=>2798,
:items=> [
- {:name=>"test4",
- :description=>"test4",
+ {
+ :name=>'test4',
+ :description=>'test4',
:quantity=>2,
:amount=>1399,
- :url=>"http://localhost:3000/products/test4"},
- ],
+ :url=>'http://localhost:3000/products/test4'
+ }
+ ],
:discount=>280,
- :no_shipping=>true}
+ :no_shipping=>true
+ }
response = @gateway.setup_authorization(amount, options)
assert response.success?, response.message
end
def test_setup_authorization_with_discount_taxes_additional
amount = 2518
- options = {:ip=>"127.0.0.1",
- :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1",
- :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1",
- :customer=>"test6@test.com",
- :email=>"test6@test.com",
- :order_id=>"#1092",
- :currency=>"GBP",
+ options = {
+ :ip=>'127.0.0.1',
+ :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1',
+ :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1',
+ :customer=>'test6@test.com',
+ :email=>'test6@test.com',
+ :order_id=>'#1092',
+ :currency=>'GBP',
:subtotal=>2798,
:items=> [
- {:name=>"test4",
- :description=>"test4",
+ {
+ :name=>'test4',
+ :description=>'test4',
:quantity=>2,
:amount=>1399,
- :url=>"http://localhost:3000/products/test4"},
- ],
+ :url=>'http://localhost:3000/products/test4'
+ }
+ ],
:discount=>280,
- :no_shipping=>true}
+ :no_shipping=>true
+ }
response = @gateway.setup_authorization(amount, options)
assert response.success?, response.message
end
def test_setup_authorization_with_discount_taxes_and_shipping_addtiional
amount = 2518
- options = {:ip=>"127.0.0.1",
- :return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1",
- :cancel_return_url=>"http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1",
- :customer=>"test6@test.com",
- :email=>"test6@test.com",
- :order_id=>"#1092",
- :currency=>"GBP",
+ options = {
+ :ip=>'127.0.0.1',
+ :return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?buyer_accepts_marketing=true&utm_nooverride=1',
+ :cancel_return_url=>'http://localhost:3000/orders/1/8e06ea26f8add7608671d433f13c2193/commit_paypal?utm_nooverride=1',
+ :customer=>'test6@test.com',
+ :email=>'test6@test.com',
+ :order_id=>'#1092',
+ :currency=>'GBP',
:subtotal=>2798,
:items=> [
- {:name=>"test4",
- :description=>"test4",
+ {
+ :name=>'test4',
+ :description=>'test4',
:quantity=>2,
:amount=>1399,
- :url=>"http://localhost:3000/products/test4"},
- ],
+ :url=>'http://localhost:3000/products/test4'
+ }
+ ],
:discount=>280,
- :no_shipping=>false}
+ :no_shipping=>false
+ }
response = @gateway.setup_authorization(amount, options)
assert response.success?, response.message
end
diff --git a/test/remote/gateways/remote_payflow_test.rb b/test/remote/gateways/remote_payflow_test.rb
index b3d49a939d3..e1370e4d4ce 100644
--- a/test/remote/gateways/remote_payflow_test.rb
+++ b/test/remote/gateways/remote_payflow_test.rb
@@ -2,38 +2,157 @@
class RemotePayflowTest < Test::Unit::TestCase
def setup
- ActiveMerchant::Billing::Base.gateway_mode = :test
+ Base.mode = :test
@gateway = PayflowGateway.new(fixtures(:payflow))
-
- @credit_card = credit_card('5105105105105100',
+
+ @credit_card = credit_card(
+ '5105105105105100',
:brand => 'master'
)
- @options = { :billing_address => address,
- :email => 'cody@example.com',
- :customer => 'codyexample'
- }
+ @options = {
+ :billing_address => address,
+ :email => 'cody@example.com',
+ :customer => 'codyexample'
+ }
+
+ @extra_options = {
+ :order_id => '123',
+ :description => 'Description string',
+ :order_desc => 'OrderDesc string',
+ :comment => 'Comment string',
+ :comment2 => 'Comment2 string'
+ }
+
+ @check = check(
+ :routing_number => '111111118',
+ :account_number => '1111111111'
+ )
+
+ @l2_json = '{
+ "Tender": {
+ "ACH": {
+ "AcctType": "C",
+ "AcctNum": "6355059797",
+ "ABA": "021000021"
+ }
+ }
+ }'
+
+ @l3_json = '{
+ "Invoice": {
+ "Date": "20190104",
+ "Level3Invoice": {
+ "CountyTax": {"Amount": "3.23"}
+ },
+ "Items":
+ "11119999Widget2INQ49.999.983.008.00101.00500 Main St.AnytownNY67890US2003063024680ABC01232003071454.10.15.0522228888Gizmo5INQ9.992.503.002.5052.95500 Main St.AnytownNY67890US2003062813579XYZ78902003071154.10.16.05"
+ }
+ }'
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(100000, @credit_card, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
+ assert !response.fraud_review?
end
-
+
+ def test_successful_purchase_with_extra_options
+ assert response = @gateway.purchase(100000, @credit_card, @options.merge(@extra_options))
+ assert_equal 'Approved', response.message
+ assert_success response
+ assert response.test?
+ assert_not_nil response.authorization
+ assert !response.fraud_review?
+ end
+
+ # In order for this remote test to pass, you must go into your Payflow test
+ # backend and enable the correct filter. Once logged in:
+ # "Service Settings" ->
+ # "Fraud Protection" ->
+ # "Test Setup" ->
+ # "Edit Standard Filters" ->
+ # Check "BIN Risk List Match" filter *only*, set to "Review" ->
+ # "Deploy" ->
+ # WAIT AT LEAST AN HOUR. FOR REALZ.
+ def test_successful_purchase_with_fraud_review
+ assert response = @gateway.purchase(
+ 100000,
+ credit_card('5555555555554444', verification_value: '')
+ )
+ assert_success response, 'This is probably failing due to your Payflow test account not being set up for fraud filters.'
+ assert_equal '126', response.params['result']
+ assert response.fraud_review?
+ end
+
+ def test_successful_purchase_with_l2_fields
+ options = @options.merge(level_two_fields: @l2_json)
+
+ assert response = @gateway.purchase(100000, @credit_card, options)
+ assert_equal 'Approved', response.message
+ assert_success response
+ assert response.test?
+ assert_not_nil response.authorization
+ end
+
+ def test_successful_purchase_with_l3_fields
+ options = @options.merge(level_three_fields: @l3_json)
+
+ assert response = @gateway.purchase(100000, @credit_card, options)
+ assert_equal 'Approved', response.message
+ assert_success response
+ assert response.test?
+ assert_not_nil response.authorization
+ end
+
+ def test_successful_purchase_with_l2_l3_fields
+ options = @options.merge(level_two_fields: @l2_json).merge(level_three_fields: @l3_json)
+
+ assert response = @gateway.purchase(100000, @credit_card, options)
+ assert_equal 'Approved', response.message
+ assert_success response
+ assert response.test?
+ assert_not_nil response.authorization
+ end
+
def test_declined_purchase
assert response = @gateway.purchase(210000, @credit_card, @options)
assert_equal 'Declined', response.message
assert_failure response
assert response.test?
end
-
+
+ # Additional steps are required to enable ACH in a Payflow Pro account.
+ # See the "Payflow ACH Payment Service Guide" for more details:
+ # http://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/pp_achpayment_guide.pdf
+ #
+ # Also, when testing against the pilot-payflowpro.paypal.com endpoint, ACH must be enabled by Payflow support.
+ # This can be accomplished by sending an email to payflow-support@paypal.com with your Merchant Login.
+ def test_successful_ach_purchase
+ assert response = @gateway.purchase(50, @check)
+ assert_success response, 'This is probably failing due to your Payflow test account not being set up for ACH.'
+ assert_equal 'Approved', response.message
+ assert response.test?
+ assert_not_nil response.authorization
+ end
+
+ def test_ach_purchase_and_refund
+ assert response = @gateway.purchase(50, @check)
+ assert_success response
+ assert_equal 'Approved', response.message
+ assert !response.authorization.blank?
+
+ assert credit = @gateway.refund(50, response.authorization)
+ assert_success credit
+ end
+
def test_successful_authorization
assert response = @gateway.authorize(100, @credit_card, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
@@ -47,23 +166,58 @@ def test_authorize_and_capture
assert capture = @gateway.capture(100, auth.authorization)
assert_success capture
end
-
+
+ def test_authorize_and_capture_with_three_d_secure_option
+ assert auth = @gateway.authorize(100, @credit_card, @options.merge(three_d_secure_option))
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(100, auth.authorization)
+ assert_success capture
+ end
+
def test_authorize_and_partial_capture
assert auth = @gateway.authorize(100 * 2, @credit_card, @options)
assert_success auth
assert_equal 'Approved', auth.message
assert auth.authorization
-
+
+ assert capture = @gateway.capture(100, auth.authorization)
+ assert_success capture
+ end
+
+ def test_authorize_and_complete_capture
+ assert auth = @gateway.authorize(100 * 2, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert auth.authorization
+
+ assert capture = @gateway.capture(100, auth.authorization, :capture_complete => 'Y')
+ assert_success capture
+
+ assert capture = @gateway.capture(100, auth.authorization)
+ assert_failure capture
+ end
+
+ def test_authorize_and_uncomplete_capture
+ assert auth = @gateway.authorize(100 * 2, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Approved', auth.message
+ assert auth.authorization
+
+ assert capture = @gateway.capture(100, auth.authorization, :capture_complete => 'N')
+ assert_success capture
+
assert capture = @gateway.capture(100, auth.authorization)
assert_success capture
end
-
+
def test_failed_capture
assert response = @gateway.capture(100, '999')
assert_failure response
assert_equal 'Invalid tender', response.message
end
-
+
def test_authorize_and_void
assert auth = @gateway.authorize(100, @credit_card, @options)
assert_success auth
@@ -72,7 +226,29 @@ def test_authorize_and_void
assert void = @gateway.void(auth.authorization)
assert_success void
end
-
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Verified', response.message
+ end
+
+ def test_successful_verify_amex
+ @amex_credit_card = credit_card(
+ '378282246310005',
+ :brand => 'american_express'
+ )
+ assert response = @gateway.verify(@amex_credit_card, @options)
+ assert_success response
+ assert_equal 'Approved', response.message
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(credit_card('4000056655665556'), @options)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
def test_invalid_login
gateway = PayflowGateway.new(
:login => '',
@@ -82,62 +258,72 @@ def test_invalid_login
assert_equal 'Invalid vendor account', response.message
assert_failure response
end
-
+
def test_duplicate_request_id
- request_id = Digest::MD5.hexdigest(rand.to_s)
- @gateway.expects(:generate_unique_id).times(2).returns(request_id)
-
+ request_id = SecureRandom.hex(16)
+ SecureRandom.expects(:hex).times(2).returns(request_id)
+
response1 = @gateway.purchase(100, @credit_card, @options)
- assert response1.success?
+ assert response1.success?
assert_nil response1.params['duplicate']
-
+
response2 = @gateway.purchase(100, @credit_card, @options)
assert response2.success?
- assert response2.params['duplicate']
+ assert response2.params['duplicate'], response2.inspect
end
-
+
def test_create_recurring_profile
- response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly)
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring(1000, @credit_card, :periodicity => :monthly)
+ end
assert_success response
assert !response.params['profile_id'].blank?
assert response.test?
end
-
+
def test_create_recurring_profile_with_invalid_date
- response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :starting_at => Time.now)
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring(1000, @credit_card, :periodicity => :monthly, :starting_at => Time.now)
+ end
assert_failure response
assert_equal 'Field format error: Start or next payment date must be a valid future date', response.message
assert response.params['profile_id'].blank?
assert response.test?
end
-
+
def test_create_and_cancel_recurring_profile
- response = @gateway.recurring(1000, @credit_card, :periodicity => :monthly)
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring(1000, @credit_card, :periodicity => :monthly)
+ end
assert_success response
assert !response.params['profile_id'].blank?
assert response.test?
-
- response = @gateway.cancel_recurring(response.params['profile_id'])
+
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.cancel_recurring(response.params['profile_id'])
+ end
assert_success response
assert response.test?
end
-
+
def test_full_feature_set_for_recurring_profiles
# Test add
@options.update(
:periodicity => :weekly,
:payments => '12',
:starting_at => Time.now + 1.day,
- :comment => "Test Profile"
+ :comment => 'Test Profile'
)
- response = @gateway.recurring(100, @credit_card, @options)
- assert_equal "Approved", response.params['message']
- assert_equal "0", response.params['result']
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring(100, @credit_card, @options)
+ end
+ assert_equal 'Approved', response.params['message']
+ assert_equal '0', response.params['result']
assert_success response
assert response.test?
assert !response.params['profile_id'].blank?
@recurring_profile_id = response.params['profile_id']
-
+
# Test modify
@options.update(
:periodicity => :monthly,
@@ -145,93 +331,156 @@ def test_full_feature_set_for_recurring_profiles
:payments => '4',
:profile_id => @recurring_profile_id
)
- response = @gateway.recurring(400, @credit_card, @options)
- assert_equal "Approved", response.params['message']
- assert_equal "0", response.params['result']
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring(400, @credit_card, @options)
+ end
+ assert_equal 'Approved', response.params['message']
+ assert_equal '0', response.params['result']
assert_success response
assert response.test?
-
+
# Test inquiry
- response = @gateway.recurring_inquiry(@recurring_profile_id)
- assert_equal "0", response.params['result']
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring_inquiry(@recurring_profile_id)
+ end
+ assert_equal '0', response.params['result']
assert_success response
assert response.test?
-
+
# Test payment history inquiry
- response = @gateway.recurring_inquiry(@recurring_profile_id, :history => true)
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring_inquiry(@recurring_profile_id, :history => true)
+ end
assert_equal '0', response.params['result']
assert_success response
assert response.test?
-
+
# Test cancel
- response = @gateway.cancel_recurring(@recurring_profile_id)
- assert_equal "Approved", response.params['message']
- assert_equal "0", response.params['result']
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.cancel_recurring(@recurring_profile_id)
+ end
+ assert_equal 'Approved', response.params['message']
+ assert_equal '0', response.params['result']
assert_success response
assert response.test?
end
-
+
# Note that this test will only work if you enable reference transactions!!
def test_reference_purchase
assert response = @gateway.purchase(10000, @credit_card, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil pn_ref = response.authorization
-
+
# now another purchase, by reference
assert response = @gateway.purchase(10000, pn_ref)
- assert_equal "Approved", response.message
- assert_success response
- assert response.test?
- end
-
- def test_recurring_with_initial_authorization
- response = @gateway.recurring(1000, @credit_card,
- :periodicity => :monthly,
- :initial_transaction => {
- :brand => :authorization
- }
- )
-
+ assert_equal 'Approved', response.message
assert_success response
- assert !response.params['profile_id'].blank?
assert response.test?
end
-
+
def test_recurring_with_initial_authorization
- response = @gateway.recurring(1000, @credit_card,
- :periodicity => :monthly,
- :initial_transaction => {
- :brand => :purchase,
- :amount => 500
- }
- )
-
+ response = assert_deprecation_warning(Gateway::RECURRING_DEPRECATION_MESSAGE) do
+ @gateway.recurring(1000, @credit_card,
+ :periodicity => :monthly,
+ :initial_transaction => {
+ :type => :purchase,
+ :amount => 500
+ }
+ )
+ end
+
assert_success response
assert !response.params['profile_id'].blank?
assert response.test?
end
-
- def test_purchase_and_referenced_credit
+
+ def test_purchase_and_refund
amount = 100
-
+
assert purchase = @gateway.purchase(amount, @credit_card, @options)
assert_success purchase
assert_equal 'Approved', purchase.message
assert !purchase.authorization.blank?
-
- assert credit = @gateway.credit(amount, purchase.authorization)
+
+ assert credit = @gateway.refund(amount, purchase.authorization)
assert_success credit
end
-
- # The default security setting for Payflow Pro accounts is Allow
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = PayflowGateway.new(login: 'unknown_login', password: 'unknown_password', partner: 'PayPal')
+ assert !gateway.verify_credentials
+ end
+
+ def test_purchase_and_refund_with_three_d_secure_option
+ amount = 100
+
+ assert purchase = @gateway.purchase(amount, @credit_card, @options.merge(three_d_secure_option))
+ assert_success purchase
+ assert_equal 'Approved', purchase.message
+ assert !purchase.authorization.blank?
+
+ assert credit = @gateway.refund(amount, purchase.authorization)
+ assert_success credit
+ end
+
+ # The default security setting for Payflow Pro accounts is Allow
# non-referenced credits = No.
#
- # Non-referenced credits will fail with Result code 117 (failed the security
+ # Non-referenced credits will fail with Result code 117 (failed the security
# check) unless Allow non-referenced credits = Yes in PayPal manager
- def test_purchase_and_non_referenced_credit
+ def test_purchase_and_credit
assert credit = @gateway.credit(100, @credit_card, @options)
- assert_success credit
+ assert_success credit, 'This is probably failing due to your Payflow test account not being set up to allow non-referenced credits.'
+ end
+
+ def test_successful_ach_credit
+ assert response = @gateway.credit(50, @check)
+ assert_success response, 'This is probably failing due to your Payflow test account not being set up for ACH.'
+ assert_equal 'Approved', response.message
+ assert response.test?
+ assert_not_nil response.authorization
+ end
+
+ def test_high_verbosity
+ assert response = @gateway.purchase(100, @credit_card, @options)
+ assert_nil response.params['transaction_time']
+ @gateway.options[:verbosity] = 'HIGH'
+ assert response = @gateway.purchase(100, @credit_card, @options)
+ assert_match %r{^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, response.params['transaction_time']
+ end
+
+ def three_d_secure_option
+ {
+ :three_d_secure => {
+ :status => 'Y',
+ :authentication_id => 'QvDbSAxSiaQs241899E0',
+ :eci => '02',
+ :cavv => 'jGvQIvG/5UhjAREALGYa6Vu/hto=',
+ :xid => 'UXZEYlNBeFNpYVFzMjQxODk5RTA='
+ }
+ }
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(50, @check)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
end
end
diff --git a/test/remote/gateways/remote_payflow_uk_test.rb b/test/remote/gateways/remote_payflow_uk_test.rb
index 485411b275e..afe6791cfc8 100644
--- a/test/remote/gateways/remote_payflow_uk_test.rb
+++ b/test/remote/gateways/remote_payflow_uk_test.rb
@@ -2,11 +2,11 @@
class RemotePayflowUkTest < Test::Unit::TestCase
def setup
- ActiveMerchant::Billing::Base.gateway_mode = :test
+ Base.mode = :test
# The default partner is PayPalUk
@gateway = PayflowUkGateway.new(fixtures(:payflow_uk))
-
+
@creditcard = CreditCard.new(
:number => '5105105105105100',
:month => 11,
@@ -16,28 +16,8 @@ def setup
:verification_value => '000',
:brand => 'master'
)
-
- @solo = CreditCard.new(
- :brand => "solo",
- :number => "6334900000000005",
- :month => Time.now.month,
- :year => Time.now.year + 1,
- :first_name => "Test",
- :last_name => "Mensch",
- :issue_number => '01'
- )
-
- @switch = CreditCard.new(
- :brand => "switch",
- :number => "5641820000000005",
- :verification_value => "000",
- :month => 1,
- :year => 2008,
- :first_name => 'Fred',
- :last_name => 'Brooks'
- )
-
- @options = {
+
+ @options = {
:billing_address => {
:name => 'Cody Fauser',
:address1 => '1234 Shady Brook Lane',
@@ -50,72 +30,72 @@ def setup
:email => 'cody@example.com'
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(100000, @creditcard, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
end
-
+
def test_declined_purchase
assert response = @gateway.purchase(210000, @creditcard, @options)
assert_equal 'Failed merchant rule check', response.message
assert_failure response
assert response.test?
end
-
+
def test_successful_purchase_solo
- assert response = @gateway.purchase(100000, @solo, @options)
- assert_equal "Approved", response.message
- assert_success response
- assert response.test?
- assert_not_nil response.authorization
- end
-
+ assert response = @gateway.purchase(100000, @solo, @options)
+ assert_equal 'Approved', response.message
+ assert_success response
+ assert response.test?
+ assert_not_nil response.authorization
+ end
+
def test_no_card_issue_or_card_start_with_switch
assert response = @gateway.purchase(100000, @switch, @options)
assert_failure response
-
- assert_equal "Field format error: CARDSTART or CARDISSUE must be present", response.message
+
+ assert_equal 'Field format error: CARDSTART or CARDISSUE must be present', response.message
assert_failure response
assert response.test?
end
-
+
def test_successful_purchase_switch_with_issue_number
@switch.issue_number = '01'
assert response = @gateway.purchase(100000, @switch, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
end
-
+
def test_successful_purchase_switch_with_start_date
@switch.start_month = 12
@switch.start_year = 1999
assert response = @gateway.purchase(100000, @switch, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
end
-
+
def test_successful_purchase_switch_with_start_date_and_issue_number
@switch.issue_number = '05'
@switch.start_month = 12
@switch.start_year = 1999
assert response = @gateway.purchase(100000, @switch, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
end
-
+
def test_successful_authorization
assert response = @gateway.authorize(100, @creditcard, @options)
- assert_equal "Approved", response.message
+ assert_equal 'Approved', response.message
assert_success response
assert response.test?
assert_not_nil response.authorization
@@ -130,13 +110,13 @@ def test_authorize_and_capture
assert capture = @gateway.capture(amount, auth.authorization)
assert_success capture
end
-
+
def test_failed_capture
assert response = @gateway.capture(100, '999')
assert_failure response
assert_equal 'Invalid tender', response.message
end
-
+
def test_authorize_and_void
assert auth = @gateway.authorize(100, @creditcard, @options)
assert_success auth
@@ -145,7 +125,7 @@ def test_authorize_and_void
assert void = @gateway.void(auth.authorization)
assert_success void
end
-
+
def test_invalid_login
gateway = PayflowGateway.new(
:login => '',
@@ -155,16 +135,16 @@ def test_invalid_login
assert_equal 'Invalid vendor account', response.message
assert_failure response
end
-
+
def test_duplicate_request_id
gateway = PayflowUkGateway.new(
:login => @login,
:password => @password
)
-
- request_id = Digest::SHA1.hexdigest(rand.to_s).slice(0,32)
+
+ request_id = Digest::SHA1.hexdigest(rand.to_s).slice(0, 32)
gateway.expects(:generate_unique_id).times(2).returns(request_id)
-
+
response1 = gateway.purchase(100, @creditcard, @options)
assert_nil response1.params['duplicate']
response2 = gateway.purchase(100, @creditcard, @options)
diff --git a/test/remote/gateways/remote_payment_express_test.rb b/test/remote/gateways/remote_payment_express_test.rb
index adf073e1bdb..a2d2a3b0135 100644
--- a/test/remote/gateways/remote_payment_express_test.rb
+++ b/test/remote/gateways/remote_payment_express_test.rb
@@ -20,26 +20,33 @@ def setup
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "The Transaction was approved", response.message
+ assert_equal 'The Transaction was approved', response.message
assert_not_nil response.authorization
end
def test_successful_purchase_with_reference_id
assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal "The Transaction was approved", response.message
+ assert_equal 'The Transaction was approved', response.message
assert_success response
assert_not_nil response.authorization
end
+ def test_successful_purchase_with_ip
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:ip => '192.168.0.1'))
+ assert_success response
+ assert_equal 'The Transaction was approved', response.message
+ assert_not_nil response.authorization
+ end
+
def test_declined_purchase
- assert response = @gateway.purchase(@amount, credit_card("5431111111111228"), @options)
+ assert response = @gateway.purchase(@amount, credit_card('5431111111111228'), @options)
assert_match %r{declined}i, response.message
assert_failure response
end
def test_successful_authorization
assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_equal "The Transaction was approved", response.message
+ assert_equal 'The Transaction was approved', response.message
assert_success response
assert_not_nil response.authorization
end
@@ -59,7 +66,7 @@ def test_purchase_and_refund
assert_success purchase
assert_equal 'The Transaction was approved', purchase.message
assert !purchase.authorization.blank?
- assert refund = @gateway.refund(amount, purchase.authorization, :description => "Giving a refund")
+ assert refund = @gateway.refund(amount, purchase.authorization, :description => 'Giving a refund')
assert_success refund
end
@@ -75,29 +82,28 @@ def test_invalid_login
:password => ''
)
assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_match %r{error}i, response.message
+ assert_match %r{Invalid Credentials}i, response.message
assert_failure response
end
def test_store_credit_card
assert response = @gateway.store(@credit_card)
assert_success response
- assert_equal "The Transaction was approved", response.message
+ assert_equal 'The Transaction was approved', response.message
assert_not_nil token = response.authorization
assert_equal token, response.token
end
def test_store_with_custom_token
- token = Time.now.to_i.to_s #hehe
+ token = Time.now.to_i.to_s
assert response = @gateway.store(@credit_card, :billing_id => token)
assert_success response
- assert_equal "The Transaction was approved", response.message
+ assert_equal 'The Transaction was approved', response.message
assert_not_nil response.authorization
assert_equal token, response.authorization
end
def test_store_invalid_credit_card
- original_number = @credit_card.number
@credit_card.number = 2
assert response = @gateway.store(@credit_card)
@@ -107,11 +113,11 @@ def test_store_invalid_credit_card
def test_store_and_charge
assert response = @gateway.store(@credit_card)
assert_success response
- assert_equal "The Transaction was approved", response.message
- assert (token = response.authorization)
+ assert_equal 'The Transaction was approved', response.message
+ assert(token = response.authorization)
- assert purchase = @gateway.purchase( @amount, token)
- assert_equal "The Transaction was approved", purchase.message
+ assert purchase = @gateway.purchase(@amount, token)
+ assert_equal 'The Transaction was approved', purchase.message
assert_success purchase
assert_not_nil purchase.authorization
end
@@ -119,8 +125,8 @@ def test_store_and_charge
def test_store_and_authorize_and_capture
assert response = @gateway.store(@credit_card)
assert_success response
- assert_equal "The Transaction was approved", response.message
- assert (token = response.authorization)
+ assert_equal 'The Transaction was approved', response.message
+ assert(token = response.authorization)
assert auth = @gateway.authorize(@amount, token, @options)
assert_success auth
@@ -130,4 +136,15 @@ def test_store_and_authorize_and_capture
assert_success capture
end
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:password], clean_transcript)
+ end
+
end
diff --git a/test/remote/gateways/remote_paymentez_test.rb b/test/remote/gateways/remote_paymentez_test.rb
new file mode 100644
index 00000000000..831b61b07ca
--- /dev/null
+++ b/test/remote/gateways/remote_paymentez_test.rb
@@ -0,0 +1,239 @@
+require 'test_helper'
+
+class RemotePaymentezTest < Test::Unit::TestCase
+ def setup
+ @gateway = PaymentezGateway.new(fixtures(:paymentez))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111', verification_value: '666')
+ @elo_credit_card = credit_card('6362970000457013',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737',
+ :brand => 'elo'
+ )
+ @declined_card = credit_card('4242424242424242', verification_value: '666')
+ @options = {
+ billing_address: address,
+ description: 'Store Purchase',
+ user_id: '998',
+ email: 'joe@example.com',
+ vat: 0,
+ dev_reference: 'Testing'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_elo
+ response = @gateway.purchase(@amount, @elo_credit_card, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ tax_percentage: 0.07,
+ phone: '333 333 3333'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ assert_success response
+ end
+
+ def test_successful_purchase_without_phone_billing_address_option
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ tax_percentage: 0.07,
+ billing_address: {
+ phone: nil
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ assert_success response
+ end
+
+ def test_successful_purchase_without_phone_option
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ tax_percentage: 0.07
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ assert_success response
+ end
+
+ def test_successful_purchase_with_extra_params
+ options = {
+ extra_params: {
+ configuration1: 'value1',
+ configuration2: 'value2',
+ configuration3: 'value3'
+ }}
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options))
+ assert_success response
+ end
+
+ def test_successful_purchase_with_token
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+ token = store_response.authorization
+ purchase_response = @gateway.purchase(@amount, token, @options)
+ assert_success purchase_response
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+
+ def test_successful_refund
+ auth = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert refund = @gateway.refund(@amount, auth.authorization, @options)
+ assert_success refund
+ end
+
+ def test_successful_refund_with_elo
+ auth = @gateway.purchase(@amount, @elo_credit_card, @options)
+ assert_success auth
+
+ assert refund = @gateway.refund(@amount, auth.authorization, @options)
+ assert_success refund
+ end
+
+ def test_successful_void
+ auth = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_void_with_elo
+ auth = @gateway.purchase(@amount, @elo_credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Carrier not supported', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Response by mock', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_elo
+ auth = @gateway.authorize(@amount, @elo_credit_card, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Response by mock', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_token
+ store_response = @gateway.store(@credit_card, @options)
+ assert_success store_response
+ token = store_response.authorization
+ auth = @gateway.authorize(@amount, token, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Response by mock', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_different_amount
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount + 100, auth.authorization)
+ assert_success capture
+ assert_equal 'Response by mock', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Response by mock', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount - 1, auth.authorization)
+ assert_failure capture # Paymentez explicitly does not support partial capture; only GREATER than auth capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'The modification of the amount is not supported by carrier', response.message
+ end
+
+ def test_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_store_with_elo
+ response = @gateway.store(@elo_credit_card, @options)
+ assert_success response
+ end
+
+ def test_unstore
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ auth = response.authorization
+ response = @gateway.unstore(auth, @options)
+ assert_success response
+ end
+
+ def test_unstore_with_elo
+ response = @gateway.store(@elo_credit_card, @options)
+ assert_success response
+ auth = response.authorization
+ response = @gateway.unstore(auth, @options)
+ assert_success response
+ end
+
+ def test_invalid_login
+ gateway = PaymentezGateway.new(application_code: '9z8y7w6x', app_key: '1a2b3c4d')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'BackendResponseError', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:config_error], response.error_code
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:app_key], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_paymill_test.rb b/test/remote/gateways/remote_paymill_test.rb
index 77b201edfc5..880f346a47d 100644
--- a/test/remote/gateways/remote_paymill_test.rb
+++ b/test/remote/gateways/remote_paymill_test.rb
@@ -2,38 +2,86 @@
class RemotePaymillTest < Test::Unit::TestCase
def setup
- @gateway = PaymillGateway.new(fixtures(:paymill))
-
+ params = fixtures(:paymill)
+ @gateway = PaymillGateway.new(public_key: params[:public_key], private_key: params[:private_key])
@amount = 100
- @credit_card = credit_card('5105105105105100')
+ @credit_card = credit_card('5500000000000004')
+ @options = {
+ :email => 'Longbob.Longse@example.com'
+ }
+ @declined_card = credit_card('5105105105105100', month: 5, year: 2020)
+
+ uri = URI.parse("https://test-token.paymill.com?transaction.mode=CONNECTOR_TEST&channel.id=#{params[:public_key]}&jsonPFunction=paymilljstests&account.number=4111111111111111&account.expiry.month=12&account.expiry.year=2018&account.verification=123&account.holder=John%20Rambo&presentation.amount3D=#{@amount}&presentation.currency3D=EUR")
+ https = Net::HTTP.new(uri.host, uri.port)
+ https.use_ssl = true
+ request = Net::HTTP::Get.new(uri.request_uri)
+ request.basic_auth(params[:private_key], '')
+ response = https.request(request)
+ @token = response.body.match('tok_[a-z|0-9]+')[0]
end
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Operation successful', response.message
+ end
+
+ def test_successful_purchase_with_token
+ assert response = @gateway.purchase(@amount, @token)
assert_success response
- assert_equal 'Transaction approved', response.message
+ assert_equal 'Operation successful', response.message
end
- def test_failed_purchase_with_invalid_card
+ def test_failed_store_card_attempting_purchase
@credit_card.number = ''
- assert response = @gateway.purchase(@amount, @credit_card)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal 'Account or Bank Details Incorrect', response.message
+ assert_equal '[account.number] This field is missing.', response.message
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Card declined', response.message
end
def test_successful_authorize_and_capture
- assert response = @gateway.authorize(@amount, @credit_card)
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Operation successful', response.message
+ assert response.authorization
+
+ assert capture_response = @gateway.capture(@amount, response.authorization)
+ assert_success capture_response
+ assert_equal 'Operation successful', capture_response.message
+ end
+
+ def test_successful_authorize_and_capture_with_token
+ assert response = @gateway.authorize(@amount, @token, @options)
assert_success response
- assert_equal 'Transaction approved', response.message
+ assert_equal 'Operation successful', response.message
assert response.authorization
assert capture_response = @gateway.capture(@amount, response.authorization)
assert_success capture_response
- assert_equal 'Transaction approved', capture_response.message
+ assert_equal 'Operation successful', capture_response.message
+ end
+
+ def test_successful_authorize_with_token
+ assert response = @gateway.authorize(@amount, @token, @options)
+ assert_success response
+ assert_equal 'Operation successful', response.message
+ assert response.authorization
+ end
+
+ def test_failed_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Preauthorisation failed', response.message
end
def test_failed_capture
- assert response = @gateway.authorize(@amount, @credit_card)
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert capture_response = @gateway.capture(@amount, response.authorization)
@@ -41,21 +89,32 @@ def test_failed_capture
assert capture_response = @gateway.capture(@amount, response.authorization)
assert_failure capture_response
- assert_equal 'Preauthorization has already been used', capture_response.message
+ assert_equal 'Transaction duplicate', capture_response.message
+ end
+
+ def test_successful_authorize_and_void
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Operation successful', response.message
+ assert response.authorization
+
+ assert void_response = @gateway.void(response.authorization)
+ assert_success void_response
+ assert_equal 'Transaction approved.', void_response.message
end
def test_successful_refund
- assert response = @gateway.purchase(@amount, @credit_card)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert response.authorization
assert refund = @gateway.refund(@amount, response.authorization)
assert_success refund
- assert_equal 'Transaction approved', refund.message
+ assert_equal 'Operation successful', refund.message
end
def test_failed_refund
- assert response = @gateway.purchase(@amount, @credit_card)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert response.authorization
@@ -65,7 +124,7 @@ def test_failed_refund
end
def test_invalid_login
- gateway = PaymillGateway.new(fixtures(:paymill).merge(:private_key => "SomeBogusValue"))
+ gateway = PaymillGateway.new(public_key: fixtures(:paymill)[:public_key], private_key: 'SomeBogusValue')
response = gateway.purchase(@amount, @credit_card)
assert_failure response
assert_equal 'Access Denied', response.message
@@ -84,7 +143,7 @@ def test_failed_store_with_invalid_card
@credit_card.number = ''
assert response = @gateway.store(@credit_card)
assert_failure response
- assert_equal 'Account or Bank Details Incorrect', response.message
+ assert_equal '[account.number] This field is missing.', response.message
end
def test_successful_store_and_authorize
@@ -96,10 +155,21 @@ def test_successful_store_and_authorize
assert_success authorize
end
- # Paymill doesn't yet offer a way to trigger a decline on a test account.
- # def test_failed_purchase_with_declined_credit_card
- # assert response = @gateway.purchase(@amount, @declined_card)
- # assert_failure response
- # assert_equal 'Unable to process the purchase transaction.', response.message
- # end
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = PaymillGateway.new(public_key: 'unknown_key', private_key: 'unknown_key')
+ assert !gateway.verify_credentials
+ end
+
end
diff --git a/test/remote/gateways/remote_paypal_express_test.rb b/test/remote/gateways/remote_paypal_express_test.rb
index d2199a48821..c599649a814 100644
--- a/test/remote/gateways/remote_paypal_express_test.rb
+++ b/test/remote/gateways/remote_paypal_express_test.rb
@@ -2,27 +2,29 @@
class PaypalExpressTest < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
-
+ Base.mode = :test
+
@gateway = PaypalExpressGateway.new(fixtures(:paypal_certificate))
-
+
@options = {
:order_id => '230000',
:email => 'buyer@jadedpallet.com',
- :billing_address => { :name => 'Fred Brooks',
- :address1 => '1234 Penny Lane',
- :city => 'Jonsetown',
- :state => 'NC',
- :country => 'US',
- :zip => '23456'
- } ,
+ :billing_address =>
+ {
+ :name => 'Fred Brooks',
+ :address1 => '1234 Penny Lane',
+ :city => 'Jonsetown',
+ :state => 'NC',
+ :country => 'US',
+ :zip => '23456'
+ },
:description => 'Stuff that you purchased, yo!',
:ip => '10.0.0.1',
:return_url => 'http://example.com/return',
:cancel_return_url => 'http://example.com/cancel'
}
end
-
+
def test_set_express_authorization
@options.update(
:return_url => 'http://example.com',
@@ -34,7 +36,7 @@ def test_set_express_authorization
assert response.test?
assert !response.params['token'].blank?
end
-
+
def test_set_express_purchase
@options.update(
:return_url => 'http://example.com',
@@ -46,4 +48,14 @@ def test_set_express_purchase
assert response.test?
assert !response.params['token'].blank?
end
-end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.setup_authorization(500, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_paypal_test.rb b/test/remote/gateways/remote_paypal_test.rb
index 1ce1310a46d..220337bb2d9 100644
--- a/test/remote/gateways/remote_paypal_test.rb
+++ b/test/remote/gateways/remote_paypal_test.rb
@@ -2,74 +2,85 @@
class PaypalTest < Test::Unit::TestCase
def setup
- Base.gateway_mode = :test
-
@gateway = PaypalGateway.new(fixtures(:paypal_signature))
- @creditcard = CreditCard.new(
- :brand => "visa",
- :number => "4381258770269608", # Use a generated CC from the paypal Sandbox
- :verification_value => "000",
- :month => 1,
- :year => Time.now.year + 1,
- :first_name => 'Fred',
- :last_name => 'Brooks'
- )
-
+ @credit_card = credit_card('4381258770269608') # Use a generated CC from the paypal Sandbox
+ @declined_card = credit_card('234234234234')
+
@params = {
:order_id => generate_unique_id,
:email => 'buyer@jadedpallet.com',
- :billing_address => { :name => 'Fred Brooks',
- :address1 => '1234 Penny Lane',
- :city => 'Jonsetown',
- :state => 'NC',
- :country => 'US',
- :zip => '23456'
- } ,
+ :billing_address =>
+ {
+ :name => 'Longbob Longsen',
+ :address1 => '4321 Penny Lane',
+ :city => 'Jonsetown',
+ :state => 'NC',
+ :country => 'US',
+ :zip => '23456'
+ },
:description => 'Stuff that you purchased, yo!',
:ip => '10.0.0.1'
}
-
+
@amount = 100
+
# test re-authorization, auth-id must be more than 3 days old.
# each auth-id can only be reauthorized and tested once.
# leave it commented if you don't want to test reauthorization.
- #
- #@three_days_old_auth_id = "9J780651TU4465545"
- #@three_days_old_auth_id2 = "62503445A3738160X"
+ #
+ # @three_days_old_auth_id = "9J780651TU4465545"
+ # @three_days_old_auth_id2 = "62503445A3738160X"
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @params)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ assert_scrubbed(@gateway.options[:signature], transcript)
end
def test_successful_purchase
- response = @gateway.purchase(@amount, @creditcard, @params)
+ response = @gateway.purchase(@amount, @credit_card, @params)
assert_success response
assert response.params['transaction_id']
end
-
- def test_successful_purchase_with_api_signature
- gateway = PaypalGateway.new(fixtures(:paypal_signature))
- response = gateway.purchase(@amount, @creditcard, @params)
+
+ def test_successful_purchase_sans_cvv
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(@amount, @credit_card, @params)
+ assert_success response
+ assert response.params['transaction_id']
+ end
+
+ def test_successful_purchase_with_descriptors
+ response = @gateway.purchase(@amount, @credit_card, @params.merge(soft_descriptor: 'Active Merchant TXN', soft_descriptor_city: '800-883-3931'))
assert_success response
assert response.params['transaction_id']
end
-
+
def test_failed_purchase
- @creditcard.number = '234234234234'
- response = @gateway.purchase(@amount, @creditcard, @params)
+ response = @gateway.purchase(@amount, @declined_card, @params)
assert_failure response
assert_nil response.params['transaction_id']
end
def test_successful_authorization
- response = @gateway.authorize(@amount, @creditcard, @params)
+ response = @gateway.authorize(@amount, @credit_card, @params)
assert_success response
assert response.params['transaction_id']
assert_equal '1.00', response.params['amount']
assert_equal 'USD', response.params['amount_currency_id']
end
-
+
def test_failed_authorization
- @creditcard.number = '234234234234'
- response = @gateway.authorize(@amount, @creditcard, @params)
+ response = @gateway.authorize(@amount, @declined_card, @params)
assert_failure response
assert_nil response.params['transaction_id']
end
@@ -79,23 +90,23 @@ def test_successful_reauthorization
auth = @gateway.reauthorize(1000, @three_days_old_auth_id)
assert_success auth
assert auth.authorization
-
+
response = @gateway.capture(1000, auth.authorization)
assert_success response
assert response.params['transaction_id']
assert_equal '10.00', response.params['gross_amount']
assert_equal 'USD', response.params['gross_amount_currency_id']
end
-
+
def test_failed_reauthorization
return if not @three_days_old_auth_id2 # was authed for $10, attempt $20
auth = @gateway.reauthorize(2000, @three_days_old_auth_id2)
assert_false auth?
assert !auth.authorization
end
-
+
def test_successful_capture
- auth = @gateway.authorize(@amount, @creditcard, @params)
+ auth = @gateway.authorize(@amount, @credit_card, @params)
assert_success auth
response = @gateway.capture(@amount, auth.authorization)
assert_success response
@@ -105,9 +116,9 @@ def test_successful_capture
end
def test_successful_incomplete_captures
- auth = @gateway.authorize(100, @creditcard, @params)
+ auth = @gateway.authorize(100, @credit_card, @params)
assert_success auth
- response = @gateway.capture(60, auth.authorization, {:complete_type => "NotComplete"})
+ response = @gateway.capture(60, auth.authorization, {:complete_type => 'NotComplete'})
assert_success response
assert response.params['transaction_id']
assert_equal '0.60', response.params['gross_amount']
@@ -116,83 +127,102 @@ def test_successful_incomplete_captures
assert response_2.params['transaction_id']
assert_equal '0.40', response_2.params['gross_amount']
end
-
- # NOTE THIS SETTING: http://skitch.com/jimmybaker/nysus/payment-receiving-preferences-paypal
- # PayPal doesn't return the InvoiceID in the response, so I am unable to check for it. Looking at the transaction
- # on PayPal's site will show "NEWID123" as the InvoiceID.
+
def test_successful_capture_updating_the_invoice_id
- auth = @gateway.authorize(@amount, @creditcard, @params)
+ auth = @gateway.authorize(@amount, @credit_card, @params)
assert_success auth
- response = @gateway.capture(@amount, auth.authorization, :order_id => "NEWID123")
+ response = @gateway.capture(@amount, auth.authorization, :order_id => "NEWID#{generate_unique_id}")
assert_success response
assert response.params['transaction_id']
assert_equal '1.00', response.params['gross_amount']
assert_equal 'USD', response.params['gross_amount_currency_id']
end
-
+
def test_successful_voiding
- auth = @gateway.authorize(@amount, @creditcard, @params)
+ auth = @gateway.authorize(@amount, @credit_card, @params)
assert_success auth
response = @gateway.void(auth.authorization)
assert_success response
end
-
+
def test_purchase_and_full_credit
- purchase = @gateway.purchase(@amount, @creditcard, @params)
+ purchase = @gateway.purchase(@amount, @credit_card, @params)
assert_success purchase
-
+
credit = @gateway.refund(@amount, purchase.authorization, :note => 'Sorry')
assert_success credit
assert credit.test?
assert_equal 'USD', credit.params['net_refund_amount_currency_id']
- assert_equal '0.67', credit.params['net_refund_amount']
+ assert_equal '0.97', credit.params['net_refund_amount']
assert_equal 'USD', credit.params['gross_refund_amount_currency_id']
assert_equal '1.00', credit.params['gross_refund_amount']
assert_equal 'USD', credit.params['fee_refund_amount_currency_id']
- assert_equal '0.33', credit.params['fee_refund_amount']
+ assert_equal '0.03', credit.params['fee_refund_amount'] # As of August 2010, PayPal keeps the flat fee ($0.30)
end
-
+
def test_failed_voiding
response = @gateway.void('foo')
assert_failure response
end
-
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @params)
+ assert_success response
+ assert_equal '0.00', response.params['amount']
+ assert_match %r{This card authorization verification is not a payment transaction}, response.message
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@declined_card, @params)
+ assert_failure response
+ assert_match %r{This transaction cannot be processed}, response.message
+ end
+
+ def test_successful_verify_non_visa_mc
+ amex_card = credit_card('371449635398431', brand: nil, verification_value: '1234')
+ assert response = @gateway.verify(amex_card, @params)
+ assert_success response
+ assert_equal '1.00', response.params['amount']
+ assert_match %r{Success}, response.message
+ assert_success response.responses.last, 'The void should succeed'
+ end
+
def test_successful_transfer
- response = @gateway.purchase(@amount, @creditcard, @params)
+ response = @gateway.purchase(@amount, @credit_card, @params)
assert_success response
-
+
response = @gateway.transfer(@amount, 'joe@example.com', :subject => 'Your money', :note => 'Thanks for taking care of that')
assert_success response
end
def test_failed_transfer
- # paypal allows a max transfer of $10,000
+ # paypal allows a max transfer of $10,000
response = @gateway.transfer(1000001, 'joe@example.com')
assert_failure response
end
-
+
def test_successful_multiple_transfer
- response = @gateway.purchase(900, @creditcard, @params)
+ response = @gateway.purchase(900, @credit_card, @params)
assert_success response
-
+
response = @gateway.transfer([@amount, 'joe@example.com'],
[600, 'jane@example.com', {:note => 'Thanks for taking care of that'}],
:subject => 'Your money')
assert_success response
end
-
+
def test_failed_multiple_transfer
- response = @gateway.purchase(25100, @creditcard, @params)
+ response = @gateway.purchase(25100, @credit_card, @params)
assert_success response
# You can only include up to 250 recipients
- recipients = (1..251).collect {|i| [100, "person#{i}@example.com"]}
+ recipients = (1..251).collect { |i| [100, "person#{i}@example.com"] }
response = @gateway.transfer(*recipients)
assert_failure response
end
def test_successful_email_transfer
- response = @gateway.purchase(@amount, @creditcard, @params)
+ response = @gateway.purchase(@amount, @credit_card, @params)
assert_success response
response = @gateway.transfer([@amount, 'joe@example.com'], :receiver_type => 'EmailAddress', :subject => 'Your money', :note => 'Thanks for taking care of that')
@@ -200,7 +230,7 @@ def test_successful_email_transfer
end
def test_successful_userid_transfer
- response = @gateway.purchase(@amount, @creditcard, @params)
+ response = @gateway.purchase(@amount, @credit_card, @params)
assert_success response
response = @gateway.transfer([@amount, '4ET96X3PQEN8H'], :receiver_type => 'UserID', :subject => 'Your money', :note => 'Thanks for taking care of that')
@@ -208,21 +238,35 @@ def test_successful_userid_transfer
end
def test_failed_userid_transfer
- response = @gateway.purchase(@amount, @creditcard, @params)
+ response = @gateway.purchase(@amount, @credit_card, @params)
assert_success response
response = @gateway.transfer([@amount, 'joe@example.com'], :receiver_type => 'UserID', :subject => 'Your money', :note => 'Thanks for taking care of that')
assert_failure response
end
-
+
# Makes a purchase then makes another purchase adding $1.00 using just a reference id (transaction id)
def test_successful_referenced_id_purchase
- response = @gateway.purchase(@amount, @creditcard, @params)
+ response = @gateway.purchase(@amount, @credit_card, @params)
assert_success response
id_for_reference = response.params['transaction_id']
-
+
@params.delete(:order_id)
response2 = @gateway.purchase(@amount + 100, id_for_reference, @params)
assert_success response2
end
+
+ def test_successful_purchase_with_3ds_version_1
+ params = @params.merge!({
+ three_d_secure: {
+ trans_status: 'Y',
+ eci: '05',
+ cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
+ xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY='
+ }
+ })
+ response = @gateway.purchase(@amount, @credit_card, params)
+ assert_success response
+ assert response.params['transaction_id']
+ end
end
diff --git a/test/remote/gateways/remote_payscout_test.rb b/test/remote/gateways/remote_payscout_test.rb
new file mode 100644
index 00000000000..ce0a0768a1e
--- /dev/null
+++ b/test/remote/gateways/remote_payscout_test.rb
@@ -0,0 +1,158 @@
+require 'test_helper'
+
+class RemotePayscoutTest < Test::Unit::TestCase
+ def setup
+ @gateway = PayscoutGateway.new(fixtures(:payscout))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111', verification_value: 999)
+ @declined_card = credit_card('34343')
+
+ @options = {
+ :order_id => '1',
+ :description => 'Store Purchase',
+ :billing_address => address
+ }
+ end
+
+ ########## Purchase ##########
+
+ def test_cvv_fail_purchase
+ @credit_card = credit_card('4111111111111111')
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'The transaction has been approved', response.message
+ assert_equal 'N', response.cvv_result['code']
+ end
+
+ def test_approved_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'The transaction has been approved', response.message
+ end
+
+ def test_declined_purchase
+ @amount = 60
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'The transaction has been declined', response.message
+ end
+
+ ########## Authorize ##########
+
+ def test_approved_authorization
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'The transaction has been approved', response.message
+ end
+
+ def test_declined_authorization
+ @amount = 60
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'The transaction has been declined', response.message
+ end
+
+ ########## Capture ##########
+
+ def test_approved_capture
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'The transaction has been approved', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_invalid_amount_capture
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'The transaction has been approved', auth.message
+ assert auth.authorization
+ amount = 200
+ assert capture = @gateway.capture(amount, auth.authorization)
+ assert_failure capture
+ assert_match 'The specified amount of 2.00 exceeds the authorization amount of 1.00', capture.message
+ end
+
+ def test_not_found_transaction_id_capture
+ assert capture = @gateway.capture(@amount, '1234567890')
+ assert_failure capture
+ assert_match 'Transaction not found', capture.message
+ end
+
+ def test_invalid_transaction_id_capture
+ assert capture = @gateway.capture(@amount, '')
+ assert_failure capture
+ assert_match 'Invalid Transaction ID', capture.message
+ end
+
+ ########## Refund ##########
+
+ def test_approved_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'The transaction has been approved', refund.message
+ end
+
+ def test_not_found_transaction_id_refund
+ assert refund = @gateway.refund(@amount, '1234567890')
+ assert_failure refund
+ assert_match 'Transaction not found', refund.message
+ end
+
+ def test_invalid_transaction_id_refund
+ assert refund = @gateway.refund(@amount, '')
+ assert_failure refund
+ assert_match 'Invalid Transaction ID', refund.message
+ end
+
+ def test_invalid_amount_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert refund = @gateway.refund(200, purchase.authorization)
+ assert_failure refund
+ assert_match 'Refund amount may not exceed the transaction balance', refund.message
+ end
+
+ ########## Void ##########
+
+ def test_approved_void_purchase
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ assert_equal 'The transaction has been approved', void.message
+ end
+
+ def test_approved_void_authorization
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'The transaction has been approved', void.message
+ end
+
+ def test_invalid_transaction_id_void
+ assert void = @gateway.void('')
+ assert_failure void
+ assert_match 'Invalid Transaction ID', void.message
+ end
+
+ def test_not_found_transaction_id_void
+ assert void = @gateway.void('1234567890')
+ assert_failure void
+ assert_match 'Transaction not found', void.message
+ end
+
+ def test_invalid_credentials
+ gateway = PayscoutGateway.new(
+ :username => 'xxx',
+ :password => 'xxx'
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Authentication Failed', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_paystation_test.rb b/test/remote/gateways/remote_paystation_test.rb
index 40b3a732080..84285905003 100644
--- a/test/remote/gateways/remote_paystation_test.rb
+++ b/test/remote/gateways/remote_paystation_test.rb
@@ -1,113 +1,126 @@
require 'test_helper'
class RemotePaystationTest < Test::Unit::TestCase
-
def setup
@gateway = PaystationGateway.new(fixtures(:paystation))
-
+
@credit_card = credit_card('5123456789012346', :month => 5, :year => 13, :verification_value => 123)
-
+
@successful_amount = 10000
@insufficient_funds_amount = 10051
@invalid_transaction_amount = 10012
@expired_card_amount = 10054
@bank_error_amount = 10091
-
- @options = {
+
+ @options = {
:billing_address => address,
:description => 'Store Purchase'
}
end
-
+
def test_successful_purchase
- assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(:order_id => get_uid))
+ assert response = @gateway.purchase(@successful_amount, @credit_card, @options)
assert_success response
-
+
assert_equal 'Transaction successful', response.message
end
-
+
def test_successful_purchase_in_gbp
- assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(:currency => "GBP", :order_id => get_uid))
+ assert response = @gateway.purchase(@successful_amount, @credit_card, @options.merge(:currency => 'GBP'))
assert_success response
-
+
assert_equal 'Transaction successful', response.message
end
-
+
def test_failed_purchases
- [
- ["insufficient_funds", @insufficient_funds_amount, "Insufficient Funds"],
- ["invalid_transaction", @invalid_transaction_amount, "Transaction Type Not Supported"],
- ["expired_card", @expired_card_amount, "Expired Card"],
- ["bank_error", @bank_error_amount, "Error Communicating with Bank"]
+ [
+ ['insufficient_funds', @insufficient_funds_amount, 'Insufficient Funds'],
+ ['invalid_transaction', @invalid_transaction_amount, 'Transaction Type Not Supported'],
+ ['expired_card', @expired_card_amount, 'Expired Card'],
+ ['bank_error', @bank_error_amount, 'Error Communicating with Bank']
].each do |name, amount, message|
-
- assert response = @gateway.purchase(amount, @credit_card, @options.merge(:order_id => get_uid))
- assert_failure response
- assert_equal message, response.message
-
+ assert response = @gateway.purchase(amount, @credit_card, @options)
+ assert_failure response
+ assert_equal message, response.message
end
end
-
- def test_storing_token
+
+ def test_storing_token
time = Time.now.to_i
- assert response = @gateway.store(@credit_card, @options.merge(:order_id => get_uid, :token => "justatest#{time}"))
+ assert response = @gateway.store(@credit_card, @options.merge(:token => "justatest#{time}"))
assert_success response
-
- assert_equal "Future Payment Saved Ok", response.message
+
+ assert_equal 'Future Payment Saved Ok', response.message
assert_equal "justatest#{time}", response.token
end
-
+
def test_billing_stored_token
- assert store_response = @gateway.store(@credit_card, @options.merge(:order_id => get_uid))
+ assert store_response = @gateway.store(@credit_card, @options)
assert_success store_response
-
- assert charge_response = @gateway.purchase(@successful_amount, store_response.token, @options.merge(:order_id => get_uid))
+
+ assert charge_response = @gateway.purchase(@successful_amount, store_response.token, @options)
assert_success charge_response
- assert_equal "Transaction successful", charge_response.message
+ assert_equal 'Transaction successful', charge_response.message
end
-
+
def test_authorize_and_capture
- assert auth = @gateway.authorize(@successful_amount, @credit_card, @options.merge(:order_id => get_uid))
-
+ assert auth = @gateway.authorize(@successful_amount, @credit_card, @options)
+
assert_success auth
- assert auth.authorization
-
- assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(:order_id => get_uid, :credit_card_verification => 123))
+ assert auth.authorization
+
+ assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(:credit_card_verification => 123))
assert_success capture
end
-
+
def test_capture_without_cvv
- # for some merchant accounts, paystation requires you send through the card vertification value
- # on a capture request
-
- assert auth = @gateway.authorize(@successful_amount, @credit_card, @options.merge(:order_id => get_uid))
-
+ assert auth = @gateway.authorize(@successful_amount, @credit_card, @options)
+
assert_success auth
assert auth.authorization
-
- assert capture = @gateway.capture(@successful_amount, auth.authorization, @options.merge(:order_id => get_uid))
+
+ assert capture = @gateway.capture(@successful_amount, auth.authorization, @options)
assert_failure capture
-
- assert_equal "Card Security Code (CVV/CSC) Required", capture.message
+
+ assert_equal 'Card Security Code (CVV/CSC) Required', capture.message
end
-
def test_invalid_login
- gateway = PaystationGateway.new(
- :paystation_id => '',
- :gateway_id => ''
- )
- assert response = gateway.purchase(@amount, @credit_card, @options.merge(:order_id => get_uid))
-
+ gateway = PaystationGateway.new(paystation_id: '', gateway_id: '')
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+
assert_failure response
assert_nil response.authorization
end
-
- private
-
- # should be unique enough for test purposes
- def get_uid
- ActiveSupport::SecureRandom.hex(16)
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@successful_amount, @credit_card, @options)
+ assert_success response
+ refund = @gateway.refund(@successful_amount, response.authorization, @options)
+ assert_success refund
+ assert_equal 'Transaction successful', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '', @options)
+ assert_failure response
+ assert_equal 'Error 11:', response.params['strong']
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Transaction successful}, response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ end
end
diff --git a/test/remote/gateways/remote_payu_in_test.rb b/test/remote/gateways/remote_payu_in_test.rb
new file mode 100644
index 00000000000..182c3a1cc17
--- /dev/null
+++ b/test/remote/gateways/remote_payu_in_test.rb
@@ -0,0 +1,129 @@
+require 'test_helper'
+
+class RemotePayuInTest < Test::Unit::TestCase
+ def setup
+ @gateway = PayuInGateway.new(fixtures(:payu_in))
+
+ @amount = 1100
+ @credit_card = credit_card('5123456789012346', month: 5, year: 2017, verification_value: 564)
+
+ @options = {
+ order_id: generate_unique_id
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'No Error', response.message
+ end
+
+ def test_successful_purchase_with_full_options
+ response = @gateway.purchase(
+ @amount,
+ @credit_card,
+ order_id: generate_unique_id,
+ description: 'Awesome!',
+ email: 'jim@example.com',
+ billing_address: {
+ name: 'Jim Smith',
+ address1: '123 Road',
+ address2: 'Suite 123',
+ city: 'Somewhere',
+ state: 'ZZ',
+ country: 'US',
+ zip: '12345',
+ phone: '12223334444'
+ },
+ shipping_address: {
+ name: 'Joe Bob',
+ address1: '987 Street',
+ address2: 'Suite 987',
+ city: 'Anyplace',
+ state: 'AA',
+ country: 'IN',
+ zip: '98765',
+ phone: '98887776666'
+ }
+ )
+
+ assert_success response
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(0, @credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid amount}i, response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(100, '')
+ assert_failure response
+ end
+
+ def test_3ds_enrolled_card_fails
+ response = @gateway.purchase(@amount, credit_card('4012001037141112'), @options)
+ assert_failure response
+ assert_equal '3D-secure enrolled cards are not supported.', response.message
+
+=begin
+ # This is handy for testing that 3DS is working with PayU
+ response = response.responses.first
+
+ # You'll probably need a new bin from http://requestb.in
+ bin = ""
+ File.open("3ds.html", "w") do |f|
+ f.puts %(
+
+
+
+
+
+ )
+ end
+ puts "Test 3D-secure via `open 3ds.html`"
+ puts "View results at http://requestb.in/#{bin}?inspect"
+ puts "Finalize with: `curl -v -d PaRes='' -d MD='' '#{response.params["form_post_vars"]["TermUrl"]}'`"
+=end
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ refute_match %r{[^\d]#{@credit_card.verification_value}(?:[^\d]|$)}, 'Expected CVV to be scrubbed out of transcript'
+ end
+
+ def test_invalid_login
+ gateway = PayuInGateway.new(
+ key: '',
+ salt: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_payu_latam_test.rb b/test/remote/gateways/remote_payu_latam_test.rb
new file mode 100644
index 00000000000..e78fd67fffa
--- /dev/null
+++ b/test/remote/gateways/remote_payu_latam_test.rb
@@ -0,0 +1,411 @@
+require 'test_helper'
+
+class RemotePayuLatamTest < Test::Unit::TestCase
+ def setup
+ @gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(payment_country: 'AR'))
+
+ @amount = 4000
+ @credit_card = credit_card('4097440000000004', verification_value: '444', first_name: 'APPROVED', last_name: '')
+ @declined_card = credit_card('4097440000000004', verification_value: '444', first_name: 'REJECTED', last_name: '')
+ @pending_card = credit_card('4097440000000004', verification_value: '444', first_name: 'PENDING', last_name: '')
+ @naranja_credit_card = credit_card('5895620000000002', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'naranja')
+ @cabal_credit_card = credit_card('5896570000000004', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'cabal')
+ @invalid_cabal_card = credit_card('6271700000000000', :verification_value => '123', :first_name => 'APPROVED', :last_name => '', :brand => 'cabal')
+
+ @options = {
+ dni_number: '5415668464654',
+ merchant_buyer_id: '1',
+ currency: 'ARS',
+ order_id: generate_unique_id,
+ description: 'Active Merchant Transaction',
+ installments_number: 1,
+ tax: 0,
+ email: 'username@domain.com',
+ ip: '127.0.0.1',
+ device_session_id: 'vghs6tvkcle931686k1900o6e1',
+ cookie: 'pt1t38347bs6jc9ruv2ecpv7o2',
+ user_agent: 'Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0',
+ billing_address: address(
+ address1: 'Viamonte',
+ address2: '1366',
+ city: 'Plata',
+ state: 'Buenos Aires',
+ country: 'AR',
+ zip: '64000',
+ phone: '7563126'
+ )
+ }
+ end
+
+ # At the time of writing this test, gateway sandbox
+ # supports auth and purchase transactions only
+
+ def test_invalid_login
+ gateway = PayuLatamGateway.new(merchant_id: '', account_id: '', api_login: 'U', api_key: 'U', payment_country: 'AR')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_naranja_card
+ response = @gateway.purchase(@amount, @naranja_credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_cabal_card
+ response = @gateway.purchase(@amount, @cabal_credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_specified_language
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(language: 'es'))
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_buyer
+ gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512327', payment_country: 'BR'))
+
+ options_buyer = {
+ currency: 'BRL',
+ billing_address: address(
+ address1: 'Calle 100',
+ address2: 'BL4',
+ city: 'Sao Paulo',
+ state: 'SP',
+ country: 'BR',
+ zip: '09210710',
+ phone: '(11)756312633'
+ ),
+ shipping_address: address(
+ address1: 'Calle 200',
+ address2: 'N107',
+ city: 'Sao Paulo',
+ state: 'SP',
+ country: 'BR',
+ zip: '01019-030',
+ phone: '(11)756312633'
+ ),
+ buyer: {
+ name: 'Jorge Borges',
+ dni_number: '5415668464123',
+ dni_type: 'TI',
+ merchant_buyer_id: '2',
+ cnpj: '32593371000110',
+ email: 'axaxaxas@mlo.org'
+ }
+ }
+
+ response = gateway.purchase(@amount, @credit_card, @options.update(options_buyer))
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_brazil
+ gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512327', payment_country: 'BR'))
+
+ options_brazil = {
+ payment_country: 'BR',
+ currency: 'BRL',
+ billing_address: address(
+ address1: 'Calle 100',
+ address2: 'BL4',
+ city: 'Sao Paulo',
+ state: 'SP',
+ country: 'BR',
+ zip: '09210710',
+ phone: '(11)756312633'
+ ),
+ shipping_address: address(
+ address1: 'Calle 200',
+ address2: 'N107',
+ city: 'Sao Paulo',
+ state: 'SP',
+ country: 'BR',
+ zip: '01019-030',
+ phone: '(11)756312633'
+ ),
+ buyer: {
+ cnpj: '32593371000110'
+ }
+ }
+
+ response = gateway.purchase(@amount, @credit_card, @options.update(options_brazil))
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_colombia
+ gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512321', payment_country: 'CO'))
+
+ options_colombia = {
+ payment_country: 'CO',
+ currency: 'COP',
+ billing_address: address(
+ address1: 'Calle 100',
+ address2: 'BL4',
+ city: 'Bogota',
+ state: 'Bogota DC',
+ country: 'CO',
+ zip: '09210710',
+ phone: '(11)756312633'
+ ),
+ shipping_address: address(
+ address1: 'Calle 200',
+ address2: 'N107',
+ city: 'Bogota',
+ state: 'Bogota DC',
+ country: 'CO',
+ zip: '01019-030',
+ phone: '(11)756312633'
+ ),
+ tax: '3193',
+ tax_return_base: '16806'
+ }
+
+ response = gateway.purchase(2000000, @credit_card, @options.update(options_colombia))
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_mexico
+ gateway = PayuLatamGateway.new(fixtures(:payu_latam).update(:account_id => '512324', payment_country: 'MX'))
+
+ options_mexico = {
+ payment_country: 'MX',
+ currency: 'MXN',
+ billing_address: address(
+ address1: 'Calle 100',
+ address2: 'BL4',
+ city: 'Guadalajara',
+ state: 'Jalisco',
+ country: 'MX',
+ zip: '09210710',
+ phone: '(11)756312633'
+ ),
+ shipping_address: address(
+ address1: 'Calle 200',
+ address2: 'N107',
+ city: 'Guadalajara',
+ state: 'Jalisco',
+ country: 'MX',
+ zip: '01019-030',
+ phone: '(11)756312633'
+ ),
+ birth_date: '1985-05-25'
+ }
+
+ response = gateway.purchase(@amount, @credit_card, @options.update(options_mexico))
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_successful_purchase_no_description
+ options = @options
+ options.delete(:description)
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert response.test?
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'DECLINED', response.params['transactionResponse']['state']
+ end
+
+ def test_failed_purchase_with_cabal_card
+ response = @gateway.purchase(@amount, @invalid_cabal_card, @options)
+ assert_failure response
+ assert_equal 'DECLINED', response.params['transactionResponse']['state']
+ end
+
+ def test_failed_purchase_with_no_options
+ response = @gateway.purchase(@amount, @declined_card, {})
+ assert_failure response
+ assert_equal 'DECLINED', response.params['transactionResponse']['state']
+ end
+
+ def test_failed_purchase_with_specified_language
+ gateway = PayuLatamGateway.new(merchant_id: '', account_id: '', api_login: 'U', api_key: 'U', payment_country: 'AR')
+ response = gateway.purchase(@amount, @declined_card, @options.merge(language: 'es'))
+ assert_failure response
+ assert_equal 'Credenciales inválidas', response.message
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_match %r(^\d+\|(\w|-)+$), response.authorization
+ end
+
+ def test_successful_authorize_with_naranja_card
+ response = @gateway.authorize(@amount, @naranja_credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_match %r(^\d+\|(\w|-)+$), response.authorization
+ end
+
+ def test_successful_authorize_with_cabal_card
+ response = @gateway.authorize(@amount, @cabal_credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_match %r(^\d+\|(\w|-)+$), response.authorization
+ end
+
+ def test_successful_authorize_with_specified_language
+ response = @gateway.authorize(@amount, @credit_card, @options.merge(language: 'es'))
+ assert_success response
+ assert_equal 'APPROVED', response.message
+ assert_match %r(^\d+\|(\w|-)+$), response.authorization
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @pending_card, @options)
+ assert_failure response
+ assert_equal 'DECLINED', response.params['transactionResponse']['state']
+ end
+
+ def test_failed_authorize_with_specified_language
+ gateway = PayuLatamGateway.new(merchant_id: '', account_id: '', api_login: 'U', api_key: 'U', payment_country: 'AR')
+ response = gateway.authorize(@amount, @pending_card, @options.merge(language: 'es'))
+ assert_failure response
+ assert_equal 'Credenciales inválidas', response.message
+ end
+
+ # As noted above, capture transactions are currently not supported, but in the hope
+ # they will one day be, here you go
+
+ # def test_successful_capture
+ # response = @gateway.authorize(@amount, @credit_card, @options)
+ # assert_success response
+ # assert_equal 'APPROVED', response.message
+ # assert_match %r(^\d+\|(\w|-)+$), response.authorization
+
+ # capture = @gateway.capture(@amount, response.authorization, @options)
+ # assert_success capture
+ # assert_equal 'APPROVED', response.message
+ # assert response.test?
+ # end
+
+ # def test_successful_partial_capture
+ # response = @gateway.authorize(@amount, @credit_card, @options)
+ # assert_success response
+ # assert_equal 'APPROVED', response.message
+ # assert_match %r(^\d+\|(\w|-)+$), response.authorization
+
+ # capture = @gateway.capture(@amount - 1, response.authorization, @options)
+ # assert_success capture
+ # assert_equal 'APPROVED', response.message
+ # assert_equal '39.99', response.params['TX_VALUE']['value']
+ # assert response.test?
+ # end
+
+ def test_well_formed_refund_fails_as_expected
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_equal 'The payment plan id cannot be empty', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_match(/property: parentTransactionId, message: must not be null/, response.message)
+ end
+
+ def test_failed_refund_with_specified_language
+ response = @gateway.refund(@amount, '', language: 'es')
+ assert_failure response
+ assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message)
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_match(/property: parentTransactionId, message: must not be null/, response.message)
+ end
+
+ def test_failed_void_with_specified_language
+ response = @gateway.void('', language: 'es')
+ assert_failure response
+ assert_match(/property: parentTransactionId, message: No puede ser vacio/, response.message)
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_match(/must not be null/, response.message)
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = PayuLatamGateway.new(merchant_id: 'X', account_id: '512322', api_login: 'X', api_key: 'X', payment_country: 'AR')
+ assert !gateway.verify_credentials
+ end
+
+ def test_successful_verify
+ verify = @gateway.verify(@credit_card, @options)
+
+ assert_success verify
+ assert_equal 'APPROVED', verify.message
+ end
+
+ def test_successful_verify_with_specified_amount
+ verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 5100))
+
+ assert_success verify
+ assert_equal 'APPROVED', verify.message
+ end
+
+ def test_successful_verify_with_specified_language
+ verify = @gateway.verify(@credit_card, @options.merge(language: 'es'))
+
+ assert_success verify
+ assert_equal 'APPROVED', verify.message
+ end
+
+ def test_failed_verify_with_specified_amount
+ verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 499))
+
+ assert_failure verify
+ assert_equal 'The order value is less than minimum allowed. Minimum value allowed 5 ARS', verify.message
+ end
+
+ def test_failed_verify_with_specified_language
+ verify = @gateway.verify(@credit_card, @options.merge(verify_amount: 499, language: 'es'))
+
+ assert_failure verify
+ assert_equal 'The order value is less than minimum allowed. Minimum value allowed 5 ARS', verify.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:api_key], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_payway_test.rb b/test/remote/gateways/remote_payway_test.rb
index a0dc5e6b086..9d900c8528b 100644
--- a/test/remote/gateways/remote_payway_test.rb
+++ b/test/remote/gateways/remote_payway_test.rb
@@ -8,42 +8,42 @@ def setup
@gateway = ActiveMerchant::Billing::PaywayGateway.new(fixtures(:payway))
- @visa = credit_card("4564710000000004",
+ @visa = credit_card('4564710000000004',
:month => 2,
:year => 2019,
- :verification_value => "847"
+ :verification_value => '847'
)
- @mastercard = credit_card("5163200000000008",
+ @mastercard = credit_card('5163200000000008',
:month => 8,
:year => 2020,
- :verification_value => "070",
- :brand => "master"
+ :verification_value => '070',
+ :brand => 'master'
)
- @expired = credit_card("4564710000000012",
+ @expired = credit_card('4564710000000012',
:month => 2,
:year => 2005,
- :verification_value => "963"
+ :verification_value => '963'
)
- @low = credit_card("4564710000000020",
+ @low = credit_card('4564710000000020',
:month => 5,
:year => 2020,
- :verification_value => "234"
+ :verification_value => '234'
)
- @stolen_mastercard = credit_card("5163200000000016",
+ @stolen_mastercard = credit_card('5163200000000016',
:month => 12,
:year => 2019,
- :verification_value => "728",
- :brand => "master"
+ :verification_value => '728',
+ :brand => 'master'
)
- @invalid = credit_card("4564720000000037",
+ @invalid = credit_card('4564720000000037',
:month => 9,
:year => 2019,
- :verification_value => "030"
+ :verification_value => '030'
)
end
diff --git a/test/remote/gateways/remote_pin_test.rb b/test/remote/gateways/remote_pin_test.rb
index 49b428c51ef..2539bc8b616 100644
--- a/test/remote/gateways/remote_pin_test.rb
+++ b/test/remote/gateways/remote_pin_test.rb
@@ -5,11 +5,12 @@ def setup
@gateway = PinGateway.new(fixtures(:pin))
@amount = 100
- @credit_card = credit_card('5520000000000000')
+ @credit_card = credit_card('5520000000000000', :year => Time.now.year + 2)
+ @visa_credit_card = credit_card('4200000000000000', :year => Time.now.year + 3)
@declined_card = credit_card('4100000000000001')
@options = {
- :email => 'roland@pin.net.au',
+ :email => 'roland@pinpayments.com',
:ip => '203.59.39.62',
:order_id => '1',
:billing_address => address,
@@ -18,18 +19,68 @@ def setup
end
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
+ assert_equal true, response.params['response']['captured']
+ end
+
+ def test_successful_purchase_with_metadata
+ options_with_metadata = {
+ metadata: {
+ order_id: generate_unique_id,
+ purchase_number: generate_unique_id
+ }
+ }
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(options_with_metadata))
+ assert_success response
+ assert_equal true, response.params['response']['captured']
+ assert_equal options_with_metadata[:metadata][:order_id], response.params['response']['metadata']['order_id']
+ assert_equal options_with_metadata[:metadata][:purchase_number], response.params['response']['metadata']['purchase_number']
+ end
+
+ def test_successful_purchase_with_reference
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(reference: 'statement descriptor'))
+ assert_success response
+ end
+
+ def test_successful_authorize_and_capture
+ authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert_equal false, authorization.params['response']['captured']
+
+ response = @gateway.capture(@amount, authorization.authorization, @options)
+ assert_success response
+ assert_equal true, response.params['response']['captured']
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_capture_due_to_invalid_token
+ response = @gateway.capture(@amount, 'bogus', @options)
+ assert_failure response
+ end
+
+ def test_failed_capture_due_to_invalid_amount
+ authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert_equal authorization.params['response']['captured'], false
+
+ response = @gateway.capture(@amount - 1, authorization.authorization, @options)
+ assert_failure response
+ assert_equal 'invalid_capture_amount', response.params['error']
end
def test_successful_purchase_without_description
@options.delete(:description)
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
end
def test_unsuccessful_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
+ response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
end
@@ -38,8 +89,8 @@ def test_unsuccessful_purchase
# falls outside of active merchant
def test_store_and_charge_with_pinjs_card_token
headers = {
- "Content-Type" => "application/json",
- "Authorization" => "Basic #{Base64.strict_encode64(@gateway.options[:api_key] + ':').strip}"
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Basic #{Base64.strict_encode64(@gateway.options[:api_key] + ':').strip}"
}
# Get a token equivalent to what is returned by Pin.js
card_attrs = {
@@ -48,17 +99,17 @@ def test_store_and_charge_with_pinjs_card_token
:expiry_year => @credit_card.year,
:cvc => @credit_card.verification_value,
:name => "#{@credit_card.first_name} #{@credit_card.last_name}",
- :address_line1 => "42 Sevenoaks St",
- :address_city => "Lathlain",
- :address_postcode => "6454",
- :address_start => "WA",
- :address_country => "Australia"
+ :address_line1 => '42 Sevenoaks St',
+ :address_city => 'Lathlain',
+ :address_postcode => '6454',
+ :address_start => 'WA',
+ :address_country => 'Australia'
}
- url = @gateway.test_url + "/cards"
+ url = @gateway.test_url + '/cards'
body = JSON.parse(@gateway.ssl_post(url, card_attrs.to_json, headers))
- card_token = body["response"]["token"]
+ card_token = body['response']['token']
store = @gateway.store(card_token, @options)
assert_success store
@@ -70,7 +121,7 @@ def test_store_and_charge_with_pinjs_card_token
end
def test_store_and_customer_token_charge
- assert response = @gateway.store(@credit_card, @options)
+ response = @gateway.store(@credit_card, @options)
assert_success response
assert_not_nil response.authorization
@@ -84,32 +135,54 @@ def test_store_and_customer_token_charge
assert_not_equal response1.authorization, response2.authorization
end
+ def test_store_and_update
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_not_nil response.authorization
+ assert_equal @credit_card.year, response.params['response']['card']['expiry_year']
+
+ response = @gateway.update(response.authorization, @visa_credit_card, :address => address)
+ assert_success response
+ assert_not_nil response.authorization
+ assert_equal @visa_credit_card.year, response.params['response']['card']['expiry_year']
+ end
+
def test_refund
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_not_nil response.authorization
token = response.authorization
- assert response = @gateway.refund(@amount, token, @options)
+ response = @gateway.refund(@amount, token, @options)
assert_success response
assert_not_nil response.authorization
end
def test_failed_refund
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_not_nil response.authorization
token = response.authorization
- assert response = @gateway.refund(@amount, token.reverse, @options)
+ response = @gateway.refund(@amount, token.reverse, @options)
assert_failure response
end
def test_invalid_login
gateway = PinGateway.new(:api_key => '')
- assert response = gateway.purchase(@amount, @credit_card, @options)
+ response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
end
diff --git a/test/remote/gateways/remote_plugnpay_test.rb b/test/remote/gateways/remote_plugnpay_test.rb
index 4340855dc29..f05f4dc7701 100644
--- a/test/remote/gateways/remote_plugnpay_test.rb
+++ b/test/remote/gateways/remote_plugnpay_test.rb
@@ -3,8 +3,8 @@
class PlugnpayTest < Test::Unit::TestCase
def setup
@gateway = PlugnpayGateway.new(fixtures(:plugnpay))
- @good_credit_card = credit_card('4242424242424242')
- @bad_credit_card = credit_card('1234123412341234')
+ @good_card = credit_card('4111111111111111', first_name: 'cardtest')
+ @bad_card = credit_card('1234123412341234')
@options = {
:billing_address => address,
:description => 'Store purchaes'
@@ -12,51 +12,57 @@ def setup
@amount = 100
end
- def test_bad_credit_card
- assert response = @gateway.authorize(@amount, @bad_credit_card, @options)
+ def test_successful_authorize
+ assert response = @gateway.authorize(@amount, @good_card, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_authorize
+ assert response = @gateway.authorize(@amount, @bad_card, @options)
assert_failure response
assert_equal 'Invalid Credit Card No.', response.message
end
- def test_good_credit_card
- assert response = @gateway.authorize(@amount, @good_credit_card, @options)
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @good_card, @options)
assert_success response
assert !response.authorization.blank?
assert_equal 'Success', response.message
end
- def test_purchase_transaction
- assert response = @gateway.purchase(@amount, @good_credit_card, @options)
- assert_success response
- assert !response.authorization.blank?
- assert_equal 'Success', response.message
+ def test_failed_purchase
+ assert response = @gateway.purchase(@amount, @bad_card, @options)
+ assert_failure response
+ assert_equal 'Invalid Credit Card No.', response.message
end
# Capture, and Void require that you Whitelist your IP address.
# In the gateway admin tool, you must add your IP address to the allowed addresses and uncheck "Remote client" under the
# "Auth Transactions" section of the "Security Requirements" area in the test account Security Administration Area.
def test_authorization_and_capture
- assert authorization = @gateway.authorize(@amount, @good_credit_card, @options)
+ assert authorization = @gateway.authorize(@amount, @good_card, @options)
assert_success authorization
assert capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
- assert capture.params['aux_msg'].include? "has been successfully marked for settlement."
+ assert capture.params['aux_msg'].include? 'has been successfully marked for settlement.'
assert_equal 'Success', capture.message
end
def test_authorization_and_partial_capture
- assert authorization = @gateway.authorize(@amount, @good_credit_card, @options)
+ assert authorization = @gateway.authorize(@amount, @good_card, @options)
assert_success authorization
assert capture = @gateway.capture(@amount - 1, authorization.authorization)
assert_success capture
- assert capture.params['aux_msg'].include? "has been successfully reauthed for usd 0.99"
+ assert capture.params['aux_msg'].include? 'has been successfully reauthed for usd 0.99'
assert_equal 'Success', capture.message
end
def test_authorization_and_void
- assert authorization = @gateway.authorize(@amount, @good_credit_card, @options)
+ assert authorization = @gateway.authorize(@amount, @good_card, @options)
assert_success authorization
assert void = @gateway.void(authorization.authorization)
@@ -64,19 +70,19 @@ def test_authorization_and_void
assert_equal 'Success', void.message
end
- def test_purchase_and_credit
- assert purchase = @gateway.purchase(@amount, @good_credit_card, @options)
+ def test_purchase_and_refund
+ assert purchase = @gateway.purchase(@amount, @good_card, @options)
assert_success purchase
- assert credit = @gateway.credit(@amount, purchase.authorization)
- assert_success credit
- assert_equal 'Success', credit.message
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Success', refund.message
end
- def test_credit_with_no_previous_transaction
- assert credit = @gateway.credit(@amount, @good_credit_card, @options)
+ def test_refund_with_no_previous_transaction
+ assert refund = @gateway.refund(@amount, @good_card, @options)
- assert_success credit
- assert_equal 'Success', credit.message
+ assert_success refund
+ assert_equal 'Success', refund.message
end
end
diff --git a/test/remote/gateways/remote_pro_pay_test.rb b/test/remote/gateways/remote_pro_pay_test.rb
new file mode 100644
index 00000000000..c447f996862
--- /dev/null
+++ b/test/remote/gateways/remote_pro_pay_test.rb
@@ -0,0 +1,160 @@
+require 'test_helper'
+
+class RemoteProPayTest < Test::Unit::TestCase
+ def setup
+ @gateway = ProPayGateway.new(fixtures(:pro_pay))
+
+ @amount = 100
+ @credit_card = credit_card('4747474747474747', verification_value: 999)
+ @declined_card = credit_card('4616161616161616')
+ @credit_card_without_cvv = credit_card('4747474747474747', verification_value: nil)
+ @options = {
+ billing_address: address,
+ account_num: '32287391'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ account_num: '32287391'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_recurring_purchase_without_cvv
+ @options[:recurring_payment] = 'Y'
+ response = @gateway.purchase(@amount, @credit_card_without_cvv, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/declined/, response.message)
+ assert_match(/Insufficient funds/, response.message)
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options)
+ assert_success capture
+ assert_equal 'Success', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match(/declined/, response.message)
+ assert_match(/Insufficient funds/, response.message)
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '', @options)
+ assert_failure response
+ assert_match(/Invalid/, response.message)
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'Success', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization, @options)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '', @options)
+ assert_failure response
+ assert_match(/Invalid/, response.message)
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization, @options)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('', @options)
+ assert_failure response
+ assert_match(/Invalid/, response.message)
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, credit_card(''), @options)
+ assert_failure response
+ assert_equal 'Invalid ccNum', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'Success', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match(/declined/, response.message)
+ assert_match(/Insufficient funds/, response.message)
+ end
+
+ def test_invalid_login
+ gateway = ProPayGateway.new(cert_str: 'bad_cert_str')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:cert_str], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_psigate_test.rb b/test/remote/gateways/remote_psigate_test.rb
index ec740a3ed37..74d643794fe 100644
--- a/test/remote/gateways/remote_psigate_test.rb
+++ b/test/remote/gateways/remote_psigate_test.rb
@@ -5,9 +5,10 @@ class PsigateRemoteTest < Test::Unit::TestCase
def setup
Base.mode = :test
@gateway = PsigateGateway.new(fixtures(:psigate))
+ PsigateGateway.ssl_strict = false
@amount = 2400
- @creditcard = credit_card('4242424242424242')
+ @creditcard = credit_card('4111111111111111')
@options = {
:order_id => generate_unique_id,
:billing_address => address,
@@ -55,4 +56,15 @@ def test_successful_void
assert void = @gateway.void(authorization.authorization)
assert_success void
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @creditcard, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@creditcard.number, transcript)
+ assert_scrubbed(@creditcard.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_psl_card_test.rb b/test/remote/gateways/remote_psl_card_test.rb
index 36af7abf443..6b1b9e6e215 100644
--- a/test/remote/gateways/remote_psl_card_test.rb
+++ b/test/remote/gateways/remote_psl_card_test.rb
@@ -1,29 +1,29 @@
require 'test_helper'
class RemotePslCardTest < Test::Unit::TestCase
-
+
def setup
@gateway = PslCardGateway.new(fixtures(:psl_card))
-
+
@uk_maestro = CreditCard.new(fixtures(:psl_maestro))
@uk_maestro_address = fixtures(:psl_maestro_address)
-
+
@solo = CreditCard.new(fixtures(:psl_solo))
@solo_address = fixtures(:psl_solo_address)
-
+
@visa = CreditCard.new(fixtures(:psl_visa))
@visa_address = fixtures(:psl_visa_address)
-
+
@visa_debit = CreditCard.new(fixtures(:psl_visa_debit))
@visa_address = fixtures(:psl_visa_debit_address)
-
+
# The test results are determined by the amount of the transaction
@accept_amount = 1000
@referred_amount = 6000
@declined_amount = 11000
@keep_card_amount = 15000
end
-
+
def test_successful_visa_purchase
response = @gateway.purchase(@accept_amount, @visa,
:billing_address => @visa_address
@@ -31,23 +31,23 @@ def test_successful_visa_purchase
assert_success response
assert response.test?
end
-
+
def test_successful_visa_debit_purchase
response = @gateway.purchase(@accept_amount, @visa_debit,
:billing_address => @visa_debit_address
)
assert_success response
end
-
+
# Fix regression discovered in production
def test_visa_debit_purchase_should_not_send_debit_info_if_present
- @visa_debit.start_month = "07"
+ @visa_debit.start_month = '07'
response = @gateway.purchase(@accept_amount, @visa_debit,
:billing_address => @visa_debit_address
)
assert_success response
end
-
+
def test_successful_visa_purchase_specifying_currency
response = @gateway.purchase(@accept_amount, @visa,
:billing_address => @visa_address,
@@ -56,67 +56,67 @@ def test_successful_visa_purchase_specifying_currency
assert_success response
assert response.test?
end
-
+
def test_successful_solo_purchase
- response = @gateway.purchase(@accept_amount, @solo,
+ response = @gateway.purchase(@accept_amount, @solo,
:billing_address => @solo_address
)
assert_success response
assert response.test?
end
-
+
def test_referred_purchase
- response = @gateway.purchase(@referred_amount, @uk_maestro,
+ response = @gateway.purchase(@referred_amount, @uk_maestro,
:billing_address => @uk_maestro_address
)
assert_failure response
assert response.test?
end
-
+
def test_declined_purchase
- response = @gateway.purchase(@declined_amount, @uk_maestro,
+ response = @gateway.purchase(@declined_amount, @uk_maestro,
:billing_address => @uk_maestro_address
)
assert_failure response
assert response.test?
end
-
+
def test_declined_keep_card_purchase
- response = @gateway.purchase(@keep_card_amount, @uk_maestro,
+ response = @gateway.purchase(@keep_card_amount, @uk_maestro,
:billing_address => @uk_maestro_address
)
assert_failure response
assert response.test?
end
-
+
def test_successful_authorization
- response = @gateway.authorize(@accept_amount, @visa,
+ response = @gateway.authorize(@accept_amount, @visa,
:billing_address => @visa_address
)
assert_success response
assert response.test?
end
-
+
def test_no_login
@gateway = PslCardGateway.new(
:login => ''
)
- response = @gateway.authorize(@accept_amount, @uk_maestro,
+ response = @gateway.authorize(@accept_amount, @uk_maestro,
:billing_address => @uk_maestro_address
)
assert_failure response
assert response.test?
end
-
+
def test_successful_authorization_and_capture
authorization = @gateway.authorize(@accept_amount, @visa,
:billing_address => @visa_address
)
assert_success authorization
assert authorization.test?
-
+
capture = @gateway.capture(@accept_amount, authorization.authorization)
-
+
assert_success capture
assert capture.test?
end
diff --git a/test/remote/gateways/remote_qbms_test.rb b/test/remote/gateways/remote_qbms_test.rb
index d88553ec1c8..017ebd1fc95 100644
--- a/test/remote/gateways/remote_qbms_test.rb
+++ b/test/remote/gateways/remote_qbms_test.rb
@@ -66,31 +66,42 @@ def test_successful_credit
end
def test_invalid_ticket
- gateway = QbmsGateway.new(@gateway_options.merge(:ticket => "test123"))
+ gateway = QbmsGateway.new(@gateway_options.merge(:ticket => 'test123'))
assert response = gateway.authorize(@amount, @card, @options)
assert_instance_of Response, response
assert_failure response
- assert_equal "Application agent not found test123", response.message
+ assert_equal 'Application agent not found test123', response.message
end
def test_invalid_card_number
assert response = @gateway.authorize(@amount, error_card('10301_ccinvalid'), @options)
assert_instance_of Response, response
assert_failure response
- assert_equal "This credit card number is invalid.", response.message
+ assert_equal 'This credit card number is invalid.', response.message
end
def test_decline
assert response = @gateway.authorize(@amount, error_card('10401_decline'), @options)
assert_instance_of Response, response
assert_failure response
- assert_equal "The request to process this transaction has been declined.", response.message
+ assert_equal 'The request to process this transaction has been declined.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:ticket], transcript)
end
private
def error_card(config_id)
- credit_card('4111111111111111', :first_name => "configid=#{config_id}", :last_name => "")
+ credit_card('4111111111111111', :first_name => "configid=#{config_id}", :last_name => '')
end
end
diff --git a/test/remote/gateways/remote_quantum_test.rb b/test/remote/gateways/remote_quantum_test.rb
index dfa3b5682a5..370e802d66c 100644
--- a/test/remote/gateways/remote_quantum_test.rb
+++ b/test/remote/gateways/remote_quantum_test.rb
@@ -1,15 +1,14 @@
require 'test_helper'
class RemoteQuantumTest < Test::Unit::TestCase
-
def setup
@gateway = QuantumGateway.new(fixtures(:quantum))
-
+
@amount = 100
@credit_card = credit_card('4000100011112224')
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card)
assert_success response
@@ -53,7 +52,7 @@ def test_void
assert response = @gateway.void(response.authorization)
assert_success response
end
-
+
def test_passing_billing_address
options = {:billing_address => address}
assert response = @gateway.purchase(@amount, @credit_card, options)
diff --git a/test/remote/gateways/remote_quickbooks_test.rb b/test/remote/gateways/remote_quickbooks_test.rb
new file mode 100644
index 00000000000..c9e95c959c8
--- /dev/null
+++ b/test/remote/gateways/remote_quickbooks_test.rb
@@ -0,0 +1,112 @@
+require 'test_helper'
+
+class RemoteTest < Test::Unit::TestCase
+ def setup
+ @gateway = QuickbooksGateway.new(fixtures(:quickbooks))
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('4000000000000001')
+
+ @partial_amount = @amount - 1
+
+ @options = {
+ order_id: '1',
+ billing_address: address({ zip: 90210,
+ country: 'US',
+ state: 'CA'
+ }),
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'CAPTURED', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'cardNumber is invalid.', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@partial_amount, auth.authorization)
+ assert_equal capture.params['captureDetail']['amount'], sprintf('%.2f', @partial_amount.to_f / 100)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(nil, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@partial_amount, purchase.authorization)
+ assert_equal refund.params['amount'], sprintf('%.2f', @partial_amount.to_f / 100)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{AUTHORIZED}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{cardNumber is invalid.}, response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_invalid_login
+ gateway = QuickbooksGateway.new(
+ consumer_key: '',
+ consumer_secret: '',
+ access_token: '',
+ token_secret: '',
+ realm: ''
+ )
+ assert_raises ActiveMerchant::ResponseError do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+ end
+
+ def test_dump_transcript
+ # See quickbooks_test.rb for an example of a scrubbed transcript
+ end
+end
diff --git a/test/remote/gateways/remote_quickpay_test.rb b/test/remote/gateways/remote_quickpay_test.rb
index 55c7edb6223..760c71f096e 100644
--- a/test/remote/gateways/remote_quickpay_test.rb
+++ b/test/remote/gateways/remote_quickpay_test.rb
@@ -56,7 +56,7 @@ def test_successful_visa_dankort_authorization
assert response = @gateway.authorize(@amount, @visa_dankort, @options)
assert_success response
assert !response.authorization.blank?
- assert_equal 'dankort', response.params['cardtype']
+ assert_equal 'visa-dk', response.params['cardtype']
end
def test_successful_visa_electron_authorization
@@ -174,22 +174,22 @@ def test_successful_purchase_and_credit
end
def test_successful_store_and_reference_purchase
- assert store = @gateway.store(@visa, @options.merge(:description => "New subscription"))
+ assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription'))
assert_success store
assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10]))
assert_success purchase
end
def test_failed_store
- assert store = @gateway.store(credit_card('400010001111222a'), @options.merge(:description => "New subscription"))
+ assert store = @gateway.store(credit_card('4'), @options.merge(:description => 'New subscription'))
assert_failure store
- assert_equal "Error in field: cardnumber", store.message
+ assert_equal 'Error in field: cardnumber', store.message
end
def test_invalid_login
gateway = QuickpayGateway.new(
- :login => '',
- :password => ''
+ :login => '',
+ :password => ''
)
assert response = gateway.purchase(@amount, @visa, @options)
assert_equal 'Invalid merchant id', response.message
diff --git a/test/remote/gateways/remote_quickpay_v10_test.rb b/test/remote/gateways/remote_quickpay_v10_test.rb
new file mode 100644
index 00000000000..8c71fcb1742
--- /dev/null
+++ b/test/remote/gateways/remote_quickpay_v10_test.rb
@@ -0,0 +1,272 @@
+require 'test_helper'
+
+class RemoteQuickPayV10Test < Test::Unit::TestCase
+
+ def setup
+ @gateway = QuickpayV10Gateway.new(fixtures(:quickpay_v10_api_key))
+ @amount = 100
+ @options = {
+ :order_id => generate_unique_id[0...10],
+ :billing_address => address(country: 'DNK')
+ }
+
+ @valid_card = credit_card('1000000000000008')
+ @invalid_card = credit_card('1000000000000016')
+ @expired_card = credit_card('1000000000000024')
+ @capture_rejected_card = credit_card('1000000000000032')
+ @refund_rejected_card = credit_card('1000000000000040')
+
+ @valid_address = address(:phone => '4500000001')
+ @invalid_address = address(:phone => '4500000002')
+ end
+
+ def card_brand(response)
+ response.params['metadata']['brand']
+ end
+
+ def test_successful_purchase_with_short_country
+ options = @options.merge({billing_address: address(country: 'DK')})
+ assert response = @gateway.purchase(@amount, @valid_card, options)
+
+ assert_equal 'OK', response.message
+ assert_equal 'DKK', response.params['currency']
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_order_id_format
+ options = @options.merge({order_id: "##{Time.new.to_f}"})
+ assert response = @gateway.purchase(@amount, @valid_card, options)
+
+ assert_equal 'OK', response.message
+ assert_equal 'DKK', response.params['currency']
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @valid_card, @options)
+
+ assert_equal 'OK', response.message
+ assert_equal 'DKK', response.params['currency']
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_unsuccessful_purchase_with_invalid_card
+ assert response = @gateway.purchase(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_match(/Rejected test operation/, response.message)
+ end
+
+ def test_successful_usd_purchase
+ assert response = @gateway.purchase(@amount, @valid_card, @options.update(:currency => 'USD'))
+ assert_equal 'OK', response.message
+ assert_equal 'USD', response.params['currency']
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_acquirers
+ assert response = @gateway.purchase(@amount, @valid_card, @options.update(:acquirer => 'nets'))
+ assert_equal 'OK', response.message
+ assert_success response
+ end
+
+ def test_unsuccessful_purchase_with_invalid_acquirers
+ assert response = @gateway.purchase(@amount, @valid_card, @options.update(:acquirer => 'invalid'))
+ assert_failure response
+ assert_equal 'Validation error', response.message
+ end
+
+ def test_unsuccessful_authorize_with_invalid_card
+ assert response = @gateway.authorize(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_match(/Rejected test operation/, response.message)
+ end
+
+ def test_successful_authorize_and_capture
+ assert auth = @gateway.authorize(@amount, @valid_card, @options)
+ assert_success auth
+ assert_equal 'OK', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'OK', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_3ds
+ options = @options.merge(
+ three_d_secure: {
+ cavv: '1234',
+ eci: '1234',
+ xid: '1234'
+ }
+ )
+ assert auth = @gateway.authorize(@amount, @valid_card, options)
+ assert_success auth
+ assert_equal 'OK', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'OK', capture.message
+ end
+
+ def test_unsuccessful_authorize_and_capture
+ assert auth = @gateway.authorize(@amount, @capture_rejected_card, @options)
+ assert_success auth
+ assert_equal 'OK', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_failure capture
+ assert_equal 'Rejected test operation', capture.message
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '1111')
+ assert_failure response
+ assert_equal 'Not found: No Payment with id 1111', response.message
+ end
+
+ def test_successful_purchase_and_void
+ assert auth = @gateway.authorize(@amount, @valid_card, @options)
+ assert_success auth
+ assert_equal 'OK', auth.message
+ assert auth.authorization
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'OK', void.message
+ end
+
+ def test_unsuccessful_void
+ assert void = @gateway.void('123')
+ assert_failure void
+ assert_equal 'Not found: No Payment with id 123', void.message
+ end
+
+ def test_successful_authorization_capture_and_credit
+ assert auth = @gateway.authorize(@amount, @valid_card, @options)
+ assert_success auth
+ assert !auth.authorization.blank?
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert credit = @gateway.refund(@amount, auth.authorization)
+ assert_success credit
+ assert_equal 'OK', credit.message
+ end
+
+ def test_successful_purchase_and_credit
+ assert purchase = @gateway.purchase(@amount, @valid_card, @options)
+ assert_success purchase
+ assert credit = @gateway.refund(@amount, purchase.authorization)
+ assert_success credit
+ end
+
+ def test_unsuccessful_authorization_capture_and_credit
+ assert auth = @gateway.authorize(@amount, @refund_rejected_card, @options)
+ assert_success auth
+ assert !auth.authorization.blank?
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert refund = @gateway.refund(@amount, auth.authorization)
+ assert_failure refund
+ assert_equal 'Rejected test operation', refund.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@valid_card, @options)
+ assert_success response
+ assert_match %r{OK}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@invalid_card, @options)
+ assert_failure response
+ assert_equal 'Rejected test operation', response.message
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@valid_card, @options)
+ assert_success response
+ end
+
+ def test_successful_store_and_reference_purchase
+ assert store = @gateway.store(@valid_card, @options)
+ assert_success store
+ assert purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+ end
+
+ def test_successful_store_and_reference_recurring_purchase
+ assert store = @gateway.store(@valid_card, @options)
+ assert_success store
+ assert signup = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success signup
+ @options[:order_id] = generate_unique_id[0...10]
+ assert renewal = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success renewal
+ end
+
+ def test_successful_store_and_reference_authorize
+ assert store = @gateway.store(@valid_card, @options)
+ assert_success store
+ assert authorization = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success authorization
+ end
+
+ def test_successful_store_and_credit
+ assert store = @gateway.store(@valid_card, @options)
+ assert_success store
+ assert purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+ assert credit = @gateway.refund(@amount, purchase.authorization)
+ assert_success credit
+ end
+
+ def test_unsuccessful_store_and_credit
+ assert store = @gateway.store(@refund_rejected_card, @options)
+ assert_success store
+ assert purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
+ assert credit = @gateway.refund(@amount, purchase.authorization)
+ assert_failure credit
+ assert_match(/Rejected test operation/, credit.message)
+ end
+
+ def test_successful_store_and_void_authorize
+ assert store = @gateway.store(@valid_card, @options)
+ assert_success store
+ assert authorize = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success authorize
+ assert void = @gateway.void(authorize.authorization)
+ assert_success void
+ assert_equal 'OK', void.message
+ end
+
+ def test_successful_unstore
+ assert response = @gateway.store(@valid_card, @options)
+ assert_success response
+
+ assert response = @gateway.unstore(response.authorization)
+ assert_success response
+ end
+
+ def test_invalid_login
+ gateway = QuickpayV10Gateway.new(api_key: '**')
+ assert response = gateway.purchase(@amount, @valid_card, @options)
+ assert_equal 'Invalid API key', response.message
+ assert_failure response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @valid_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@valid_card.number, clean_transcript)
+ assert_scrubbed(@valid_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:api_key], clean_transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_quickpay_v4_test.rb b/test/remote/gateways/remote_quickpay_v4_test.rb
index 448527ef320..9eb4ca69970 100644
--- a/test/remote/gateways/remote_quickpay_v4_test.rb
+++ b/test/remote/gateways/remote_quickpay_v4_test.rb
@@ -24,9 +24,7 @@ def setup
@mastercard_dk = credit_card('5413031000000000')
@amex_dk = credit_card('3747100000000000')
@amex = credit_card('3700100000000000')
-
- # forbrugsforeningen doesn't use a verification value
- @forbrugsforeningen = credit_card('6007221000000000', :verification_value => nil)
+ @fbg1886 = credit_card('6007221000000000')
end
def test_successful_purchase
@@ -41,10 +39,10 @@ def test_successful_purchase_with_all_fraud_parameters
@options[:fraud_http_referer] = 'http://www.excample.com'
@options[:fraud_remote_addr] = '127.0.0.1'
@options[:fraud_http_accept] = 'foo'
- @options[:fraud_http_accept_language] = "DK"
- @options[:fraud_http_accept_encoding] = "UFT8"
- @options[:fraud_http_accept_charset] = "Latin"
- @options[:fraud_http_user_agent] = "Safari"
+ @options[:fraud_http_accept_language] = 'DK'
+ @options[:fraud_http_accept_encoding] = 'UFT8'
+ @options[:fraud_http_accept_charset] = 'Latin'
+ @options[:fraud_http_user_agent] = 'Safari'
assert response = @gateway.purchase(@amount, @visa, @options)
assert_equal 'OK', response.message
@@ -53,8 +51,6 @@ def test_successful_purchase_with_all_fraud_parameters
assert !response.authorization.blank?
end
-
-
def test_successful_usd_purchase
assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD'))
assert_equal 'OK', response.message
@@ -74,6 +70,7 @@ def test_successful_visa_dankort_authorization
assert response = @gateway.authorize(@amount, @visa_dankort, @options)
assert_success response
assert !response.authorization.blank?
+ # A Visa-Dankort is considered a Dankort when processed by Nets
assert_equal 'dankort', response.params['cardtype']
end
@@ -134,7 +131,7 @@ def test_successful_american_express_authorization
end
def test_successful_forbrugsforeningen_authorization
- assert response = @gateway.authorize(@amount, @forbrugsforeningen, @options)
+ assert response = @gateway.authorize(@amount, @fbg1886, @options)
assert_success response
assert !response.authorization.blank?
assert_equal 'fbg1886', response.params['cardtype']
@@ -192,7 +189,7 @@ def test_successful_purchase_and_credit
end
def test_successful_store_and_reference_purchase
- assert store = @gateway.store(@visa, @options.merge(:description => "New subscription"))
+ assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription'))
assert_success store
assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10]))
assert_success purchase
@@ -200,8 +197,8 @@ def test_successful_store_and_reference_purchase
def test_invalid_login
gateway = QuickpayGateway.new(
- :login => '',
- :password => ''
+ :login => '999999999',
+ :password => ''
)
assert response = gateway.purchase(@amount, @visa, @options)
assert_equal 'Invalid merchant id', response.message
diff --git a/test/remote/gateways/remote_quickpay_v5_test.rb b/test/remote/gateways/remote_quickpay_v5_test.rb
index e59611a35a4..02838bf2bb8 100644
--- a/test/remote/gateways/remote_quickpay_v5_test.rb
+++ b/test/remote/gateways/remote_quickpay_v5_test.rb
@@ -24,9 +24,7 @@ def setup
@mastercard_dk = credit_card('5413031000000000')
@amex_dk = credit_card('3747100000000000')
@amex = credit_card('3700100000000000')
-
- # forbrugsforeningen doesn't use a verification value
- @forbrugsforeningen = credit_card('6007221000000000', :verification_value => nil)
+ @fbg1886 = credit_card('6007221000000000')
end
def test_successful_purchase
@@ -41,10 +39,10 @@ def test_successful_purchase_with_all_fraud_parameters
@options[:fraud_http_referer] = 'http://www.excample.com'
@options[:fraud_remote_addr] = '127.0.0.1'
@options[:fraud_http_accept] = 'foo'
- @options[:fraud_http_accept_language] = "DK"
- @options[:fraud_http_accept_encoding] = "UFT8"
- @options[:fraud_http_accept_charset] = "Latin"
- @options[:fraud_http_user_agent] = "Safari"
+ @options[:fraud_http_accept_language] = 'DK'
+ @options[:fraud_http_accept_encoding] = 'UFT8'
+ @options[:fraud_http_accept_charset] = 'Latin'
+ @options[:fraud_http_user_agent] = 'Safari'
assert response = @gateway.purchase(@amount, @visa, @options)
assert_equal 'OK', response.message
@@ -53,8 +51,6 @@ def test_successful_purchase_with_all_fraud_parameters
assert !response.authorization.blank?
end
-
-
def test_successful_usd_purchase
assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD'))
assert_equal 'OK', response.message
@@ -74,6 +70,7 @@ def test_successful_visa_dankort_authorization
assert response = @gateway.authorize(@amount, @visa_dankort, @options)
assert_success response
assert !response.authorization.blank?
+ # A Visa-Dankort is considered a Dankort when processed by Nets
assert_equal 'dankort', response.params['cardtype']
end
@@ -134,7 +131,7 @@ def test_successful_american_express_authorization
end
def test_successful_forbrugsforeningen_authorization
- assert response = @gateway.authorize(@amount, @forbrugsforeningen, @options)
+ assert response = @gateway.authorize(@amount, @fbg1886, @options)
assert_success response
assert !response.authorization.blank?
assert_equal 'fbg1886', response.params['cardtype']
@@ -192,7 +189,7 @@ def test_successful_purchase_and_credit
end
def test_successful_store_and_reference_purchase
- assert store = @gateway.store(@visa, @options.merge(:description => "New subscription"))
+ assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription'))
assert_success store
assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10]))
assert_success purchase
@@ -200,8 +197,8 @@ def test_successful_store_and_reference_purchase
def test_invalid_login
gateway = QuickpayGateway.new(
- :login => '',
- :password => ''
+ :login => '999999999',
+ :password => ''
)
assert response = gateway.purchase(@amount, @visa, @options)
assert_equal 'Invalid merchant id', response.message
diff --git a/test/remote/gateways/remote_quickpay_v6_test.rb b/test/remote/gateways/remote_quickpay_v6_test.rb
index fd1a4b93952..2d113657c0c 100644
--- a/test/remote/gateways/remote_quickpay_v6_test.rb
+++ b/test/remote/gateways/remote_quickpay_v6_test.rb
@@ -4,7 +4,7 @@ class RemoteQuickpayV6Test < Test::Unit::TestCase
# These test assumes that you have not added your development IP in
# the Quickpay Manager.
def setup
- @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key))
+ @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key).merge(:version => 6))
@amount = 100
@options = {
@@ -24,9 +24,7 @@ def setup
@mastercard_dk = credit_card('5413031000000000')
@amex_dk = credit_card('3747100000000000')
@amex = credit_card('3700100000000000')
-
- # forbrugsforeningen doesn't use a verification value
- @forbrugsforeningen = credit_card('6007221000000000', :verification_value => nil)
+ @fbg1886 = credit_card('6007221000000000')
end
def test_successful_purchase
@@ -41,10 +39,10 @@ def test_successful_purchase_with_all_fraud_parameters
@options[:fraud_http_referer] = 'http://www.excample.com'
@options[:fraud_remote_addr] = '127.0.0.1'
@options[:fraud_http_accept] = 'foo'
- @options[:fraud_http_accept_language] = "DK"
- @options[:fraud_http_accept_encoding] = "UFT8"
- @options[:fraud_http_accept_charset] = "Latin"
- @options[:fraud_http_user_agent] = "Safari"
+ @options[:fraud_http_accept_language] = 'DK'
+ @options[:fraud_http_accept_encoding] = 'UFT8'
+ @options[:fraud_http_accept_charset] = 'Latin'
+ @options[:fraud_http_user_agent] = 'Safari'
assert response = @gateway.purchase(@amount, @visa, @options)
assert_equal 'OK', response.message
@@ -53,8 +51,6 @@ def test_successful_purchase_with_all_fraud_parameters
assert !response.authorization.blank?
end
-
-
def test_successful_usd_purchase
assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD'))
assert_equal 'OK', response.message
@@ -74,6 +70,7 @@ def test_successful_visa_dankort_authorization
assert response = @gateway.authorize(@amount, @visa_dankort, @options)
assert_success response
assert !response.authorization.blank?
+ # A Visa-Dankort is considered a Dankort when processed by Nets
assert_equal 'dankort', response.params['cardtype']
end
@@ -134,7 +131,7 @@ def test_successful_american_express_authorization
end
def test_successful_forbrugsforeningen_authorization
- assert response = @gateway.authorize(@amount, @forbrugsforeningen, @options)
+ assert response = @gateway.authorize(@amount, @fbg1886, @options)
assert_success response
assert !response.authorization.blank?
assert_equal 'fbg1886', response.params['cardtype']
@@ -192,7 +189,7 @@ def test_successful_purchase_and_credit
end
def test_successful_store_and_reference_purchase
- assert store = @gateway.store(@visa, @options.merge(:description => "New subscription"))
+ assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription'))
assert_success store
assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10]))
assert_success purchase
@@ -200,8 +197,8 @@ def test_successful_store_and_reference_purchase
def test_invalid_login
gateway = QuickpayGateway.new(
- :login => '',
- :password => ''
+ :login => '999999999',
+ :password => ''
)
assert response = gateway.purchase(@amount, @visa, @options)
assert_equal 'Invalid merchant id', response.message
diff --git a/test/remote/gateways/remote_quickpay_v7_test.rb b/test/remote/gateways/remote_quickpay_v7_test.rb
new file mode 100644
index 00000000000..8b8def4f168
--- /dev/null
+++ b/test/remote/gateways/remote_quickpay_v7_test.rb
@@ -0,0 +1,229 @@
+require 'test_helper'
+
+class RemoteQuickpayV7Test < Test::Unit::TestCase
+ # These test assumes that you have not added your development IP in
+ # the Quickpay Manager.
+ def setup
+ @gateway = QuickpayGateway.new(fixtures(:quickpay_with_api_key))
+
+ @amount = 100
+ @options = {
+ :order_id => generate_unique_id[0...10],
+ :billing_address => address
+ }
+
+ @visa_no_cvv2 = credit_card('4000300011112220', :verification_value => nil)
+ @visa = credit_card('4000100011112224')
+ @dankort = credit_card('5019717010103742')
+ @visa_dankort = credit_card('4571100000000000')
+ @electron_dk = credit_card('4175001000000000')
+ @diners_club = credit_card('30401000000000')
+ @diners_club_dk = credit_card('36148010000000')
+ @maestro = credit_card('5020100000000000')
+ @maestro_dk = credit_card('6769271000000000')
+ @mastercard_dk = credit_card('5413031000000000')
+ @amex_dk = credit_card('3747100000000000')
+ @amex = credit_card('3700100000000000')
+ @fbg1886 = credit_card('6007221000000000')
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @visa, @options)
+ assert_equal 'OK', response.message
+ assert_equal 'DKK', response.params['currency']
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_all_fraud_parameters
+ @options[:ip] = '127.0.0.1' # will set :fraud_remote_addr
+ @options[:fraud_http_referer] = 'http://www.excample.com'
+ @options[:fraud_http_accept] = 'foo'
+ @options[:fraud_http_accept_language] = 'DK'
+ @options[:fraud_http_accept_encoding] = 'UFT8'
+ @options[:fraud_http_accept_charset] = 'Latin'
+ @options[:fraud_http_user_agent] = 'Safari'
+
+ assert response = @gateway.purchase(@amount, @visa, @options)
+ assert_equal 'OK', response.message
+ assert_equal 'DKK', response.params['currency']
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_usd_purchase
+ assert response = @gateway.purchase(@amount, @visa, @options.update(:currency => 'USD'))
+ assert_equal 'OK', response.message
+ assert_equal 'USD', response.params['currency']
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_acquirers
+ assert response = @gateway.purchase(@amount, @visa, @options.update(:acquirers => 'nets'))
+ assert_equal 'OK', response.message
+ assert_success response
+ end
+
+ def test_unsuccessful_purchase_with_invalid_acquirers
+ assert response = @gateway.purchase(@amount, @visa, @options.update(:acquirers => 'invalid'))
+ assert_equal 'Error in field: acquirers', response.message
+ assert_failure response
+ end
+
+ def test_successful_dankort_authorization
+ assert response = @gateway.authorize(@amount, @dankort, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'dankort', response.params['cardtype']
+ end
+
+ def test_successful_visa_dankort_authorization
+ assert response = @gateway.authorize(@amount, @visa_dankort, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ # A Visa-Dankort is considered a Dankort when processed by Nets
+ assert_equal 'dankort', response.params['cardtype']
+ end
+
+ def test_successful_visa_electron_authorization
+ assert response = @gateway.authorize(@amount, @electron_dk, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'visa-electron-dk', response.params['cardtype']
+ end
+
+ def test_successful_diners_club_authorization
+ assert response = @gateway.authorize(@amount, @diners_club, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'diners', response.params['cardtype']
+ end
+
+ def test_successful_diners_club_dk_authorization
+ assert response = @gateway.authorize(@amount, @diners_club_dk, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'diners-dk', response.params['cardtype']
+ end
+
+ def test_successful_maestro_authorization
+ assert response = @gateway.authorize(@amount, @maestro, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'maestro', response.params['cardtype']
+ end
+
+ def test_successful_maestro_dk_authorization
+ assert response = @gateway.authorize(@amount, @maestro_dk, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'maestro-dk', response.params['cardtype']
+ end
+
+ def test_successful_mastercard_dk_authorization
+ assert response = @gateway.authorize(@amount, @mastercard_dk, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'mastercard-dk', response.params['cardtype']
+ end
+
+ def test_successful_american_express_dk_authorization
+ assert response = @gateway.authorize(@amount, @amex_dk, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'american-express-dk', response.params['cardtype']
+ end
+
+ def test_successful_american_express_authorization
+ assert response = @gateway.authorize(@amount, @amex, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'american-express', response.params['cardtype']
+ end
+
+ def test_successful_forbrugsforeningen_authorization
+ assert response = @gateway.authorize(@amount, @fbg1886, @options)
+ assert_success response
+ assert !response.authorization.blank?
+ assert_equal 'fbg1886', response.params['cardtype']
+ end
+
+ def test_unsuccessful_purchase_with_missing_cvv2
+ assert response = @gateway.purchase(@amount, @visa_no_cvv2, @options)
+ # Quickpay has made the cvd field optional in order to support forbrugsforeningen cards which don't have them
+ assert_equal 'OK', response.message
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_authorize_and_capture
+ assert auth = @gateway.authorize(@amount, @visa, @options)
+ assert_success auth
+ assert_equal 'OK', auth.message
+ assert auth.authorization
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'OK', capture.message
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Missing field: transaction', response.message
+ end
+
+ def test_successful_purchase_and_void
+ assert auth = @gateway.authorize(@amount, @visa, @options)
+ assert_success auth
+ assert_equal 'OK', auth.message
+ assert auth.authorization
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'OK', void.message
+ end
+
+ def test_successful_authorization_capture_and_credit
+ assert auth = @gateway.authorize(@amount, @visa, @options)
+ assert_success auth
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert credit = @gateway.refund(@amount, auth.authorization)
+ assert_success credit
+ assert_equal 'OK', credit.message
+ end
+
+ def test_successful_purchase_and_credit
+ assert purchase = @gateway.purchase(@amount, @visa, @options)
+ assert_success purchase
+ assert credit = @gateway.refund(@amount, purchase.authorization)
+ assert_success credit
+ end
+
+ def test_successful_store_and_reference_purchase
+ assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription'))
+ assert_success store
+ assert purchase = @gateway.purchase(@amount, store.authorization, @options.merge(:order_id => generate_unique_id[0...10]))
+ assert_success purchase
+ end
+
+ def test_successful_store_with_acquirers
+ assert store = @gateway.store(@visa, @options.merge(:description => 'New subscription', :acquirers => 'nets'))
+ assert_success store
+ end
+
+ def test_successful_store_sans_description
+ assert store = @gateway.store(@visa, @options.merge(:acquirers => 'nets'))
+ assert_success store
+ end
+
+ def test_invalid_login
+ gateway = QuickpayGateway.new(
+ :login => '999999999',
+ :password => ''
+ )
+ assert response = gateway.purchase(@amount, @visa, @options)
+ assert_equal 'Invalid merchant id', response.message
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_qvalent_test.rb b/test/remote/gateways/remote_qvalent_test.rb
new file mode 100644
index 00000000000..95653323048
--- /dev/null
+++ b/test/remote/gateways/remote_qvalent_test.rb
@@ -0,0 +1,242 @@
+require 'test_helper'
+
+class RemoteQvalentTest < Test::Unit::TestCase
+ def setup
+ @gateway = QvalentGateway.new(fixtures(:qvalent))
+
+ @amount = 100
+ @credit_card = credit_card('4242424242424242')
+ @mastercard = credit_card('5163200000000008', brand: 'master')
+ @declined_card = credit_card('4000000000000000')
+ @expired_card = credit_card('4111111113444494')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_invalid_login
+ gateway = QvalentGateway.new(
+ username: 'bad',
+ password: 'bad',
+ merchant: '101',
+ pem: 'bad',
+ pem_password: 'bad'
+ )
+
+ assert_raise OpenSSL::X509::CertificateError do
+ gateway.purchase(@amount, @credit_card, @options)
+ end
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_soft_descriptors
+ options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase',
+ customer_merchant_name: 'Some Merchant',
+ customer_merchant_street_address: '42 Wallaby Way',
+ customer_merchant_location: 'Sydney',
+ customer_merchant_country: 'AU',
+ customer_merchant_post_code: '2060',
+ customer_merchant_state: 'NSW'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_3d_secure
+ options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase',
+ xid: '123',
+ cavv: '456',
+ eci: '5'
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card number (no such number)', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card number (no such number)', response.message
+ end
+
+ def test_successful_capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Succeeded', auth.message
+ assert_not_nil auth.authorization
+
+ assert capture = @gateway.capture(@amount, auth.authorization, @options.merge({ order_id: generate_unique_id }))
+ assert_success capture
+ end
+
+ def test_failed_capture
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Succeeded', auth.message
+ assert_not_nil auth.authorization
+
+ assert capture = @gateway.capture(@amount, '', @options.merge({ order_id: generate_unique_id }))
+ assert_failure capture
+ end
+
+ def test_successful_partial_capture
+ assert auth = @gateway.authorize(200, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Succeeded', auth.message
+ assert_not_nil auth.authorization
+
+ assert capture = @gateway.capture(100, auth.authorization, @options.merge({ order_id: generate_unique_id }))
+ assert_success capture
+ end
+
+ def test_successful_void
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Succeeded', auth.message
+ assert_not_nil auth.authorization
+
+ assert void = @gateway.void(auth.authorization, @options.merge({ order_id: generate_unique_id }))
+ assert_success void
+ end
+
+ def test_failed_void
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Succeeded', auth.message
+ assert_not_nil auth.authorization
+
+ assert void = @gateway.void('', @options.merge({ order_id: generate_unique_id }))
+ assert_failure void
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ assert_match %r{Invalid card number}, response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card number (no such number)', response.message
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_store
+ response = @gateway.store(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card number (no such number)', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value, clean_transcript)
+ assert_scrubbed(@gateway.options[:password], clean_transcript)
+ end
+
+ def test_successful_purchase_initial
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: true,
+ initiator: 'merchant',
+ reason_type: 'unscheduled'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential))
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_not_nil response.params['response.authTraceId']
+ end
+
+ def test_successful_purchase_cardholder
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'cardholder',
+ reason_type: 'unscheduled',
+ network_transaction_id: 'qwerty7890'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(stored_credential))
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_mastercard
+ stored_credential = {
+ stored_credential: {
+ initial_transaction: false,
+ initiator: 'merchant',
+ reason_type: 'recurring',
+ network_transaction_id: 'qwerty7890'
+ }
+ }
+
+ response = @gateway.purchase(@amount, @mastercard, @options.merge(stored_credential))
+
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+end
diff --git a/test/remote/gateways/remote_realex_test.rb b/test/remote/gateways/remote_realex_test.rb
index 3f30d9f7122..92b415ec0c2 100644
--- a/test/remote/gateways/remote_realex_test.rb
+++ b/test/remote/gateways/remote_realex_test.rb
@@ -3,14 +3,15 @@
class RemoteRealexTest < Test::Unit::TestCase
def setup
- @gateway = RealexGateway.new(fixtures(:realex))
+ @gateway = RealexGateway.new(fixtures(:realex_with_account))
# Replace the card numbers with the test account numbers from Realex
- @visa = card_fixtures(:realex_visa)
- @visa_declined = card_fixtures(:realex_visa_declined)
- @visa_referral_b = card_fixtures(:realex_visa_referral_b)
- @visa_referral_a = card_fixtures(:realex_visa_referral_a)
- @visa_coms_error = card_fixtures(:realex_visa_coms_error)
+ @visa = card_fixtures(:realex_visa)
+ @visa_declined = card_fixtures(:realex_visa_declined)
+ @visa_referral_b = card_fixtures(:realex_visa_referral_b)
+ @visa_referral_a = card_fixtures(:realex_visa_referral_a)
+ @visa_coms_error = card_fixtures(:realex_visa_coms_error)
+ @visa_3ds_enrolled = card_fixtures(:realex_visa_3ds_enrolled)
@mastercard = card_fixtures(:realex_mastercard)
@mastercard_declined = card_fixtures(:realex_mastercard_declined)
@@ -18,6 +19,19 @@ def setup
@mastercard_referral_a = card_fixtures(:realex_mastercard_referral_a)
@mastercard_coms_error = card_fixtures(:realex_mastercard_coms_error)
+ @apple_pay = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :apple_pay
+ )
+
+ @declined_apple_pay = network_tokenization_credit_card('4000120000001154',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :apple_pay
+ )
@amount = 10000
end
@@ -27,7 +41,6 @@ def card_fixtures(name)
def test_realex_purchase
[ @visa, @mastercard ].each do |card|
-
response = @gateway.purchase(@amount, card,
:order_id => generate_unique_id,
:description => 'Test Realex Purchase',
@@ -40,7 +53,7 @@ def test_realex_purchase
assert_success response
assert response.test?
assert response.authorization.length > 0
- assert_equal "Successful", response.message
+ assert_equal 'Successful', response.message
end
end
@@ -58,26 +71,31 @@ def test_realex_purchase_with_invalid_login
assert_failure response
assert_equal '504', response.params['result']
- assert_equal "There is no such merchant id. Please contact realex payments if you continue to experience this problem.", response.message
+ assert_match %r{no such}i, response.message
end
def test_realex_purchase_with_invalid_account
- response = RealexGateway.new(fixtures(:realex_with_account)).purchase(@amount, @visa,
+ response = RealexGateway.new(fixtures(:realex_with_account).merge(account: 'invalid')).purchase(@amount, @visa,
:order_id => generate_unique_id,
- :description => 'Test Realex purchase with invalid acocunt'
+ :description => 'Test Realex purchase with invalid account'
)
assert_not_nil response
assert_failure response
assert_equal '506', response.params['result']
- assert_equal "There is no such merchant account. Please contact realex payments if you continue to experience this problem.", response.message
+ assert_match %r{no such}i, response.message
end
- def test_realex_purchase_declined
+ def test_realex_purchase_with_apple_pay
+ response = @gateway.purchase(1000, @apple_pay, :order_id => generate_unique_id, :description => 'Test Realex with ApplePay')
+ assert_success response
+ assert response.test?
+ assert_equal 'Successful', response.message
+ end
+ def test_realex_purchase_declined
[ @visa_declined, @mastercard_declined ].each do |card|
-
response = @gateway.purchase(@amount, card,
:order_id => generate_unique_id,
:description => 'Test Realex purchase declined'
@@ -88,12 +106,54 @@ def test_realex_purchase_declined
assert_equal '101', response.params['result']
assert_equal response.params['message'], response.message
end
+ end
+ def test_realex_purchase_with_apple_pay_declined
+ response = @gateway.purchase(1101, @declined_apple_pay, :order_id => generate_unique_id, :description => 'Test Realex with ApplePay')
+ assert_failure response
+ assert response.test?
+ assert_equal '101', response.params['result']
+ assert_match %r{DECLINED}i, response.message
+ end
+
+ def test_realex_purchase_with_three_d_secure_1
+ response = @gateway.purchase(
+ 1000,
+ @visa_3ds_enrolled,
+ three_d_secure: {
+ eci: '05',
+ cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
+ xid: 'MDAwMDAwMDAwMDAwMDAwMzIyNzY=',
+ version: '1.0.2',
+ },
+ :order_id => generate_unique_id,
+ :description => 'Test Realex with 3DS'
+ )
+ assert_success response
+ assert response.test?
+ assert_equal 'Successful', response.message
+ end
+
+ def test_realex_purchase_with_three_d_secure_2
+ response = @gateway.purchase(
+ 1000,
+ @visa_3ds_enrolled,
+ three_d_secure: {
+ eci: '05',
+ cavv: 'AgAAAAAAAIR8CQrXcIhbQAAAAAA',
+ ds_transaction_id: 'bDE9Aa1A-C5Ac-AD3a-4bBC-aC918ab1de3E',
+ version: '2.1.0',
+ },
+ :order_id => generate_unique_id,
+ :description => 'Test Realex with 3DS'
+ )
+ assert_success response
+ assert response.test?
+ assert_equal 'Successful', response.message
end
def test_realex_purchase_referral_b
[ @visa_referral_b, @mastercard_referral_b ].each do |card|
-
response = @gateway.purchase(@amount, card,
:order_id => generate_unique_id,
:description => 'Test Realex Referral B'
@@ -108,7 +168,6 @@ def test_realex_purchase_referral_b
def test_realex_purchase_referral_a
[ @visa_referral_a, @mastercard_referral_a ].each do |card|
-
response = @gateway.purchase(@amount, card,
:order_id => generate_unique_id,
:description => 'Test Realex Rqeferral A'
@@ -118,39 +177,20 @@ def test_realex_purchase_referral_a
assert_equal '103', response.params['result']
assert_equal RealexGateway::DECLINED, response.message
end
-
end
def test_realex_purchase_coms_error
-
[ @visa_coms_error, @mastercard_coms_error ].each do |card|
-
response = @gateway.purchase(@amount, card,
:order_id => generate_unique_id,
:description => 'Test Realex coms error'
)
-
assert_not_nil response
assert_failure response
assert_equal '200', response.params['result']
assert_equal RealexGateway::BANK_ERROR, response.message
end
-
- end
-
- def test_realex_ccn_error
- @visa.number = '5'
-
- response = @gateway.purchase(@amount, @visa,
- :order_id => generate_unique_id,
- :description => 'Test Realex ccn error'
- )
- assert_not_nil response
- assert_failure response
-
- assert_equal '508', response.params['result']
- assert_match(/invalid/i, response.message)
end
def test_realex_expiry_month_error
@@ -164,7 +204,7 @@ def test_realex_expiry_month_error
assert_failure response
assert_equal '509', response.params['result']
- assert_equal "Expiry date invalid", response.message
+ assert_match %r{invalid}i, response.message
end
def test_realex_expiry_year_error
@@ -178,12 +218,12 @@ def test_realex_expiry_year_error
assert_failure response
assert_equal '509', response.params['result']
- assert_equal "Expiry date invalid", response.message
+ assert_equal 'Expiry date invalid', response.message
end
def test_invalid_credit_card_name
- @visa.first_name = ""
- @visa.last_name = ""
+ @visa.first_name = ''
+ @visa.last_name = ''
response = @gateway.purchase(@amount, @visa,
:order_id => generate_unique_id,
@@ -192,13 +232,13 @@ def test_invalid_credit_card_name
assert_not_nil response
assert_failure response
- assert_equal '502', response.params['result']
- assert_match(/mandatory field not present/i, response.message)
+ assert_equal '506', response.params['result']
+ assert_match(/does not conform/i, response.message)
end
def test_cvn
@visa_cvn = @visa.clone
- @visa_cvn.verification_value = "111"
+ @visa_cvn.verification_value = '111'
response = @gateway.purchase(@amount, @visa_cvn,
:order_id => generate_unique_id,
:description => 'test_cvn'
@@ -247,12 +287,34 @@ def test_realex_authorize_then_capture
:country => 'US'
}
)
+ assert auth_response.test?
+
+ capture_response = @gateway.capture(nil, auth_response.authorization)
+
+ assert_not_nil capture_response
+ assert_success capture_response
+ assert capture_response.authorization.length > 0
+ assert_equal 'Successful', capture_response.message
+ assert_match(/Settled Successfully/, capture_response.params['message'])
+ end
+
+ def test_realex_authorize_then_capture_with_extra_amount
+ order_id = generate_unique_id
+
+ auth_response = @gateway.authorize(@amount*115, @visa,
+ :order_id => order_id,
+ :description => 'Test Realex Purchase',
+ :billing_address => {
+ :zip => '90210',
+ :country => 'US'
+ }
+ )
+ assert auth_response.test?
capture_response = @gateway.capture(@amount, auth_response.authorization)
assert_not_nil capture_response
assert_success capture_response
- assert capture_response.test?
assert capture_response.authorization.length > 0
assert_equal 'Successful', capture_response.message
assert_match(/Settled Successfully/, capture_response.params['message'])
@@ -269,13 +331,12 @@ def test_realex_purchase_then_void
:country => 'US'
}
)
+ assert purchase_response.test?
void_response = @gateway.void(purchase_response.authorization)
assert_not_nil void_response
assert_success void_response
- assert void_response.test?
- assert void_response.authorization.length > 0
assert_equal 'Successful', void_response.message
assert_match(/Voided Successfully/, void_response.params['message'])
end
@@ -283,7 +344,7 @@ def test_realex_purchase_then_void
def test_realex_purchase_then_refund
order_id = generate_unique_id
- gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(:rebate_secret => 'refund'))
+ gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(:rebate_secret => 'rebate'))
purchase_response = gateway_with_refund_password.purchase(@amount, @visa,
:order_id => order_id,
@@ -293,14 +354,107 @@ def test_realex_purchase_then_refund
:country => 'US'
}
)
+ assert purchase_response.test?
rebate_response = gateway_with_refund_password.refund(@amount, purchase_response.authorization)
assert_not_nil rebate_response
assert_success rebate_response
- assert rebate_response.test?
assert rebate_response.authorization.length > 0
assert_equal 'Successful', rebate_response.message
end
+ def test_realex_verify
+ response = @gateway.verify(@visa,
+ :order_id => generate_unique_id,
+ :description => 'Test Realex verify'
+ )
+
+ assert_not_nil response
+ assert_success response
+ assert response.test?
+ assert response.authorization.length > 0
+ assert_equal 'Successful', response.message
+ end
+
+ def test_realex_verify_declined
+ response = @gateway.verify(@visa_declined,
+ :order_id => generate_unique_id,
+ :description => 'Test Realex verify declined'
+ )
+
+ assert_not_nil response
+ assert_failure response
+ assert response.test?
+ assert_equal '101', response.params['result']
+ assert_match %r{DECLINED}i, response.message
+ end
+
+ def test_successful_credit
+ gateway_with_refund_password = RealexGateway.new(fixtures(:realex).merge(:refund_secret => 'refund'))
+
+ credit_response = gateway_with_refund_password.credit(@amount, @visa,
+ :order_id => generate_unique_id,
+ :description => 'Test Realex Credit',
+ :billing_address => {
+ :zip => '90210',
+ :country => 'US'
+ }
+ )
+
+ assert_not_nil credit_response
+ assert_success credit_response
+ assert credit_response.authorization.length > 0
+ assert_equal 'Successful', credit_response.message
+ end
+
+ def test_failed_credit
+ credit_response = @gateway.credit(@amount, @visa,
+ :order_id => generate_unique_id,
+ :description => 'Test Realex Credit',
+ :billing_address => {
+ :zip => '90210',
+ :country => 'US'
+ }
+ )
+
+ assert_not_nil credit_response
+ assert_failure credit_response
+ assert credit_response.authorization.length > 0
+ assert_equal 'Refund Hash not present.', credit_response.message
+ end
+
+ def test_maps_avs_and_cvv_response_codes
+ [ @visa, @mastercard ].each do |card|
+ response = @gateway.purchase(@amount, card,
+ :order_id => generate_unique_id,
+ :description => 'Test Realex Purchase',
+ :billing_address => {
+ :zip => '90210',
+ :country => 'US'
+ }
+ )
+ assert_not_nil response
+ assert_success response
+ assert_equal 'M', response.avs_result['code']
+ assert_equal 'M', response.cvv_result['code']
+ end
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @visa_declined,
+ :order_id => generate_unique_id,
+ :description => 'Test Realex Purchase',
+ :billing_address => {
+ :zip => '90210',
+ :country => 'US'
+ }
+ )
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@visa_declined.number, clean_transcript)
+ assert_scrubbed(@visa_declined.verification_value.to_s, clean_transcript)
+ end
end
diff --git a/test/remote/gateways/remote_redsys_sha256_test.rb b/test/remote/gateways/remote_redsys_sha256_test.rb
new file mode 100644
index 00000000000..43718a5f67a
--- /dev/null
+++ b/test/remote/gateways/remote_redsys_sha256_test.rb
@@ -0,0 +1,186 @@
+require 'test_helper'
+
+class RemoteRedsysSHA256Test < Test::Unit::TestCase
+ def setup
+ @gateway = RedsysGateway.new(fixtures(:redsys_sha256))
+ @credit_card = credit_card('4548812049400004')
+ @declined_card = credit_card
+ @options = {
+ order_id: generate_order_id,
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(100, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_purchase_with_invalid_order_id
+ response = @gateway.purchase(100, @credit_card, order_id: "a%4#{generate_order_id}")
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_successful_purchase_using_vault_id
+ response = @gateway.purchase(100, @credit_card, @options.merge(store: true))
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+
+ credit_card_token = response.params['ds_merchant_identifier']
+ assert_not_nil credit_card_token
+
+ @options[:order_id] = generate_order_id
+ response = @gateway.purchase(100, credit_card_token, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(100, @declined_card, @options)
+ assert_failure response
+ assert_equal 'SIS0093 ERROR', response.message
+ end
+
+ def test_purchase_and_refund
+ purchase = @gateway.purchase(100, @credit_card, @options)
+ assert_success purchase
+ refund = @gateway.refund(100, purchase.authorization)
+ assert_success refund
+ end
+
+ # Multiple currencies are not supported in test, but should at least fail.
+ def test_purchase_and_refund_with_currency
+ response = @gateway.purchase(600, @credit_card, @options.merge(:currency => 'PEN'))
+ assert_failure response
+ assert_equal 'SIS0027 ERROR', response.message
+ end
+
+ def test_successful_authorise_and_capture
+ authorize = @gateway.authorize(100, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ capture = @gateway.capture(100, authorize.authorization)
+ assert_success capture
+ assert_match(/Refund.*approved/, capture.message)
+ end
+
+ def test_successful_authorise_using_vault_id
+ authorize = @gateway.authorize(100, @credit_card, @options.merge(store: true))
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ credit_card_token = authorize.params['ds_merchant_identifier']
+ assert_not_nil credit_card_token
+
+ @options[:order_id] = generate_order_id
+ authorize = @gateway.authorize(100, credit_card_token, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(100, @declined_card, @options)
+ assert_failure response
+ assert_equal 'SIS0093 ERROR', response.message
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(100, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+ assert_equal '100', void.params['ds_amount']
+ assert_equal 'Cancellation Accepted', void.message
+ end
+
+ def test_failed_void
+ authorize = @gateway.authorize(100, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+
+ another_void = @gateway.void(authorize.authorization)
+ assert_failure another_void
+ assert_equal 'SIS0222 ERROR', another_void.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Transaction Approved', response.message
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'Cancellation Accepted', response.responses.last.message
+ end
+
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'SIS0093 ERROR', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_transcript_scrubbing_on_failed_transactions
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @declined_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_nil_cvv_transcript_scrubbing
+ @credit_card.verification_value = nil
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('[BLANK]'), true
+ end
+
+ def test_empty_string_cvv_transcript_scrubbing
+ @credit_card.verification_value = ''
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('[BLANK]'), true
+ end
+
+ def test_whitespace_string_cvv_transcript_scrubbing
+ @credit_card.verification_value = ' '
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('[BLANK]'), true
+ end
+
+ private
+
+ def generate_order_id
+ (Time.now.to_f * 100).to_i.to_s
+ end
+end
diff --git a/test/remote/gateways/remote_redsys_test.rb b/test/remote/gateways/remote_redsys_test.rb
index 4c5afbd5883..cfaf52723b2 100644
--- a/test/remote/gateways/remote_redsys_test.rb
+++ b/test/remote/gateways/remote_redsys_test.rb
@@ -5,54 +5,179 @@ def setup
@gateway = RedsysGateway.new(fixtures(:redsys))
@credit_card = credit_card('4548812049400004')
@declined_card = credit_card
+ @options = {
+ order_id: generate_order_id,
+ description: 'Test Description'
+ }
+ @amount = 100
end
def test_successful_purchase
- order_id = generate_order_id
- result = @gateway.purchase(100, @credit_card, :order_id => order_id)
- assert_success result
- assert_equal "#{order_id}|100|978", result.authorization
+ response = @gateway.purchase(100, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_purchase_with_invalid_order_id
+ response = @gateway.purchase(100, @credit_card, order_id: "a%4#{generate_order_id}")
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+ end
+
+ def test_successful_purchase_using_vault_id
+ response = @gateway.purchase(100, @credit_card, @options.merge(store: true))
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
+
+ credit_card_token = response.params['ds_merchant_identifier']
+ assert_not_nil credit_card_token
+
+ @options[:order_id] = generate_order_id
+ response = @gateway.purchase(100, credit_card_token, @options)
+ assert_success response
+ assert_equal 'Transaction Approved', response.message
end
def test_failed_purchase
- order_id = generate_order_id
- result = @gateway.purchase(100, @declined_card, :order_id => order_id)
- assert_failure result
- assert_nil result.authorization
+ response = @gateway.purchase(100, @declined_card, @options)
+ assert_failure response
+ assert_equal 'SIS0093 ERROR', response.message
end
def test_purchase_and_refund
- order_id = generate_order_id
- result = @gateway.purchase(100, @credit_card, :order_id => order_id)
- assert_success result
- result = @gateway.refund(100, order_id)
- assert_success result
+ purchase = @gateway.purchase(100, @credit_card, @options)
+ assert_success purchase
+ refund = @gateway.refund(100, purchase.authorization)
+ assert_success refund
end
# Multiple currencies are not supported in test, but should at least fail.
def test_purchase_and_refund_with_currency
- order_id = generate_order_id
- result = @gateway.purchase(600, @credit_card, :order_id => order_id, :currency => 'PEN')
- assert_failure result
- assert_equal "SIS0027 ERROR", result.message
- end
-
- def test_authorise_and_capture
- order_id = generate_order_id
- result = @gateway.authorize(100, @credit_card, :order_id => order_id)
- assert_success result
- assert_equal "#{order_id}|100|978", result.authorization
- result = @gateway.capture(100, order_id)
- assert_success result
- end
-
- def test_authorise_and_void
- order_id = generate_order_id
- result = @gateway.authorize(100, @credit_card, :order_id => order_id)
- assert_success result
- result = @gateway.void(result.authorization)
- assert_success result
- assert_equal "100", result.params["ds_amount"]
+ response = @gateway.purchase(600, @credit_card, @options.merge(:currency => 'PEN'))
+ assert_failure response
+ assert_equal 'SIS0027 ERROR', response.message
+ end
+
+ def test_successful_authorise_and_capture
+ authorize = @gateway.authorize(100, @credit_card, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ capture = @gateway.capture(100, authorize.authorization)
+ assert_success capture
+ assert_match(/Refund.*approved/, capture.message)
+ end
+
+ def test_successful_authorise_using_vault_id
+ authorize = @gateway.authorize(100, @credit_card, @options.merge(store: true))
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+
+ credit_card_token = authorize.params['ds_merchant_identifier']
+ assert_not_nil credit_card_token
+
+ @options[:order_id] = generate_order_id
+ authorize = @gateway.authorize(100, credit_card_token, @options)
+ assert_success authorize
+ assert_equal 'Transaction Approved', authorize.message
+ assert_not_nil authorize.authorization
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(100, @declined_card, @options)
+ assert_failure response
+ assert_equal 'SIS0093 ERROR', response.message
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(100, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+ assert_equal '100', void.params['ds_amount']
+ assert_equal 'Cancellation Accepted', void.message
+ end
+
+ def test_failed_void
+ authorize = @gateway.authorize(100, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+
+ another_void = @gateway.void(authorize.authorization)
+ assert_failure another_void
+ assert_equal 'SIS0222 ERROR', another_void.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Transaction Approved', response.message
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'Cancellation Accepted', response.responses.last.message
+ end
+
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'SIS0093 ERROR', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_transcript_scrubbing_on_failed_transactions
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @declined_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@gateway.options[:secret_key], clean_transcript)
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_nil_cvv_transcript_scrubbing
+ @credit_card.verification_value = nil
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('[BLANK]'), true
+ end
+
+ def test_empty_string_cvv_transcript_scrubbing
+ @credit_card.verification_value = ''
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('[BLANK]'), true
+ end
+
+ def test_whitespace_string_cvv_transcript_scrubbing
+ @credit_card.verification_value = ' '
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_equal clean_transcript.include?('[BLANK]'), true
end
private
diff --git a/test/remote/gateways/remote_s5_test.rb b/test/remote/gateways/remote_s5_test.rb
new file mode 100644
index 00000000000..1c9f31bf0ab
--- /dev/null
+++ b/test/remote/gateways/remote_s5_test.rb
@@ -0,0 +1,181 @@
+require 'test_helper'
+
+class RemoteS5Test < Test::Unit::TestCase
+ def setup
+ @gateway = S5Gateway.new(fixtures(:s5))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224')
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ def test_successful_purchase_sans_cvv
+ @options[:recurring] = true
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ def test_successful_purchase_with_utf_character
+ card = credit_card('4000100011112224', last_name: 'Wåhlin')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ def test_successful_purchase_without_address
+ response = @gateway.purchase(@amount, @credit_card, {})
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ def test_failed_purchase
+ @options[:memo] = '800.100.151'
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'transaction declined (invalid card)', response.message
+ end
+
+ def test_failed_purchase_sans_cvv
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{empty CVV .* not allowed}, response.message
+ end
+
+ def test_successful_authorize_without_address
+ auth = @gateway.authorize(@amount, @credit_card, {})
+ assert_success auth
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ @options[:memo] = '100.400.080'
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(100, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ def test_failed_verify
+ @options[:memo] = '100.400.080'
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{authorization failure}, response.message
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ def test_purchase_using_stored_card
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_match %r{Request successfully processed}, response.message
+ end
+
+ def test_failed_store
+ credit_card = credit_card('4111')
+ response = @gateway.store(credit_card, @options)
+ assert_failure response
+ assert_match %r{invalid creditcard}, response.message
+ end
+
+ def test_invalid_login
+ gateway = S5Gateway.new(
+ sender: '',
+ channel: '',
+ login: '',
+ password: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_safe_charge_test.rb b/test/remote/gateways/remote_safe_charge_test.rb
new file mode 100644
index 00000000000..fc64bacb1c1
--- /dev/null
+++ b/test/remote/gateways/remote_safe_charge_test.rb
@@ -0,0 +1,234 @@
+require 'test_helper'
+
+class RemoteSafeChargeTest < Test::Unit::TestCase
+ def setup
+ @gateway = SafeChargeGateway.new(fixtures(:safe_charge))
+
+ @amount = 100
+ @credit_card = credit_card('4000100011112224', verification_value: '912')
+ @declined_card = credit_card('4000300011112220')
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase',
+ currency: 'EUR'
+ }
+
+ @three_ds_options = @options.merge(three_d_secure: true)
+ @three_ds_gateway = SafeChargeGateway.new(fixtures(:safe_charge_three_ds))
+ @three_ds_enrolled_card = credit_card('4012 0010 3749 0014')
+ @three_ds_non_enrolled_card = credit_card('5333 3062 3122 6927')
+ @three_ds_invalid_pa_res_card = credit_card('4012 0010 3749 0006')
+ end
+
+ def test_successful_3ds_purchase
+ response = @three_ds_gateway.purchase(@amount, @three_ds_enrolled_card, @three_ds_options)
+ assert_success response
+ assert !response.params['acsurl'].blank?
+ assert !response.params['pareq'].blank?
+ assert !response.params['xid'].blank?
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_regular_purchase_through_3ds_flow_with_non_enrolled_card
+ response = @three_ds_gateway.purchase(@amount, @three_ds_non_enrolled_card, @three_ds_options)
+ assert_success response
+ assert response.params['acsurl'].blank?
+ assert response.params['pareq'].blank?
+ assert response.params['xid'].blank?
+ assert response.params['threedflow'] = 1
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_regular_purchase_through_3ds_flow_with_invalid_pa_res
+ response = @three_ds_gateway.purchase(@amount, @three_ds_invalid_pa_res_card, @three_ds_options)
+ assert_success response
+ assert !response.params['acsurl'].blank?
+ assert !response.params['pareq'].blank?
+ assert !response.params['xid'].blank?
+ assert response.params['threedflow'] = 1
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ user_id: '123',
+ auth_type: '2',
+ expected_fulfillment_count: '3',
+ merchant_descriptor: 'Test Descriptor',
+ merchant_phone_number: '(555)555-5555',
+ merchant_name: 'Test Merchant',
+ stored_credential_mode: true
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Decline', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', capture.message
+ end
+
+ def test_successful_authorize_and_capture_with_more_options
+ extra = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ user_id: '123',
+ auth_type: '2',
+ expected_fulfillment_count: '3',
+ merchant_descriptor: 'Test Descriptor',
+ merchant_phone_number: '(555)555-5555',
+ merchant_name: 'Test Merchant',
+ stored_credential_mode: true
+ }
+ auth = @gateway.authorize(@amount, @credit_card, extra)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'Success', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Decline', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Transaction must contain a Card/Token/Account', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Success', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(200, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(100, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '')
+ assert_failure response
+ assert_equal 'Transaction must contain a Card/Token/Account', response.message
+ end
+
+ def test_successful_credit
+ response = @gateway.credit(@amount, credit_card('4444436501403986'), @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_credit_with_extra_options
+ extra = {
+ order_id: '1',
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ user_id: '123',
+ auth_type: '2',
+ expected_fulfillment_count: '3',
+ merchant_descriptor: 'Test Descriptor',
+ merchant_phone_number: '(555)555-5555',
+ merchant_name: 'Test Merchant',
+ stored_credential_mode: true
+ }
+
+ response = @gateway.credit(@amount, credit_card('4444436501403986'), extra)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_credit
+ response = @gateway.credit(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Decline', response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'Success', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Invalid Amount', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match 'Success', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match 'Decline', response.message
+ end
+
+ def test_invalid_login
+ gateway = SafeChargeGateway.new(client_login_id: '', client_password: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Invalid login', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:client_password], transcript)
+ end
+
+end
diff --git a/test/remote/gateways/remote_sage_bankcard_test.rb b/test/remote/gateways/remote_sage_bankcard_test.rb
deleted file mode 100644
index 1a37aaaaba0..00000000000
--- a/test/remote/gateways/remote_sage_bankcard_test.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-require 'test_helper'
-
-class RemoteSageBankcardTest < Test::Unit::TestCase
-
- def setup
- @gateway = SageBankcardGateway.new(fixtures(:sage))
-
- @amount = 100
-
- @visa = credit_card("4111111111111111")
- @mastercard = credit_card("5499740000000057")
- @discover = credit_card("6011000993026909")
- @amex = credit_card("371449635392376")
-
- @options = {
- :order_id => generate_unique_id,
- :billing_address => address,
- :shipping_address => address,
- :email => 'longbob@example.com'
- }
- end
-
- def test_successful_visa_purchase
- assert response = @gateway.purchase(@amount, @visa, @options)
- assert_success response
- assert response.test?
- assert_false response.authorization.blank?
- end
-
- def test_successful_visa_authorization
- assert response = @gateway.authorize(@amount, @visa, @options)
- assert_success response
- assert response.test?
- assert_false response.authorization.blank?
- end
-
- def test_declined_visa_purchase
- @amount = 200
-
- assert response = @gateway.purchase(@amount, @visa, @options)
- assert_failure response
- assert response.test?
- end
-
- def test_successful_mastercard_purchase
- assert response = @gateway.purchase(@amount, @mastercard, @options)
- assert_success response
- assert response.test?
- assert_false response.authorization.blank?
- end
-
- def test_successful_discover_purchase
- assert response = @gateway.purchase(@amount, @discover, @options)
- assert_success response
- assert response.test?
- assert_false response.authorization.blank?
- end
-
- def test_successful_amex_purchase
- assert response = @gateway.purchase(@amount, @amex, @options)
- assert_success response
- assert response.test?
- assert_false response.authorization.blank?
- end
-
- def test_authorization_and_capture
- assert auth = @gateway.authorize(@amount, @visa, @options)
- assert_success auth
-
- assert capture = @gateway.capture(@amount, auth.authorization)
- assert_success capture
- end
-
- def test_failed_capture
- assert response = @gateway.capture(@amount, '')
- assert_failure response
- assert_equal 'INVALID T_REFERENCE', response.message
- end
-
- def test_authorization_and_void
- assert auth = @gateway.authorize(@amount, @visa, @options)
- assert_success auth
-
- assert void = @gateway.void(auth.authorization)
- assert_success void
- end
-
- def test_failed_void
- assert response = @gateway.void('')
- assert_failure response
- assert_equal 'INVALID T_REFERENCE', response.message
- end
-
- def test_successful_refund
- assert response = @gateway.refund(@amount, @visa, @options)
- assert_success response
- assert response.test?
- end
-
- def test_invalid_login
- gateway = SageBankcardGateway.new(
- :login => '',
- :password => ''
- )
- assert response = gateway.purchase(@amount, @visa, @options)
- assert_failure response
- assert_equal 'SECURITY VIOLATION', response.message
- end
-end
diff --git a/test/remote/gateways/remote_sage_pay_test.rb b/test/remote/gateways/remote_sage_pay_test.rb
index 369b02e3c2b..c7647d53969 100644
--- a/test/remote/gateways/remote_sage_pay_test.rb
+++ b/test/remote/gateways/remote_sage_pay_test.rb
@@ -2,15 +2,15 @@
# Some of the standard tests have been removed at SagePay test
# server is pants and accepts anything and says Status=OK. (shift)
-# The tests for American Express will only pass if your account is
+# The tests for American Express will only pass if your account is
# American express enabled.
class RemoteSagePayTest < Test::Unit::TestCase
# set to true to run the tests in the simulated environment
SagePayGateway.simulate = false
-
+
def setup
@gateway = SagePayGateway.new(fixtures(:sage_pay))
-
+
@amex = CreditCard.new(
:number => '374200000000004',
:month => 12,
@@ -44,19 +44,6 @@ def setup
:brand => 'visa'
)
- @solo = CreditCard.new(
- :number => '6334900000000005',
- :month => 6,
- :year => next_year,
- :issue_number => 1,
- :start_month => 12,
- :start_year => next_year - 2,
- :verification_value => 227,
- :first_name => 'Tekin',
- :last_name => 'Suleyman',
- :brand => 'solo'
- )
-
@mastercard = CreditCard.new(
:number => '5404000000000001',
:month => 12,
@@ -66,7 +53,7 @@ def setup
:last_name => 'Suleyman',
:brand => 'master'
)
-
+
@electron = CreditCard.new(
:number => '4917300000000008',
:month => 12,
@@ -86,20 +73,20 @@ def setup
:brand => 'visa'
)
- @options = {
- :billing_address => {
+ @options = {
+ :billing_address => {
:name => 'Tekin Suleyman',
:address1 => 'Flat 10 Lapwing Court',
:address2 => 'West Didsbury',
- :city => "Manchester",
+ :city => 'Manchester',
:county => 'Greater Manchester',
:country => 'GB',
:zip => 'M20 2PS'
},
- :shipping_address => {
+ :shipping_address => {
:name => 'Tekin Suleyman',
:address1 => '120 Grosvenor St',
- :city => "Manchester",
+ :city => 'Manchester',
:county => 'Greater Manchester',
:country => 'GB',
:zip => 'M1 7QW'
@@ -110,61 +97,75 @@ def setup
:email => 'tekin@tekin.co.uk',
:phone => '0161 123 4567'
}
-
+
@amount = 100
end
def test_successful_mastercard_purchase
assert response = @gateway.purchase(@amount, @mastercard, @options)
assert_success response
-
+
assert response.test?
assert !response.authorization.blank?
end
-
+
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
-
+
assert response.test?
end
-
+
def test_successful_authorization_and_capture
assert auth = @gateway.authorize(@amount, @mastercard, @options)
assert_success auth
-
+
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
end
-
+
+ def test_successful_authorization_and_capture_and_refund
+ assert auth = @gateway.authorize(@amount, @mastercard, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+
+ assert refund = @gateway.refund(@amount, capture.authorization,
+ :description => 'Crediting trx',
+ :order_id => generate_unique_id
+ )
+ assert_success refund
+ end
+
def test_successful_authorization_and_void
assert auth = @gateway.authorize(@amount, @mastercard, @options)
- assert_success auth
-
+ assert_success auth
+
assert abort = @gateway.void(auth.authorization)
assert_success abort
end
-
+
def test_successful_purchase_and_void
assert purchase = @gateway.purchase(@amount, @mastercard, @options)
- assert_success purchase
-
+ assert_success purchase
+
assert void = @gateway.void(purchase.authorization)
assert_success void
end
-
- def test_successful_purchase_and_credit
+
+ def test_successful_purchase_and_refund
assert purchase = @gateway.purchase(@amount, @mastercard, @options)
- assert_success purchase
-
- assert credit = @gateway.credit(@amount, purchase.authorization,
- :description => 'Crediting trx',
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization,
+ :description => 'Crediting trx',
:order_id => generate_unique_id
)
-
- assert_success credit
+
+ assert_success refund
end
-
+
def test_successful_visa_purchase
assert response = @gateway.purchase(@amount, @visa, @options)
assert_success response
@@ -172,48 +173,297 @@ def test_successful_visa_purchase
assert !response.authorization.blank?
end
- def test_successful_maestro_purchase
- assert response = @gateway.purchase(@amount, @maestro, @options)
+ def test_successful_amex_purchase
+ assert response = @gateway.purchase(@amount, @amex, @options)
assert_success response
assert response.test?
assert !response.authorization.blank?
end
- def test_successful_solo_purchase
- assert response = @gateway.purchase(@amount, @solo, @options)
+ def test_successful_electron_purchase
+ assert response = @gateway.purchase(@amount, @electron, @options)
assert_success response
assert response.test?
assert !response.authorization.blank?
end
-
- def test_successful_amex_purchase
- assert response = @gateway.purchase(@amount, @amex, @options)
+
+ def test_successful_purchase_with_overly_long_fields
+ options = {
+ description: 'SagePay transactions fail if the description is more than 100 characters. Therefore, we truncate it to 100 characters.',
+ order_id: "#{generate_unique_id} SagePay order_id cannot be more than 40 characters.",
+ billing_address: {
+ name: 'FirstNameCannotBeMoreThanTwentyChars SurnameCannotBeMoreThanTwenty',
+ address1: 'The Billing Address 1 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.',
+ address2: 'The Billing Address 2 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.',
+ phone: '111222333444555666777888999',
+ city: 'TheCityCannotBeMoreThanFortyCharactersReally',
+ state: 'NCStateIsTwoChars',
+ country: 'USMustBeTwoChars',
+ zip: 'PostalCodeCannotExceedTenChars'
+ },
+ shipping_address: {
+ name: 'FirstNameCannotBeMoreThanTwentyChars SurnameCannotBeMoreThanTwenty',
+ address1: 'The Shipping Address 1 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.',
+ address2: 'The Shipping Address 2 Cannot Be More Than One Hundred Characters if it is it will fail. Therefore, we truncate it.',
+ phone: '111222333444555666777888999',
+ city: 'TheCityCannotBeMoreThanFortyCharactersReally',
+ state: 'NCStateIsTwoChars',
+ country: 'USMustBeTwoChars',
+ zip: 'PostalCodeCannotExceedTenChars'
+ }
+ }
+
+ @visa.first_name = 'FullNameOnACardMustBeLessThanFiftyCharacters'
+ @visa.last_name = 'OtherwiseSagePayFailsIt'
+
+ assert response = @gateway.purchase(@amount, @visa, options)
assert_success response
- assert response.test?
- assert !response.authorization.blank?
end
-
- def test_successful_electron_purchase
- assert response = @gateway.purchase(@amount, @electron, @options)
+
+ def test_successful_mastercard_purchase_with_optional_FIxxxx_fields
+ @options[:recipient_account_number] = '1234567890'
+ @options[:recipient_surname] = 'Withnail'
+ @options[:recipient_postcode] = 'AB11AB'
+ @options[:recipient_dob] = '19701223'
+ assert response = @gateway.purchase(@amount, @mastercard, @options)
assert_success response
+
assert response.test?
assert !response.authorization.blank?
end
-
+
+ def test_successful_purchase_with_apply_avscv2_field
+ @options[:apply_avscv2] = 1
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ assert_equal 'Y', response.cvv_result['code']
+ end
+
+ def test_successful_purchase_with_pay_pal_callback_url
+ @options[:paypal_callback_urll] = 'callback.com'
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_basket
+ # Example from "Sage Pay Direct Integration and Protocol Guidelines 3.00"
+ # Published: 27/08/2015
+ @options[:basket] = '4:Pioneer NSDV99 DVD-Surround Sound System:1:424.68:' \
+ '74.32:499.00: 499.00:Donnie Darko Director’s Cut:3:11.91:2.08:13.99:' \
+ '41.97: Finding Nemo:2:11.05:1.94:12.99:25.98: Delivery:---:---:---:---' \
+ ':4.99'
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_gift_aid_payment
+ @options[:gift_aid_payment] = 1
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_transaction_registration_with_apply_3d_secure
+ @options[:apply_3d_secure] = 1
+ response = @gateway.purchase(@amount, @visa, @options)
+ # We receive a different type of response for 3D Secure requiring to
+ # redirect the user to the ACSURL given inside the response
+ assert response.params.include?('ACSURL')
+ assert_equal 'OK', response.params['3DSecureStatus']
+ assert_equal '3DAUTH', response.params['Status']
+ end
+
+ def test_successful_purchase_with_account_type
+ @options[:account_type] = 'E'
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_billing_agreement
+ @options[:billing_agreement] = 1
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_basket_xml
+ @options[:basket_xml] = basket_xml
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_customer_xml
+ @options[:customer_xml] = customer_xml
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_surcharge_xml
+ @options[:surcharge_xml] = surcharge_xml
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_vendor_data
+ @options[:vendor_data] = 'Data displayed against the transaction in MySagePay'
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_language
+ @options[:language] = 'FR'
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_website
+ @options[:website] = 'origin-of-transaction.com'
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ end
+
+ def test_successful_repeat_purchase
+ response = @gateway.purchase(@amount, @visa, @options)
+ assert_success response
+ repeat = @gateway.purchase(@amount, response.authorization, @options.merge(order_id: generate_unique_id))
+ assert_success repeat
+ end
+
def test_invalid_login
- message = SagePayGateway.simulate ? 'VSP Simulator cannot find your vendor name. Ensure you have have supplied a Vendor field with your VSP Vendor name assigned to it.' : '3034 : The Vendor or VendorName value is required.'
-
+ message = SagePayGateway.simulate ? 'VSP Simulator cannot find your vendor name. Ensure you have have supplied a Vendor field with your VSP Vendor name assigned to it.' : '3034 : The Vendor or VendorName value is required.'
+
gateway = SagePayGateway.new(
- :login => ''
+ :login => ''
)
assert response = gateway.purchase(@amount, @mastercard, @options)
assert_equal message, response.message
assert_failure response
end
-
+
+ def test_successful_store_and_purchace
+ assert response = @gateway.store(@visa)
+ assert_success response
+ assert !response.authorization.blank?
+ assert purchase = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success purchase
+ end
+
+ def test_successful_store_and_repurchase_with_resupplied_verification_value
+ assert response = @gateway.store(@visa)
+ assert_success response
+ assert !response.authorization.blank?
+ assert @gateway.purchase(@amount, response.authorization, @options.merge(customer: 1))
+ assert purchase = @gateway.purchase(@amount, response.authorization, @options.merge(verification_value: '123', order_id: generate_unique_id))
+ assert_success purchase
+ end
+
+ def test_successful_store_and_authorize
+ assert response = @gateway.store(@visa)
+ assert_success response
+ assert !response.authorization.blank?
+ assert authorize = @gateway.authorize(@amount, response.authorization, @options)
+ assert_success authorize
+ end
+
+ def test_successful_token_creation_from_purchase
+ assert response = @gateway.purchase(@amount, @visa, @options.merge(:store => true))
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_token_creation_from_authorize
+ assert response = @gateway.authorize(@amount, @visa, @options.merge(:store => true))
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_unstore
+ assert response = @gateway.store(@visa)
+ assert_success response
+ assert !response.authorization.blank?
+ assert unstore = @gateway.unstore(response.authorization)
+ assert_success unstore
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@visa, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match(/Card Range not supported/, response.message)
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @visa, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@visa.number, clean_transcript)
+ assert_scrubbed(@visa.verification_value.to_s, clean_transcript)
+ end
+
private
def next_year
Date.today.year + 1
end
+
+ # Based on example from http://www.sagepay.co.uk/support/basket-xml
+ # Only kept required fields to make sense
+ def basket_xml
+ <<-XML
+
+
+ DVD 1
+ 2
+ 24.50
+ 00.50
+ 25.00
+ 50.00
+
+
+ XML
+ end
+
+ # Example from http://www.sagepay.co.uk/support/customer-xml
+ def customer_xml
+ <<-XML
+
+ W
+ 1983-01-01
+ 020 1234567
+ 0799 1234567
+ 0
+ 10
+ CUST123
+
+ XML
+ end
+
+ # Example from https://www.sagepay.co.uk/support/12/36/protocol-3-00-surcharge-xml
+ def surcharge_xml
+ <<-XML
+
+
+ DELTA
+ 2.50
+
+
+ VISA
+ 2.50
+
+
+ AMEX
+ 1.50
+
+
+ MC
+ 1.50
+
+
+ XML
+ end
end
diff --git a/test/remote/gateways/remote_sage_test.rb b/test/remote/gateways/remote_sage_test.rb
index dd106096d94..5107a12e97d 100644
--- a/test/remote/gateways/remote_sage_test.rb
+++ b/test/remote/gateways/remote_sage_test.rb
@@ -4,77 +4,192 @@ class RemoteSageTest < Test::Unit::TestCase
def setup
@gateway = SageGateway.new(fixtures(:sage))
-
+
@amount = 100
-
- @visa = credit_card("4111111111111111")
+
+ @visa = credit_card('4111111111111111')
@check = check
-
- @options = {
+ @mastercard = credit_card('5499740000000057')
+ @discover = credit_card('6011000993026909')
+ @amex = credit_card('371449635392376')
+
+ @declined_card = credit_card('4000')
+
+ @options = {
:order_id => generate_unique_id,
:billing_address => address,
:shipping_address => address,
:email => 'longbob@example.com'
}
end
-
+
def test_successful_visa_purchase
assert response = @gateway.purchase(@amount, @visa, @options)
assert_success response
assert response.test?
assert_false response.authorization.blank?
end
-
+
+ def test_declined_visa_purchase
+ @amount = 200
+
+ assert response = @gateway.purchase(@amount, @visa, @options)
+ assert_failure response
+ assert response.test?
+ end
+
+ def test_successful_mastercard_purchase
+ assert response = @gateway.purchase(@amount, @mastercard, @options)
+ assert_success response
+ assert response.test?
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_discover_purchase
+ assert response = @gateway.purchase(@amount, @discover, @options)
+ assert_success response
+ assert response.test?
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_amex_purchase
+ assert response = @gateway.purchase(@amount, @amex, @options)
+ assert_success response
+ assert response.test?
+ assert_false response.authorization.blank?
+ end
+
def test_successful_check_purchase
assert response = @gateway.purchase(@amount, @check, @options)
assert_success response
assert response.test?
assert_false response.authorization.blank?
end
-
+
def test_successful_visa_authorization
assert response = @gateway.authorize(@amount, @visa, @options)
assert_success response
assert response.test?
assert_false response.authorization.blank?
end
-
+
+ def test_successful_with_minimal_options
+ assert response = @gateway.purchase(@amount, @visa, billing_address: address)
+ assert_success response
+ assert response.test?
+ assert_false response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_blank_state
+ assert response = @gateway.purchase(@amount, @visa, billing_address: address(state: ''))
+ assert_success response
+ assert response.test?
+ assert_false response.authorization.blank?
+ end
+
def test_authorization_and_capture
assert auth = @gateway.authorize(@amount, @visa, @options)
assert_success auth
-
+
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
end
-
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'INVALID T_REFERENCE', response.message
+ end
+
def test_visa_authorization_and_void
assert auth = @gateway.authorize(@amount, @visa, @options)
assert_success auth
-
+
assert void = @gateway.void(auth.authorization)
assert_success void
end
-
+
+ def test_failed_void
+ assert response = @gateway.void('')
+ assert_failure response
+ assert_equal 'INVALID T_REFERENCE', response.message
+ end
+
def test_check_purchase_and_void
assert purchase = @gateway.purchase(@amount, @check, @options)
assert_success purchase
-
+
assert void = @gateway.void(purchase.authorization)
assert_success void
end
- def test_visa_refund
- assert response = @gateway.refund(@amount, @visa, @options)
+ def test_visa_credit
+ assert response = @gateway.credit(@amount, @visa, @options)
assert_success response
assert response.test?
end
-
- def test_check_refund
- assert response = @gateway.refund(@amount, @check, @options)
+
+ def test_check_credit
+ assert response = @gateway.credit(@amount, @check, @options)
assert_success response
assert response.test?
end
-
+
+ def test_visa_refund
+ purchase = @gateway.purchase(@amount, @visa, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'APPROVED', refund.message
+ end
+
+ def test_visa_failed_refund
+ purchase = @gateway.purchase(@amount, @visa, @options)
+ assert_success purchase
+
+ response = @gateway.refund(@amount, 'UnknownReference', @options)
+ assert_failure response
+ assert_equal 'INVALID T_REFERENCE', response.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @visa, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization, @options)
+ assert_success refund
+ assert_equal 'APPROVED', refund.message
+ end
+
+ def test_store_visa
+ assert response = @gateway.store(@visa, @options)
+ assert_success response
+ assert response.authorization,
+ 'Store card authorization should not be nil'
+ assert_not_nil response.message
+ end
+
+ def test_failed_store
+ assert response = @gateway.store(@declined_card, @options)
+ assert_failure response
+ assert_nil response.authorization
+ end
+
+ def test_unstore_visa
+ assert auth = @gateway.store(@visa, @options).authorization,
+ 'Unstore card authorization should not be nil'
+ assert response = @gateway.unstore(auth, @options)
+ assert_success response
+ end
+
+ def test_failed_unstore_visa
+ assert auth = @gateway.store(@visa, @options).authorization,
+ 'Unstore card authorization should not be nil'
+ assert response = @gateway.unstore(auth, @options)
+ assert_success response
+ end
+
def test_invalid_login
gateway = SageGateway.new(
:login => '',
@@ -84,4 +199,37 @@ def test_invalid_login
assert_failure response
assert_equal 'SECURITY VIOLATION', response.message
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @visa, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@visa.number, transcript)
+ assert_scrubbed(@visa.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_transcript_scrubbing_store
+ transcript = capture_transcript(@gateway) do
+ @gateway.store(@visa, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@visa.number, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_echeck_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
end
diff --git a/test/remote/gateways/remote_sage_virtual_check_test.rb b/test/remote/gateways/remote_sage_virtual_check_test.rb
deleted file mode 100644
index 934012b5c93..00000000000
--- a/test/remote/gateways/remote_sage_virtual_check_test.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'test_helper'
-
-class RemoteSageCheckTest < Test::Unit::TestCase
-
- def setup
- @gateway = SageVirtualCheckGateway.new(fixtures(:sage))
-
- @amount = 100
- @check = check
-
- @options = {
- :order_id => generate_unique_id,
- :billing_address => address,
- :shipping_address => address,
- :email => 'longbob@example.com',
- :drivers_license_state => 'CA',
- :drivers_license_number => '12345689',
- :date_of_birth => Date.new(1978, 8, 11),
- :ssn => '078051120'
- }
- end
-
- def test_successful_check_purchase
- assert response = @gateway.purchase(@amount, @check, @options)
- assert_success response
- assert response.test?
- assert_false response.authorization.blank?
- end
-
- def test_failed_check_purchase
- @check.routing_number = ""
-
- assert response = @gateway.purchase(@amount, @check, @options)
- assert_failure response
- assert response.test?
- assert_false response.authorization.blank?
- end
-
- def test_purchase_and_void
- assert purchase = @gateway.purchase(@amount, @check, @options)
- assert_success purchase
-
- assert void = @gateway.void(purchase.authorization)
- assert_success void
- end
-
- def test_credit
- assert response = @gateway.credit(@amount, @check, @options)
- assert_success response
- assert response.test?
- end
-
- def test_invalid_login
- gateway = SageVirtualCheckGateway.new(
- :login => '',
- :password => ''
- )
- assert response = gateway.purchase(@amount, @check, @options)
- assert_failure response
- assert_equal 'SECURITY VIOLATION', response.message
- end
-end
diff --git a/test/remote/gateways/remote_sallie_mae_test.rb b/test/remote/gateways/remote_sallie_mae_test.rb
index 09f14cb2337..f2b47acef22 100644
--- a/test/remote/gateways/remote_sallie_mae_test.rb
+++ b/test/remote/gateways/remote_sallie_mae_test.rb
@@ -3,12 +3,12 @@
class RemoteSallieMaeTest < Test::Unit::TestCase
def setup
@gateway = SallieMaeGateway.new(fixtures(:sallie_mae))
-
+
@amount = 100
@credit_card = credit_card('5454545454545454')
@declined_card = credit_card('4000300011112220')
-
- @options = {
+
+ @options = {
:billing_address => address,
:description => 'Store Purchase'
}
diff --git a/test/remote/gateways/remote_samurai_test.rb b/test/remote/gateways/remote_samurai_test.rb
deleted file mode 100644
index 3d049461852..00000000000
--- a/test/remote/gateways/remote_samurai_test.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-require 'test_helper'
-
-class RemoteSamuraiTest < Test::Unit::TestCase
-
- def setup
- @gateway = SamuraiGateway.new(fixtures(:samurai))
-
- @amount = 100
- @declined_amount = 102
- @invalid_card_amount = 107
- @expired_card_amount = 108
- @credit_card = credit_card('4111111111111111', :verification_value => '111')
- end
-
- def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card)
- assert_success response
- assert_equal 'OK', response.message
- end
-
- def test_declined_purchase
- assert response = @gateway.purchase(@declined_amount, @credit_card)
- assert_failure response
- assert_equal 'The card was declined.', response.message
- end
-
- def test_invalid_purchase
- assert response = @gateway.purchase(@invalid_card_amount, @credit_card)
- assert_failure response
- assert_equal 'The card number was invalid.', response.message
- end
-
- def test_expired_purchase
- assert response = @gateway.purchase(@expired_card_amount, @credit_card)
- assert_failure response
- assert_equal 'The expiration date month was invalid, or prior to today. The expiration date year was invalid, or prior to today.', response.message
- end
-
- def test_successful_auth_and_capture
- assert authorize = @gateway.authorize(@amount, @credit_card)
- assert_success authorize
- assert_equal 'OK', authorize.message
- assert capture = @gateway.capture(@amount, authorize.authorization)
- assert_success capture
- assert_equal 'OK', capture.message
- end
-
- def test_successful_auth_and_void
- assert_success authorize = @gateway.authorize(@amount, @credit_card)
- assert_success @gateway.void(authorize.authorization)
- end
-
- def test_successful_store_and_retain
- assert_success @gateway.store(@credit_card, :retain => true)
- end
-
- def test_invalid_login
- assert_raise(ArgumentError) do
- SamuraiGateway.new( :login => '', :password => '' )
- end
- end
-end
diff --git a/test/remote/gateways/remote_secure_net_test.rb b/test/remote/gateways/remote_secure_net_test.rb
index 4b82cb59ad8..fc92a5276e1 100644
--- a/test/remote/gateways/remote_secure_net_test.rb
+++ b/test/remote/gateways/remote_secure_net_test.rb
@@ -12,15 +12,15 @@ def setup
n = Time.now
order_id = n.to_i.to_s + n.usec.to_s
- @options = {
- :order_id => order_id,
- :billing_address => address,
- :description => 'Store Purchase'
+ @options = {
+ order_id: order_id,
+ billing_address: address,
+ description: 'Store Purchase'
}
end
def test_expired_credit_card
- @credit_card.year = 2004
+ @credit_card.year = 2004
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert response.test?
@@ -97,7 +97,7 @@ def test_unsuccessful_capture
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @bad_card_number, @options)
assert_failure response
- assert_equal "CARD TYPE COULD NOT BE IDENTIFIED", response.message
+ assert_equal 'CARD TYPE COULD NOT BE IDENTIFIED', response.message
end
def test_unsuccessful_purchase_and_credit
@@ -111,4 +111,29 @@ def test_unsuccessful_purchase_and_credit
assert_equal 'CREDIT CANNOT BE COMPLETED ON AN UNSETTLED TRANSACTION', refund.message
end
+ def test_invoice_description_and_number
+ options = @options.merge({
+ invoice_description: 'TheInvoiceDescriptions',
+ invoice_number: 'TheInvoiceNumber'
+ })
+
+ assert auth = @gateway.authorize(@amount, @credit_card, options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization, options)
+ assert_success capture
+ assert_equal 'Approved', capture.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
end
diff --git a/test/remote/gateways/remote_secure_pay_au_test.rb b/test/remote/gateways/remote_secure_pay_au_test.rb
index b3c2c771da0..fc99db2ef81 100644
--- a/test/remote/gateways/remote_secure_pay_au_test.rb
+++ b/test/remote/gateways/remote_secure_pay_au_test.rb
@@ -4,7 +4,6 @@ class RemoteSecurePayAuTest < Test::Unit::TestCase
class MyCreditCard
include ActiveMerchant::Billing::CreditCardMethods
- include ActiveMerchant::Validateable
attr_accessor :number, :month, :year, :first_name, :last_name, :verification_value, :brand
def verification_value?
@@ -131,7 +130,7 @@ def test_successful_unstore
end
def test_repeat_unstore
- @gateway.unstore('test1234') rescue nil #Ensure it is already missing
+ @gateway.unstore('test1234') rescue nil # Ensure it is already missing
response = @gateway.unstore('test1234')
@@ -148,7 +147,7 @@ def test_successful_store
end
def test_failed_store
- @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil #Ensure it already exists
+ @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil # Ensure it already exists
assert response = @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000})
assert_failure response
@@ -157,7 +156,7 @@ def test_failed_store
end
def test_successful_triggered_payment
- @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil #Ensure it already exists
+ @gateway.store(@credit_card, {:billing_id => 'test1234', :amount => 15000}) rescue nil # Ensure it already exists
assert response = @gateway.purchase(12300, 'test1234', @options)
assert_success response
@@ -167,7 +166,7 @@ def test_successful_triggered_payment
end
def test_failure_triggered_payment
- @gateway.unstore('test1234') rescue nil #Ensure its no longer there
+ @gateway.unstore('test1234') rescue nil # Ensure its no longer there
assert response = @gateway.purchase(12300, 'test1234', @options)
assert_failure response
@@ -182,6 +181,17 @@ def test_invalid_login
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "Invalid merchant ID", response.message
+ assert_equal 'Invalid merchant ID', response.message
+ end
+
+ def test_purchase_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(credit_card.number, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
end
end
diff --git a/test/remote/gateways/remote_secure_pay_tech_test.rb b/test/remote/gateways/remote_secure_pay_tech_test.rb
index 737d2d92280..3b76bf77eef 100644
--- a/test/remote/gateways/remote_secure_pay_tech_test.rb
+++ b/test/remote/gateways/remote_secure_pay_tech_test.rb
@@ -5,14 +5,14 @@ class RemoteSecurePayTechTest < Test::Unit::TestCase
def setup
@gateway = SecurePayTechGateway.new(fixtures(:secure_pay_tech))
-
+
@accepted_amount = 10000
@declined_amount = 10075
-
+
@credit_card = credit_card('4987654321098769', :month => '5', :year => '2013')
@options = { :billing_address => address }
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@accepted_amount, @credit_card, @options)
assert_equal 'Transaction OK', response.message
diff --git a/test/remote/gateways/remote_secure_pay_test.rb b/test/remote/gateways/remote_secure_pay_test.rb
index 5806fd0ab72..63f83f5e40b 100644
--- a/test/remote/gateways/remote_secure_pay_test.rb
+++ b/test/remote/gateways/remote_secure_pay_test.rb
@@ -1,7 +1,7 @@
require 'test_helper'
-class RemoteSecurePayTest < Test::Unit::TestCase
-
+class RemoteSecurePayTest < Test::Unit::TestCase
+
def setup
@gateway = SecurePayGateway.new(fixtures(:secure_pay))
@@ -9,15 +9,16 @@ def setup
:month => '7',
:year => '2014'
)
-
- @options = { :order_id => generate_unique_id,
+
+ @options = {
+ :order_id => generate_unique_id,
:description => 'Store purchase',
:billing_address => address
}
-
+
@amount = 100
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert response.success?
diff --git a/test/remote/gateways/remote_securion_pay_test.rb b/test/remote/gateways/remote_securion_pay_test.rb
new file mode 100644
index 00000000000..59b600899f1
--- /dev/null
+++ b/test/remote/gateways/remote_securion_pay_test.rb
@@ -0,0 +1,256 @@
+require 'test_helper'
+
+class RemoteSecurionPayTest < Test::Unit::TestCase
+ CHARGE_ID_REGEX = /char_[a-zA-Z\d]+/
+ TOKEN_ID_REGEX = /tok_[a-zA-Z\d]+/
+
+ def setup
+ @gateway = SecurionPayGateway.new(fixtures(:securion_pay))
+
+ @amount = 2000
+ @refund_amount = 300
+ @credit_card = credit_card('4242424242424242')
+ @declined_card = credit_card('4916018475814056')
+ @new_credit_card = credit_card('4012888888881881')
+ @invalid_token = 'tok_invalid'
+
+ @options = {
+ description: 'ActiveMerchant test charge',
+ email: 'foo@example.com'
+ }
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_match %r(^cust_\w+$), response.authorization
+ assert_equal 'customer', response.params['objectType']
+ assert_match %r(^card_\w+$), response.params['cards'][0]['id']
+ assert_equal 'card', response.params['cards'][0]['objectType']
+
+ @options[:customer_id] = response.authorization
+ response = @gateway.store(@new_credit_card, @options)
+ assert_success response
+ assert_match %r(^card_\w+$), response.params['card']['id']
+ assert_equal @options[:customer_id], response.params['card']['customerId']
+
+ response = @gateway.customer(@options)
+ assert_success response
+ assert_equal @options[:customer_id], response.params['id']
+ assert_equal '401288', response.params['cards'][0]['first6']
+ assert_equal '1881', response.params['cards'][0]['last4']
+ assert_equal '424242', response.params['cards'][1]['first6']
+ assert_equal '4242', response.params['cards'][1]['last4']
+ end
+
+ # def test_dump_transcript
+ # skip("Transcript scrubbing for this gateway has been tested.")
+ # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options)
+ # end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:secret_key], transcript)
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'foo@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_unsuccessful_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_match %r{The card was declined for other reason.}, response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+
+ def test_authorization_and_capture
+ authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert !authorization.params['captured']
+ assert_equal @options[:description], authorization.params['description']
+ assert_equal @options[:email], authorization.params['metadata']['email']
+
+ response = @gateway.capture(@amount, authorization.authorization)
+ assert_success response
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, 'invalid_authorization_token')
+ assert_failure response
+ assert_match %r{Requested Charge does not exist}, response.message
+ end
+
+ def test_successful_full_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert purchase.authorization
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+
+ assert refund.params['refunded']
+ assert_equal 0, refund.params['amount']
+ assert_equal 1, refund.params['refunds'].size
+ assert_equal @amount, refund.params['refunds'].map { |r| r['amount'] }.sum
+
+ assert refund.authorization
+ end
+
+ def test_successful_partially_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert purchase.authorization
+
+ first_refund = @gateway.refund(@refund_amount, purchase.authorization)
+ assert_success first_refund
+
+ second_refund = @gateway.refund(@refund_amount, purchase.authorization)
+ assert_success second_refund
+ assert second_refund.params['refunded']
+ assert_equal @amount - 2 * @refund_amount, second_refund.params['amount']
+ assert_equal 2, second_refund.params['refunds'].size
+ assert_equal 2 * @refund_amount, second_refund.params['refunds'].map { |r| r['amount'] }.sum
+ assert second_refund.authorization
+ end
+
+ def test_unsuccessful_authorize_refund
+ response = @gateway.refund(@amount, 'invalid_authorization_token')
+ assert_failure response
+ assert_match %r{Requested Charge does not exist}, response.message
+ end
+
+ def test_unsuccessful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert purchase.authorization
+
+ refund = @gateway.refund(@amount + 1, purchase.authorization, @options)
+ assert_failure refund
+ assert_match %r{Invalid Refund data}, refund.message
+ end
+
+ def test_successful_void
+ authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert !authorization.params['captured']
+
+ void = @gateway.void(authorization.authorization, @options)
+ assert_success void
+ assert void.params['refunded']
+ assert_equal 0, void.params['amount']
+ assert_equal 1, void.params['refunds'].size
+ assert_equal @amount, void.params['refunds'].map { |r| r['amount'] }.sum
+ assert void.authorization
+ end
+
+ def test_failed_void
+ response = @gateway.void('invalid_authorization_token', @options)
+ assert_failure response
+ assert_match %r{Requested Charge does not exist}, response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Transaction approved}, response.responses.last.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{The card was declined for other reason.}, response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.primary_response.error_code
+ end
+
+ def test_incorrect_number_for_purchase
+ card = credit_card('4242424242424241')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_invalid_login
+ gateway = SecurionPayGateway.new(secret_key: 'active_merchant_test')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match 'Provided API key is invalid', response.message
+ end
+
+ def test_invalid_number_for_purchase
+ card = credit_card('-1')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_invalid_expiry_month_for_purchase
+ card = credit_card('4242424242424242', month: 16)
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code
+ end
+
+ def test_invalid_expiry_year_for_purchase
+ card = credit_card('4242424242424242', year: 'xx')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code
+ end
+
+ def test_expired_card_for_purchase
+ card = credit_card('4916487051294548')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:expired_card], response.error_code
+ end
+
+ def test_invalid_cvc_for_purchase
+ card = credit_card('4242424242424242', verification_value: -1)
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code
+ end
+
+ def test_incorrect_cvc_for_purchase
+ card = credit_card('4024007134364842')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_cvc], response.error_code
+ end
+
+ def test_processing_error
+ card = credit_card('4024007114166316')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_incorrect_zip
+ card = credit_card('4929225021529113')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_zip], response.error_code
+ end
+
+ def test_card_declined
+ card = credit_card('4916018475814056')
+ response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ end
+end
diff --git a/test/remote/gateways/remote_skipjack_test.rb b/test/remote/gateways/remote_skipjack_test.rb
index 760aed91a7c..d8ec78f732b 100644
--- a/test/remote/gateways/remote_skipjack_test.rb
+++ b/test/remote/gateways/remote_skipjack_test.rb
@@ -7,46 +7,46 @@ def setup
@gateway = SkipJackGateway.new(fixtures(:skip_jack))
@credit_card = credit_card('4445999922225',
- :verification_value => '999'
- )
+ :verification_value => '999'
+ )
@amount = 100
-
+
@options = {
:order_id => generate_unique_id,
:email => 'email@foo.com',
:billing_address => address
}
end
-
+
def test_successful_authorization
authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
assert_false authorization.authorization.blank?
end
-
+
def test_unsuccessful_authorization
@credit_card.number = '1234'
authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_failure authorization
end
-
+
def test_authorization_fails_without_phone_number
@options[:billing_address][:phone] = nil
authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_failure authorization
end
-
+
def test_successful_purchase
assert_success @gateway.purchase(@amount, @credit_card, @options)
end
def test_successful_authorization_and_capture
authorization = @gateway.authorize(@amount, @credit_card, @options)
-
+
assert_success authorization
assert_false authorization.authorization.blank?
-
+
capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
end
@@ -54,16 +54,16 @@ def test_successful_authorization_and_capture
def test_successful_authorization_and_partial_capture
@amount = 2000
authorization = @gateway.authorize(@amount, @credit_card, @options)
-
+
assert_success authorization
assert_false authorization.authorization.blank?
-
+
capture = @gateway.capture(1000, authorization.authorization)
assert_success capture
- assert_equal "1000", capture.params["TransactionAmount"]
+ assert_equal '1000', capture.params['TransactionAmount']
end
-
+
def test_authorization_and_void
authorization = @gateway.authorize(101, @credit_card, @options)
assert_success authorization
@@ -72,11 +72,11 @@ def test_authorization_and_void
end
def test_successful_authorization_and_credit
- authorization = @gateway.authorize(@amount, @credit_card, @options)
+ authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
-
+
capture = @gateway.capture(@amount, authorization.authorization, :force_settlement => true)
- assert_success capture
+ assert_success capture
# developer login won't change transaction immediately to settled, so status will have to mismatch
credit = @gateway.credit(@amount, authorization.authorization)
@@ -85,7 +85,7 @@ def test_successful_authorization_and_credit
def test_authorization_with_invalid_verification_value
@credit_card.verification_value = '123'
-
+
response = @gateway.authorize(@amount, @credit_card, @options)
assert_failure response
assert_equal "Card verification number didn't match", response.message
@@ -94,7 +94,7 @@ def test_authorization_with_invalid_verification_value
def test_authorization_and_status
authorization = @gateway.authorize(101, @credit_card, @options)
assert_success authorization
-
+
status = @gateway.status(@options[:order_id])
assert_success status
end
@@ -102,9 +102,9 @@ def test_authorization_and_status
def test_status_unkown_order
status = @gateway.status(generate_unique_id)
assert_failure status
- assert_match /No Records Found/, status.message
+ assert_match %r{No Records Found}, status.message
end
-
+
def test_invalid_login
gateway = SkipJackGateway.new(
:login => '555555555555',
@@ -113,6 +113,6 @@ def test_invalid_login
response = gateway.authorize(@amount, @credit_card, @options)
assert_failure response
- assert_match /Invalid serial number/, response.message
+ assert_match %r{Invalid serial number}, response.message
end
end
diff --git a/test/remote/gateways/remote_so_easy_pay_test.rb b/test/remote/gateways/remote_so_easy_pay_test.rb
new file mode 100644
index 00000000000..e24d837703b
--- /dev/null
+++ b/test/remote/gateways/remote_so_easy_pay_test.rb
@@ -0,0 +1,64 @@
+require 'test_helper'
+
+class RemoteSoEasyPayTest < Test::Unit::TestCase
+
+ def setup
+ @gateway = SoEasyPayGateway.new(fixtures(:so_easy_pay))
+
+ @amount = 100
+ @credit_card = credit_card('4111111111111111', {:verification_value => '000', :month => '12', :year => '2015'})
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ :currency => 'EUR',
+ :ip => '192.168.19.123',
+ :email => 'test@blaha.com',
+ :order_id => generate_unique_id,
+ :billing_address => address,
+ :description => 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Transaction successful', response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_authorize_and_capture
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ assert auth.authorization
+ assert capture = @gateway.capture(amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ assert response = @gateway.capture(@amount, '')
+ assert_failure response
+ end
+
+ def test_successful_void
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert response = @gateway.void(response.authorization)
+ assert_success response
+ end
+
+ def test_invalid_login
+ gateway = SoEasyPayGateway.new(
+ :login => 'one',
+ :password => 'wrong'
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Website verification failed, wrong websiteID or password', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_spreedly_core_test.rb b/test/remote/gateways/remote_spreedly_core_test.rb
index 73f48e56042..a162d35a6fc 100644
--- a/test/remote/gateways/remote_spreedly_core_test.rb
+++ b/test/remote/gateways/remote_spreedly_core_test.rb
@@ -8,8 +8,11 @@ def setup
@amount = 100
@credit_card = credit_card('5555555555554444')
@declined_card = credit_card('4012888888881881')
- @existing_payment_method = '9AjLflWs7SOKuqJLveOZya9bixa'
- @declined_payment_method = 'n3JElNt9joT1mJ3CxvWjyEN39N'
+ @check = check({routing_number: '021000021', account_number: '9876543210'})
+ @existing_payment_method = '3rEkRlZur2hXKbwwRBidHJAIUTO'
+ @declined_payment_method = 'UPfh3J3JbekLeYC88BP741JWnS5'
+ @existing_transaction = 'PJ5ICgM6h7v9pBNxDCJjRHDDxBC'
+ @not_found_transaction = 'AdyQXaG0SVpSoMPdmFlvd3aA3uz'
end
def test_successful_purchase_with_token
@@ -21,7 +24,7 @@ def test_successful_purchase_with_token
def test_failed_purchase_with_token
assert response = @gateway.purchase(@amount, @declined_payment_method)
assert_failure response
- assert_match %r(Unable to process the transaction), response.message
+ assert_match %r(Unable to process the purchase transaction), response.message
end
def test_successful_authorize_with_token_and_capture
@@ -38,7 +41,7 @@ def test_successful_authorize_with_token_and_capture
def test_failed_authorize_with_token
assert response = @gateway.authorize(@amount, @declined_payment_method)
assert_failure response
- assert_match %r(Unable to process the transaction), response.message
+ assert_match %r(Unable to process the authorize transaction), response.message
end
def test_failed_capture
@@ -55,6 +58,15 @@ def test_successful_purchase_with_credit_card
assert_success response
assert_equal 'Succeeded!', response.message
assert_equal 'Purchase', response.params['transaction_type']
+ assert_equal 'cached', response.params['payment_method_storage_state']
+ end
+
+ def test_successful_purchase_with_check
+ assert response = @gateway.purchase(@amount, @check)
+ assert_success response
+ assert_equal 'Succeeded!', response.message
+ assert_equal 'Purchase', response.params['transaction_type']
+ assert_equal 'cached', response.params['payment_method_storage_state']
end
def test_successful_purchase_with_card_and_address
@@ -68,7 +80,7 @@ def test_successful_purchase_with_card_and_address
assert_equal 'Succeeded!', response.message
assert_equal 'joebob@example.com', response.params['payment_method_email']
- assert_equal '1234 My Street', response.params['payment_method_address1']
+ assert_equal '456 My Street', response.params['payment_method_address1']
assert_equal 'Apt 1', response.params['payment_method_address2']
assert_equal 'Ottawa', response.params['payment_method_city']
assert_equal 'ON', response.params['payment_method_state']
@@ -85,7 +97,17 @@ def test_failed_purchase_with_invalid_credit_card
@credit_card.first_name = ' '
assert response = @gateway.purchase(@amount, @credit_card)
assert_failure response
- assert_equal "First name can't be blank", response.message
+ assert_equal 'The payment method is invalid.', response.message
+ assert_equal "First name can't be blank", response.params['payment_method_errors'].strip
+ end
+
+ def test_successful_purchase_with_store
+ assert response = @gateway.purchase(@amount, @credit_card, store: true)
+ assert_success response
+ assert_equal 'Succeeded!', response.message
+ assert_equal 'Purchase', response.params['transaction_type']
+ assert %w(retained cached).include?(response.params['payment_method_storage_state'])
+ assert !response.params['payment_method_token'].blank?
end
def test_successful_authorize_and_capture_with_credit_card
@@ -112,7 +134,7 @@ def test_successful_authorize_with_card_and_address
assert_equal 'Authorization', response.params['transaction_type']
assert_equal 'joebob@example.com', response.params['payment_method_email']
- assert_equal '1234 My Street', response.params['payment_method_address1']
+ assert_equal '456 My Street', response.params['payment_method_address1']
assert_equal 'Apt 1', response.params['payment_method_address2']
assert_equal 'Ottawa', response.params['payment_method_city']
assert_equal 'ON', response.params['payment_method_state']
@@ -129,7 +151,17 @@ def test_failed_authrorize_with_invalid_credit_card
@credit_card.first_name = ' '
assert response = @gateway.authorize(@amount, @credit_card)
assert_failure response
- assert_equal "First name can't be blank", response.message
+ assert_equal 'The payment method is invalid.', response.message
+ assert_equal "First name can't be blank", response.params['payment_method_errors'].strip
+ end
+
+ def test_successful_authorize_with_store
+ assert response = @gateway.authorize(@amount, @credit_card, store: true)
+ assert_success response
+ assert_equal 'Succeeded!', response.message
+ assert_equal 'Authorization', response.params['transaction_type']
+ assert %w(retained cached).include?(response.params['payment_method_storage_state'])
+ assert !response.params['payment_method_token'].blank?
end
def test_successful_store
@@ -169,7 +201,7 @@ def test_successful_store_with_address
assert response = @gateway.store(@credit_card, options)
assert_success response
assert_equal 'joebob@example.com', response.params['payment_method_email']
- assert_equal '1234 My Street', response.params['payment_method_address1']
+ assert_equal '456 My Street', response.params['payment_method_address1']
assert_equal 'Apt 1', response.params['payment_method_address2']
assert_equal 'Ottawa', response.params['payment_method_city']
assert_equal 'ON', response.params['payment_method_state']
@@ -219,11 +251,74 @@ def test_successful_void
assert_equal 'Succeeded!', response.message
end
+ def test_successful_verify_with_token
+ assert response = @gateway.verify(@existing_payment_method)
+ assert_success response
+ assert_equal 'Succeeded!', response.message
+ assert_equal 'Verification', response.params['transaction_type']
+ assert_includes %w(cached retained), response.params['payment_method_storage_state']
+ end
+
+ def test_failed_verify_with_token
+ assert response = @gateway.verify(@declined_payment_method)
+ assert_failure response
+ end
+
+ def test_successful_verify_with_credit_card
+ assert response = @gateway.verify(@credit_card)
+ assert_success response
+ assert_equal 'Succeeded!', response.message
+ assert_equal 'Verification', response.params['transaction_type']
+ assert_includes %w(cached retained), response.params['payment_method_storage_state']
+ end
+
+ def test_failed_verify_with_declined_credit_card
+ assert response = @gateway.verify(@declined_card)
+ assert_failure response
+ assert_match %r(Unable to process the verify transaction), response.message
+ end
+
+ def test_successful_find_transaction
+ assert response = @gateway.find(@existing_transaction)
+ assert_success response
+ assert_equal 'Succeeded!', response.message
+ assert_equal 'Purchase', response.params['transaction_type']
+ end
+
+ def test_failed_find_transaction
+ assert response = @gateway.find(@not_found_transaction)
+ assert_failure response
+ assert_match %r(Unable to find the transaction), response.message
+ end
+
def test_invalid_login
gateway = SpreedlyCoreGateway.new(:login => 'Bogus', :password => 'MoreBogus', :gateway_token => 'EvenMoreBogus')
assert response = gateway.purchase(@amount, @existing_payment_method)
assert_failure response
- assert_equal 'HTTP Basic: Access denied.', response.message
+ assert_match %r{Unable to authenticate}, response.message
+ end
+
+ def test_scrubbing_purchase
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_scrubbing_purchase_with_token
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @existing_payment_method)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@existing_payment_method, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
end
end
diff --git a/test/remote/gateways/remote_stripe_3ds_test.rb b/test/remote/gateways/remote_stripe_3ds_test.rb
new file mode 100644
index 00000000000..fb596dc05ae
--- /dev/null
+++ b/test/remote/gateways/remote_stripe_3ds_test.rb
@@ -0,0 +1,194 @@
+require 'test_helper'
+require 'mechanize'
+
+class RemoteStripe3DSTest < Test::Unit::TestCase
+ CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/
+
+ def setup
+ @gateway = StripeGateway.new(fixtures(:stripe))
+ @amount = 100
+ @billing_details = address()
+
+ @options = {
+ :currency => 'USD',
+ :description => 'ActiveMerchant Test Purchase',
+ :email => 'wow@example.com',
+ :execute_threed => true,
+ :redirect_url => 'http://www.example.com/redirect',
+ :callback_url => 'http://www.example.com/callback',
+ :billing_address => @billing_details
+ }
+ @credit_card = credit_card('4000000000003063')
+ @non_3ds_card = credit_card('378282246310005')
+
+ @stripe_account = fixtures(:stripe_destination)[:stripe_user_id]
+ end
+
+ def test_create_3ds_card_source
+ assert response = @gateway.send(:create_source, @amount, @credit_card, 'card', @options)
+ assert_card_source(response)
+ end
+
+ def test_create_non3ds_card_source
+ assert response = @gateway.send(:create_source, @amount, @non_3ds_card, 'card', @options)
+ assert_card_source(response, 'not_supported')
+ end
+
+ def test_create_3ds_source
+ card_source = @gateway.send(:create_source, @amount, @credit_card, 'card', @options)
+ assert response = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options)
+ assert_success response
+ assert_three_ds_source(response)
+ end
+
+ def test_show_3ds_source
+ card_source = @gateway.send(:create_source, @amount, @credit_card, 'card', @options)
+ assert three_d_secure_source = @gateway.send(:create_source, @amount, card_source.params['id'], 'three_d_secure', @options)
+ assert_success three_d_secure_source
+ assert_three_ds_source(three_d_secure_source)
+
+ assert response = @gateway.send(:show_source, three_d_secure_source.params['id'], @options)
+ assert_three_ds_source(response)
+ end
+
+ def test_create_webhook_endpoint
+ response = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable'])
+ assert_includes response.params['enabled_events'], 'source.chargeable'
+ assert_equal @options[:callback_url], response.params['url']
+ assert_equal 'enabled', response.params['status']
+ assert_nil response.params['application']
+
+ deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id']))
+ assert_equal true, deleted_response.params['deleted']
+ end
+
+ def test_create_webhook_endpoint_on_connected_account
+ response = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable'])
+ assert_includes response.params['enabled_events'], 'source.chargeable'
+ assert_equal @options[:callback_url], response.params['url']
+ assert_equal 'enabled', response.params['status']
+ assert_not_nil response.params['application']
+
+ deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id']))
+ assert_equal true, deleted_response.params['deleted']
+ end
+
+ def test_delete_webhook_endpoint
+ webhook = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable'])
+ response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id']))
+ assert_equal response.params['id'], webhook.params['id']
+ assert_equal true, response.params['deleted']
+ end
+
+ def test_delete_webhook_endpoint_on_connected_account
+ webhook = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable'])
+ response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id']))
+ assert_equal response.params['id'], webhook.params['id']
+ assert_equal true, response.params['deleted']
+ end
+
+ def test_show_webhook_endpoint
+ webhook = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable'])
+ response = @gateway.send(:show_webhook_endpoint, @options.merge(:webhook_id => webhook.params['id']))
+ assert_includes response.params['enabled_events'], 'source.chargeable'
+ assert_equal @options[:callback_url], response.params['url']
+ assert_equal 'enabled', response.params['status']
+ assert_nil response.params['application']
+
+ deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id']))
+ assert_equal true, deleted_response.params['deleted']
+ end
+
+ def test_show_webhook_endpoint_on_connected_account
+ webhook = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable'])
+ response = @gateway.send(:show_webhook_endpoint, @options.merge({:webhook_id => webhook.params['id'], stripe_account: @stripe_account}))
+
+ assert_includes response.params['enabled_events'], 'source.chargeable'
+ assert_equal @options[:callback_url], response.params['url']
+ assert_equal 'enabled', response.params['status']
+ assert_not_nil response.params['application']
+
+ deleted_response = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => response.params['id']))
+ assert_equal true, deleted_response.params['deleted']
+ end
+
+ def test_list_webhook_endpoints
+ webhook1 = @gateway.send(:create_webhook_endpoint, @options, ['source.chargeable'])
+ webhook2 = @gateway.send(:create_webhook_endpoint, @options.merge({stripe_account: @stripe_account}), ['source.chargeable'])
+ assert_nil webhook1.params['application']
+ assert_not_nil webhook2.params['application']
+
+ response = @gateway.send(:list_webhook_endpoints, @options.merge({limit: 100}))
+ assert_not_nil response.params
+ assert_equal 'list', response.params['object']
+ assert response.params['data'].size >= 2
+ webhook_id_set = Set.new(response.params['data'].map { |webhook| webhook['id'] }.uniq)
+ assert Set[webhook1.params['id'], webhook2.params['id']].subset?(webhook_id_set)
+
+ deleted_response1 = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook1.params['id']))
+ deleted_response2 = @gateway.send(:delete_webhook_endpoint, @options.merge(:webhook_id => webhook2.params['id']))
+ assert_equal true, deleted_response1.params['deleted']
+ assert_equal true, deleted_response2.params['deleted']
+ end
+
+ def test_3ds_purchase
+ card_source_response = @gateway.send(:create_source, @amount, @credit_card, 'card', @options)
+ assert_card_source(card_source_response)
+
+ assert three_ds_source_response = @gateway.send(:create_source, @amount, card_source_response.params['id'], 'three_d_secure', @options)
+ assert_success three_ds_source_response
+ assert_three_ds_source(three_ds_source_response)
+
+ # Simulate 3DS 1.0 authentication in the test environment
+ authentication_url = three_ds_source_response.params['redirect']['url']
+ agent = Mechanize.new
+ page = agent.get(authentication_url)
+
+ form = page.forms.first
+ form.submit.tap do |result_page|
+ assert_equal '200', result_page.code
+ end
+
+ # Test charging of the 3DS source
+ threeds_params = {}
+ threeds_params[:source] = three_ds_source_response.params['id']
+ threeds_params[:capture] = 'true'
+
+ @gateway.send(:add_charge_details, threeds_params, @amount, @credit_card, @options)
+
+ assert response = @gateway.send(:commit, :post, 'charges', threeds_params, @options)
+ assert_equal 'charge', response.params['object']
+ assert_equal 'succeeded', response.params['status']
+ assert_equal true, response.params['captured']
+ assert_equal 'three_d_secure', response.params.dig('source', 'type')
+ assert_equal true, response.params.dig('payment_method_details', 'card', 'three_d_secure', 'authenticated')
+
+ # Check that billing details have been propagated from the card source to the charge
+ billing_details = response.params['billing_details']
+ assert_equal @options[:email], billing_details['email']
+ assert_equal @credit_card.name, billing_details['name']
+ assert_equal @billing_details[:phone], billing_details['phone']
+ assert_equal @billing_details[:address1], billing_details['address']['line1']
+ assert_equal @billing_details[:address2], billing_details['address']['line2']
+ assert_equal @billing_details[:city], billing_details['address']['city']
+ assert_equal @billing_details[:state], billing_details['address']['state']
+ assert_equal @billing_details[:zip], billing_details['address']['postal_code']
+ assert_equal @billing_details[:country], billing_details['address']['country']
+ end
+
+ def assert_card_source(response, three_d_secure_status = 'required')
+ assert_success response
+ assert_equal 'source', response.params['object']
+ assert_equal 'chargeable', response.params['status']
+ assert_equal three_d_secure_status, response.params['card']['three_d_secure']
+ assert_equal 'card', response.params['type']
+ end
+
+ def assert_three_ds_source(response)
+ assert_equal 'source', response.params['object']
+ assert_equal 'pending', response.params['status']
+ assert_equal 'three_d_secure', response.params['type']
+ assert_equal false, response.params['three_d_secure']['authenticated']
+ end
+
+end
diff --git a/test/remote/gateways/remote_stripe_android_pay_test.rb b/test/remote/gateways/remote_stripe_android_pay_test.rb
new file mode 100644
index 00000000000..5abf0974a17
--- /dev/null
+++ b/test/remote/gateways/remote_stripe_android_pay_test.rb
@@ -0,0 +1,48 @@
+require 'test_helper'
+
+class RemoteStripeAndroidPayTest < Test::Unit::TestCase
+ CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/
+
+ def setup
+ @gateway = StripeGateway.new(fixtures(:stripe))
+ @amount = 100
+
+ @options = {
+ :currency => 'USD',
+ :description => 'ActiveMerchant Test Purchase',
+ :email => 'wow@example.com'
+ }
+ end
+
+ def test_successful_purchase_with_android_pay_raw_cryptogram
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :android_pay
+ )
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_auth_with_android_pay_raw_cryptogram
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :android_pay
+ )
+ assert response = @gateway.authorize(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+end
diff --git a/test/remote/gateways/remote_stripe_apple_pay_test.rb b/test/remote/gateways/remote_stripe_apple_pay_test.rb
new file mode 100644
index 00000000000..c5231114490
--- /dev/null
+++ b/test/remote/gateways/remote_stripe_apple_pay_test.rb
@@ -0,0 +1,165 @@
+require 'test_helper'
+
+class RemoteStripeApplePayTest < Test::Unit::TestCase
+ CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/
+
+ def setup
+ @gateway = StripeGateway.new(fixtures(:stripe))
+ @amount = 100
+
+ @options = {
+ :currency => 'USD',
+ :description => 'ActiveMerchant Test Purchase',
+ :email => 'wow@example.com'
+ }
+ @apple_pay_payment_token = apple_pay_payment_token
+ end
+
+ def test_successful_purchase_with_apple_pay_payment_token
+ assert response = @gateway.purchase(@amount, @apple_pay_payment_token, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_authorization_and_capture_with_apple_pay_payment_token
+ assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options)
+ assert_success authorization
+ refute authorization.params['captured']
+ assert_equal 'ActiveMerchant Test Purchase', authorization.params['description']
+ assert_equal 'wow@example.com', authorization.params['metadata']['email']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+ end
+
+ def test_authorization_and_void_with_apple_pay_payment_token
+ assert authorization = @gateway.authorize(@amount, @apple_pay_payment_token, @options)
+ assert_success authorization
+ refute authorization.params['captured']
+
+ assert void = @gateway.void(authorization.authorization)
+ assert_success void
+ end
+
+ def test_successful_void_with_apple_pay_payment_token
+ assert response = @gateway.purchase(@amount, @apple_pay_payment_token, @options)
+ assert_success response
+ assert response.authorization
+ assert void = @gateway.void(response.authorization)
+ assert_success void
+ end
+
+ def test_successful_store_with_apple_pay_payment_token
+ assert response = @gateway.store(@apple_pay_payment_token, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'})
+ assert_success response
+ assert_equal 'customer', response.params['object']
+ assert_equal 'Active Merchant Test Customer', response.params['description']
+ assert_equal 'email@example.com', response.params['email']
+ first_card = response.params['cards']['data'].first
+ assert_equal response.params['default_card'], first_card['id']
+ assert_equal '4242', first_card['dynamic_last4'] # when stripe is in test mode, token exchanged will return a test card with dynamic_last4 4242
+ assert_equal '0000', first_card['last4'] # last4 is 0000 when using an apple pay token
+ end
+
+ def test_successful_store_with_existing_customer_and_apple_pay_payment_token
+ assert response = @gateway.store(@credit_card, {:description => 'Active Merchant Test Customer'})
+ assert_success response
+
+ assert response = @gateway.store(@apple_pay_payment_token, {:customer => response.params['id'], :description => 'Active Merchant Test Customer', :email => 'email@example.com'})
+ assert_success response
+ assert_equal 2, response.responses.size
+
+ card_response = response.responses[0]
+ assert_equal 'card', card_response.params['object']
+ assert_equal '4242', card_response.params['dynamic_last4'] # when stripe is in test mode, token exchanged will return a test card with dynamic_last4 4242
+ assert_equal '0000', card_response.params['last4'] # last4 is 0000 when using an apple pay token
+
+ customer_response = response.responses[1]
+ assert_equal 'customer', customer_response.params['object']
+ assert_equal 'Active Merchant Test Customer', customer_response.params['description']
+ assert_equal 'email@example.com', customer_response.params['email']
+ assert_equal 2, customer_response.params['cards']['count']
+ end
+
+ def test_successful_recurring_with_apple_pay_payment_token
+ assert response = @gateway.store(@apple_pay_payment_token, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'})
+ assert_success response
+ assert recharge_options = @options.merge(:customer => response.params['id'])
+ assert response = @gateway.purchase(@amount, nil, recharge_options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ end
+
+ def test_purchase_with_unsuccessful_apple_pay_token_exchange
+ assert response = @gateway.purchase(@amount, ApplePayPaymentToken.new('garbage'), @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase_with_apple_pay_raw_cryptogram_with_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :apple_pay
+ )
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_purchase_with_apple_pay_raw_cryptogram_without_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ source: :apple_pay
+ )
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_auth_with_apple_pay_raw_cryptogram_with_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ eci: '05',
+ source: :apple_pay
+ )
+ assert response = @gateway.authorize(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_auth_with_apple_pay_raw_cryptogram_without_eci
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ payment_cryptogram: 'EHuWW9PiBkWvqE5juRwDzAUFBAk=',
+ verification_value: nil,
+ source: :apple_pay
+ )
+ assert response = @gateway.authorize(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+end
diff --git a/test/remote/gateways/remote_stripe_connect_test.rb b/test/remote/gateways/remote_stripe_connect_test.rb
new file mode 100644
index 00000000000..8309d62f98d
--- /dev/null
+++ b/test/remote/gateways/remote_stripe_connect_test.rb
@@ -0,0 +1,60 @@
+require 'test_helper'
+
+class RemoteStripeConnectTest < Test::Unit::TestCase
+ def setup
+ @gateway = StripeGateway.new(fixtures(:stripe))
+
+ @amount = 100
+ @credit_card = credit_card('4242424242424242')
+ @declined_card = credit_card('4000000000000002')
+ @new_credit_card = credit_card('5105105105105100')
+
+ @options = {
+ :currency => 'USD',
+ :description => 'ActiveMerchant Test Purchase',
+ :email => 'wow@example.com',
+ :stripe_account => fixtures(:stripe_destination)[:stripe_user_id]
+ }
+ end
+
+ def test_application_fee_for_stripe_connect
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12))
+ assert_success response
+ end
+
+ def test_successful_refund_with_application_fee
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12))
+ assert refund = @gateway.refund(@amount, response.authorization, @options.merge(:refund_application_fee => true))
+ assert_success refund
+
+ # Verify the application fee is refunded
+ fetch_fee_id = @gateway.send(:fetch_application_fee, response.authorization, @options)
+ fee_id = @gateway.send(:application_fee_from_response, fetch_fee_id)
+ refund_check = @gateway.send(:refund_application_fee, 10, fee_id, @options)
+ assert_equal 'Application fee could not be refunded: Refund amount ($0.10) is greater than unrefunded amount on fee ($0.00)', refund_check.message
+ end
+
+ def test_refund_partial_application_fee
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12))
+ assert refund = @gateway.refund(@amount-20, response.authorization, @options.merge(:refund_fee_amount => '10'))
+ assert_success refund
+
+ # Verify the application fee is partially refunded
+ fetch_fee_id = @gateway.send(:fetch_application_fee, response.authorization, @options)
+ fee_id = @gateway.send(:application_fee_from_response, fetch_fee_id)
+ refund_check = @gateway.send(:refund_application_fee, 10, fee_id, @options)
+ assert_equal 'Application fee could not be refunded: Refund amount ($0.10) is greater than unrefunded amount on fee ($0.02)', refund_check.message
+ end
+
+ def test_refund_application_fee_amount_zero
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12))
+ assert refund = @gateway.refund(@amount-20, response.authorization, @options.merge(:refund_fee_amount => '0'))
+ assert_success refund
+
+ # Verify the application fee is not refunded
+ fetch_fee_id = @gateway.send(:fetch_application_fee, response.authorization, @options)
+ fee_id = @gateway.send(:application_fee_from_response, fetch_fee_id)
+ refund_check = @gateway.send(:refund_application_fee, 14, fee_id, @options)
+ assert_equal 'Application fee could not be refunded: Refund amount ($0.14) is greater than fee amount ($0.12)', refund_check.message
+ end
+end
diff --git a/test/remote/gateways/remote_stripe_emv_test.rb b/test/remote/gateways/remote_stripe_emv_test.rb
new file mode 100644
index 00000000000..6d2742d7d35
--- /dev/null
+++ b/test/remote/gateways/remote_stripe_emv_test.rb
@@ -0,0 +1,146 @@
+require 'test_helper'
+
+class RemoteStripeEmvTest < Test::Unit::TestCase
+ CHARGE_ID_REGEX = /ch_[a-zA-Z\d]{24}/
+
+ def setup
+ @gateway = StripeGateway.new(fixtures(:stripe))
+
+ @amount = 100
+ @emv_credit_cards = {
+ uk: ActiveMerchant::Billing::CreditCard.new(icc_data: '5A08476173900101011957114761739001010119D221220117589288899F131F3137353839383930303238383030303030304F07A0000000031010500B564953412043524544495482025C008407A00000000310108A025A318E0E00000000000000001E0302031F00950542000080009A031707259B02E8009C01005F24032212315F25030907015F2A0208265F3401019F01060000000000019F02060000000001009F03060000000000009F0607A00000000310109F0702FF009F0902008C9F0D05F0400088009F0E0500100000009F0F05F0400098009F100706010A03A000009F120F4352454449544F20444520564953419F160F3132333435363738393031323334359F1A0208269F1C0831313232333334349F1E0831323334353637389F21031137269F26084A3000C111F061539F2701809F33036028C89F34031E03009F3501219F360200029F370467D5DD109F3901059F40057E0000A0019F4104000001979F4E0D54657374204D65726368616E749F110101DF834F0F434842313136373235303030343439DF83620100'),
+ us: ActiveMerchant::Billing::CreditCard.new(icc_data: '5A08476173900101011957114761739001010119D221220117589288899F131F3137353839383930303238383030303030304F07A0000000031010500B564953412043524544495482025C008407A00000000310108A025A318E0E00000000000000001E0302031F00950542000080009A031707259B02E8009C01005F24032212315F25030907015F2A0208405F3401019F01060000000000019F02060000000001009F03060000000000009F0607A00000000310109F0702FF009F0902008C9F0D05F0400088009F0E0500100000009F0F05F0400098009F100706010A03A000009F120F4352454449544F20444520564953419F160F3132333435363738393031323334359F1A0208409F1C0831313232333334349F1E0831323334353637389F21031137269F26084A3000C111F061539F2701809F33036028C89F34031E03009F3501219F360200029F370467D5DD109F3901059F40057E0000A0019F4104000001979F4E0D54657374204D65726368616E749F110101DF834F0F434842313136373235303030343439DF83620100'),
+ contactless: ActiveMerchant::Billing::CreditCard.new(icc_data: '5A08476173900101011957114761739001010119D22122011758928889500D5649534120454C454354524F4E5F20175649534120434445542032312F434152443035202020205F2A0208405F340111820200008407A00000000320109A031505119C01009F02060000000006009F0607A00000000320109F090200019F100706011103A000009F1A0200569F1C0831323334353637389F1E0831303030333236389F260852A5A96394EDA96D9F2701809F3303E0B8C89F3501229F360200069F3704A4428D7A9F410400000289')
+ }
+
+ @options = {
+ :currency => 'USD',
+ :description => 'ActiveMerchant Test Purchase',
+ :email => 'wow@example.com'
+ }
+
+ # This capture hex says that the payload is a transaction cryptogram (TC) but does not
+ # provide the actual cryptogram. This will only work in test mode and would cause real
+ # cards to be declined.
+ @capture_options = { icc_data: '9F270140' }
+ end
+
+ # for EMV contact transactions, it's advised to do a separate auth + capture
+ # to satisfy the EMV chip's transaction flow, but this works as a legal
+ # API call. You shouldn't use it in a real EMV implementation, though.
+ def test_successful_purchase_with_emv_credit_card_in_uk
+ assert response = @gateway.purchase(@amount, @emv_credit_cards[:uk], @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ # perform separate auth & capture rather than a purchase in practice for the
+ # reasons mentioned above.
+ def test_successful_purchase_with_emv_credit_card_in_us
+ assert response = @gateway.purchase(@amount, @emv_credit_cards[:us], @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_successful_purchase_with_quickchip_credit_card_in_us
+ credit_card = @emv_credit_cards[:us]
+ credit_card.read_method = 'contact_quickchip'
+ assert response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['captured']
+ assert response.params['paid']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ # For EMV contactless transactions, generally a purchase is preferred since
+ # a TC is typically generated at the point of sale.
+ def test_successful_purchase_with_emv_contactless_credit_card
+ emv_credit_card = @emv_credit_cards[:contactless]
+ emv_credit_card.read_method = 'contactless'
+ assert response = @gateway.purchase(@amount, emv_credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_match CHARGE_ID_REGEX, response.authorization
+ end
+
+ def test_authorization_and_capture_with_emv_credit_card_in_uk
+ assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:uk], @options)
+ assert_success authorization
+ assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC'
+ refute authorization.params['captured']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization, @capture_options)
+ assert_success capture
+ assert capture.emv_authorization, 'Capture should contain emv_authorization containing the EMV TC'
+ end
+
+ def test_authorization_and_capture_with_emv_credit_card_in_us
+ assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options)
+ assert_success authorization
+ assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC'
+ refute authorization.params['captured']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization, @capture_options)
+ assert_success capture
+ assert capture.emv_authorization, 'Capture should contain emv_authorization containing the EMV TC'
+ end
+
+ def test_authorization_and_capture_of_online_pin_with_emv_credit_card_in_us
+ emv_credit_card = @emv_credit_cards[:us]
+ emv_credit_card.encrypted_pin_cryptogram = '8b68af72199529b8'
+ emv_credit_card.encrypted_pin_ksn = 'ffff0102628d12000001'
+
+ assert authorization = @gateway.authorize(@amount, emv_credit_card, @options)
+ assert_success authorization
+ assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC'
+ refute authorization.params['captured']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization, @capture_options)
+ assert_success capture
+ assert capture.emv_authorization, 'Capture should contain emv_authorization containing the EMV TC'
+ end
+
+ def test_authorization_and_void_with_emv_credit_card_in_us
+ assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options)
+ assert_success authorization
+ assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC'
+ refute authorization.params['captured']
+
+ assert void = @gateway.void(authorization.authorization)
+ assert_success void
+ end
+
+ def test_authorization_and_void_with_emv_credit_card_in_uk
+ assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:uk], @options)
+ assert_success authorization
+ assert authorization.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC'
+ refute authorization.params['captured']
+
+ assert void = @gateway.void(authorization.authorization)
+ assert_success void
+ end
+
+ def test_purchase_and_void_with_emv_contactless_credit_card
+ emv_credit_card = @emv_credit_cards[:contactless]
+ emv_credit_card.read_method = 'contactless'
+ assert purchase = @gateway.purchase(@amount, emv_credit_card, @options)
+ assert_success purchase
+ assert purchase.emv_authorization, 'Authorization should contain emv_authorization containing the EMV ARPC'
+ assert purchase.params['captured']
+ assert purchase.params['paid']
+
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ end
+
+ def test_authorization_emv_credit_card_in_us_with_metadata
+ assert authorization = @gateway.authorize(@amount, @emv_credit_cards[:us], @options.merge({:metadata => {:this_is_a_random_key_name => 'with a random value', :i_made_up_this_key_too => 'canyoutell'}, :order_id => '42', :email => 'foo@wonderfullyfakedomain.com'}))
+ assert_success authorization
+ end
+end
diff --git a/test/remote/gateways/remote_stripe_payment_intents_test.rb b/test/remote/gateways/remote_stripe_payment_intents_test.rb
new file mode 100644
index 00000000000..a8928f5cf3e
--- /dev/null
+++ b/test/remote/gateways/remote_stripe_payment_intents_test.rb
@@ -0,0 +1,439 @@
+require 'test_helper'
+
+class RemoteStripeIntentsTest < Test::Unit::TestCase
+ def setup
+ @gateway = StripePaymentIntentsGateway.new(fixtures(:stripe))
+ @customer = fixtures(:stripe)[:customer_id]
+ @amount = 2000
+ @three_ds_payment_method = 'pm_card_threeDSecure2Required'
+ @visa_payment_method = 'pm_card_visa'
+ @declined_payment_method = 'pm_card_chargeDeclined'
+ @three_ds_moto_enabled = 'pm_card_authenticationRequiredOnSetup'
+ @three_ds_authentication_required = 'pm_card_authenticationRequired'
+ @three_ds_credit_card = credit_card('4000000000003220',
+ verification_value: '737',
+ month: 10,
+ year: 2020
+ )
+ @visa_card = credit_card('4242424242424242',
+ verification_value: '737',
+ month: 10,
+ year: 2020
+ )
+ @destination_account = fixtures(:stripe_destination)[:stripe_user_id]
+ end
+
+ def test_authorization_and_void
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ }
+ assert authorization = @gateway.authorize(@amount, @visa_payment_method, options)
+
+ assert_equal 'requires_capture', authorization.params['status']
+ refute authorization.params.dig('charges', 'data')[0]['captured']
+
+ assert void = @gateway.void(authorization.authorization)
+ assert_success void
+ end
+
+ def test_successful_purchase
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ }
+ assert purchase = @gateway.purchase(@amount, @visa_payment_method, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ end
+
+ def test_unsuccessful_purchase
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ }
+ assert purchase = @gateway.purchase(@amount, @declined_payment_method, options)
+
+ assert_equal 'Your card was declined.', purchase.message
+ refute purchase.params.dig('error', 'payment_intent', 'charges', 'data')[0]['captured']
+ end
+
+ def test_create_payment_intent_manual_capture_method
+ options = {
+ currency: 'USD',
+ capture_method: 'manual'
+ }
+
+ assert response = @gateway.create_intent(@amount, nil, options)
+
+ assert_success response
+ assert_equal 'payment_intent', response.params['object']
+ assert_equal 'manual', response.params['capture_method']
+ end
+
+ def test_create_payment_intent_manual_confimation_method
+ options = {
+ currency: 'USD',
+ description: 'ActiveMerchant Test Purchase',
+ confirmation_method: 'manual'
+ }
+
+ assert response = @gateway.create_intent(@amount, nil, options)
+
+ assert_success response
+ assert_equal 'payment_intent', response.params['object']
+ assert_equal 'manual', response.params['confirmation_method']
+ end
+
+ def test_create_payment_intent_with_customer
+ options = {
+ currency: 'USD',
+ customer: @customer || 'set customer in fixtures'
+ }
+
+ assert response = @gateway.create_intent(@amount, nil, options)
+
+ assert_success response
+ assert_equal 'payment_intent', response.params['object']
+ assert_equal @customer, response.params['customer']
+ end
+
+ def test_create_payment_intent_with_credit_card
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ }
+
+ assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options)
+
+ assert_success response
+ assert_equal 'payment_intent', response.params['object']
+ end
+
+ def test_create_payment_intent_with_return_url
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ confirm: true,
+ return_url: 'https://www.example.com'
+ }
+
+ assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options)
+
+ assert_success response
+ assert_equal 'https://www.example.com', response.params['next_action']['redirect_to_url']['return_url']
+ end
+
+ def test_create_payment_intent_with_metadata
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ description: 'ActiveMerchant Test Purchase',
+ receipt_email: 'test@example.com',
+ statement_descriptor: 'Statement Descriptor',
+ metadata: { key_1: 'value_1', key_2: 'value_2' }
+ }
+
+ assert response = @gateway.create_intent(@amount, nil, options)
+
+ assert_success response
+ assert_equal 'value_1', response.params['metadata']['key_1']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'test@example.com', response.params['receipt_email']
+ assert_equal 'Statement Descriptor', response.params['statement_descriptor']
+ end
+
+ def test_create_payment_intent_that_saves_payment_method
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ save_payment_method: true
+ }
+
+ assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options)
+ assert_success response
+
+ assert response = @gateway.create_intent(@amount, nil, options)
+ assert_failure response
+ assert_equal 'A payment method must be provided or already '\
+ 'attached to the PaymentIntent when `save_payment_method=true`.', response.message
+
+ options.delete(:customer)
+ assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options)
+ assert_failure response
+ assert_equal 'A valid `customer` must be provided when `save_payment_method=true`.', response.message
+ end
+
+ def test_create_payment_intent_with_setup_future_usage
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ setup_future_usage: 'on_session'
+ }
+
+ assert response = @gateway.create_intent(@amount, @three_ds_credit_card, options)
+ assert_success response
+ assert_equal 'on_session', response.params['setup_future_usage']
+ end
+
+ def test_3ds_unauthenticated_authorize_with_off_session
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ off_session: true,
+ }
+
+ assert response = @gateway.authorize(@amount, @three_ds_credit_card, options)
+ assert_failure response
+ end
+
+ def test_create_payment_intent_with_shipping_address
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ shipping: {
+ address: {
+ line1: '1 Test Ln',
+ city: 'Durham'
+ },
+ name: 'John Doe',
+ tracking_number: '123456789'
+ }
+ }
+
+ assert response = @gateway.create_intent(@amount, nil, options)
+ assert_success response
+ assert response.params['shipping']['address']
+ assert_equal 'John Doe', response.params['shipping']['name']
+ end
+
+ def test_create_payment_intent_with_billing_address
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ billing_address: address,
+ confirm: true
+ }
+
+ assert response = @gateway.create_intent(@amount, @visa_card, options)
+ assert_success response
+ assert billing = response.params.dig('charges', 'data')[0].dig('billing_details', 'address')
+ assert_equal 'Ottawa', billing['city']
+ end
+
+ def test_create_payment_intent_with_connected_account
+ options = {
+ currency: 'USD',
+ customer: @customer,
+ application_fee: 100,
+ transfer_destination: @destination_account
+ }
+
+ assert response = @gateway.create_intent(@amount, nil, options)
+
+ assert_success response
+ assert_equal 100, response.params['application_fee_amount']
+ assert_equal @destination_account, response.params.dig('transfer_data', 'destination')
+ end
+
+ def test_create_a_payment_intent_and_confirm
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ return_url: 'https://www.example.com',
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ }
+ assert create_response = @gateway.create_intent(@amount, @three_ds_payment_method, options)
+ assert_equal 'requires_confirmation', create_response.params['status']
+ intent_id = create_response.params['id']
+
+ assert get_response = @gateway.show_intent(intent_id, options)
+ assert_equal 'requires_confirmation', get_response.params['status']
+
+ assert confirm_response = @gateway.confirm_intent(intent_id, nil, return_url: 'https://example.com/return-to-me')
+ assert_equal 'redirect_to_url', confirm_response.params.dig('next_action', 'type')
+ end
+
+ def test_create_a_payment_intent_and_manually_capture
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ confirm: true
+ }
+ assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options)
+ intent_id = create_response.params['id']
+ assert_equal 'requires_capture', create_response.params['status']
+
+ assert capture_response = @gateway.capture(@amount, intent_id, options)
+ assert_equal 'succeeded', capture_response.params['status']
+ assert_equal 'Payment complete.', capture_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message')
+ end
+
+ def test_create_a_payment_intent_and_automatically_capture
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ confirm: true
+ }
+ assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options)
+ assert_nil create_response.params['next_action']
+ assert_equal 'succeeded', create_response.params['status']
+ assert_equal 'Payment complete.', create_response.params.dig('charges', 'data')[0].dig('outcome', 'seller_message')
+ end
+
+ def test_failed_capture_after_creation
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ confirm: true
+ }
+ assert create_response = @gateway.create_intent(@amount, 'pm_card_chargeDeclined', options)
+ assert_equal 'requires_payment_method', create_response.params.dig('error', 'payment_intent', 'status')
+ assert_equal false, create_response.params.dig('error', 'payment_intent', 'charges', 'data')[0].dig('captured')
+ end
+
+ def test_create_a_payment_intent_and_update
+ update_amount = 2050
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ }
+ assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options)
+ intent_id = create_response.params['id']
+ assert_equal @amount, create_response.params['amount']
+
+ assert update_response = @gateway.update_intent(update_amount, intent_id, nil, options.merge(payment_method_types: 'card'))
+ assert_equal update_amount, update_response.params['amount']
+ assert_equal 'requires_confirmation', update_response.params['status']
+ end
+
+ def test_create_a_payment_intent_and_void
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ confirm: true
+ }
+ assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options)
+ intent_id = create_response.params['id']
+
+ assert cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer')
+ assert_equal @amount, cancel_response.params.dig('charges', 'data')[0].dig('amount_refunded')
+ assert_equal 'canceled', cancel_response.params['status']
+ assert_equal 'requested_by_customer', cancel_response.params['cancellation_reason']
+ end
+
+ def test_failed_void_after_capture
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ confirm: true
+ }
+ assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options)
+ assert_equal 'succeeded', create_response.params['status']
+ intent_id = create_response.params['id']
+
+ assert cancel_response = @gateway.void(intent_id, cancellation_reason: 'requested_by_customer')
+ assert_equal 'You cannot cancel this PaymentIntent because ' \
+ 'it has a status of succeeded. Only a PaymentIntent with ' \
+ 'one of the following statuses may be canceled: ' \
+ 'requires_payment_method, requires_capture, requires_confirmation, requires_action.', cancel_response.message
+ end
+
+ def test_refund_a_payment_intent
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ confirm: true
+ }
+ assert create_response = @gateway.create_intent(@amount, @visa_payment_method, options)
+ intent_id = create_response.params['id']
+
+ assert @gateway.capture(@amount, intent_id, options)
+
+ assert refund = @gateway.refund(@amount - 20, intent_id)
+ assert_equal @amount - 20, refund.params['charge']['amount_refunded']
+ assert_equal true, refund.params['charge']['captured']
+ refund_id = refund.params['id']
+ assert_equal refund.authorization, refund_id
+ end
+
+ def test_successful_store_purchase_and_unstore
+ options = {
+ currency: 'GBP',
+ }
+ assert store = @gateway.store(@visa_card, options)
+ assert store.params['customer'].start_with?('cus_')
+
+ assert purchase = @gateway.purchase(@amount, store.authorization, options)
+ assert 'succeeded', purchase.params['status']
+
+ assert unstore = @gateway.unstore(store.authorization)
+ assert_nil unstore.params['customer']
+ end
+
+ def test_moto_enabled_card_requires_action_when_not_marked
+ options = {
+ currency: 'GBP',
+ confirm: true,
+ }
+ assert purchase = @gateway.purchase(@amount, @three_ds_moto_enabled, options)
+
+ assert_equal 'requires_action', purchase.params['status']
+ end
+
+ def test_moto_enabled_card_succeeds_when_marked
+ options = {
+ currency: 'GBP',
+ confirm: true,
+ moto: true,
+ }
+ assert purchase = @gateway.purchase(@amount, @three_ds_moto_enabled, options)
+
+ assert_equal 'succeeded', purchase.params['status']
+ assert purchase.params.dig('charges', 'data')[0]['captured']
+ end
+
+ def test_certain_cards_require_action_even_when_marked_as_moto
+ options = {
+ currency: 'GBP',
+ confirm: true,
+ moto: true,
+ }
+ assert purchase = @gateway.purchase(@amount, @three_ds_authentication_required, options)
+
+ assert_failure purchase
+ assert_equal 'Your card was declined. This transaction requires authentication.', purchase.message
+ end
+
+ def test_transcript_scrubbing
+ options = {
+ currency: 'GBP',
+ customer: @customer,
+ confirmation_method: 'manual',
+ capture_method: 'manual',
+ return_url: 'https://www.example.com/return',
+ confirm: true
+ }
+ transcript = capture_transcript(@gateway) do
+ @gateway.create_intent(@amount, @three_ds_credit_card, options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@three_ds_credit_card.number, transcript)
+ assert_scrubbed(@three_ds_credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_stripe_test.rb b/test/remote/gateways/remote_stripe_test.rb
index a2ca420cab5..04a5b144138 100644
--- a/test/remote/gateways/remote_stripe_test.rb
+++ b/test/remote/gateways/remote_stripe_test.rb
@@ -1,51 +1,197 @@
require 'test_helper'
class RemoteStripeTest < Test::Unit::TestCase
-
def setup
@gateway = StripeGateway.new(fixtures(:stripe))
@amount = 100
@credit_card = credit_card('4242424242424242')
- @declined_card = credit_card('4000')
+ @declined_card = credit_card('4000000000000002')
@new_credit_card = credit_card('5105105105105100')
+ @debit_card = credit_card('4000056655665556')
+
+ @check = check({
+ bank_name: 'STRIPE TEST BANK',
+ account_number: '000123456789',
+ routing_number: '110000000',
+ })
+ @verified_bank_account = fixtures(:stripe_verified_bank_account)
@options = {
- :currency => 'CAD',
+ :currency => 'USD',
:description => 'ActiveMerchant Test Purchase',
- :email => 'wow@example.com',
- :currency => 'CAD'
+ :email => 'wow@example.com'
}
end
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ end
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "charge", response.params["object"]
- assert response.params["paid"]
+ assert_equal 'charge', response.params['object']
+ assert_equal response.authorization, response.params['id']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ end
+
+ def test_successful_purchase_with_blank_referer
+ options = @options.merge({referrer: ''})
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert_equal response.authorization, response.params['id']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ end
+
+ def test_successful_purchase_with_recurring_flag
+ custom_options = @options.merge(:eci => 'recurring')
+ assert response = @gateway.purchase(@amount, @credit_card, custom_options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ end
+
+ def test_successful_purchase_with_destination
+ destination = fixtures(:stripe_destination)[:stripe_user_id]
+ custom_options = @options.merge(:destination => destination)
+ assert response = @gateway.purchase(@amount, @credit_card, custom_options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert_equal destination, response.params['destination']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
end
- def test_purchase_description
- assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD', :description => "TheDescription", :email => "email@example.com" })
- assert_equal "TheDescription", response.params["description"], "Use the description if it's specified."
+ def test_successful_purchase_with_destination_and_amount
+ destination = fixtures(:stripe_destination)[:stripe_user_id]
+ custom_options = @options.merge(:destination => destination, :destination_amount => @amount - 20)
+ assert response = @gateway.purchase(@amount, @credit_card, custom_options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert_equal destination, response.params['destination']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ end
- assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD', :email => "email@example.com" })
- assert_equal "email@example.com", response.params["description"], "Use the email if no description is specified."
+ def test_successful_purchase_with_level3_data
+ @options[:merchant_reference] = 123
+ @options[:customer_reference] = 456
+ @options[:shipping_address_zip] = 98765
+ @options[:shipping_from_zip] = 54321
+ @options[:shipping_amount] = 10
+ @options[:line_items] = [
+ {
+ 'product_code' => 1234,
+ 'product_description' => 'An item',
+ 'unit_cost' => 15,
+ 'quantity' => 2,
+ 'tax_amount' => 0
+ },
+ {
+ 'product_code' => 999,
+ 'product_description' => 'A totes different item',
+ 'tax_amount' => 10,
+ 'unit_cost' => 50,
+ 'quantity' => 1,
+ }
+ ]
- assert response = @gateway.purchase(@amount, @credit_card, { :currency => 'CAD' })
- assert_nil response.params["description"], "No description or email specified."
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert_equal response.authorization, response.params['id']
+ assert response.params['paid']
+ assert_equal 'ActiveMerchant Test Purchase', response.params['description']
+ assert_equal 'wow@example.com', response.params['metadata']['email']
end
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_match /card number.* invalid/, response.message
+ assert_match %r{Your card was declined}, response.message
+ assert_match Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+ assert_match(/ch_[a-zA-Z\d]+/, response.authorization)
+ end
+
+ def test_unsuccessful_purchase_with_destination_and_amount
+ destination = fixtures(:stripe_destination)[:stripe_user_id]
+ custom_options = @options.merge(:destination => destination, :destination_amount => @amount + 20)
+ assert response = @gateway.purchase(@amount, @credit_card, custom_options)
+ assert_failure response
+ assert_match %r{must be less than or equal to the charge amount}, response.message
+ end
+
+ def test_successful_echeck_purchase_with_verified_account
+ customer_id = @verified_bank_account[:customer_id]
+ bank_account_id = @verified_bank_account[:bank_account_id]
+
+ payment = [customer_id, bank_account_id].join('|')
+
+ response = @gateway.purchase(@amount, payment, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_unsuccessful_direct_bank_account_purchase
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_failure response
+ assert_equal 'Direct bank account transactions are not supported. Bank accounts must be stored and verified before use.', response.message
end
def test_authorization_and_capture
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
- assert !authorization.params["captured"]
+ refute authorization.params['captured']
+ assert_equal 'ActiveMerchant Test Purchase', authorization.params['description']
+ assert_equal 'wow@example.com', authorization.params['metadata']['email']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+ end
+
+ def test_authorization_and_capture_with_destination
+ destination = fixtures(:stripe_destination)[:stripe_user_id]
+ custom_options = @options.merge(:destination => destination)
+
+ assert authorization = @gateway.authorize(@amount, @credit_card, custom_options)
+ assert_success authorization
+ refute authorization.params['captured']
+ assert_equal destination, authorization.params['destination']
+ assert_equal 'ActiveMerchant Test Purchase', authorization.params['description']
+ assert_equal 'wow@example.com', authorization.params['metadata']['email']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+ end
+
+ def test_authorization_and_capture_with_destination_and_amount
+ destination = fixtures(:stripe_destination)[:stripe_user_id]
+ custom_options = @options.merge(:destination => destination, :destination_amount => @amount - 20)
+
+ assert authorization = @gateway.authorize(@amount, @credit_card, custom_options)
+ assert_success authorization
+ refute authorization.params['captured']
+ assert_equal destination, authorization.params['destination']
+ assert_equal 'ActiveMerchant Test Purchase', authorization.params['description']
+ assert_equal 'wow@example.com', authorization.params['metadata']['email']
assert capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
@@ -54,7 +200,7 @@ def test_authorization_and_capture
def test_authorization_and_void
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
- assert !authorization.params["captured"]
+ refute authorization.params['captured']
assert void = @gateway.void(authorization.authorization)
assert_success void
@@ -65,108 +211,459 @@ def test_successful_void
assert_success response
assert response.authorization
assert void = @gateway.void(response.authorization)
+ assert void.test?
+ assert_success void
+ end
+
+ def test_successful_void_with_metadata
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+
+ assert void = @gateway.void(response.authorization, metadata: { test_metadata: 123 })
+ assert void.test?
+ assert_success void
+ assert_equal '123', void.params['metadata']['test_metadata']
+ end
+
+ def test_successful_void_with_reason
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+
+ assert void = @gateway.void(response.authorization, reason: 'fraudulent')
+ assert void.test?
assert_success void
+ assert_equal 'fraudulent', void.params['reason']
end
def test_unsuccessful_void
- assert void = @gateway.void("active_merchant_fake_charge")
+ assert void = @gateway.void('active_merchant_fake_charge')
assert_failure void
- assert_match /active_merchant_fake_charge/, void.message
+ assert_match %r{active_merchant_fake_charge}, void.message
end
def test_successful_refund
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert response.authorization
- assert void = @gateway.refund(@amount - 20, response.authorization)
- assert_success void
+ assert refund = @gateway.refund(@amount - 20, response.authorization)
+ assert refund.test?
+ refund_id = refund.params['id']
+ assert_equal refund.authorization, refund_id
+ assert_success refund
+ end
+
+ def test_successful_refund_with_reason
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+
+ assert refund = @gateway.refund(@amount - 20, response.authorization, reason: 'fraudulent')
+ assert refund.test?
+ refund_id = refund.params['id']
+ assert_equal refund.authorization, refund_id
+ assert_success refund
+ assert_equal 'fraudulent', refund.params['reason']
+ end
+
+ def test_successful_refund_on_verified_bank_account
+ customer_id = @verified_bank_account[:customer_id]
+ bank_account_id = @verified_bank_account[:bank_account_id]
+ payment = [customer_id, bank_account_id].join('|')
+
+ purchase = @gateway.purchase(@amount, payment, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert refund.test?
+ refund_id = refund.params['id']
+ assert_equal refund.authorization, refund_id
+ end
+
+ def test_refund_with_reverse_transfer
+ destination = fixtures(:stripe_destination)[:stripe_user_id]
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(destination: destination))
+ assert_success response
+
+ assert refund = @gateway.refund(@amount - 20, response.authorization, reverse_transfer: true)
+ assert_success refund
+ assert_equal 'Transaction approved', refund.message
end
def test_unsuccessful_refund
- assert refund = @gateway.refund(@amount, "active_merchant_fake_charge")
+ assert refund = @gateway.refund(@amount, 'active_merchant_fake_charge')
assert_failure refund
- assert_match /active_merchant_fake_charge/, refund.message
+ assert_match %r{active_merchant_fake_charge}, refund.message
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+
+ assert_equal 'Transaction approved', response.message
+ assert_equal 'wow@example.com', response.params['metadata']['email']
+ assert_equal 'charge', response.params['object']
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'refund', response.responses.last.params['object']
+ end
+
+ def test_successful_verify_cad
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'cad'))
+ assert_success response
+ assert_equal 100, response.params['amount']
+ assert_equal 'cad', response.params['currency']
+ end
+
+ def test_successful_verify_gbp
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'gbp'))
+ assert_success response
+ assert_equal 60, response.params['amount']
+ assert_equal 'gbp', response.params['currency']
+ end
+
+ def test_successful_verify_eur
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'eur'))
+ assert_success response
+ assert_equal 100, response.params['amount']
+ assert_equal 'eur', response.params['currency']
+ end
+
+ def test_successful_verify_dkk
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'dkk'))
+ assert_success response
+ assert_equal 500, response.params['amount']
+ assert_equal 'dkk', response.params['currency']
+ end
+
+ def test_successful_verify_nok
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'nok'))
+ assert_success response
+ assert_equal 600, response.params['amount']
+ assert_equal 'nok', response.params['currency']
+ end
+
+ def test_successful_verify_sek
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'sek'))
+ assert_success response
+ assert_equal 600, response.params['amount']
+ assert_equal 'sek', response.params['currency']
+ end
+
+ def test_successful_verify_chf
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'chf'))
+ assert_success response
+ assert_equal 100, response.params['amount']
+ assert_equal 'chf', response.params['currency']
+ end
+
+ def test_successful_verify_aud
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'aud'))
+ assert_success response
+ assert_equal 100, response.params['amount']
+ assert_equal 'aud', response.params['currency']
+ end
+
+ def test_successful_verify_jpy
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'jpy'))
+ assert_success response
+ assert_equal 100, response.params['amount']
+ assert_equal 'jpy', response.params['currency']
+ end
+
+ def test_successful_verify_mxn
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'mxn'))
+ assert_success response
+ assert_equal 2000, response.params['amount']
+ assert_equal 'mxn', response.params['currency']
+ end
+
+ def test_successful_verify_sgd
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'sgd'))
+ assert_success response
+ assert_equal 100, response.params['amount']
+ assert_equal 'sgd', response.params['currency']
+ end
+
+ def test_successful_verify_hkd
+ assert response = @gateway.verify(@credit_card, @options.merge(currency: 'hkd'))
+ assert_success response
+ assert_equal 800, response.params['amount']
+ assert_equal 'hkd', response.params['currency']
+ end
+
+ def test_unsuccessful_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{Your card was declined}, response.message
end
def test_successful_store
- assert response = @gateway.store(@credit_card, {:currency => 'CAD', :description => "Active Merchant Test Customer", :email => "email@example.com"})
+ assert response = @gateway.store(@credit_card, description: 'TheDescription', email: 'email@example.com')
assert_success response
- assert_equal "customer", response.params["object"]
- assert_equal "Active Merchant Test Customer", response.params["description"]
- assert_equal "email@example.com", response.params["email"]
- assert_equal @credit_card.last_digits, response.params["active_card"]["last4"]
+ assert_equal 'customer', response.params['object']
+ assert_equal 'TheDescription', response.params['description']
+ assert_equal 'email@example.com', response.params['email']
+ first_card = response.params['sources']['data'].first
+ assert_equal response.params['default_source'], first_card['id']
+ assert_equal @credit_card.last_digits, first_card['last4']
end
- def test_successful_update
- creation = @gateway.store(@credit_card, {:description => "Active Merchant Update Customer"})
- assert response = @gateway.update(creation.params['id'], @new_credit_card)
+ def test_successful_store_with_validate_false
+ assert response = @gateway.store(@credit_card, validate: false)
assert_success response
- assert_equal "Active Merchant Update Customer", response.params["description"]
- assert_equal @new_credit_card.last_digits, response.params["active_card"]["last4"]
+ assert_equal 'customer', response.params['object']
end
- def test_successful_unstore
- creation = @gateway.store(@credit_card, {:description => "Active Merchant Unstore Customer"})
- assert response = @gateway.unstore(creation.params['id'])
+ def test_successful_store_with_existing_customer
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+
+ assert response = @gateway.store(@new_credit_card, customer: response.params['id'], email: 'email@example.com', description: 'TheDesc')
assert_success response
- assert_equal true, response.params["deleted"]
+ assert_equal 2, response.responses.size
+
+ card_response = response.responses[0]
+ assert_equal 'card', card_response.params['object']
+ assert_equal @new_credit_card.last_digits, card_response.params['last4']
+
+ customer_response = response.responses[1]
+ assert_equal 'customer', customer_response.params['object']
+ assert_equal 'TheDesc', customer_response.params['description']
+ assert_equal 'email@example.com', customer_response.params['email']
+ assert_equal 2, customer_response.params['sources']['total_count']
end
- def test_successful_recurring
- assert response = @gateway.store(@credit_card, {:description => "Active Merchant Test Customer", :email => "email@example.com"})
+ def test_successful_store_with_existing_account
+ account = fixtures(:stripe_destination)[:stripe_user_id]
+
+ assert response = @gateway.store(@debit_card, account: account)
assert_success response
- assert recharge_options = @options.merge(:customer => response.params["id"])
- assert response = @gateway.purchase(@amount, nil, recharge_options)
+ assert_equal 'card', response.params['object']
+ end
+
+ def test_successful_purchase_using_stored_card
+ assert store = @gateway.store(@credit_card)
+ assert_success store
+
+ assert response = @gateway.purchase(@amount, store.authorization)
assert_success response
- assert_equal "charge", response.params["object"]
- assert response.params["paid"]
+ assert_equal 'Transaction approved', response.message
+
+ assert response.params['paid']
+ assert_equal '4242', response.params['source']['last4']
+ end
+
+ def test_successful_purchase_using_stored_card_on_existing_customer
+ assert first_store_response = @gateway.store(@credit_card)
+ assert_success first_store_response
+
+ assert second_store_response = @gateway.store(@new_credit_card, customer: first_store_response.params['id'])
+ assert_success second_store_response
+
+ assert response = @gateway.purchase(@amount, second_store_response.authorization)
+ assert_success response
+ assert_equal '5100', response.params['source']['last4']
+ end
+
+ def test_successful_purchase_using_stored_card_and_deprecated_api
+ assert store = @gateway.store(@credit_card)
+ assert_success store
+
+ recharge_options = @options.merge(:customer => store.params['id'])
+ assert_deprecation_warning do
+ response = @gateway.purchase(@amount, nil, recharge_options)
+ assert_success response
+ assert_equal '4242', response.params['source']['last4']
+ end
+ end
+
+ def test_successful_unstore
+ creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'})
+ card_id = creation.params['sources']['data'].first['id']
+
+ assert response = @gateway.unstore(creation.authorization)
+ assert_success response
+ assert_equal card_id, response.params['id']
+ assert_equal true, response.params['deleted']
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_successful_unstore_using_deprecated_api
+ creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'})
+ card_id = creation.params['sources']['data'].first['id']
+ customer_id = creation.params['id']
+
+ assert_deprecation_warning do
+ response = @gateway.unstore(customer_id, card_id)
+ assert_success response
+ assert_equal true, response.params['deleted']
+ end
+ end
+
+ def test_successful_store_of_bank_account
+ response = @gateway.store(@check, @options)
+ assert_success response
+ customer_id, bank_account_id = response.authorization.split('|')
+ assert_match(/^cus_/, customer_id)
+ assert_match(/^ba_/, bank_account_id)
+ end
+
+ def test_unsuccessful_purchase_from_stored_but_unverified_bank_account
+ store = @gateway.store(@check)
+ assert_success store
+
+ purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_failure purchase
+ assert_match "The customer's bank account must be verified", purchase.message
+ end
+
+ def test_successful_purchase_from_stored_and_verified_bank_account
+ store = @gateway.store(@check)
+ assert_success store
+
+ # verify the account using special test amounts from Stripe
+ # https://stripe.com/docs/guides/ach#manually-collecting-and-verifying-bank-accounts
+ customer_id, bank_account_id = store.authorization.split('|')
+ verify_url = "customers/#{customer_id}/sources/#{bank_account_id}/verify"
+ verify_response = @gateway.send(:api_request, :post, verify_url, { amounts: [32, 45] })
+ assert_match 'verified', verify_response['status']
+
+ purchase = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success purchase
end
def test_invalid_login
gateway = StripeGateway.new(:login => 'active_merchant_test')
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_match "Invalid API Key provided", response.message
- end
-
- def test_application_fee_for_stripe_connect
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12 ))
- assert response.params['fee_details'], 'This test will only work if your gateway login is a Stripe Connect access_token.'
- assert response.params['fee_details'].any? do |fee|
- (fee['type'] == 'application_fee') && (fee['amount'] == 12)
- end
+ assert_match 'Invalid API Key provided', response.message
end
+ # These "track data present" tests fail with invalid expiration dates. The
+ # test track data probably needs to be updated.
def test_card_present_purchase
- @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?'
+ @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2205101130504392?'
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "charge", response.params["object"]
- assert response.params["paid"]
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
end
def test_card_present_authorize_and_capture
- @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?'
+ @credit_card.track_data = '%B378282246310005^LONGSON/LONGBOB^2205101130504392?'
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
- assert !authorization.params["captured"]
+ refute authorization.params['captured']
assert capture = @gateway.capture(@amount, authorization.authorization)
assert_success capture
end
- def test_successful_refund_with_application_fee
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12))
- assert response.params['fee_details'], 'This test will only work if your gateway login is a Stripe Connect access_token.'
- assert refund = @gateway.refund(@amount, response.authorization, :refund_application_fee => true)
- assert_success refund
- assert_equal 12, refund.params["fee_details"].first["amount_refunded"]
+ def test_creditcard_purchase_with_customer
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:customer => '1234'))
+ assert_success response
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
end
- def test_refund_partial_application_fee
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:application_fee => 12))
- assert response.params['fee_details'], 'This test will only work if your gateway login is a Stripe Connect access_token.'
- assert refund = @gateway.refund(@amount - 20, response.authorization, { :refund_fee_amount => 10 })
- assert_success refund
+ def test_expanding_objects
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:expand => 'balance_transaction'))
+ assert_success response
+ assert response.params['balance_transaction'].is_a?(Hash)
+ assert_equal 'balance_transaction', response.params['balance_transaction']['object']
+ end
+
+ def test_successful_update
+ creation = @gateway.store(@credit_card, {:description => 'Active Merchant Update Credit Card'})
+ customer_id = creation.params['id']
+ card_id = creation.params['sources']['data'].first['id']
+
+ assert response = @gateway.update(customer_id, card_id, { :name => 'John Doe', :address_line1 => '123 Main Street', :address_city => 'Pleasantville', :address_state => 'NY', :address_zip => '12345', :exp_year => Time.now.year + 2, :exp_month => 6 })
+ assert_success response
+ assert_equal 'John Doe', response.params['name']
+ assert_equal '123 Main Street', response.params['address_line1']
+ assert_equal 'Pleasantville', response.params['address_city']
+ assert_equal 'NY', response.params['address_state']
+ assert_equal '12345', response.params['address_zip']
+ assert_equal Time.now.year + 2, response.params['exp_year']
+ assert_equal 6, response.params['exp_month']
+ end
+
+ def test_incorrect_number_for_purchase
+ card = credit_card('4242424242424241')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code
+ end
+
+ def test_invalid_number_for_purchase
+ card = credit_card('-1')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_number], response.error_code
+ end
+
+ def test_invalid_expiry_month_for_purchase
+ card = credit_card('4242424242424242', month: 16)
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code
end
+
+ def test_invalid_expiry_year_for_purchase
+ card = credit_card('4242424242424242', year: 'xx')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code
+ end
+
+ def test_expired_card_for_purchase
+ card = credit_card('4000000000000069')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:expired_card], response.error_code
+ end
+
+ def test_invalid_cvc_for_purchase
+ card = credit_card('4242424242424242', verification_value: -1)
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:invalid_cvc], response.error_code
+ end
+
+ def test_incorrect_cvc_for_purchase
+ card = credit_card('4000000000000127')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:incorrect_cvc], response.error_code
+ end
+
+ def test_processing_error
+ card = credit_card('4000000000000119')
+ assert response = @gateway.purchase(@amount, card, @options)
+ assert_failure response
+ assert_match Gateway::STANDARD_ERROR_CODE[:processing_error], response.error_code
+ end
+
+ def test_statement_description
+ assert response = @gateway.purchase(@amount, @credit_card, statement_description: 'Eggcellent Description')
+ assert_success response
+ assert_equal 'Eggcellent Description', response.params['statement_descriptor']
+ end
+
+ def test_stripe_account_header
+ account = fixtures(:stripe_destination)[:stripe_user_id]
+ assert response = @gateway.purchase(@amount, @credit_card, stripe_account: account)
+ assert_success response
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = StripeGateway.new(login: 'an_unknown_api_key')
+ assert !gateway.verify_credentials
+ end
+
end
diff --git a/test/remote/gateways/remote_swipe_checkout_test.rb b/test/remote/gateways/remote_swipe_checkout_test.rb
new file mode 100644
index 00000000000..6299a506fd2
--- /dev/null
+++ b/test/remote/gateways/remote_swipe_checkout_test.rb
@@ -0,0 +1,69 @@
+require 'test_helper'
+
+class RemoteSwipeCheckoutTest < Test::Unit::TestCase
+ def setup
+ @gateway = SwipeCheckoutGateway.new(fixtures(:swipe_checkout))
+
+ @amount = 100
+ @accepted_card = credit_card('1234123412341234')
+ @declined_card = credit_card('1111111111111111')
+ @invalid_card = credit_card('1000000000000000')
+ @empty_card = credit_card('')
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @accepted_card, @options)
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_region_switching
+ assert response = @gateway.purchase(@amount, @accepted_card, @options.merge(:region => 'CA'))
+ assert_success response
+ assert_equal 'Transaction approved', response.message
+ end
+
+ def test_unsuccessful_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Transaction declined', response.message
+ end
+
+ def test_invalid_login
+ gateway = SwipeCheckoutGateway.new(
+ login: 'invalid',
+ api_key: 'invalid',
+ region: 'NZ'
+ )
+ assert response = gateway.purchase(@amount, @accepted_card, @options)
+ assert_failure response
+ assert_equal 'Access Denied', response.message
+ end
+
+ def test_invalid_card
+ # Note: Swipe Checkout transaction API returns declined if the card number
+ # is invalid, and "invalid card data" if the card number is empty
+ assert response = @gateway.purchase(@amount, @invalid_card, @options)
+ assert_failure response
+ assert_equal 'Transaction declined', response.message
+ assert_equal 200, response.params['response_code']
+ end
+
+ def test_empty_card
+ assert response = @gateway.purchase(@amount, @empty_card, @options)
+ assert_failure response
+ assert_equal 'Invalid card data', response.message
+ assert_equal 303, response.params['response_code']
+ end
+
+ def test_no_options
+ assert response = @gateway.purchase(@amount, @accepted_card, {})
+ assert_success response
+ end
+end
diff --git a/test/remote/gateways/remote_telr_test.rb b/test/remote/gateways/remote_telr_test.rb
new file mode 100644
index 00000000000..bf0477ac36f
--- /dev/null
+++ b/test/remote/gateways/remote_telr_test.rb
@@ -0,0 +1,167 @@
+require 'test_helper'
+
+class RemoteTelrTest < Test::Unit::TestCase
+ def setup
+ @gateway = TelrGateway.new(fixtures(:telr))
+
+ @amount = 100
+ @credit_card = credit_card('5105105105105100')
+ @declined_card = credit_card('5105105105105100', verification_value: '031')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Test transaction',
+ email: 'email@address.com'
+ }
+ end
+
+ def test_invalid_login
+ gateway = TelrGateway.new(merchant_id: '', api_key: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Invalid request', response.message
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_sans_options
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Not authorised', response.message
+ assert_equal '31', response.error_code
+ end
+
+ def test_successful_reference_purchase
+ assert ref_response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success ref_response
+
+ response = @gateway.purchase(@amount, ref_response.authorization, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'Not authorised', response.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_equal 'Invalid transaction reference', response.message
+ assert_equal '22', response.error_code
+ end
+
+ def test_successful_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_equal 'Transaction cost or currency not valid', response.message
+ assert_equal '05', response.error_code
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ end
+
+ def test_partial_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(50, response.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '0')
+ assert_failure response
+ assert_equal 'Invalid transaction reference', response.message
+ assert_equal '22', response.error_code
+ end
+
+ def test_excess_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(200, response.authorization)
+ assert_failure refund
+ assert_equal 'Amount greater than available balance', refund.message
+ assert_equal '29', refund.error_code
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{Succeeded}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Not authorised', response.message
+ assert_equal '31', response.error_code
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = TelrGateway.new(merchant_id: 'unknown', api_key: 'unknown')
+ assert !gateway.verify_credentials
+ gateway = TelrGateway.new(merchant_id: fixtures(:telr)[:merchant_id], api_key: 'unknown')
+ assert !gateway.verify_credentials
+ end
+
+ def test_cvv_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'M', response.cvv_result['code']
+ end
+
+ def test_avs_result
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'I', response.avs_result['code']
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:api_key], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_tns_test.rb b/test/remote/gateways/remote_tns_test.rb
new file mode 100644
index 00000000000..cc1aa80efa1
--- /dev/null
+++ b/test/remote/gateways/remote_tns_test.rb
@@ -0,0 +1,133 @@
+require 'test_helper'
+
+class RemoteTnsTest < Test::Unit::TestCase
+
+ def setup
+ TnsGateway.ssl_strict = false # Sandbox has an improperly installed cert
+ @gateway = TnsGateway.new(fixtures(:tns))
+
+ @amount = 100
+ @credit_card = credit_card('5123456789012346')
+ @ap_credit_card = credit_card('5424180279791732', month: 05, year: 2017, verification_value: 222)
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def teardown
+ TnsGateway.ssl_strict = true
+ end
+
+ def test_successful_purchase
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_sans_options
+ assert response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ more_options = @options.merge({
+ ip: '127.0.0.1',
+ email: 'joe@example.com',
+ })
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(more_options))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_region
+ @gateway = TnsGateway.new(fixtures(:tns_ap).merge(region: 'asia_pacific'))
+
+ assert response = @gateway.purchase(@amount, @ap_credit_card, @options.merge(currency: 'AUD'))
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ assert response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'FAILURE - DECLINED', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^.+\|\d+$), response.authorization
+
+ assert capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 'FAILURE - DECLINED', response.message
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+
+ assert_success response.responses.last, 'The void should succeed'
+ assert_equal 'SUCCESS', response.responses.last.params['result']
+ end
+
+ def test_invalid_login
+ gateway = TnsGateway.new(
+ :userid => 'nosuch',
+ :password => 'thing'
+ )
+ response = gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'ERROR - INVALID_REQUEST - Invalid credentials.', response.message
+ end
+
+ def test_transcript_scrubbing
+ card = credit_card('5123456789012346', verification_value: '834')
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_verify_credentials
+ assert @gateway.verify_credentials
+
+ gateway = TnsGateway.new(userid: 'unknown', password: 'unknown')
+ assert !gateway.verify_credentials
+ end
+end
diff --git a/test/remote/gateways/remote_trans_first_test.rb b/test/remote/gateways/remote_trans_first_test.rb
index e5c13209ba9..3423954560e 100644
--- a/test/remote/gateways/remote_trans_first_test.rb
+++ b/test/remote/gateways/remote_trans_first_test.rb
@@ -5,21 +5,104 @@ class RemoteTransFirstTest < Test::Unit::TestCase
def setup
@gateway = TransFirstGateway.new(fixtures(:trans_first))
- @credit_card = credit_card('4111111111111111')
- @amount = 100
- @options = {
+ @credit_card = credit_card('4485896261017708', verification_value: 999)
+ @check = check
+ @amount = 1201
+ @options = {
:order_id => generate_unique_id,
:invoice => 'ActiveMerchant Sale',
:billing_address => address
}
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_equal 'test transaction', response.message
assert response.test?
assert_success response
assert !response.authorization.blank?
+
+ @gateway.void(response.authorization)
+ end
+
+ def test_successful_purchase_no_address
+ @options.delete(:billing_address)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response.test?
+ assert_success response
+ assert !response.authorization.blank?
+
+ @gateway.void(response.authorization)
+ end
+
+ def test_successful_purchase_sans_cvv
+ @credit_card.verification_value = ''
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert response.test?
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_echeck_no_address
+ @options.delete(:billing_address)
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert response.test?
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_successful_purchase_with_echeck_defaults
+ @check = check(account_holder_type: nil, account_type: nil)
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert response.test?
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
+ def test_failed_purchase
+ @amount = 21
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Insufficient funds', response.message
+ end
+
+ def test_successful_void
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert void = @gateway.void(purchase.authorization)
+ assert_success void
+ end
+
+ # Refunds can only be successfully run on settled transactions which take 24 hours
+ # def test_successful_refund
+ # assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_success purchase
+
+ # assert refund = @gateway.refund(@amount, purchase.authorization)
+ # assert_equal @amount, refund.params["amount"].to_i*100
+ # assert_success refund
+ # end
+
+ # def test_successful_partial_refund
+ # assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_success purchase
+
+ # assert refund = @gateway.refund(@amount-1, purchase.authorization)
+ # assert_equal @amount-1, refund.params["amount"].to_i*100
+ # assert_success refund
+ # end
+
+ def test_successful_refund_with_echeck
+ assert purchase = @gateway.purchase(@amount, @check, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
end
def test_invalid_login
@@ -27,8 +110,29 @@ def test_invalid_login
:login => '',
:password => ''
)
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_equal 'invalid account', response.message
+ assert response = gateway.purchase(1100, @credit_card, @options)
assert_failure response
end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_transcript_scrubbing_echecks
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@check.routing_number, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
end
diff --git a/test/remote/gateways/remote_trans_first_transaction_express_test.rb b/test/remote/gateways/remote_trans_first_transaction_express_test.rb
new file mode 100644
index 00000000000..3e69c82ecee
--- /dev/null
+++ b/test/remote/gateways/remote_trans_first_transaction_express_test.rb
@@ -0,0 +1,397 @@
+require 'test_helper'
+
+class RemoteTransFirstTransactionExpressTest < Test::Unit::TestCase
+
+ def setup
+ @gateway = TransFirstTransactionExpressGateway.new(fixtures(:trans_first_transaction_express))
+
+ @amount = 100
+ @declined_amount = 21
+ @credit_card = credit_card('4485896261017708', verification_value: 999)
+ @check = check
+
+ billing_address = address({
+ address1: '450 Main',
+ address2: 'Suite 100',
+ city: 'Broomfield',
+ state: 'CO',
+ zip: '85284',
+ phone: '(333) 444-5555',
+ })
+
+ @options = {
+ order_id: generate_unique_id,
+ company_name: 'Acme',
+ title: 'QA Manager',
+ billing_address: billing_address,
+ shipping_address: billing_address,
+ email: 'example@example.com',
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_invalid_login
+ gateway = TransFirstTransactionExpressGateway.new(gateway_id: '', reg_key: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_not_nil response.avs_result
+ assert_not_nil response.cvv_result
+ assert_equal 'Street address does not match, but 5-digit postal code matches.', response.avs_result['message']
+ assert_equal 'CVV matches', response.cvv_result['message']
+ end
+
+ def test_successful_purchase_no_avs
+ options = @options.dup
+ options[:shipping_address] = nil
+ options[:billing_address] = nil
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_only_required
+ # Test the purchase with only the required billing and shipping information
+ options = @options.dup
+ options[:shipping_address] = {
+ address1: '450 Main',
+ zip: '85284',
+ }
+
+ options[:billing_address] = {
+ address1: '450 Main',
+ zip: '85284',
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_not_nil response.avs_result
+ assert_not_nil response.cvv_result
+ assert_equal 'Street address does not match, but 5-digit postal code matches.', response.avs_result['message']
+ assert_equal 'CVV matches', response.cvv_result['message']
+ end
+
+ def test_successful_purchase_without_address2
+ # Test that empty string in `address2` doesn't cause transaction failure
+ options = @options.dup
+ options[:shipping_address] = {
+ address1: '450 Main',
+ address2: '',
+ zip: '85284',
+ }
+
+ options[:billing_address] = {
+ address1: '450 Main',
+ address2: '',
+ zip: '85284',
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_without_cvv
+ credit_card_opts = {
+ :number => 4485896261017708,
+ :month => Date.new((Time.now.year + 1), 9, 30).month,
+ :year => Date.new((Time.now.year + 1), 9, 30).year,
+ :first_name => 'Longbob',
+ :last_name => 'Longsen',
+ :brand => 'visa'
+ }
+
+ credit_card = CreditCard.new(credit_card_opts)
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_empty_string_cvv
+ credit_card_opts = {
+ :number => 4485896261017708,
+ :month => Date.new((Time.now.year + 1), 9, 30).month,
+ :year => Date.new((Time.now.year + 1), 9, 30).year,
+ :first_name => 'Longbob',
+ :last_name => 'Longsen',
+ :verification_value => '',
+ :brand => 'visa'
+ }
+
+ credit_card = CreditCard.new(credit_card_opts)
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_without_name
+ credit_card_opts = {
+ :number => 4485896261017708,
+ :month => Date.new((Time.now.year + 1), 9, 30).month,
+ :year => Date.new((Time.now.year + 1), 9, 30).year,
+ :first_name => '',
+ :last_name => ''
+ }
+
+ credit_card = CreditCard.new(credit_card_opts)
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+
+ credit_card_opts = {
+ :number => 4485896261017708,
+ :month => Date.new((Time.now.year + 1), 9, 30).month,
+ :year => Date.new((Time.now.year + 1), 9, 30).year
+ }
+
+ credit_card = CreditCard.new(credit_card_opts)
+ response = @gateway.purchase(@amount, credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Not sufficient funds', response.message
+ assert_equal '51', response.params['rspCode']
+ end
+
+ def test_failed_purchase_with_echeck
+ assert response = @gateway.purchase(@amount, check(routing_number: '121042883'), @options)
+ assert_failure response
+ assert_equal 'Error. Bank routing number validation negative (ABA).', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^authorize\|\d+$), response.authorization
+
+ capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Not sufficient funds', response.message
+ assert_equal '51', response.error_code
+ end
+
+ def test_failed_capture
+ authorize = @gateway.authorize(@declined_amount, @credit_card, @options)
+ assert_failure authorize
+
+ response = @gateway.capture(@amount, authorize.authorization)
+ assert_failure response
+ assert_equal 'Invalid transaction', response.message
+ assert_equal '12', response.error_code
+ end
+
+ def test_successful_purchase_void
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_successful_authorization_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_successful_capture_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ capture = @gateway.capture(@amount, authorize.authorization)
+ assert_success capture
+
+ void = @gateway.void(capture.authorization, void_type: :void_capture)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('purchase|000015212561')
+ assert_failure response
+ assert_equal 'Invalid transaction', response.message
+ assert_equal '12', response.error_code
+ end
+
+ def test_successful_echeck_purchase_void
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'Succeeded', void.message
+ end
+
+ # gateway does not settle fast enough to test refunds
+ # def test_successful_refund
+ # response = @gateway.purchase(@amount, @credit_card, @options)
+ # assert_success response
+
+ # refund = @gateway.refund(@amount, response.authorization)
+ # assert_success refund
+ # assert_equal "Succeeded", refund.message
+ # end
+
+ def test_helpful_message_when_refunding_unsettled_purchase
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+
+ assert_failure refund
+ assert_equal 'Invalid transaction. Declined Post – Credit linked to unextracted settle transaction', refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ assert_equal 'Validation Failure', response.message
+ assert_equal '50011', response.error_code
+ end
+
+ def test_successful_refund_with_echeck
+ purchase = @gateway.purchase(@amount, @check, @options)
+ assert_success purchase
+ assert_match(/purchase_echeck/, purchase.authorization)
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund_with_echeck
+ refund = @gateway.refund(@amount, 'purchase_echeck|000028706091')
+ assert_failure refund
+ assert_equal 'Invalid transaction', refund.message
+ end
+
+ # Credit is only supported with specific approval from Transaction Express
+ # def test_successful_credit
+ # response = @gateway.credit(@amount, @credit_card, @options)
+ # assert_success response
+ # assert_equal "Succeeded", response.message
+ # end
+
+ def test_failed_credit
+ response = @gateway.credit(0, @credit_card, @options)
+ assert_failure response
+ assert_equal '51334', response.error_code
+ assert_equal 'Validation Error', response.message
+ end
+
+ def test_successful_verify
+ visa = credit_card('4485896261017708')
+ amex = credit_card('371449635392376', verification_value: 1234)
+ mastercard = credit_card('5499740000000057')
+ discover = credit_card('6011000991001201')
+
+ [visa, amex, mastercard, discover].each do |credit_card|
+ response = @gateway.verify(credit_card, @options)
+ assert_success response
+ assert_match 'Succeeded', response.message
+ end
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(credit_card(''), @options)
+ assert_failure response
+ assert_equal 'Validation Failure', response.message
+ assert_equal '51308', response.error_code
+ end
+
+ def test_successful_store
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert response.authorization
+ end
+
+ def test_successful_authorize_using_stored_card
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+
+ response = @gateway.authorize(@amount, response.authorization, @options)
+ assert_success response
+ assert_match 'Succeeded', response.message
+ end
+
+ def test_failed_authorize_using_stored_card
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+
+ response = @gateway.authorize(@declined_amount, response.authorization, @options)
+ assert_failure response
+ assert_match 'Not sufficient funds', response.message
+ end
+
+ def test_successful_purchase_using_stored_card
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+
+ response = @gateway.purchase(@amount, response.authorization, @options)
+ assert_success response
+ assert_match 'Succeeded', response.message
+ end
+
+ def test_failed_purchase_using_stored_card
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+
+ response = @gateway.purchase(@declined_amount, response.authorization, @options)
+ assert_failure response
+ assert_match 'Not sufficient funds', response.message
+ end
+
+ def test_failed_store
+ response = @gateway.store(credit_card('123'), @options)
+ assert_failure response
+ assert_equal 'Validation Failure', response.message
+ assert_equal '51308', response.error_code
+ end
+
+ # def test_dump_transcript
+ # skip("Transcript scrubbing for this gateway has been tested.")
+ # # This test will run a purchase transaction on your gateway
+ # # and dump a transcript of the HTTP conversation so that
+ # # you can use that transcript as a reference while
+ # # implementing your scrubbing logic
+ # dump_transcript_and_fail(@gateway, @amount, @credit_card, @options)
+ # end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:gateway_id], clean_transcript)
+ assert_scrubbed(@gateway.options[:reg_key], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_transact_pro_test.rb b/test/remote/gateways/remote_transact_pro_test.rb
new file mode 100644
index 00000000000..5e5c428ae29
--- /dev/null
+++ b/test/remote/gateways/remote_transact_pro_test.rb
@@ -0,0 +1,130 @@
+require 'test_helper'
+require 'active_support/core_ext/hash/slice'
+
+class RemoteTransactProTest < Test::Unit::TestCase
+ def setup
+ test_credentials = fixtures(:transact_pro).slice(:guid, :password, :terminal)
+ test_card = fixtures(:transact_pro).slice(:card_number, :verification_value, :month, :year)
+
+ @gateway = TransactProGateway.new(test_credentials)
+
+ @amount = 100
+ @credit_card = credit_card(test_card.delete(:card_number), test_card)
+ @declined_card = credit_card('4000300011112220')
+
+ @options = {
+ order_id: Time.now.to_i,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert response.authorization
+ assert_equal 'Success', response.message
+ assert response.test?
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+
+ assert_failure response
+ assert_equal 'Failed', response.message
+ assert_equal '908', response.params['result_code']
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ capture = @gateway.capture(nil, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+
+ assert_failure response
+ assert_equal 'Failed', response.message
+ assert_equal '908', response.params['result_code']
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert_raise(ArgumentError) do
+ @gateway.capture(@amount-1, auth.authorization)
+ end
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, 'bogus|100')
+ assert_failure response
+ assert_equal 'bogus|100', response.authorization
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(nil, purchase.authorization)
+ assert_success refund
+ assert_equal 'Refund Success', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ assert_equal 'Refund Success', refund.message
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount+1, purchase.authorization)
+ assert_failure refund
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 'Failed', response.message
+ end
+
+ def test_invalid_login
+ gateway = TransactProGateway.new(
+ guid: '',
+ password: '',
+ terminal: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{bad access data}, response.message
+ end
+end
diff --git a/test/remote/gateways/remote_transax_test.rb b/test/remote/gateways/remote_transax_test.rb
index 0e9a0e4e16b..1e7bbc00399 100644
--- a/test/remote/gateways/remote_transax_test.rb
+++ b/test/remote/gateways/remote_transax_test.rb
@@ -20,19 +20,19 @@ def setup
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "This transaction has been approved", response.message
+ assert_equal 'This transaction has been approved', response.message
end
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_match /invalid/i, response.message
+ assert_match %r{invalid}i, response.message
end
def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
- assert_equal "This transaction has been approved", auth.message
+ assert_equal 'This transaction has been approved', auth.message
assert auth.authorization
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
@@ -41,43 +41,43 @@ def test_authorize_and_capture
def test_authorize_and_void
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
- assert_equal "This transaction has been approved", auth.message
+ assert_equal 'This transaction has been approved', auth.message
assert auth.authorization
assert void = @gateway.void(auth.authorization)
- assert_equal "Transaction Void Successful", void.message
+ assert_equal 'Transaction Void Successful', void.message
assert_success void
end
def test_purchase_and_refund
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "This transaction has been approved", response.message
+ assert_equal 'This transaction has been approved', response.message
assert response.authorization
assert refund = @gateway.refund(nil, response.authorization)
- assert_equal "This transaction has been approved", refund.message
+ assert_equal 'This transaction has been approved', refund.message
assert_success refund
end
def test_failed_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
- assert_match /Invalid Transaction ID/, response.message
+ assert_match %r{Invalid Transaction ID}, response.message
end
def test_credit
assert response = @gateway.credit(@amount, @credit_card, @options)
assert_success response
assert response.authorization
- assert_equal "This transaction has been approved", response.message
+ assert_equal 'This transaction has been approved', response.message
end
def test_purchase_and_update
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "This transaction has been approved", response.message
+ assert_equal 'This transaction has been approved', response.message
assert response.authorization
assert update = @gateway.amend(response.authorization, :shipping_carrier => 'usps')
- assert_equal "This transaction has been approved", update.message
+ assert_equal 'This transaction has been approved', update.message
assert_success update
end
@@ -85,7 +85,7 @@ def test_successful_purchase_with_sku
@options['product_sku_#']='123456'
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "This transaction has been approved", response.message
+ assert_equal 'This transaction has been approved', response.message
end
def test_store_credit_card
@@ -100,6 +100,19 @@ def test_store_check
assert !response.params['customer_vault_id'].blank?
end
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'This transaction has been approved', response.message
+ end
+
+ def test_failed_verify
+ bogus_card = credit_card('4424222222222222')
+ assert response = @gateway.verify(bogus_card, @options)
+ assert_failure response
+ assert_match %r{Invalid Credit Card Number}, response.message
+ end
+
def test_invalid_login
gateway = TransaxGateway.new(
:login => '',
@@ -107,6 +120,6 @@ def test_invalid_login
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "Invalid Username", response.message
+ assert_equal 'Invalid Username', response.message
end
end
diff --git a/test/remote/gateways/remote_trexle_test.rb b/test/remote/gateways/remote_trexle_test.rb
new file mode 100644
index 00000000000..149a8530797
--- /dev/null
+++ b/test/remote/gateways/remote_trexle_test.rb
@@ -0,0 +1,170 @@
+require 'test_helper'
+
+class RemoteTrexleTest < Test::Unit::TestCase
+ def setup
+ @gateway = TrexleGateway.new(fixtures(:trexle))
+
+ @amount = 100
+ @credit_card = credit_card('5555555555554444', year: Time.now.year + 2)
+ @visa_credit_card = credit_card('4242424242424242', year: Time.now.year + 3)
+ @declined_card = credit_card('4000000000000119')
+
+ @options = {
+ email: 'john@trexle.com',
+ ip: '66.249.79.118',
+ order_id: '1',
+ billing_address: address,
+ description: "ActiveMerchant Demo Purchase #{DateTime.now.to_i}"
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal true, response.params['response']['captured']
+ end
+
+ def test_successful_authorize_and_capture
+ authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert_equal false, authorization.params['response']['captured']
+
+ response = @gateway.capture(@amount, authorization.authorization, @options)
+ assert_success response
+ assert_equal true, response.params['response']['captured']
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_capture_due_to_invalid_token
+ response = @gateway.capture(@amount, 'bogus', @options)
+ assert_failure response
+ end
+
+ def test_failed_capture_due_to_invalid_amount
+ authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert_equal authorization.params['response']['captured'], false
+
+ response = @gateway.capture(@amount + 1, authorization.authorization, @options)
+ assert_failure response
+ assert_equal 'Payment failed', response.params['error']
+ end
+
+ def test_successful_purchase_without_description
+ @options.delete(:description)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ end
+
+ def test_unsuccessful_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ # This is a bit manual as we have to create a working card token as
+ # would be returned from trexle.js / the card tokens API which
+ # falls outside of active merchant
+ def test_store_and_charge_with_trexle_js_card_token
+ headers = {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Basic #{Base64.strict_encode64(@gateway.options[:api_key] + ':').strip}"
+ }
+ # Get a token equivalent to what is returned by trexle.js
+ card_attrs = {
+ number: @credit_card.number,
+ expiry_month: @credit_card.month,
+ expiry_year: @credit_card.year,
+ cvc: @credit_card.verification_value,
+ name: "#{@credit_card.first_name} #{@credit_card.last_name}",
+ address_line1: '321 Shoreline Park',
+ address_line2: 'suite #7',
+ address_city: 'Mountain View',
+ address_postcode: '94043',
+ address_state: 'CA',
+ address_country: 'United States'
+ }
+ url = @gateway.test_url + '/tokens'
+
+ body = JSON.parse(@gateway.ssl_post(url, card_attrs.to_json, headers))
+
+ card_token = body['response']['token']
+
+ store = @gateway.store(card_token, @options)
+ assert_success store
+ assert_not_nil store.authorization
+
+ purchase = @gateway.purchase(@amount, card_token, @options)
+ assert_success purchase
+ assert_not_nil purchase.authorization
+ end
+
+ def test_store_and_customer_token_charge
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_not_nil response.authorization
+
+ token = response.params['response']['card']['token']
+
+ assert response1 = @gateway.purchase(@amount, token, @options)
+ assert_success response1
+
+ assert response2 = @gateway.purchase(@amount, token, @options)
+ assert_success response2
+ assert_not_equal response1.authorization, response2.authorization
+ end
+
+ def test_store_and_update
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_not_nil response.authorization
+ assert_equal @credit_card.year, response.params['response']['card']['expiry_year']
+
+ response = @gateway.update(response.authorization, @credit_card, address: address)
+ assert_success response
+ assert_not_nil response.authorization
+ assert_equal @credit_card.year, response.params['response']['card']['expiry_year']
+ end
+
+ def test_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_not_nil response.authorization
+
+ token = response.authorization
+
+ response = @gateway.refund(@amount, token, @options)
+ assert_success response
+ assert_not_nil response.authorization
+ end
+
+ def test_failed_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_not_nil response.authorization
+
+ token = response.authorization
+
+ response = @gateway.refund(@amount, token.reverse, @options)
+ assert_failure response
+ end
+
+ def test_invalid_login
+ gateway = TrexleGateway.new(api_key: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_trust_commerce_test.rb b/test/remote/gateways/remote_trust_commerce_test.rb
index d62c61af52a..9625597ffc1 100644
--- a/test/remote/gateways/remote_trust_commerce_test.rb
+++ b/test/remote/gateways/remote_trust_commerce_test.rb
@@ -3,22 +3,24 @@
class TrustCommerceTest < Test::Unit::TestCase
def setup
@gateway = TrustCommerceGateway.new(fixtures(:trust_commerce))
-
+
@credit_card = credit_card('4111111111111111')
-
+ @declined_credit_card = credit_card('4111111111111112')
+ @check = check({account_number: 55544433221, routing_number: 789456124})
+
@amount = 100
-
+
@valid_verification_value = '123'
@invalid_verification_value = '1234'
-
+
@valid_address = {
- :address1 => '123 Test St.',
- :address2 => nil,
- :city => 'Somewhere',
- :state => 'CA',
+ :address1 => '123 Test St.',
+ :address2 => nil,
+ :city => 'Somewhere',
+ :state => 'CA',
:zip => '90001'
}
-
+
@invalid_address = {
:address1 => '187 Apple Tree Lane.',
:address2 => nil,
@@ -26,127 +28,196 @@ def setup
:state => 'CA',
:zip => '94062'
}
-
- @options = {
+
+ # The Trust Commerce API does not return anything different when custom fields are present.
+ # To confirm that the field values are being stored with the transactions, add a custom
+ # field in your account in the Vault UI, then examine the transactions after running the
+ # test suite.
+ custom_fields = {
+ 'customfield1' => 'test1'
+ }
+
+ @options = {
:ip => '10.10.10.10',
:order_id => '#1000.1',
- :email => 'cody@example.com',
+ :email => 'cody@example.com',
:billing_address => @valid_address,
- :shipping_address => @valid_address
+ :shipping_address => @valid_address,
+ :custom_fields => custom_fields
}
end
-
+
def test_bad_login
@gateway.options[:login] = 'X'
assert response = @gateway.purchase(@amount, @credit_card, @options)
-
+
assert_equal Response, response.class
- assert_equal ["error",
- "offenders",
- "status"], response.params.keys.sort
+ assert_equal ['error',
+ 'offenders',
+ 'status'], response.params.keys.sort
+
+ assert_match %r{A field was improperly formatted, such as non-digit characters in a number field}, response.message
- assert_match /A field was improperly formatted, such as non-digit characters in a number field/, response.message
-
assert_failure response
end
-
+
def test_successful_purchase_with_avs
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'Y', response.avs_result['code']
- assert_match /The transaction was successful/, response.message
-
+ assert_match %r{The transaction was successful}, response.message
+
assert_success response
assert !response.authorization.blank?
end
-
+
+ def test_successful_purchase_with_check
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert_match %r{The transaction was successful}, response.message
+
+ assert_success response
+ assert !response.authorization.blank?
+ end
+
def test_unsuccessful_purchase_with_invalid_cvv
@credit_card.verification_value = @invalid_verification_value
assert response = @gateway.purchase(@amount, @credit_card, @options)
-
+
assert_equal Response, response.class
- assert_match /CVV failed; the number provided is not the correct verification number for the card/, response.message
+ assert_match %r{CVV failed; the number provided is not the correct verification number for the card}, response.message
assert_failure response
end
-
+
def test_purchase_with_avs_for_invalid_address
assert response = @gateway.purchase(@amount, @credit_card, @options.update(:billing_address => @invalid_address))
- assert_equal "N", response.params["avs"]
- assert_match /The transaction was successful/, response.message
+ assert_equal 'N', response.params['avs']
+ assert_match %r{The transaction was successful}, response.message
assert_success response
end
-
+
+ # Requires enabling the setting: 'Allow voids to process or settle on processing node' in the Trust Commerce vault UI
+ def test_purchase_and_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ void = @gateway.void(purchase.authorization)
+ assert_success void
+ assert_equal 'The transaction was successful', void.message
+ assert_equal 'accepted', void.params['status']
+ assert void.params['transid']
+ end
+
def test_successful_authorize_with_avs
assert response = @gateway.authorize(@amount, @credit_card, :billing_address => @valid_address)
-
- assert_equal "Y", response.avs_result["code"]
- assert_match /The transaction was successful/, response.message
+
+ assert_equal 'Y', response.avs_result['code']
+ assert_match %r{The transaction was successful}, response.message
assert_success response
assert !response.authorization.blank?
end
-
+
def test_unsuccessful_authorize_with_invalid_cvv
@credit_card.verification_value = @invalid_verification_value
assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_match /CVV failed; the number provided is not the correct verification number for the card/, response.message
+ assert_match %r{CVV failed; the number provided is not the correct verification number for the card}, response.message
assert_failure response
end
-
+
def test_authorization_with_avs_for_invalid_address
assert response = @gateway.authorize(@amount, @credit_card, @options.update(:billing_address => @invalid_address))
- assert_equal "N", response.params["avs"]
- assert_match /The transaction was successful/, response.message
+ assert_equal 'N', response.params['avs']
+ assert_match %r{The transaction was successful}, response.message
assert_success response
end
-
+
def test_successful_capture
auth = @gateway.authorize(300, @credit_card)
assert_success auth
response = @gateway.capture(300, auth.authorization)
-
+
assert_success response
- assert_equal 'The transaction was successful', response.message
+ assert_equal 'The transaction was successful', response.message
assert_equal 'accepted', response.params['status']
assert response.params['transid']
end
-
+
def test_authorization_and_void
auth = @gateway.authorize(300, @credit_card, @options)
assert_success auth
-
+
void = @gateway.void(auth.authorization)
assert_success void
- assert_equal 'The transaction was successful', void.message
+ assert_equal 'The transaction was successful', void.message
assert_equal 'accepted', void.params['status']
assert void.params['transid']
end
-
+
def test_successful_credit
assert response = @gateway.credit(@amount, '011-0022698151')
-
- assert_match /The transaction was successful/, response.message
- assert_success response
+
+ assert_match %r{The transaction was successful}, response.message
+ assert_success response
end
-
- def test_store_failure
+
+ def test_successful_check_refund
+ purchase = @gateway.purchase(@amount, @check, @options)
+
+ assert response = @gateway.refund(@amount, purchase.authorization)
+
+ assert_match %r{The transaction was successful}, response.message
+ assert_success response
+ end
+
+ def test_successful_store
assert response = @gateway.store(@credit_card)
-
+
assert_equal Response, response.class
- assert_match %r{The merchant can't accept data passed in this field}, response.message
- assert_failure response
+ assert_equal 'approved', response.params['status']
+ assert_match %r{The transaction was successful}, response.message
end
-
+
+ def test_failed_store
+ assert response = @gateway.store(@declined_credit_card)
+
+ assert_bad_data_response(response)
+ end
+
def test_unstore_failure
- assert response = @gateway.unstore('testme')
+ assert response = @gateway.unstore('does-not-exist')
- assert_match %r{The merchant can't accept data passed in this field}, response.message
+ assert_match %r{A field was longer or shorter than the server allows}, response.message
assert_failure response
end
-
- def test_recurring_failure
+
+ def test_successful_recurring
assert response = @gateway.recurring(@amount, @credit_card, :periodicity => :weekly)
- assert_match %r{The merchant can't accept data passed in this field}, response.message
- assert_failure response
+ assert_match %r{The transaction was successful}, response.message
+ assert_success response
+ end
+
+ def test_failed_recurring
+ assert response = @gateway.recurring(@amount, @declined_credit_card, :periodicity => :weekly)
+
+ assert_bad_data_response(response)
+ end
+
+ def test_transcript_scrubbing
+ @credit_card.verification_value = @invalid_verification_value
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ private
+
+ def assert_bad_data_response(response)
+ assert_equal Response, response.class
+ assert_equal 'A field was improperly formatted, such as non-digit characters in a number field', response.message
+ assert_equal 'baddata', response.params['status']
end
-end
\ No newline at end of file
+end
diff --git a/test/remote/gateways/remote_usa_epay_advanced_test.rb b/test/remote/gateways/remote_usa_epay_advanced_test.rb
index 440c9a350c3..021f73d910a 100644
--- a/test/remote/gateways/remote_usa_epay_advanced_test.rb
+++ b/test/remote/gateways/remote_usa_epay_advanced_test.rb
@@ -2,54 +2,45 @@
require 'logger'
class RemoteUsaEpayAdvancedTest < Test::Unit::TestCase
-
def setup
- # Optional Logger Setup
- # UsaEpayAdvancedGateway.logger = Logger.new('/tmp/usa_epay.log')
- # UsaEpayAdvancedGateway.logger.level = Logger::DEBUG
-
- # Optional Wiredump Setup
- # UsaEpayAdvancedGateway.wiredump_device = File.open('/tmp/usa_epay_dump.log', 'a+')
- # UsaEpayAdvancedGateway.wiredump_device.sync = true
-
@gateway = UsaEpayAdvancedGateway.new(fixtures(:usa_epay_advanced))
@amount = 2111
-
+
@credit_card = ActiveMerchant::Billing::CreditCard.new(
:number => '4000100011112224',
- :month => 12,
- :year => 12,
+ :month => 9,
+ :year => Time.now.year + 1,
:brand => 'visa',
:verification_value => '123',
- :first_name => "Fred",
- :last_name => "Flintstone"
+ :first_name => 'Fred',
+ :last_name => 'Flintstone'
)
@bad_credit_card = ActiveMerchant::Billing::CreditCard.new(
:number => '4000300011112220',
- :month => 12,
- :year => 12,
+ :month => 9,
+ :year => 14,
:brand => 'visa',
:verification_value => '999',
- :first_name => "Fred",
- :last_name => "Flintstone"
+ :first_name => 'Fred',
+ :last_name => 'Flintstone'
)
@check = ActiveMerchant::Billing::Check.new(
- :number => '123456789',
+ :account_number => '123456789',
:routing_number => '120450780',
:account_type => 'checking',
- :first_name => "Fred",
- :last_name => "Flintstone"
+ :first_name => 'Fred',
+ :last_name => 'Flintstone'
)
-
+
cc_method = [
- {:name => "My CC", :sort => 5, :method => @credit_card},
- {:name => "Other CC", :sort => 12, :method => @credit_card}
+ {:name => 'My CC', :sort => 5, :method => @credit_card},
+ {:name => 'Other CC', :sort => 12, :method => @credit_card}
]
- @options = {
+ @options = {
:client_ip => '127.0.0.1',
:billing_address => address,
}
@@ -61,20 +52,20 @@ def setup
@customer_options = {
:id => 123,
- :notes => "Customer note.",
- :data => "complex data",
- :url => "somesite.com",
+ :notes => 'Customer note.',
+ :data => 'complex data',
+ :url => 'somesite.com',
:payment_methods => cc_method
}
@update_customer_options = {
- :notes => "NEW NOTE!"
+ :notes => 'NEW NOTE!'
}
@add_payment_options = {
:make_default => true,
:payment_method => {
- :name => "My new card.",
+ :name => 'My new card.',
:sort => 10,
:method => @credit_card
}
@@ -86,6 +77,12 @@ def setup
:amount => 10000
}
+ @run_transaction_check_options = {
+ :payment_method => @check,
+ :command => 'check',
+ :amount => 10000
+ }
+
@run_sale_options = {
:payment_method => @credit_card,
:amount => 5000
@@ -95,22 +92,10 @@ def setup
:payment_method => @check,
:amount => 2500
}
-
- payment_methods = [
- {
- :name => "My Visa", # optional
- :sort => 2, # optional
- :method => @credit_card
- },
- {
- :name => "My Checking",
- :method => @check
- }
- ]
end
# Standard Gateway ==================================================
-
+
def test_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'A', response.params['run_sale_return']['result_code']
@@ -130,7 +115,7 @@ def test_capture
def test_void
assert purchase = @gateway.purchase(@amount, @credit_card, @options.dup)
-
+
assert credit = @gateway.void(purchase.authorization, @options)
assert_equal 'true', credit.params['void_transaction_return']
end
@@ -138,7 +123,7 @@ def test_void
def test_credit
assert purchase = @gateway.purchase(@amount, @credit_card, @options.dup)
- assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do
+ assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE) do
assert credit = @gateway.credit(@amount, purchase.authorization, @options)
assert_equal 'A', credit.params['refund_transaction_return']['result_code']
end
@@ -161,7 +146,7 @@ def test_invalid_login
assert_failure response
assert_equal 'Invalid software ID', response.message
end
-
+
# Customer ==========================================================
def test_add_customer
@@ -172,12 +157,20 @@ def test_add_customer
def test_update_customer
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
-
+
@options.merge!(@update_customer_options.merge!(:customer_number => customer_number))
response = @gateway.update_customer(@options)
assert response.params['update_customer_return']
end
+ def test_quick_update_customer
+ response = @gateway.add_customer(@options.merge(@customer_options))
+ customer_number = response.params['add_customer_return']
+
+ response = @gateway.quick_update_customer({customer_number: customer_number, update_data: @update_customer_options})
+ assert response.params['quick_update_customer_return']
+ end
+
def test_enable_disable_customer
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
@@ -192,7 +185,7 @@ def test_enable_disable_customer
def test_add_customer_payment_method
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
-
+
@options.merge!(:customer_number => customer_number).merge!(@add_payment_options)
response = @gateway.add_customer_payment_method(@options)
assert response.params['add_customer_payment_method_return']
@@ -201,7 +194,7 @@ def test_add_customer_payment_method
def test_add_customer_payment_method_verify
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
-
+
@add_payment_options[:payment_method][:method] = @bad_credit_card
@options.merge!(:customer_number => customer_number, :verify => true).merge!(@add_payment_options)
response = @gateway.add_customer_payment_method(@options)
@@ -211,7 +204,7 @@ def test_add_customer_payment_method_verify
def test_get_customer_payment_methods
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
-
+
response = @gateway.get_customer_payment_methods(:customer_number => customer_number)
assert response.params['get_customer_payment_methods_return']['item']
end
@@ -230,13 +223,14 @@ def test_get_customer_payment_method
def test_update_customer_payment_method
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
-
+
@options.merge!(:customer_number => customer_number).merge!(@add_payment_options)
response = @gateway.add_customer_payment_method(@options)
payment_method_id = response.params['add_customer_payment_method_return']
- update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id,
- :name => "Updated Card.")
+ update_payment_options = @add_payment_options[:payment_method].merge(:method_id => payment_method_id,
+ :name => 'Updated Card.')
+
response = @gateway.update_customer_payment_method(update_payment_options)
assert response.params['update_customer_payment_method_return']
end
@@ -244,7 +238,7 @@ def test_update_customer_payment_method
def test_delete_customer_payment_method
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
-
+
@options.merge!(:customer_number => customer_number).merge!(@add_payment_options)
response = @gateway.add_customer_payment_method(@options)
id = response.params['add_customer_payment_method_return']
@@ -256,7 +250,7 @@ def test_delete_customer_payment_method
def test_delete_customer
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
-
+
response = @gateway.delete_customer(:customer_number => customer_number)
assert response.params['delete_customer_return']
end
@@ -265,8 +259,8 @@ def test_run_customer_transaction
response = @gateway.add_customer(@options.merge(@customer_options))
customer_number = response.params['add_customer_return']
- response = @gateway.run_customer_transaction(:customer_number => customer_number,# :method_id => 0, # optional
- :command => "Sale", :amount => 3000)
+ response = @gateway.run_customer_transaction(:customer_number => customer_number, # :method_id => 0, # optional
+ :command => 'Sale', :amount => 3000)
assert response.params['run_customer_transaction_return']
end
@@ -276,6 +270,14 @@ def test_run_transaction
@options.merge!(@run_transaction_options)
response = @gateway.run_transaction(@options)
assert response.params['run_transaction_return']
+ assert response.success?
+ end
+
+ def test_run_transaction_check
+ @options.merge!(@run_transaction_check_options)
+ response = @gateway.run_transaction(@options)
+ assert response.params['run_transaction_return']
+ assert response.success?
end
def test_run_sale
@@ -310,7 +312,7 @@ def test_run_check_credit
# TODO get offline auth_code?
def test_post_auth
- @options.merge!(:authorization_code => 123456)
+ @options[:authorization_code] = 123456
response = @gateway.post_auth(@options)
assert response.params['post_auth_return']
end
@@ -345,13 +347,13 @@ def test_refund_transaction
assert response.params['refund_transaction_return']
end
- # TODO how to test override_transction
+ # TODO how to test override_transaction
def test_override_transaction
options = @options.merge(@run_check_sale_options)
response = @gateway.run_check_sale(options)
reference_number = response.params['run_check_sale_return']['ref_num']
- response = @gateway.override_transaction(:reference_number => reference_number, :reason => "Because I said so")
+ response = @gateway.override_transaction(:reference_number => reference_number, :reason => 'Because I said so')
assert response.params['faultstring']
end
@@ -413,9 +415,12 @@ def test_get_transaction_custom
response = @gateway.run_sale(@options.merge(@run_sale_options))
reference_number = response.params['run_sale_return']['ref_num']
- response = @gateway.get_transaction_custom(:reference_number => reference_number,
+ response = @gateway.get_transaction_custom(:reference_number => reference_number,
:fields => ['Response.StatusCode', 'Response.Status'])
assert response.params['get_transaction_custom_return']
+ response = @gateway.get_transaction_custom(:reference_number => reference_number,
+ :fields => ['Response.StatusCode'])
+ assert response.params['get_transaction_custom_return']
end
def test_get_check_trace
diff --git a/test/remote/gateways/remote_usa_epay_transaction_test.rb b/test/remote/gateways/remote_usa_epay_transaction_test.rb
index 0be0bd1e161..476b2461206 100644
--- a/test/remote/gateways/remote_usa_epay_transaction_test.rb
+++ b/test/remote/gateways/remote_usa_epay_transaction_test.rb
@@ -3,20 +3,108 @@
class RemoteUsaEpayTransactionTest < Test::Unit::TestCase
def setup
@gateway = UsaEpayTransactionGateway.new(fixtures(:usa_epay))
- @creditcard = credit_card('4000100011112224')
+ @credit_card = credit_card('4000100011112224')
@declined_card = credit_card('4000300011112220')
- @options = { :billing_address => address(:zip => "27614", :state => "NC") }
+ @credit_card_with_track_data = credit_card_with_track_data('4000100011112224')
+ @invalid_transaction_card = credit_card('4000300511112225')
+ @check = check
+ @options = { :billing_address => address(:zip => '27614', :state => 'NC'), :shipping_address => address }
@amount = 100
end
def test_successful_purchase
- assert response = @gateway.purchase(@amount, @creditcard, @options)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_track_data
+ assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_echeck_and_extra_options
+ extra_options = @options.merge(check_format: 'ARC', account_type: 'savings')
+ assert response = @gateway.purchase(@amount, @check, extra_options)
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_authorization_with_manual_entry
+ @credit_card.manual_entry = true
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_manual_entry
+ @credit_card.manual_entry = true
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_equal 'Success', response.message
assert_success response
end
def test_successful_purchase_with_extra_details
- assert response = @gateway.purchase(@amount, @creditcard, @options.merge(:order_id => generate_unique_id, :description => "socool"))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:order_id => generate_unique_id, :description => 'socool'))
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_extra_test_mode
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:test_mode => true))
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_email_receipt
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:email => 'hank@hill.com', :cust_receipt => 'Yes'))
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_recurring_fields
+ recurring_fields = [
+ add_customer: true,
+ schedule: 'quarterly',
+ bill_source_key: 'bill source key',
+ bill_amount: 123,
+ num_left: 5,
+ start: '20501212',
+ recurring_receipt: true
+ ]
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(recurring_fields: recurring_fields))
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_custom_fields
+ custom_fields = {
+ 1 => 'multi',
+ 2 => 'pass',
+ 3 => 'korben',
+ 4 => 'dallas'
+ }
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(custom_fields: custom_fields))
+ assert_equal 'Success', response.message
+ assert_success response
+ end
+
+ def test_successful_purchase_with_line_items
+ line_items = [
+ {sku: 'abc123', cost: 119, quantity: 1},
+ {sku: 'def456', cost: 200, quantity: 2, name: 'an item' }
+ ]
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(line_items: line_items))
assert_equal 'Success', response.message
assert_success response
end
@@ -28,10 +116,11 @@ def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options.merge(:order_id => generate_unique_id))
assert_failure response
assert_match(/declined/i, response.message)
+ assert Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
end
def test_authorize_and_capture
- assert auth = @gateway.authorize(@amount, @creditcard, @options)
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal 'Success', auth.message
assert auth.authorization
@@ -46,7 +135,23 @@ def test_failed_capture
end
def test_successful_refund
- assert response = @gateway.purchase(@amount, @creditcard, @options)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+ assert refund = @gateway.refund(@amount - 20, response.authorization)
+ assert_success refund
+ end
+
+ def test_successful_refund_with_track_data
+ assert response = @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ assert_success response
+ assert response.authorization
+ assert refund = @gateway.refund(@amount - 20, response.authorization)
+ assert_success refund
+ end
+
+ def test_successful_refund_of_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
assert_success response
assert response.authorization
assert refund = @gateway.refund(@amount - 20, response.authorization)
@@ -54,13 +159,21 @@ def test_successful_refund
end
def test_unsuccessful_refund
- assert refund = @gateway.refund(@amount - 20, "unknown_authorization")
+ assert refund = @gateway.refund(@amount - 20, 'unknown_authorization')
assert_failure refund
assert_match(/Unable to find original transaction/, refund.message)
end
def test_successful_void
- assert response = @gateway.purchase(@amount, @creditcard, @options)
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+ assert void = @gateway.void(response.authorization)
+ assert_success void
+ end
+
+ def test_successful_void_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
assert_success response
assert response.authorization
assert void = @gateway.void(response.authorization)
@@ -68,15 +181,83 @@ def test_successful_void
end
def test_unsuccessful_void
- assert void = @gateway.void("unknown_authorization")
+ assert void = @gateway.void('unknown_authorization')
+ assert_failure void
+ assert_match(/Unable to locate transaction/, void.message)
+ end
+
+ def test_successful_void_release
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+ assert void = @gateway.void(response.authorization, void_mode: :void_release)
+ assert_success void
+ end
+
+ def test_successful_void_release_with_echeck
+ assert response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert response.authorization
+ assert void = @gateway.void(response.authorization, void_mode: :void_release)
+ assert_success void
+ end
+
+ def test_unsuccessful_void_release
+ assert void = @gateway.void('unknown_authorization', void_mode: :void_release)
assert_failure void
assert_match(/Unable to locate transaction/, void.message)
end
def test_invalid_key
gateway = UsaEpayTransactionGateway.new(:login => '')
- assert response = gateway.purchase(@amount, @creditcard, @options)
+ assert response = gateway.purchase(@amount, @credit_card, @options)
assert_equal 'Specified source key not found.', response.message
assert_failure response
end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ assert_success response.responses.last, 'The void should succeed'
+ end
+
+ def test_failed_verify
+ assert response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match 'Card Declined (00)', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card_with_track_data, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card_with_track_data.track_data, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@gateway.options[:login], transcript)
+ end
+
+ def test_processing_error
+ assert response = @gateway.purchase(@amount, @invalid_transaction_card, @options)
+ assert_equal 'processing_error', response.error_code
+ assert_failure response
+ end
end
diff --git a/test/remote/gateways/remote_vanco_test.rb b/test/remote/gateways/remote_vanco_test.rb
new file mode 100644
index 00000000000..077b9b1a248
--- /dev/null
+++ b/test/remote/gateways/remote_vanco_test.rb
@@ -0,0 +1,110 @@
+require 'test_helper'
+
+class RemoteVancoTest < Test::Unit::TestCase
+ def setup
+ @gateway = VancoGateway.new(fixtures(:vanco))
+
+ @amount = 10005
+ @credit_card = credit_card('4111111111111111')
+ @declined_card = credit_card('4111111111111111', year: 2011)
+ @check = check
+
+ @options = {
+ order_id: '1',
+ billing_address: address(country: 'US', state: 'NC', zip: '06085'),
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_fund_id
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(fund_id: 'TheFund'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_ip_address
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(ip: '192.168.19.123'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_sans_minimal_options
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card)
+ assert_failure response
+ assert_equal('Invalid Expiration Date', response.message)
+ assert_equal('183', response.params['error_codes'])
+ end
+
+ def test_successful_echeck_purchase
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_success response
+ assert response.test?
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_echeck_purchase
+ response = @gateway.purchase(@amount, check(routing_number: '121042883'), @options)
+ assert_failure response
+ assert_equal 'Invalid Routing Number', response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_success refund
+ assert_equal 'Success', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount-1, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount+500, purchase.authorization)
+ assert_failure refund
+ assert_match(/Amount Cannot Be Greater Than/, refund.message)
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:password], transcript)
+ end
+
+ def test_invalid_login
+ gateway = VancoGateway.new(
+ user_id: 'unknown_id',
+ password: 'unknown_pwd',
+ client_id: ''
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Invalid Login Key', response.message
+ end
+end
diff --git a/test/remote/gateways/remote_verifi_test.rb b/test/remote/gateways/remote_verifi_test.rb
index a941fdcb143..2514864a96f 100644
--- a/test/remote/gateways/remote_verifi_test.rb
+++ b/test/remote/gateways/remote_verifi_test.rb
@@ -5,103 +5,94 @@ class VerifiTest < Test::Unit::TestCase
def setup
@gateway = VerifiGateway.new(fixtures(:verify))
-
+
@credit_card = credit_card('4111111111111111')
-
+
# Replace with your login and password for the Verifi test environment
@options = {
:order_id => '37',
- :email => "test@example.com",
+ :email => 'test@example.com',
:billing_address => address
}
-
+
@amount = 100
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction was Approved', response.message
assert !response.authorization.blank?
end
-
+
def test_unsuccessful_purchase
@credit_card.number = 'invalid'
-
+
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal 'Transaction was Rejected by Gateway', response.message
end
-
+
# FOR SOME REASON Verify DOESN'T MIND EXPIRED CARDS
# I talked to support and they said that they are loose on expiration dates being expired.
def test_expired_credit_card
- @credit_card.year = (Time.now.year - 3)
+ @credit_card.year = (Time.now.year - 3)
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal 'Transaction was Approved', response.message
+ assert_equal 'Transaction was Approved', response.message
end
-
+
def test_successful_authorization
assert response = @gateway.authorize(@amount, @credit_card, @options)
assert_success response
assert_equal 'Transaction was Approved', response.message
assert response.authorization
end
-
+
def test_authorization_and_capture
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
assert authorization
- assert capture = @gateway.capture(@amount, authorization.authorization, @options)
+ assert capture = @gateway.capture(@amount, authorization.authorization, @options)
assert_success capture
assert_equal 'Transaction was Approved', capture.message
end
-
- def test_authorization_and_void
- assert authorization = @gateway.authorize(@amount, @credit_card, @options)
- assert_success authorization
- assert authorization
- assert void = @gateway.void(authorization.authorization, @options)
- assert_success void
- assert_equal 'Transaction was Approved', void.message
- end
-
- # Credits are not enabled on test accounts, so this should always fail
+
+ # Credits are not enabled on test accounts, so this should always fail
def test_credit
assert response = @gateway.credit(@amount, @credit_card, @options)
- assert_match /Credits are not enabled/, response.params['responsetext']
- assert_failure response
+ assert_match %r{Credits are not enabled}, response.params['responsetext']
+ assert_failure response
end
-
+
def test_authorization_and_void
assert authorization = @gateway.authorize(@amount, @credit_card, @options)
assert_success authorization
assert void = @gateway.void(authorization.authorization, @options)
assert_success void
assert_equal 'Transaction was Approved', void.message
- assert_match /Transaction Void Successful/, void.params['responsetext']
+ assert_match %r{Transaction Void Successful}, void.params['responsetext']
end
-
+
def test_purchase_and_credit
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
-
+
assert credit = @gateway.credit(@amount, purchase.authorization, @options)
assert_success credit
assert_equal 'Transaction was Approved', credit.message
end
-
+
def test_bad_login
gateway = VerifiGateway.new(
:login => 'X',
:password => 'Y'
)
-
+
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_equal 'Transaction was Rejected by Gateway', response.message
assert_equal 'Authentication Failed', response.params['responsetext']
-
+
assert_failure response
end
end
diff --git a/test/remote/gateways/remote_viaklix_test.rb b/test/remote/gateways/remote_viaklix_test.rb
index 319914004dc..c3dc47c1a43 100644
--- a/test/remote/gateways/remote_viaklix_test.rb
+++ b/test/remote/gateways/remote_viaklix_test.rb
@@ -3,41 +3,41 @@
class RemoteViaklixTest < Test::Unit::TestCase
def setup
@gateway = ViaklixGateway.new(fixtures(:viaklix))
-
- @credit_card = credit_card
+
+ @credit_card = credit_card
@bad_credit_card = credit_card('invalid')
-
+
@options = {
:order_id => '#1000.1',
- :email => "paul@domain.com",
+ :email => 'paul@domain.com',
:description => 'Test Transaction',
:billing_address => address
}
@amount = 100
end
-
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
-
+
assert_success response
assert response.test?
assert_equal 'APPROVED', response.message
assert response.authorization
end
-
+
def test_failed_purchase
assert response = @gateway.purchase(@amount, @bad_credit_card, @options)
-
+
assert_failure response
assert response.test?
assert_equal 'The Credit Card Number supplied in the authorization request appears invalid.', response.message
end
-
+
def test_credit
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
-
+
assert credit = @gateway.credit(@amount, @credit_card)
assert_success credit
end
-end
\ No newline at end of file
+end
diff --git a/test/remote/gateways/remote_vindicia_test.rb b/test/remote/gateways/remote_vindicia_test.rb
deleted file mode 100644
index 9201bb709f3..00000000000
--- a/test/remote/gateways/remote_vindicia_test.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-require 'test_helper'
-
-class RemoteVindiciaTest < Test::Unit::TestCase
- def setup
- @account_id = rand(9000000)
-
- @gateway = VindiciaGateway.new(fixtures(:vindicia).merge(
- :account_id => @account_id, :avs_success => %{IU}
- ))
-
- @amount = 500
- @credit_card = credit_card('4112344112344113')
- @declined_card = credit_card('4000300011112220')
-
- @recurring_product_sku = 'CHANGE TO A VALID PRODUCT SKU'
-
- @options = {
- :order_id => rand(4000000),
- :billing_address => address,
- :shipping_address => address,
- :line_items => {
- :name => 'Test Product',
- :sku => 'CHANGE TO A VALID PRODUCT SKU',
- :price => 5,
- :quantity => 1
- }
- }
- end
-
- def test_successful_purchase
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_success response
- assert_equal 'Ok', response.message
- end
-
- def test_unsuccessful_purchase
- assert response = @gateway.purchase(@amount, @declined_card, @options)
- assert_failure response
- assert_equal 'OK', response.message
- end
-
- def test_authorize_and_capture
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
- assert_success auth
- assert_equal 'OK', auth.message
- assert auth.authorization
-
- assert capture = @gateway.capture(@amount, auth.authorization)
- assert_success capture
- end
-
- def test_failed_capture
- assert response = @gateway.capture(@amount, '')
- assert_equal response.params["qtyFail"].to_i, 1
- assert_failure response
- assert_equal 'Ok', response.message
- end
-
- def test_successful_void
- assert auth = @gateway.authorize(@amount, @credit_card, @options)
- assert_success auth
- assert_equal 'OK', auth.message
- assert auth.authorization
-
- assert void = @gateway.void(auth.authorization)
- assert_equal void.params["qtySuccess"].to_i, 1
- assert_success void
- assert_equal 'Ok', void.message
- end
-
- def test_failed_void
- assert void = @gateway.void('')
- assert_equal void.params["qtyFail"].to_i, 1
- assert_failure void
- assert_equal 'Ok', void.message
- end
-
- def test_recurrence_setup
- @options.merge!(:product_sku => @recurring_product_sku)
-
- assert response = @gateway.recurring(@amount, @credit_card, @options)
- assert_success response
- assert_equal 'OK', response.message
- end
-
- def test_invalid_login
- gateway = VindiciaGateway.new(
- :login => '',
- :password => '',
- :account_id => 1
- )
-
- assert response = gateway.purchase(@amount, @credit_card, @options)
- assert_failure response
- assert response.message.include?("Permission denied")
- end
-end
diff --git a/test/remote/gateways/remote_visanet_peru_test.rb b/test/remote/gateways/remote_visanet_peru_test.rb
new file mode 100644
index 00000000000..950705e68b5
--- /dev/null
+++ b/test/remote/gateways/remote_visanet_peru_test.rb
@@ -0,0 +1,167 @@
+require 'test_helper'
+
+class RemoteVisanetPeruTest < Test::Unit::TestCase
+ def setup
+ @gateway = VisanetPeruGateway.new(fixtures(:visanet_peru))
+
+ @amount = 100
+ @credit_card = credit_card('4500340090000016', verification_value: '377')
+ @declined_card = credit_card('4111111111111111')
+
+ @options = {
+ billing_address: address,
+ order_id: generate_unique_id,
+ email: 'visanetperutest@mailinator.com'
+ }
+ end
+
+ def test_invalid_login
+ gateway = VisanetPeruGateway.new(access_key_id: '', secret_access_key: '', merchant_id: '')
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+ assert response.authorization
+ assert_equal @options[:order_id], response.params['externalTransactionId']
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_merchant_define_data
+ options = @options.merge(merchant_define_data: { field3: 'movil', field91: '101266802', field92: 'TheMerchant' })
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+
+ def test_successful_purchase_sans_options
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+ assert_equal 'OK', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 400, response.error_code
+ assert_equal 'Operacion Denegada.', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+ assert response.authorization
+ assert_equal @options[:order_id], response.params['externalTransactionId']
+ assert_equal '1.00', response.params['data']['IMP_AUTORIZADO']
+
+ capture = @gateway.capture(response.authorization, @options)
+ assert_success capture
+ assert_equal 'OK', capture.message
+ assert capture.authorization
+ assert_equal @options[:order_id], capture.params['externalTransactionId']
+ end
+
+ def test_successful_authorize_fractional_amount
+ amount = 199
+ response = @gateway.authorize(amount, @credit_card)
+ assert_success response
+ assert_equal 'OK', response.message
+ assert_equal '1.99', response.params['data']['IMP_AUTORIZADO']
+ end
+
+ def test_failed_authorize_declined_card
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert_equal 400, response.error_code
+ assert_equal 'Operacion Denegada.', response.message
+ end
+
+ def test_failed_authorize_bad_email
+ @options[:email] = 'cybersource@reject.com'
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 400, response.error_code
+
+ # this also exercises message joining for errorMessage and DSC_COD_ACCION when both are present
+ assert_equal 'REJECT | Operacion denegada', response.message
+ end
+
+ def test_failed_capture
+ response = @gateway.capture('900000044')
+ assert_failure response
+ assert_match(/NUMORDEN 900000044 no se encuentra registrado/, response.message)
+ assert_equal 400, response.error_code
+ end
+
+ def test_successful_refund
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'OK', refund.message
+ end
+
+ def test_successful_refund_unsettled
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ new_auth = "_|#{response.authorization.split('|')[1]}"
+ @gateway.refund(@amount, new_auth, @options.merge(force_full_refund_if_unsettled: true, ruc: '20341198217'))
+ # this test will fail currently because there is no E2E test working for visanet
+ # assert_success refund
+ # assert_equal "OK", refund.message
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '900000044')
+ assert_failure response
+ assert_match(/NUMORDEN 900000044 no se encuentra registrado/, response.message)
+ assert_equal 400, response.error_code
+ end
+
+ def test_successful_void
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ void = @gateway.void(response.authorization)
+ assert_success void
+ assert_equal 'OK', void.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('900000044')
+ assert_failure response
+ assert_match(/NUMORDEN no se encuentra registrado/, response.message)
+ assert_equal 400, response.error_code
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'OK', response.message
+ assert_equal @options[:order_id], response.params['externalTransactionId']
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_equal 400, response.error_code
+ assert_equal 'Operacion Denegada.', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ assert_scrubbed(@gateway.options[:secret_access_key], clean_transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_webpay_test.rb b/test/remote/gateways/remote_webpay_test.rb
index 3aa0edf27ea..3b0e099ea33 100644
--- a/test/remote/gateways/remote_webpay_test.rb
+++ b/test/remote/gateways/remote_webpay_test.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+
require 'test_helper'
class RemoteWebpayTest < Test::Unit::TestCase
@@ -21,31 +22,49 @@ def setup
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal "charge", response.params["object"]
- assert response.params["paid"]
+ assert_equal 'charge', response.params['object']
+ assert response.params['paid']
end
def test_appropriate_purchase_amount
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
- assert_equal @amount / 100, response.params["amount"]
+ assert_equal @amount / 100, response.params['amount']
end
def test_purchase_description
- assert response = @gateway.purchase(@amount, @credit_card, { :description => "TheDescription", :email => "email@example.com" })
- assert_equal "TheDescription", response.params["description"], "Use the description if it's specified."
+ assert response = @gateway.purchase(@amount, @credit_card, { :description => 'TheDescription', :email => 'email@example.com' })
+ assert_equal 'TheDescription', response.params['description'], "Use the description if it's specified."
- assert response = @gateway.purchase(@amount, @credit_card, { :email => "email@example.com" })
- assert_equal "email@example.com", response.params["description"], "Use the email if no description is specified."
+ assert response = @gateway.purchase(@amount, @credit_card, { :email => 'email@example.com' })
+ assert_equal 'email@example.com', response.params['description'], 'Use the email if no description is specified.'
assert response = @gateway.purchase(@amount, @credit_card, { })
- assert_nil response.params["description"], "No description or email specified."
+ assert_nil response.params['description'], 'No description or email specified.'
end
def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
- assert_equal 'Your card number is incorrect', response.message
+ assert_equal 'The card number is invalid. Make sure the number entered matches your credit card.', response.message
+ end
+
+ def test_authorization_and_capture
+ assert authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert !authorization.params['captured']
+
+ assert capture = @gateway.capture(@amount, authorization.authorization)
+ assert_success capture
+ end
+
+ def test_authorization_and_void
+ assert authorization = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorization
+ assert !authorization.params['captured']
+
+ assert void = @gateway.void(authorization.authorization)
+ assert_success void
end
def test_successful_void
@@ -57,9 +76,9 @@ def test_successful_void
end
def test_unsuccessful_void
- assert void = @gateway.void("active_merchant_fake_charge")
+ assert void = @gateway.void('active_merchant_fake_charge')
assert_failure void
- assert_match 'No such charge: active_merchant_fake_charge', void.message
+ assert_match 'No such charge', void.message
end
def test_successful_refund
@@ -76,44 +95,44 @@ def test_appropriate_refund_amount
assert response.authorization
assert void = @gateway.refund(@refund_amount, response.authorization)
assert_success void
- assert_equal @refund_amount / 100, void.params["amount_refunded"]
+ assert_equal @refund_amount / 100, void.params['amount_refunded']
end
def test_unsuccessful_refund
- assert refund = @gateway.refund(@amount, "active_merchant_fake_charge")
+ assert refund = @gateway.refund(@amount, 'active_merchant_fake_charge')
assert_failure refund
- assert_match 'No such charge: active_merchant_fake_charge', refund.message
+ assert_match 'No such charge', refund.message
end
def test_successful_store
- assert response = @gateway.store(@credit_card, {:description => "Active Merchant Test Customer", :email => "email@example.com"})
+ assert response = @gateway.store(@credit_card, {:description => 'Active Merchant Test Customer', :email => 'email@example.com'})
assert_success response
- assert_equal "customer", response.params["object"]
- assert_equal "Active Merchant Test Customer", response.params["description"]
- assert_equal "email@example.com", response.params["email"]
- assert_equal @credit_card.last_digits, response.params["active_card"]["last4"]
+ assert_equal 'customer', response.params['object']
+ assert_equal 'Active Merchant Test Customer', response.params['description']
+ assert_equal 'email@example.com', response.params['email']
+ assert_equal @credit_card.last_digits, response.params['active_card']['last4']
end
def test_successful_update
- creation = @gateway.store(@credit_card, {:description => "Active Merchant Update Customer"})
+ creation = @gateway.store(@credit_card, {:description => 'Active Merchant Update Customer'})
assert response = @gateway.update(creation.params['id'], @new_credit_card)
assert_success response
- assert_equal "Active Merchant Update Customer", response.params["description"]
- assert_equal @new_credit_card.last_digits, response.params["active_card"]["last4"]
+ assert_equal 'Active Merchant Update Customer', response.params['description']
+ assert_equal @new_credit_card.last_digits, response.params['active_card']['last4']
end
def test_successful_unstore
- creation = @gateway.store(@credit_card, {:description => "Active Merchant Unstore Customer"})
+ creation = @gateway.store(@credit_card, {:description => 'Active Merchant Unstore Customer'})
assert response = @gateway.unstore(creation.params['id'])
assert_success response
- assert_equal true, response.params["deleted"]
+ assert_equal true, response.params['deleted']
end
def test_invalid_login
gateway = WebpayGateway.new(:login => 'active_merchant_test')
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "Invalid API key provided. Check your API key is correct.", response.message
+ assert_equal 'Invalid API key provided. Check your API key is correct.', response.message
end
end
diff --git a/test/remote/gateways/remote_wepay_test.rb b/test/remote/gateways/remote_wepay_test.rb
new file mode 100644
index 00000000000..4a1b01f061d
--- /dev/null
+++ b/test/remote/gateways/remote_wepay_test.rb
@@ -0,0 +1,212 @@
+require 'test_helper'
+
+class RemoteWepayTest < Test::Unit::TestCase
+ def setup
+ @gateway = WepayGateway.new(fixtures(:wepay))
+
+ @amount = 2000
+ @credit_card = credit_card('5496198584584769', verification_value: '321')
+ @credit_card_without_cvv = credit_card('5496198584584769', verification_value: nil)
+
+ @declined_card = credit_card('')
+
+ @options = {
+ billing_address: address,
+ email: 'test@example.com'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase_with_token
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ response = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_recurring_and_ip
+ store = @gateway.store(@credit_card, @options.merge(recurring: true, ip: '127.0.0.1'))
+ assert_success store
+
+ response = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_sans_cvv
+ @options[:recurring] = true
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ response = @gateway.purchase(@amount, store.authorization, @options)
+ assert_success response
+ end
+
+ def test_successful_purchase_with_few_options
+ options = { address: { zip: '27701' }, email: 'test@example.com' }
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_purchase_sans_ccv
+ @credit_card.verification_value = nil
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_failed_purchase_with_token
+ response = @gateway.purchase(@amount, '12345', @options)
+ assert_failure response
+ end
+
+ def test_successful_purchase_with_fee
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(application_fee: 3, fee_payer: 'payee'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_unique_id
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(unique_id: generate_unique_id))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_purchase_with_ip_and_risk_token
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(ip: '100.166.99.123', risk_token: '123e4567-e89b-12d3-a456-426655440000'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_successful_authorize
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_store_via_create_with_cvv
+ # POST /credit_card/create
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ end
+
+ def test_successful_store_via_transfer_without_cvv
+ # special permission required
+ # POST /credit_card/transfer
+ response = @gateway.store(@credit_card_without_cvv, @options.merge(recurring: true))
+ assert_success response
+ end
+
+ def test_unsuccessful_store_via_create_with_cvv
+ response = @gateway.store(@credit_card_without_cvv, @options)
+
+ assert_failure response
+ assert_equal('This app does not have permissions to create credit cards without a cvv', response.message)
+ end
+
+ # # Requires commenting out `unless options[:recurring]` when building post hash in `store` method.
+ # def test_unsuccessful_store_via_transfer_with_cvv
+ # response = @gateway.store(@credit_card, @options.merge(recurring: true))
+ #
+ # assert_failure response
+ # assert_equal('cvv parameter is unexpected', response.message)
+ # end
+
+ def test_successful_store_with_defaulted_email
+ response = @gateway.store(@credit_card, {billing_address: address})
+ assert_success response
+ end
+
+ def test_failed_store
+ response = @gateway.store(@declined_card, @options)
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ sleep 30 # Wait for purchase to clear. Doesn't always work.
+ response = @gateway.refund(@amount - 100, purchase.authorization)
+ assert_success response
+ end
+
+ def test_successful_full_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ sleep 30 # Wait for purchase to clear. Doesn't always work.
+ response = @gateway.refund(@amount, purchase.authorization)
+ assert_success response
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '123')
+ assert_failure response
+ end
+
+ def test_failed_void
+ response = @gateway.void('123')
+ assert_failure response
+ end
+
+ def test_authorize_and_capture
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ sleep 30 # Wait for authorization to clear. Doesn't always work.
+ assert capture = @gateway.capture(nil, authorize.authorization)
+ assert_success capture
+ end
+
+ def test_authorize_and_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization, cancel_reason: 'Cancel')
+ assert_success void
+ end
+
+ # Version sent here will need to match or be one ahead of the version set in the test account's dashboard
+ def test_successful_purchase_with_version
+ response = @gateway.purchase(@amount, @credit_card, @options.merge(version: '2017-05-31'))
+ assert_success response
+ assert_equal 'Success', response.message
+ end
+
+ def test_invalid_login
+ gateway = WepayGateway.new(
+ client_id: 12515,
+ account_id: 'abc',
+ access_token: 'def'
+ )
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:access_token], transcript)
+ end
+end
diff --git a/test/remote/gateways/remote_wirecard_test.rb b/test/remote/gateways/remote_wirecard_test.rb
index 0499974350e..586e1567351 100644
--- a/test/remote/gateways/remote_wirecard_test.rb
+++ b/test/remote/gateways/remote_wirecard_test.rb
@@ -1,32 +1,35 @@
+# encoding: UTF-8
+
require 'test_helper'
class RemoteWirecardTest < Test::Unit::TestCase
def setup
test_account = fixtures(:wirecard)
- test_account[:signature] = test_account[:login]
@gateway = WirecardGateway.new(test_account)
@amount = 100
@credit_card = credit_card('4200000000000000')
@declined_card = credit_card('4000300011112220')
+ @amex_card = credit_card('370000000000010', brand: 'american_express')
@options = {
- :order_id => 1,
- :billing_address => address,
- :description => 'Wirecard remote test purchase',
- :email => 'soleone@example.com'
+ order_id: 1,
+ billing_address: address,
+ description: 'Wirecard remote test purchase',
+ email: 'soleone@example.com',
+ ip: '127.0.0.1'
}
@german_address = {
- :name => 'Jim Deutsch',
- :address1 => '1234 Meine Street',
- :company => 'Widgets Inc',
- :city => 'Koblenz',
- :state => 'Rheinland-Pfalz',
- :zip => '56070',
- :country => 'DE',
- :phone => '0261 12345 23',
- :fax => '0261 12345 23-4'
+ name: 'Jim Deutsch',
+ address1: '1234 Meine Street',
+ company: 'Widgets Inc',
+ city: 'Koblenz',
+ state: 'Rheinland-Pfalz',
+ zip: '56070',
+ country: 'DE',
+ phone: '0261 12345 23',
+ fax: '0261 12345 23-4'
}
end
@@ -43,7 +46,7 @@ def test_successful_authorize_and_capture
amount = @amount
assert auth = @gateway.authorize(amount, @credit_card, @options)
assert_success auth
- assert auth.message[/THIS IS A DEMO/]
+ assert_match %r{THIS IS A DEMO}, auth.message
assert auth.authorization
assert capture = @gateway.capture(amount, auth.authorization, @options)
assert_success capture
@@ -52,42 +55,152 @@ def test_successful_authorize_and_capture
def test_successful_authorize_and_partial_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
- assert auth.message[/THIS IS A DEMO/]
+ assert_match %r{THIS IS A DEMO}, auth.message
assert auth.authorization
- #Capture some of the authorized amount
+ # Capture some of the authorized amount
assert capture = @gateway.capture(@amount - 10, auth.authorization, @options)
assert_success capture
end
+ def test_successful_void
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+
+ assert void = @gateway.void(response.authorization)
+ assert_success void
+ assert_match %r{THIS IS A DEMO}, void.message
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert response.authorization
+
+ assert refund = @gateway.refund(@amount - 20, response.authorization)
+ assert_success refund
+ assert_match %r{THIS IS A DEMO}, refund.message
+ end
+
def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
- # puts response.message
assert_success response
- assert response.message[/THIS IS A DEMO/]
+ assert_match %r{THIS IS A DEMO}, response.message
+ end
+
+ def test_successful_purchase_with_commerce_type
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(commerce_type: 'MOTO'))
+ assert_success response
+ assert_match %r{THIS IS A DEMO}, response.message
+ end
+
+ def test_successful_reference_purchase
+ assert purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+ assert purchase.authorization
+
+ assert reference_purchase = @gateway.purchase(@amount, purchase.authorization)
+ assert_success reference_purchase
+ assert_match %r{THIS IS A DEMO}, reference_purchase.message
+ end
+
+ def test_utf8_description_does_not_blow_up
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(description: 'Habitación'))
+ assert_success response
+ assert_match %r{THIS IS A DEMO}, response.message
end
def test_successful_purchase_with_german_address_german_state_and_german_phone
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => @german_address))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address))
assert_success response
assert response.message[/THIS IS A DEMO/]
end
def test_successful_purchase_with_german_address_no_state_and_invalid_phone
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => @german_address.merge({:state => nil, :phone => '1234'})))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({state: nil, phone: '1234'})))
assert_success response
assert response.message[/THIS IS A DEMO/]
end
def test_successful_purchase_with_german_address_and_valid_phone
- assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => @german_address.merge({:phone => '+049-261-1234-123'})))
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: @german_address.merge({phone: '+049-261-1234-123'})))
assert_success response
assert response.message[/THIS IS A DEMO/]
end
+ def test_successful_cvv_result
+ @credit_card.verification_value = '666' # Magic Value = "Matched (correct) CVC-2"
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+
+ assert_success response
+ assert_equal 'M', response.cvv_result['code']
+ end
+
+ def test_successful_visa_avs_result
+ # Magic Wirecard address to return an AVS 'M' result
+ m_address = {
+ address1: '99 DERRY STREET',
+ state: 'London',
+ zip: 'W8 5TE',
+ country: 'GB'
+ }
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(billing_address: m_address))
+
+ assert_success response
+ assert_equal 'M', response.avs_result['code']
+ end
+
+ def test_successful_amex_avs_result
+ a_address = {
+ address1: '10 Edward Street',
+ state: 'London',
+ zip: 'BN66 6AB',
+ country: 'GB'
+ }
+
+ assert response = @gateway.purchase(@amount, @amex_card, @options.merge(billing_address: a_address))
+
+ assert_success response
+ assert_equal 'U', response.avs_result['code']
+ end
+
+ def test_successful_store
+ assert response = @gateway.store(@credit_card)
+ assert_success response
+ assert response.authorization
+ end
+
+ def test_successful_store_with_amex
+ assert response = @gateway.store(@amex_card)
+ assert_success response
+ assert response.authorization
+ end
+
+ def test_successful_store_then_purchase_by_reference
+ assert auth = @gateway.store(@credit_card, @options.dup)
+ assert_success auth
+ assert auth.authorization
+ assert purchase = @gateway.purchase(@amount, auth.authorization, @options.dup)
+ assert_success purchase
+ end
+
+ def test_successful_authorization_as_recurring_transaction_type_initial
+ assert response = @gateway.authorize(@amount, @credit_card, @options.merge(:recurring => 'Initial'))
+ assert_success response
+ assert response.authorization
+ end
+
+ def test_successful_purchase_as_recurring_transaction_type_initial
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:recurring => 'Initial'))
+ assert_success response
+ assert response.authorization
+ end
+
# Failure tested
def test_wrong_creditcard_authorization
@@ -101,19 +214,55 @@ def test_wrong_creditcard_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert response.test?
assert_failure response
- assert response.message[ /Credit card number not allowed in demo mode/ ], "Got wrong response message"
+ assert response.message[/Credit card number not allowed in demo mode/], 'Got wrong response message'
+ assert_equal '24997', response.params['ErrorCode']
+ end
+
+ def test_wrong_creditcard_store
+ assert response = @gateway.store(@declined_card, @options)
+ assert response.test?
+ assert_failure response
+ assert response.message[/Credit card number not allowed in demo mode/], 'Got wrong response message'
end
def test_unauthorized_capture
- assert response = @gateway.capture(@amount, "1234567890123456789012")
+ assert response = @gateway.capture(@amount, '1234567890123456789012')
+ assert_failure response
+ assert_equal 'Could not find referenced transaction for GuWID 1234567890123456789012.', response.message
+ end
+
+ def test_failed_refund
+ assert refund = @gateway.refund(@amount - 20, 'C428094138244444404448')
+ assert_failure refund
+ assert_match %r{Could not find referenced transaction}, refund.message
+ end
+
+ def test_failed_void
+ assert void = @gateway.void('C428094138244444404448')
+ assert_failure void
+ assert_match %r{Could not find referenced transaction}, void.message
+ end
+
+ def test_unauthorized_purchase
+ assert response = @gateway.purchase(@amount, '1234567890123456789012')
assert_failure response
- assert_equal "Could not find referenced transaction for GuWID 1234567890123456789012.", response.message
+ assert_equal 'Could not find referenced transaction for GuWID 1234567890123456789012.', response.message
end
def test_invalid_login
- gateway = WirecardGateway.new(:login => '', :password => '', :signature => '')
+ gateway = WirecardGateway.new(login: '', password: '', signature: '')
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
- assert_equal "Invalid Login", response.message
+ assert_equal 'Invalid Login', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
end
end
diff --git a/test/remote/gateways/remote_world_net_test.rb b/test/remote/gateways/remote_world_net_test.rb
new file mode 100644
index 00000000000..67d198f1fbf
--- /dev/null
+++ b/test/remote/gateways/remote_world_net_test.rb
@@ -0,0 +1,189 @@
+require 'test_helper'
+
+class RemoteWorldNetTest < Test::Unit::TestCase
+ def setup
+ @gateway = WorldNetGateway.new(fixtures(:world_net))
+
+ @amount = 100
+ @declined_amount = 101
+ @credit_card = credit_card('3779810000000005')
+ @options = {
+ order_id: generate_order_id,
+ }
+ @refund_options = {
+ operator: 'mr.nobody',
+ reason: 'returned'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'APPROVAL', response.message
+ end
+
+ def test_successful_purchase_with_more_options
+ options = {
+ order_id: generate_order_id,
+ email: 'joe@example.com',
+ billing_address: address,
+ description: 'Store Purchase',
+ ip: '127.0.0.1',
+ }
+
+ response = @gateway.purchase(@amount, @credit_card, options)
+ assert_success response
+ assert_equal 'APPROVAL', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'DECLINED', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:card_declined], response.error_code
+
+ response = @gateway.purchase(103, @credit_card, @options)
+ assert_failure response
+ assert_equal 'CVV FAILURE', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:incorrect_cvc], response.error_code
+
+ response = @gateway.purchase(@amount, credit_card('3779810000000005', month: '13'), @options)
+ assert_failure response
+ assert_equal 'Invalid CARDEXPIRY field', response.message
+ assert_equal Gateway::STANDARD_ERROR_CODE[:invalid_expiry_date], response.error_code
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'APPROVAL', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@declined_amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'DECLINED', response.message
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(@amount, '')
+ assert_failure response
+ assert_match %r{not facet-valid with respect to minLength}, response.message
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount, purchase.authorization, @refund_options)
+ assert_success refund
+ assert_equal 'SUCCESS', refund.message
+ end
+
+ def test_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(@amount-1, purchase.authorization, @refund_options)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(@amount, '', @refund_options)
+ assert_failure response
+ assert_match %r{not facet-valid with respect to minLength}, response.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert @gateway.void(auth.authorization)
+ # UNSUPPORTED
+ # assert_success void
+ # assert_equal 'REPLACE WITH SUCCESSFUL VOID MESSAGE', response.message
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ assert_match %r{Cannot find the declaration of element}, response.message
+ end
+
+ def test_invalid_login
+ gateway = WorldNetGateway.new(terminal_id: '', secret: '')
+
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_match %r{not facet-valid with respect to minLength}, response.message
+ end
+
+ def test_successful_store
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ end
+
+ def test_unsuccessful_store
+ store = @gateway.store(credit_card('3779810000000005', month: '13'), @options)
+ assert_failure store
+ end
+
+ def test_successful_unstore
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+ card_reference = response.authorization
+
+ assert response = @gateway.unstore(card_reference, @options)
+ assert_success response
+
+ assert response = @gateway.purchase(@amount, card_reference, @options)
+ assert_failure response
+ end
+
+ def test_unsuccessful_unstore
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+
+ assert response = @gateway.unstore('123456789', @options)
+ assert_failure response
+ end
+
+ def test_purchase_with_stored_card
+ response = @gateway.store(@credit_card, @options)
+ assert_success response
+ assert_equal nil, response.message
+ card_reference = response.authorization
+
+ assert response = @gateway.purchase(@amount, card_reference, @options)
+ assert_success response
+ assert_equal 'APPROVAL', response.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:secret], transcript)
+ end
+
+ def generate_order_id
+ (Time.now.to_f * 100).to_i.to_s
+ end
+end
diff --git a/test/remote/gateways/remote_worldpay_online_payments_test.rb b/test/remote/gateways/remote_worldpay_online_payments_test.rb
new file mode 100644
index 00000000000..4f0b2ff05c2
--- /dev/null
+++ b/test/remote/gateways/remote_worldpay_online_payments_test.rb
@@ -0,0 +1,163 @@
+require 'test_helper'
+
+class RemoteWorldpayOnlinePaymentsTest < Test::Unit::TestCase
+ def setup
+ @gateway = WorldpayOnlinePaymentsGateway.new(fixtures(:worldpay_online_payments))
+
+ @amount = 1000
+ @credit_card = credit_card('4444333322221111')
+ @declined_card = credit_card('2424242424242424')
+
+ @options = {
+ order_id: '1',
+ currency: 'GBP',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert_not_equal 'SUCCESS', response.message
+ end
+
+ def test_failed_card_purchase
+ @options[:billing_address][:name] = 'FAILED'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_not_equal 'SUCCESS', response.message
+ end
+
+ def test_error_card_purchase
+ @options[:billing_address][:name] = 'ERROR'
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_not_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_authorize_and_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_authorize_and_capture
+ auth = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure auth
+
+ assert capture = @gateway.capture(@amount, auth.authorization)
+ assert_not_equal 'SUCCESS', capture.message
+ end
+
+ def test_failed_authorize
+ response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ end
+
+ def test_partial_capture
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert capture = @gateway.capture(@amount-1, auth.authorization)
+ assert_success capture
+ end
+
+ def test_failed_capture
+ response = @gateway.capture(nil, '')
+ assert_failure response
+ end
+
+ def test_successful_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(nil, purchase.authorization)
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = @gateway.refund(nil, '')
+ assert_failure response
+ end
+
+ def test_failed_double_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ assert refund = @gateway.refund(nil, purchase.authorization)
+ assert_success refund
+
+ assert refund = @gateway.refund(nil, purchase.authorization)
+ assert_failure refund
+ end
+
+ def test_failed_partial_refund
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ refund = @gateway.refund(@amount+1, purchase.authorization)
+ assert_failure refund
+ end
+
+ def test_successful_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+ end
+
+ def test_successful_order_void
+ purchase = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success purchase
+
+ void = @gateway.void(purchase.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ void = @gateway.void('InvalidOrderCode')
+ assert_failure void
+ end
+
+ def test_failed_double_void
+ authorize = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success authorize
+
+ void = @gateway.void(authorize.authorization)
+ assert_success void
+
+ void = @gateway.void(authorize.authorization)
+ assert_failure void
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{SUCCESS}, response.message
+ end
+
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_not_match %r{SUCCESS}, response.message
+ end
+
+ def test_invalid_login
+ badgateway = WorldpayOnlinePaymentsGateway.new(
+ client_key: 'T_C_NOT_VALID',
+ service_key: 'T_S_NOT_VALID'
+ )
+ response = badgateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ end
+end
diff --git a/test/remote/gateways/remote_worldpay_test.rb b/test/remote/gateways/remote_worldpay_test.rb
index 2b5ee8d98cb..c93542ce7bd 100644
--- a/test/remote/gateways/remote_worldpay_test.rb
+++ b/test/remote/gateways/remote_worldpay_test.rb
@@ -4,12 +4,31 @@ class RemoteWorldpayTest < Test::Unit::TestCase
def setup
@gateway = WorldpayGateway.new(fixtures(:world_pay_gateway))
+ @cftgateway = WorldpayGateway.new(fixtures(:world_pay_gateway_cft))
@amount = 100
@credit_card = credit_card('4111111111111111')
+ @elo_credit_card = credit_card('4514 1600 0000 0008',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737',
+ :brand => 'elo'
+ )
+ @sodexo_voucher = credit_card('6060704495764400', brand: 'sodexo')
@declined_card = credit_card('4111111111111111', :first_name => nil, :last_name => 'REFUSED')
+ @threeDS_card = credit_card('4111111111111111', :first_name => nil, :last_name => '3D')
+ @threeDS_card_external_MPI = credit_card('4444333322221111', :first_name => 'AA', :last_name => 'BD')
- @options = {:order_id => generate_unique_id}
+ @options = {
+ order_id: generate_unique_id,
+ email: 'wow@example.com'
+ }
+ @store_options = {
+ customer: generate_unique_id,
+ email: 'wow@example.com'
+ }
end
def test_successful_purchase
@@ -18,9 +37,37 @@ def test_successful_purchase
assert_equal 'SUCCESS', response.message
end
+ def test_successful_purchase_with_elo
+ assert response = @gateway.purchase(@amount, @elo_credit_card, @options.merge(currency: 'BRL'))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_authorize_avs_and_cvv
+ card = credit_card('4111111111111111', :verification_value => 555)
+ assert response = @gateway.authorize(@amount, card, @options.merge(billing_address: address.update(zip: 'CCCC')))
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ assert_match %r{Street address does not match, but 5-digit postal code matches}, response.avs_result['message']
+ assert_match %r{CVV matches}, response.cvv_result['message']
+ end
+
+ def test_successful_purchase_with_hcg_additional_data
+ @options[:hcg_additional_data] = {
+ key1: 'value1',
+ key2: 'value2',
+ key3: 'value3'
+ }
+
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
def test_failed_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
+ assert_equal '5', response.error_code
assert_equal 'REFUSED', response.message
end
@@ -29,10 +76,257 @@ def test_authorize_and_capture
assert_success auth
assert_equal 'SUCCESS', auth.message
assert auth.authorization
- assert capture = @gateway.capture(@amount, auth.authorization)
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
assert_success capture
end
+ def test_authorize_and_capture_by_reference
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'SUCCESS', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+ assert reference = auth.authorization
+ @options[:order_id] = generate_unique_id
+
+ assert auth = @gateway.authorize(@amount, reference, @options)
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+ end
+
+ def test_authorize_and_purchase_by_reference
+ assert auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'SUCCESS', auth.message
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+ assert reference = auth.authorization
+
+ @options[:order_id] = generate_unique_id
+ assert auth = @gateway.authorize(@amount, reference, @options)
+
+ @options[:order_id] = generate_unique_id
+ assert capture = @gateway.purchase(@amount, auth.authorization, @options)
+ assert_success capture
+ end
+
+ def test_authorize_and_purchase_with_instalments
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(instalment: 3))
+ assert_success auth
+ assert_equal 'SUCCESS', auth.message
+ assert auth.authorization
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+ end
+
+ def test_successful_authorize_with_3ds
+ session_id = generate_unique_id
+ options = @options.merge(
+ {
+ execute_threed: true,
+ accept_header: 'text/html',
+ user_agent: 'Mozilla/5.0',
+ session_id: session_id,
+ ip: '127.0.0.1',
+ cookie: 'machine=32423423'
+ })
+ assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
+ assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message
+ assert first_message.test?
+ refute first_message.authorization.blank?
+ refute first_message.params['issuer_url'].blank?
+ refute first_message.params['pa_request'].blank?
+ refute first_message.params['cookie'].blank?
+ refute first_message.params['session_id'].blank?
+ end
+
+ def test_successful_auth_and_capture_with_normalized_stored_credential
+ stored_credential_params = {
+ initial_transaction: true,
+ reason_type: 'unscheduled',
+ initiator: 'merchant',
+ network_transaction_id: nil
+ }
+
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge({stored_credential: stored_credential_params}))
+ assert_success auth
+ assert auth.authorization
+ assert auth.params['scheme_response']
+ assert auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+
+ @options[:order_id] = generate_unique_id
+ @options[:stored_credential] = {
+ initial_transaction: false,
+ reason_type: 'installment',
+ initiator: 'merchant',
+ network_transaction_id: auth.params['transaction_identifier']
+ }
+
+ assert next_auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert next_auth.authorization
+ assert next_auth.params['scheme_response']
+ assert next_auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true)
+ assert_success capture
+ end
+
+ def test_successful_auth_and_capture_with_gateway_specific_stored_credentials
+ assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential_usage: 'FIRST'))
+ assert_success auth
+ assert auth.authorization
+ assert auth.params['scheme_response']
+ assert auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ assert_success capture
+
+ options = @options.merge(
+ order_id: generate_unique_id,
+ stored_credential_usage: 'USED',
+ stored_credential_initiated_reason: 'UNSCHEDULED',
+ stored_credential_transaction_id: auth.params['transaction_identifier']
+ )
+ assert next_auth = @gateway.authorize(@amount, @credit_card, options)
+ assert next_auth.authorization
+ assert next_auth.params['scheme_response']
+ assert next_auth.params['transaction_identifier']
+
+ assert capture = @gateway.capture(@amount, next_auth.authorization, authorization_validated: true)
+ assert_success capture
+ end
+
+ def test_successful_authorize_with_3ds_with_normalized_stored_credentials
+ session_id = generate_unique_id
+ stored_credential_params = {
+ initial_transaction: true,
+ reason_type: 'unscheduled',
+ initiator: 'merchant',
+ network_transaction_id: nil
+ }
+ options = @options.merge(
+ {
+ execute_threed: true,
+ accept_header: 'text/html',
+ user_agent: 'Mozilla/5.0',
+ session_id: session_id,
+ ip: '127.0.0.1',
+ cookie: 'machine=32423423',
+ stored_credential: stored_credential_params
+ })
+ assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
+ assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message
+ assert first_message.test?
+ refute first_message.authorization.blank?
+ refute first_message.params['issuer_url'].blank?
+ refute first_message.params['pa_request'].blank?
+ refute first_message.params['cookie'].blank?
+ refute first_message.params['session_id'].blank?
+ end
+
+ def test_successful_authorize_with_3ds_with_gateway_specific_stored_credentials
+ session_id = generate_unique_id
+ options = @options.merge(
+ {
+ execute_threed: true,
+ accept_header: 'text/html',
+ user_agent: 'Mozilla/5.0',
+ session_id: session_id,
+ ip: '127.0.0.1',
+ cookie: 'machine=32423423',
+ stored_credential_usage: 'FIRST'
+ })
+ assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
+ assert_equal "A transaction status of 'AUTHORISED' is required.", first_message.message
+ assert first_message.test?
+ refute first_message.authorization.blank?
+ refute first_message.params['issuer_url'].blank?
+ refute first_message.params['pa_request'].blank?
+ refute first_message.params['cookie'].blank?
+ refute first_message.params['session_id'].blank?
+ end
+
+ # Fails currently because the sandbox doesn't actually validate the stored_credential options
+ # def test_failed_authorize_with_bad_stored_cred_options
+ # assert auth = @gateway.authorize(@amount, @credit_card, @options.merge(stored_credential_usage: 'FIRST'))
+ # assert_success auth
+ # assert auth.authorization
+ # assert auth.params['scheme_response']
+ # assert auth.params['transaction_identifier']
+ #
+ # assert capture = @gateway.capture(@amount, auth.authorization, authorization_validated: true)
+ # assert_success capture
+ #
+ # options = @options.merge(
+ # order_id: generate_unique_id,
+ # stored_credential_usage: 'MEH',
+ # stored_credential_initiated_reason: 'BLAH',
+ # stored_credential_transaction_id: 'nah'
+ # )
+ # assert next_auth = @gateway.authorize(@amount, @credit_card, options)
+ # assert_failure next_auth
+ # end
+
+ def test_failed_authorize_with_3ds
+ session_id = generate_unique_id
+ options = @options.merge(
+ {
+ execute_threed: true,
+ accept_header: 'text/html',
+ session_id: session_id,
+ ip: '127.0.0.1',
+ cookie: 'machine=32423423'
+ })
+ assert first_message = @gateway.authorize(@amount, @threeDS_card, options)
+ assert_match %r{missing info for 3D-secure transaction}i, first_message.message
+ assert first_message.test?
+ assert first_message.params['issuer_url'].blank?
+ assert first_message.params['pa_request'].blank?
+ end
+
+ def test_3ds_version_1_parameters_pass_thru
+ options = @options.merge(
+ {
+ three_d_secure: {
+ version: '1.0.2',
+ xid: '',
+ cavv: 'MAAAAAAAAAAAAAAAAAAAAAAAAAA=',
+ eci: '05'
+ }
+ }
+ )
+
+ assert response = @gateway.authorize(@amount, @threeDS_card_external_MPI, @options.merge(options))
+ assert response.test?
+ assert response.success?
+ assert response.params['last_event'] || response.params['ok']
+ end
+
+ def test_3ds_version_2_parameters_pass_thru
+ options = @options.merge(
+ {
+ three_d_secure: {
+ version: '2.1.0',
+ xid: 'A' * 40,
+ cavv: 'MAAAAAAAAAAAAAAAAAAAAAAAAAA=',
+ eci: '05'
+ }
+ }
+ )
+
+ assert response = @gateway.authorize(@amount, @threeDS_card_external_MPI, @options.merge(options))
+ assert response.test?
+ assert response.success?
+ assert response.params['last_event'] || response.params['ok']
+ end
+
def test_failed_capture
assert response = @gateway.capture(@amount, 'bogus')
assert_failure response
@@ -43,46 +337,56 @@ def test_billing_address
assert_success @gateway.authorize(@amount, @credit_card, @options.merge(:billing_address => address))
end
- def test_void
- assert_success(response = @gateway.authorize(@amount, @credit_card, @options))
- assert_success (void = @gateway.void(response.authorization))
- assert_equal "SUCCESS", void.message
- assert void.params["cancel_received_order_code"]
+ def test_partial_address
+ billing_address = address
+ billing_address.delete(:address1)
+ billing_address.delete(:zip)
+ billing_address.delete(:country)
+ assert_success @gateway.authorize(@amount, @credit_card, @options.merge(:billing_address => billing_address))
end
- def test_void_nonexistent_transaction
- assert_failure response = @gateway.void('non_existent_authorization')
- assert_equal "Could not find payment for order", response.message
+ def test_ip_address
+ assert_success @gateway.authorize(@amount, @credit_card, @options.merge(ip: '192.18.123.12'))
end
- def test_currency
- assert_success(result = @gateway.authorize(@amount, @credit_card, @options.merge(:currency => 'USD')))
- assert_equal "USD", result.params['amount_currency_code']
+ def test_void
+ assert_success response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success void = @gateway.void(response.authorization, authorization_validated: true)
+ assert_equal 'SUCCESS', void.message
+ assert void.params['cancel_received_order_code']
end
- def test_authorize_currency_without_fractional_units
- assert_success(result = @gateway.authorize(1200, @credit_card, @options.merge(:currency => 'HUF')))
- assert_equal "HUF", result.params['amount_currency_code']
- assert_equal "12", result.params['amount_value']
+ def test_void_with_elo
+ assert_success response = @gateway.authorize(@amount, @elo_credit_card, @options.merge(currency: 'BRL'))
+ assert_success void = @gateway.void(response.authorization, authorization_validated: true)
+ assert_equal 'SUCCESS', void.message
+ assert void.params['cancel_received_order_code']
end
- def test_authorize_currency_without_fractional_units_and_fractions_in_amount
- assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'HUF')))
- assert_equal "HUF", result.params['amount_currency_code']
- assert_equal "12", result.params['amount_value']
+ def test_void_nonexistent_transaction
+ assert_failure response = @gateway.void('non_existent_authorization')
+ assert_equal 'Could not find payment for order', response.message
end
- def test_authorize_and_capture_currency_without_fractional_units_and_fractions_in_amount
- assert_success(auth = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'HUF')))
- assert_equal "12", auth.params['amount_value']
+ def test_authorize_fractional_currency
+ assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'USD')))
+ assert_equal 'USD', result.params['amount_currency_code']
+ assert_equal '1234', result.params['amount_value']
+ assert_equal '2', result.params['amount_exponent']
+ end
- assert_success(result = @gateway.capture(1234, auth.authorization))
- assert_equal "12", result.params['amount_value']
+ def test_authorize_nonfractional_currency
+ assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'IDR')))
+ assert_equal 'IDR', result.params['amount_currency_code']
+ assert_equal '12', result.params['amount_value']
+ assert_equal '0', result.params['amount_exponent']
end
- def test_purchase_currency_without_fractional_units_and_fractions_in_amount
- assert_success(result = @gateway.purchase(1234, @credit_card, @options.merge(:currency => 'HUF')))
- assert_equal "12", result.params['amount_value']
+ def test_authorize_three_decimal_currency
+ assert_success(result = @gateway.authorize(1234, @credit_card, @options.merge(:currency => 'OMR')))
+ assert_equal 'OMR', result.params['amount_currency_code']
+ assert_equal '1234', result.params['amount_value']
+ assert_equal '3', result.params['amount_exponent']
end
def test_reference_transaction
@@ -103,30 +407,182 @@ def test_refund_fails_unless_status_is_captured
assert refund = @gateway.refund(30, response.authorization)
assert_failure refund
- assert_equal "A transaction status of 'CAPTURED' or 'SETTLED' is required.", refund.message
+ assert_equal 'Order not ready', refund.message
end
def test_refund_nonexistent_transaction
- assert_failure response = @gateway.refund(@amount, "non_existent_authorization")
- assert_equal "Could not find payment for order", response.message
+ assert_failure response = @gateway.refund(@amount, 'non_existent_authorization')
+ assert_equal 'Could not find payment for order', response.message
+ end
+
+ def test_successful_verify
+ response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_match %r{SUCCESS}, response.message
end
+ def test_successful_verify_with_elo
+ response = @gateway.verify(@elo_credit_card, @options.merge(currency: 'BRL'))
+ assert_success response
+ assert_match %r{SUCCESS}, response.message
+ end
- # Worldpay has a delay between asking for a transaction to be captured and actually marking it as captured
- # These 2 tests work if you take the auth code, wait some time and then perform the next operation.
+ def test_failed_verify
+ response = @gateway.verify(@declined_card, @options)
+ assert_failure response
+ assert_match %r{REFUSED}, response.message
+ end
- # def test_refund
- # assert_success(response = @gateway.purchase(@amount, @credit_card, @options))
+ def test_successful_visa_credit_on_cft_gateway
+ credit = @cftgateway.credit(@amount, @credit_card, @options)
+ assert_success credit
+ assert_equal 'SUCCESS', credit.message
+ end
+
+ def test_successful_mastercard_credit_on_cft_gateway
+ cc = credit_card('5555555555554444')
+ credit = @cftgateway.credit(@amount, cc, @options)
+ assert_success credit
+ assert_equal 'SUCCESS', credit.message
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ clean_transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, clean_transcript)
+ assert_scrubbed(@credit_card.verification_value.to_s, clean_transcript)
+ end
+
+ def test_failed_authorize_with_unknown_card
+ assert auth = @gateway.authorize(@amount, @sodexo_voucher, @options)
+ assert_failure auth
+ assert_equal '5', auth.error_code
+ assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, auth.message
+ end
+
+ def test_failed_purchase_with_unknown_card
+ assert response = @gateway.purchase(@amount, @sodexo_voucher, @options)
+ assert_failure response
+ assert_equal '5', response.error_code
+ assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message
+ end
+
+ def test_failed_verify_with_unknown_card
+ response = @gateway.verify(@sodexo_voucher, @options)
+ assert_failure response
+ assert_equal '5', response.error_code
+ assert_match %r{XML failed validation: Invalid payment details : Card number not recognised:}, response.message
+ end
+
+ # Worldpay has a delay between asking for a transaction to be captured and actually marking it as captured
+ # These 2 tests work if you get authorizations from a purchase, wait some time and then perform the refund/void operation.
+ #
+ # def test_get_authorization
+ # response = @gateway.purchase(@amount, @credit_card, @options)
# assert response.authorization
- # refund = @gateway.refund(@amount, capture.authorization)
+ # puts 'auth: ' + response.authorization
+ # end
+ #
+ # def test_refund
+ # refund = @gateway.refund(@amount, '39270fd70be13aab55f84e28be45cad3')
# assert_success refund
- # assert_equal "SUCCESS", refund.message
+ # assert_equal 'SUCCESS', refund.message
# end
-
+ #
# def test_void_fails_unless_status_is_authorised
- # response = @gateway.void("33d6dfa9726198d44a743488cf611d3b") # existing transaction in CAPTURED state
+ # response = @gateway.void('replace_with_authorization') # existing transaction in CAPTURED state
# assert_failure response
- # assert_equal "A transaction status of 'AUTHORISED' is required.", response.message
+ # assert_equal 'A transaction status of 'AUTHORISED' is required.', response.message
# end
+ def test_successful_store
+ assert response = @gateway.store(@credit_card, @store_options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ assert_match response.params['payment_token_id'], response.authorization
+ assert_match 'shopper', response.authorization
+ assert_match @store_options[:customer], response.authorization
+ end
+
+ def test_successful_authorize_using_token
+ assert store = @gateway.store(@credit_card, @store_options)
+ assert_success store
+
+ assert response = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_authorize_using_token_and_minimum_options
+ assert store = @gateway.store(@credit_card, @store_options)
+ assert_success store
+
+ assert response = @gateway.authorize(@amount, store.authorization, order_id: generate_unique_id)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_purchase_using_token
+ assert store = @gateway.store(@credit_card, @store_options)
+ assert_success store
+
+ assert response = @gateway.authorize(@amount, store.authorization, @options)
+ assert_success response
+ assert_equal 'SUCCESS', response.message
+ end
+
+ def test_successful_verify_using_token
+ assert store = @gateway.store(@credit_card, @store_options)
+ assert_success store
+
+ response = @gateway.verify(store.authorization, @options)
+ assert_success response
+ assert_match %r{SUCCESS}, response.message
+ end
+
+ def test_successful_credit_using_token
+ assert store = @cftgateway.store(@credit_card, @store_options)
+ assert_success store
+
+ credit = @cftgateway.credit(@amount, store.authorization, @options)
+ assert_success credit
+ assert_equal 'SUCCESS', credit.message
+ end
+
+ def test_failed_store
+ assert response = @gateway.store(@credit_card, @store_options.merge(customer: '_invalidId'))
+ assert_failure response
+ assert_equal '2', response.error_code
+ assert_equal 'authenticatedShopperID cannot start with an underscore', response.message
+ end
+
+ def test_failed_authorize_using_token
+ assert store = @gateway.store(@declined_card, @store_options)
+ assert_success store
+
+ assert response = @gateway.authorize(@amount, store.authorization, @options)
+ assert_failure response
+ assert_equal '5', response.error_code
+ assert_equal 'REFUSED', response.message
+ end
+
+ def test_failed_authorize_using_bogus_token
+ assert response = @gateway.authorize(@amount, '|this|is|bogus', @options)
+ assert_failure response
+ assert_equal '2', response.error_code
+ assert_match 'tokenScope', response.message
+ end
+
+ def test_failed_verify_using_token
+ assert store = @gateway.store(@declined_card, @store_options)
+ assert_success store
+
+ response = @gateway.verify(store.authorization, @options)
+ assert_failure response
+ assert_equal '5', response.error_code
+ assert_match %r{REFUSED}, response.message
+ end
end
diff --git a/test/remote/gateways/remote_worldpay_us_test.rb b/test/remote/gateways/remote_worldpay_us_test.rb
new file mode 100644
index 00000000000..a0e50945fa6
--- /dev/null
+++ b/test/remote/gateways/remote_worldpay_us_test.rb
@@ -0,0 +1,137 @@
+require 'test_helper'
+
+class RemoteWorldpayUsTest < Test::Unit::TestCase
+ def setup
+ @gateway = WorldpayUsGateway.new(fixtures(:worldpay_us))
+
+ @amount = 100
+ @credit_card = credit_card('4446661234567892', :verification_value => '987')
+ @declined_card = credit_card('4000300011112220')
+ @check = check(:number => '12345654321')
+
+ @options = {
+ order_id: generate_unique_id,
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+ end
+
+ def test_successful_purchase
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_successful_purchase_on_backup_url
+ gateway = WorldpayUsGateway.new(fixtures(:worldpay_us).merge({ use_backup_url: true}))
+ response = gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_purchase
+ response = @gateway.purchase(@amount, @declined_card, @options)
+ assert_failure response
+ assert response.message =~ /DECLINED/
+ end
+
+ def test_successful_echeck_purchase
+ response = @gateway.purchase(@amount, @check, @options)
+ assert_equal 'Succeeded', response.message
+ assert_success response
+ end
+
+ def test_failed_echeck_purchase
+ response = @gateway.purchase(@amount, check(routing_number: '23433'), @options)
+ assert_failure response
+ assert response.message =~ /DECLINED/
+ end
+
+ def test_successful_authorize_and_capture
+ assert response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_match %r(^\d+\|.+$), response.authorization
+
+ assert capture = @gateway.capture(@amount, response.authorization)
+ assert_success capture
+ assert_equal 'Succeeded', capture.message
+ end
+
+ def test_failed_authorize
+ assert response = @gateway.authorize(@amount, @declined_card, @options)
+ assert_failure response
+ assert response.message =~ /DECLINED/
+ end
+
+ def test_successful_refund
+ assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+
+ assert refund = @gateway.refund(@amount, response.authorization)
+ assert_success refund
+ assert_equal 'Succeeded', refund.message
+ end
+
+ def test_successful_void
+ auth = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success auth
+
+ assert void = @gateway.void(auth.authorization)
+ assert_success void
+ end
+
+ def test_failed_void
+ response = @gateway.void('')
+ assert_failure response
+ end
+
+ def test_successful_verify
+ assert response = @gateway.verify(@credit_card, @options)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ assert_success response.responses.last, 'The void should succeed'
+ end
+
+ def test_failed_verify
+ bogus_card = credit_card('4424222222222222')
+ assert response = @gateway.verify(bogus_card, @options)
+ assert_failure response
+ assert response.message =~ /DECLINED/
+ end
+
+ def test_passing_billing_address
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(:billing_address => address))
+ assert_success response
+ end
+
+ def test_invalid_login
+ gateway = WorldpayUsGateway.new(
+ :acctid => '',
+ :subid => '',
+ :merchantpin => ''
+ )
+ assert response = gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert response.message =~ /DECLINED/
+ end
+
+ def test_transcript_scrubbing
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@credit_card.number, transcript)
+ assert_scrubbed(@credit_card.verification_value, transcript)
+ assert_scrubbed(@gateway.options[:merchantpin], transcript)
+
+ transcript = capture_transcript(@gateway) do
+ @gateway.purchase(@amount, @check, @options)
+ end
+ transcript = @gateway.scrub(transcript)
+
+ assert_scrubbed(@check.account_number, transcript)
+ assert_scrubbed(@gateway.options[:merchantpin], transcript)
+ end
+end
diff --git a/test/remote/integrations/remote_direc_pay_integration_test.rb b/test/remote/integrations/remote_direc_pay_integration_test.rb
deleted file mode 100644
index 863b43cb708..00000000000
--- a/test/remote/integrations/remote_direc_pay_integration_test.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'test_helper'
-
-class RemoteDirecPayIntegrationTest < Test::Unit::TestCase
- include ActiveMerchant::Billing::Integrations
-
- def setup
- @helper = DirecPay::Helper.new('#1234', fixtures(:direc_pay)[:mid], :amount => 500, :currency => 'INR')
- @notification = DirecPay::Notification.new('test=dummy-value')
- end
-
- def tear_down
- ActiveMerchant::Billing::Base.integration_mode = :test
- end
-
- def test_return_is_always_acknowledged
- assert_equal "https://test.timesofmoney.com/direcpay/secure/dpMerchantTransaction.jsp", DirecPay.service_url
- assert_nothing_raised do
- assert_equal true, @notification.acknowledge
- end
- end
-
-end
diff --git a/test/remote/integrations/remote_gestpay_integration_test.rb b/test/remote/integrations/remote_gestpay_integration_test.rb
deleted file mode 100644
index a56b1d742a0..00000000000
--- a/test/remote/integrations/remote_gestpay_integration_test.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'test_helper'
-
-class RemoteGestpayIntegrationTest < Test::Unit::TestCase
- include ActiveMerchant::Billing::Integrations
-
- def setup
- # Your Gestpay ShopLogin
- @shop_login = 'SHOPLOGIN'
-
- @helper = Gestpay::Helper.new('order-500', @shop_login, :amount => '5.00', :currency => 'EUR')
- end
-
- def test_get_encryption_string
- # Extra fields don't work yet
- # @helper.customer :first_name => 'Cody', :last_name => 'Fauser', :email => 'cody@example.com'
- response = @helper.send(:get_encrypted_string)
- assert !response.blank?
- end
-
- def test_get_encryption_string_fails
- @helper = Gestpay::Helper.new('order-500','99999999', :amount => '5.00', :currency => 'EUR')
- assert_raise(StandardError) do
- assert @helper.send(:get_encrypted_string).blank?
- end
- end
-
- def test_unknown_shop_for_decryption_request
- assert_raise(StandardError) do
- Gestpay::Notification.new(raw_query_string)
- end
- end
-
- private
- def raw_query_string
- "a=900000&b=F7DEB36478FD84760F9134F23C922697272D57DE6D4518EB9B4D468B769D9A3A8071B6EB160B35CB412FC1820C7CC12D17B3141855B1ED55468613702A2E213DDE9DE5B0209E13C416448AE833525959F05693172D7F0656"
- end
-end
diff --git a/test/remote/integrations/remote_integration_helper.rb b/test/remote/integrations/remote_integration_helper.rb
deleted file mode 100644
index 2dedd3de04b..00000000000
--- a/test/remote/integrations/remote_integration_helper.rb
+++ /dev/null
@@ -1,99 +0,0 @@
-require 'mechanize'
-require 'action_view/base'
-require 'launchy'
-require 'mongrel' unless defined?(JRUBY_VERSION)
-
-module RemoteIntegrationHelper
- class FakeView < ActionView::Base
- include ActiveMerchant::Billing::Integrations::ActionViewHelper
- end
-
- def submit(string)
- view = FakeView.new
- body = view.render(:inline => string)
- page = Mechanize::Page.new(nil, {'content-type' => 'text/html; charset=utf-8'}, body, nil, agent)
- page.forms.first.submit
- end
-
- def agent
- @agent ||= Mechanize.new{|a| a.log = Logger.new(STDERR) if verbose? }
- end
-
- def listen_for_notification(external_port=42063)
- exception = nil
- requests = []
- test = self
- server = notification_server do |request, response|
- begin
- test.log("[HANDLER] request received")
- requests << request
- response.start(200, true){|h,b| b << "OK"}
- rescue Exception => e
- exception = e
- end
- end
- listener = server.run
-
- mapper = Thread.new do
- require 'UPnP.rb'
- upnp = UPnP::UPnP.new(true, 10)
- begin
- log "[MAPPER] adding port mapping"
- upnp.addPortMapping(external_port, 42063, UPnP::Protocol::TCP, "AM Test Port")
- log "[MAPPER] yielding"
- yield("http://#{upnp.externalIP}#{":#{external_port}" unless external_port == 80}/")
- count = 0
- 20.times do
- log "[MAPPER] waiting"
- sleep 1
- break if requests.size > 0 && requests.size == count
- count = requests.size
- end
- log "[MAPPER] returned"
- rescue Exception => e
- log "[MAPPER] exception #{e}"
- exception = e
- ensure
- log "[MAPPER] deleting port mapping"
- upnp.deletePortMapping(external_port, UPnP::Protocol::TCP)
- log "[MAPPER] stopping server"
- server.stop(true)
- log "[MAPPER] server stopped"
- end
- end
-
- [listener, mapper].each{|t| t.abort_on_exception = true}
- [listener, mapper].each{|t| t.join}
-
- raise exception if exception
-
- assert requests.size > 0
-
- request = requests.last
- log "[REQUEST] QUERY: #{request.params["QUERY_STRING"]}"
- log "[REQUEST] BODY: #{request.body.string}"
- request
- end
-
- def notification_server(&handler_body)
- http_server = Mongrel::HttpServer.new('0.0.0.0', 42063)
- handler = Mongrel::HttpHandler.new
- handler.class_eval do
- define_method(:process, &handler_body)
- end
- http_server.register('/', handler)
- end
-
- def log(message)
- puts message if verbose?
- end
-
- def verbose?
- (ENV["ACTIVE_MERCHANT_DEBUG"] == "true")
- end
-
- def open_in_browser(body)
- File.open('tmp.html', 'w'){|f| f.write body}
- Launchy::Browser.run("file:///#{Dir.pwd}/tmp.html")
- end
-end
diff --git a/test/remote/integrations/remote_paypal_integration_test.rb b/test/remote/integrations/remote_paypal_integration_test.rb
deleted file mode 100644
index 006ca399d84..00000000000
--- a/test/remote/integrations/remote_paypal_integration_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'test_helper'
-
-class RemotePaypalIntegrationTest < Test::Unit::TestCase
- include ActiveMerchant::Billing::Integrations
-
- def setup
- @paypal = Paypal::Notification.new('')
- end
-
- def tear_down
- ActiveMerchant::Billing::Base.integration_mode = :test
- end
-
- def test_raw
- assert_equal "https://www.sandbox.paypal.com/cgi-bin/webscr", Paypal.service_url
- assert_nothing_raised do
- assert_equal false, @paypal.acknowledge
- end
- end
-
- def test_valid_sender_always_true
- ActiveMerchant::Billing::Base.integration_mode = :production
- assert @paypal.valid_sender?(nil)
- assert @paypal.valid_sender?('127.0.0.1')
- end
-end
diff --git a/test/remote/integrations/remote_payu_in_integration_test.rb b/test/remote/integrations/remote_payu_in_integration_test.rb
deleted file mode 100644
index be8aa9c8547..00000000000
--- a/test/remote/integrations/remote_payu_in_integration_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'test_helper'
-
-class RemotePayuInIntegrationTest < Test::Unit::TestCase
- include ActiveMerchant::Billing::Integrations
-
- def setup
- @payu_in = PayuIn::Notification.new(http_raw_data, :credential1 => 'C0Dr8m', :credential2 => '3sf0jURk')
- end
-
- def test_raw
- ActiveMerchant::Billing::Base.integration_mode = :production
- assert_equal "https://secure.payu.in/_payment.php", PayuIn.service_url
-
- ActiveMerchant::Billing::Base.integration_mode = :test
- assert_equal "https://test.payu.in/_payment.php", PayuIn.service_url
-
- assert_nothing_raised do
- assert @payu_in.checksum_ok?
- end
- end
-
- private
- def http_raw_data
- "mihpayid=403993715508030204&mode=CC&status=success&unmappedstatus=captured&key=C0Dr8m&txnid=4ba4afe87f7e73468f2a&amount=10.00&discount=0.00&addedon=2013-05-10 18 32 30&productinfo=Product Info&firstname=Payu-Admin&lastname=&address1=&address2=&city=&state=&country=&zipcode=&email=test@example.com&phone=1234567890&udf1=&udf2=&udf3=&udf4=&udf5=&udf6=&udf7=&udf8=&udf9=&udf10=&hash=ef0c1b509a42b802a4938c25dc9bb9efe40b75a7dfb8bde1a6f126fa1f86cee264c5e5a17e87db85150d6d8912eafda838416e669712f1989dcb9cbdb8c24219&field1=313069903923&field2=999999&field3=59117331831301&field4=-1&field5=&field6=&field7=&field8=&PG_TYPE=HDFC&bank_ref_num=59117331831301&bankcode=CC&error=E000&cardnum=512345XXXXXX2346&cardhash=766f0227cc4b4c5f773a04cb31d8d1c5be071dd8d08fe365ecf5e2e5c947546d"
- end
-end
diff --git a/test/remote/integrations/remote_pxpay_integration_test.rb b/test/remote/integrations/remote_pxpay_integration_test.rb
deleted file mode 100644
index 77ace6e631e..00000000000
--- a/test/remote/integrations/remote_pxpay_integration_test.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-require 'test_helper'
-require 'remote/integrations/remote_integration_helper'
-
-class RemotePxpayIntegrationTest < Test::Unit::TestCase
- include ActiveMerchant::Billing::Integrations
- include ActionViewHelperTestHelper
-
- def setup
- @output_buffer = ""
- @options = fixtures(:pxpay)
-
- @helper = Pxpay::Helper.new('500', @options[:login], :amount => "120.99", :currency => 'USD', :credential2 => @options[:password])
- end
-
- def test_merchant_references_longer_than_50_characters_should_be_trimmed
- @helper.description = "more than 50 chars--------------------40--------50---55"
- request = @helper.send(:generate_request)
- assert_match /more than 50 chars--------------------40--------50<\/MerchantReference>/, request
- end
-
- def test_valid_credentials_returns_secure_token
- @helper.return_url "http://t/pxpay/return_url"
- @helper.cancel_return_url "http://t/pxpay/cancel_url"
-
- response = @helper.send :request_secure_redirect
-
- assert_equal "1", response[:valid]
- assert response[:redirect].present?
- end
-
- def test_redirect_url_matches_expected
- @helper.return_url "http://t/pxpay/return_url"
- @helper.cancel_return_url "http://t/pxpay/cancel_url"
-
- response = @helper.send :request_secure_redirect
-
- url = URI.parse(response[:redirect])
- assert_equal Pxpay.service_url, "#{url.scheme}://#{url.host}#{url.path}"
- end
-
- def test_entire_payment
- @order_id = Digest::MD5.hexdigest("#{Time.now}+#{rand}")[0,6]
-
- # generate form to redirect customer to pxpay gateway
- generate_valid_redirect_form @order_id
-
- # submit generated form and ensure we're redirected to the CC info page
- agent = Mechanize.new { |a|
- a.user_agent_alias = 'Mac Safari'
- }
-
- page = Mechanize::Page.new(nil,{'content-type'=>'text/html'}, @output_buffer, nil, agent)
- gateway_page = agent.submit page.forms.first
-
- # entire valid test CC credentials and submit form
-
- assert gateway_page.forms.size > 0
-
- confirm_page = gateway_page.form_with(:name => 'PmtEnt') do |form|
- form.CardNum = '4111111111111111'
- form.ExMnth = '12'
- form.ExYr = '20'
- form.NmeCard = 'Firstname Lastname'
- form.Cvc2 = '123'
- end.submit
-
- # pull out redirected URL params
- return_url = confirm_page.link_with(:text => 'Click Here to Proceed to the Next step').href
-
- assert !return_url.empty?
-
- param_string = return_url.sub(/.*\?/, "")
-
- notification = Pxpay.notification(param_string, :credential1 => @options[:login], :credential2 => @options[:password])
-
- assert notification.acknowledge
- assert notification.complete?
- assert_match "Completed", notification.status
- assert_match "157.00", notification.gross
- assert notification.transaction_id.present?
- assert_match @order_id, notification.item_id
- end
-
- def test_failed_payment
-
- @order_id = Digest::MD5.hexdigest("#{Time.now}+#{rand}")[0,6]
-
- # generate form to redirect customer to pxpay gateway
- generate_valid_redirect_form @order_id
-
- # submit generated form and ensure we're redirected to the CC info page
- agent = Mechanize.new { |a|
- a.user_agent_alias = 'Mac Safari'
- }
-
- page = Mechanize::Page.new(nil,{'content-type'=>'text/html'}, @output_buffer, nil, agent)
- gateway_page = agent.submit page.forms.first
-
- # entire valid test CC credentials and submit form
-
- assert gateway_page.forms.size > 0
-
- confirm_page = gateway_page.form_with(:name => 'PmtEnt') do |form|
- form.CardNum = '4111111111111112'
- form.ExMnth = '12'
- form.ExYr = '10'
- form.NmeCard = 'Firstname Lastname'
- form.Cvc2 = '123'
- end.submit
-
- # pull out redirected URL params
- return_url = confirm_page.link_with(:text => 'Click Here to Proceed to the Next step').href
-
- assert !return_url.empty?
-
- param_string = return_url.sub(/.*\?/, "")
-
- notification = Pxpay.notification(param_string, :credential1 => @options[:login], :credential2 => @options[:password])
-
- assert_false notification.complete?
- assert notification.acknowledge
- assert_match "Failed", notification.status
- assert_match @order_id, notification.item_id
- end
-
- private
-
- def generate_valid_redirect_form(order_id)
- payment_service_for(order_id, @options[:login], :service => :pxpay, :amount => "157.0") do |service|
- # You must set :credential2 to your pxpay key
- service.credential2 @options[:password]
-
- service.customer_id 8
- service.customer :first_name => 'g',
- :last_name => 'g',
- :email => 'g@g.com',
- :phone => '3'
-
- service.description "Order Description"
-
- # The end-user is presented with the HTML produced by the notify_url.
- service.return_url "http://t/pxpay/payment_received_notification_sub_step"
- service.cancel_return_url "http://t/pxpay/payment_cancelled"
- service.currency 'USD'
- end
- end
-end
-
diff --git a/test/remote/integrations/remote_quickpay_integration_test.rb b/test/remote/integrations/remote_quickpay_integration_test.rb
deleted file mode 100644
index 71cfa0b4c8e..00000000000
--- a/test/remote/integrations/remote_quickpay_integration_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'test_helper'
-
-class RemoteQuickPayIntegrationTest < Test::Unit::TestCase
- include ActiveMerchant::Billing::Integrations
-
- def setup
- @quickpay = Quickpay::Notification.new('')
- end
-
- def tear_down
- ActiveMerchant::Billing::Base.integration_mode = :test
- end
-
- def test_raw
- assert_equal "https://secure.quickpay.dk/form/", Quickpay.service_url
- assert_nothing_raised do
- assert_equal false, @quickpay.acknowledge
- end
- end
-
- def test_valid_sender_always_true
- ActiveMerchant::Billing::Base.integration_mode = :production
- assert @quickpay.valid_sender?(nil)
- assert @quickpay.valid_sender?('127.0.0.1')
- end
-end
diff --git a/test/remote/integrations/remote_valitor_integration_test.rb b/test/remote/integrations/remote_valitor_integration_test.rb
deleted file mode 100644
index 2f93cfd4372..00000000000
--- a/test/remote/integrations/remote_valitor_integration_test.rb
+++ /dev/null
@@ -1,157 +0,0 @@
-require 'test_helper'
-require 'remote/integrations/remote_integration_helper'
-require 'nokogiri'
-
-class RemoteValitorIntegrationTest < Test::Unit::TestCase
- include RemoteIntegrationHelper
-
- def setup
- @order = "order#{generate_unique_id}"
- @login = fixtures(:valitor)[:login]
- @password = fixtures(:valitor)[:password]
- end
-
- def test_full_purchase
- notification_request = listen_for_notification(80) do |notify_url|
- payment_page = submit %(
- <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %>
- <% service.product(1, :amount => 100, :description => 'PRODUCT1', :discount => '0') %>
- <% service.return_url = 'http://example.org/return' %>
- <% service.cancel_return_url = 'http://example.org/cancel' %>
- <% service.notify_url = '#{notify_url}' %>
- <% service.success_text = 'SuccessText!' %>
- <% service.language = 'en' %>
- <% end %>
- )
-
- assert_match(%r(http://example.org/cancel)i, payment_page.body)
- assert_match(%r(PRODUCT1), payment_page.body)
-
- form = payment_page.forms.first
- form['tbKortnumer'] = '4111111111111111'
- form['drpGildistimiManudur'] = '12'
- form['drpGildistimiAr'] = Time.now.year
- form['tbOryggisnumer'] = '000'
- result_page = form.submit(form.submits.first)
-
- assert continue_link = result_page.links.detect{|e| e.text =~ /successtext!/i}
- assert_match(%r(^http://example.org/return\?)i, continue_link.href)
-
- check_common_fields(return_from(continue_link.href))
- end
-
- check_common_fields(notification_from(notification_request))
- end
-
- def test_customer_fields
- payment_page = submit %(
- <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %>
- <% service.product(1, :amount => 100, :description => 'test', :discount => '0') %>
- <% service.return_url = 'http://example.org/return' %>
- <% service.cancel_return_url = 'http://example.org/cancel' %>
- <% service.success_text = 'SuccessText!' %>
- <% service.language = 'en' %>
- <% service.collect_customer_info %>
- <% end %>
- )
-
- form = payment_page.forms.first
- form['tbKortnumer'] = '4111111111111111'
- form['drpGildistimiManudur'] = '12'
- form['drpGildistimiAr'] = Time.now.year
- form['tbOryggisnumer'] = '000'
- form['tbKaupNafn'] = "NAME"
- form['tbKaupHeimilisfang'] = "123 ADDRESS"
- form['tbKaupPostnumer'] = "98765"
- form['tbKaupStadur'] = "CITY"
- form['tbKaupLand'] = "COUNTRY"
- form['tbKaupTolvupostfang'] = "EMAIL@EXAMPLE.COM"
- form['tbAthugasemdir'] = "COMMENTS"
- result_page = form.submit(form.submits.first)
-
- assert continue_link = result_page.links.detect{|e| e.text =~ /successtext!/i}
- assert_match(%r(^http://example.org/return\?)i, continue_link.href)
-
- ret = return_from(continue_link.href)
- check_common_fields(ret)
- assert_equal "NAME", ret.customer_name
- assert_equal "123 ADDRESS", ret.customer_address
- assert_equal "98765", ret.customer_zip
- assert_equal "CITY", ret.customer_city
- assert_equal "COUNTRY", ret.customer_country
- assert_equal "EMAIL@EXAMPLE.COM", ret.customer_email
- assert_equal "COMMENTS", ret.customer_comment
- end
-
- def test_products
- payment_page = submit %(
- <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %>
- <% service.product(1, :amount => 100, :description => 'PRODUCT1') %>
- <% service.product(2, :amount => 200, :description => 'PRODUCT2', :discount => '50') %>
- <% service.product(3, :amount => 300, :description => 'PRODUCT3', :quantity => '6') %>
- <% service.return_url = 'http://example.org/return' %>
- <% service.cancel_return_url = 'http://example.org/cancel' %>
- <% service.success_text = 'SuccessText!' %>
- <% service.language = 'en' %>
- <% service.collect_customer_info %>
- <% end %>
- )
-
- assert_match(%r(http://example.org/cancel)i, payment_page.body)
-
- doc = Nokogiri::HTML(payment_page.body)
- rows = doc.xpath("//table[@class='VoruTafla']//tr")
- assert_equal 5, rows.size
- check_product_row(rows[1], "PRODUCT1", "1", "100 ISK", "0 ISK", "100 ISK")
- check_product_row(rows[2], "PRODUCT2", "1", "200 ISK", "50 ISK", "150 ISK")
- check_product_row(rows[3], "PRODUCT3", "6", "300 ISK", "0 ISK", "1.800 ISK")
- assert_match /2.050 ISK/, rows[4].element_children.first.text
- end
-
- def test_default_product_if_none_provided
- payment_page = submit %(
- <% payment_service_for('#{@order}', '#{@login}', :service => :valitor, :credential2 => #{@password}, :html => {:method => 'GET'}) do |service| %>
- <% service.return_url = 'http://example.org/return' %>
- <% service.cancel_return_url = 'http://example.org/cancel' %>
- <% service.success_text = 'SuccessText!' %>
- <% service.language = 'en' %>
- <% service.collect_customer_info %>
- <% end %>
- )
-
- assert_match(%r(http://example.org/cancel)i, payment_page.body)
-
- doc = Nokogiri::HTML(payment_page.body)
- rows = doc.xpath("//table[@class='VoruTafla']//tr")
- assert_equal 5, rows.size
- check_product_row(rows[1], "PRODUCT1", "1", "100 ISK", "0 ISK", "100 ISK")
- assert_match /2.050 ISK/, rows[4].element_children.first.text
- end
-
- def check_product_row(row, desc, quantity, amount, discount, total)
- assert_equal desc, row.element_children[0].text.strip
- assert_equal quantity, row.element_children[1].text.strip
- assert_equal amount, row.element_children[2].text.strip
- assert_equal discount, row.element_children[3].text.strip
- assert_equal total, row.element_children[4].text.strip
- end
-
- def check_common_fields(response)
- assert response.success?
- assert_equal 'VISA', response.card_type
- assert_equal '9999', response.card_last_four # No idea why this comes back with 9's
- assert_equal @order, response.order
- assert response.received_at.length > 0
- assert response.authorization_number.length > 0
- assert response.transaction_number.length > 0
- assert response.transaction_id.length > 0
- end
-
- def return_from(uri)
- ActiveMerchant::Billing::Integrations::Valitor.return(uri.split('?').last, :credential2 => @password)
- end
-
- def notification_from(request)
- ActiveMerchant::Billing::Integrations::Valitor.notification(request.params["QUERY_STRING"], :credential2 => @password)
- end
-end
\ No newline at end of file
diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.121.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.121.xsd
new file mode 100644
index 00000000000..dbf57b71011
--- /dev/null
+++ b/test/schema/cyber_source/CyberSourceTransaction_1.121.xsd
@@ -0,0 +1,3627 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd
new file mode 100644
index 00000000000..1bd9f4a1b04
--- /dev/null
+++ b/test/schema/cyber_source/CyberSourceTransaction_1.153.xsd
@@ -0,0 +1,4770 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd
new file mode 100644
index 00000000000..577ae9fc8a6
--- /dev/null
+++ b/test/schema/cyber_source/CyberSourceTransaction_1.155.xsd
@@ -0,0 +1,4857 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd b/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd
new file mode 100644
index 00000000000..74dc2e7b7a5
--- /dev/null
+++ b/test/schema/cyber_source/CyberSourceTransaction_1.156.xsd
@@ -0,0 +1,4894 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/schema/firstdata_e4/v11.xsd b/test/schema/firstdata_e4/v11.xsd
new file mode 100644
index 00000000000..b2bbc2996f6
--- /dev/null
+++ b/test/schema/firstdata_e4/v11.xsd
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/schema/firstdata_e4/v27.xsd b/test/schema/firstdata_e4/v27.xsd
new file mode 100644
index 00000000000..775018838c6
--- /dev/null
+++ b/test/schema/firstdata_e4/v27.xsd
@@ -0,0 +1,223 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/schema/orbital/Request_PTI54.xsd b/test/schema/orbital/Request_PTI77.xsd
similarity index 81%
rename from test/schema/orbital/Request_PTI54.xsd
rename to test/schema/orbital/Request_PTI77.xsd
index 0a75e3a164d..bcae535f6f5 100755
--- a/test/schema/orbital/Request_PTI54.xsd
+++ b/test/schema/orbital/Request_PTI77.xsd
@@ -1,951 +1,1093 @@
-
-
-
-
- Top level element for all XML request transaction types
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- New order Transaction Types
-
-
-
-
-
- Auth Only No Capture
-
-
-
-
- Auth and Capture
-
-
-
-
- Force Auth No Capture and no online authorization
-
-
-
-
- Force Auth No Capture and no online authorization
-
-
-
-
- Force Auth and Capture no online authorization
-
-
-
-
- Refund and Capture no online authorization
-
-
-
-
-
-
- New order Industry Types
-
-
-
-
-
- Ecommerce transaction
-
-
-
-
- Recurring Payment transaction
-
-
-
-
- Mail Order Telephone Order transaction
-
-
-
-
- Interactive Voice Response
-
-
-
-
- Interactive Voice Response
-
-
-
-
-
-
-
-
-
-
-
- Tax not provided
-
-
-
-
- Tax included
-
-
-
-
- Non-taxable transaction
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Stratus
-
-
-
-
- Tandam
-
-
-
-
-
-
-
-
-
-
-
- No mapping to order data
-
-
-
-
- Use customer reference for OrderID
-
-
-
-
- Use customer reference for both Order Id and Order Description
-
-
-
-
- Use customer reference for Order Description
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Auto Generate the CustomerRefNum
-
-
-
-
- Use OrderID as the CustomerRefNum
-
-
-
-
- Use CustomerRefNum Element
-
-
-
-
- Use the description as the CustomerRefNum
-
-
-
-
- Ignore. We will Ignore this entry if it's passed in the XML
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- American Express
-
-
-
-
- Carte Blanche
-
-
-
-
- Diners Club
-
-
-
-
- Discover
-
-
-
-
- GE Twinpay Credit
-
-
-
-
- GECC Private Label Credit
-
-
-
-
- JCB
-
-
-
-
- Mastercard
-
-
-
-
- Visa
-
-
-
-
- GE Twinpay Debit
-
-
-
-
- Switch / Solo
-
-
-
-
- Electronic Check
-
-
-
-
- Flex Cache
-
-
-
-
- European Direct Debit
-
-
-
-
- Bill Me Later
-
-
-
-
- PINLess Debit
-
-
-
-
- International Maestro
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Credit Card
-
-
-
-
- Swith/Solo
-
-
-
-
- Electronic Check
-
-
-
-
- PINLess Debit
-
-
-
-
- European Direct Debit
-
-
-
-
- International Maestro
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- United States
-
-
-
-
- Canada
-
-
-
-
- Germany
-
-
-
-
- Great Britain
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Yes
-
-
-
-
- Yes
-
-
-
-
- No
-
-
-
-
- No
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Authenticated
-
-
-
-
- Attempted
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- First Recurring Transaction
-
-
-
-
- Subsequent Recurring Transactions
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Top level element for all XML request transaction types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ New order Transaction Types
+
+
+
+
+
+ Auth Only No Capture
+
+
+
+
+ Auth and Capture
+
+
+
+
+ Force Auth No Capture and no online authorization
+
+
+
+
+ Force Auth No Capture and no online authorization
+
+
+
+
+ Force Auth and Capture no online authorization
+
+
+
+
+ Refund and Capture no online authorization
+
+
+
+
+
+
+ New order Industry Types
+
+
+
+
+
+ Ecommerce transaction
+
+
+
+
+ Recurring Payment transaction
+
+
+
+
+ Mail Order Telephone Order transaction
+
+
+
+
+ Interactive Voice Response
+
+
+
+
+ Interactive Voice Response
+
+
+
+
+
+
+
+
+
+
+
+ Tax not provided
+
+
+
+
+ Tax included
+
+
+
+
+ Non-taxable transaction
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Stratus
+
+
+
+
+ Tandam
+
+
+
+
+
+
+
+
+
+
+
+ No mapping to order data
+
+
+
+
+ Use customer reference for OrderID
+
+
+
+
+ Use customer reference for both Order Id and Order Description
+
+
+
+
+ Use customer reference for Order Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto Generate the CustomerRefNum
+
+
+
+
+ Use OrderID as the CustomerRefNum
+
+
+
+
+ Use CustomerRefNum Element
+
+
+
+
+ Use the description as the CustomerRefNum
+
+
+
+
+ Ignore. We will Ignore this entry if it's passed in the XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ American Express
+
+
+
+
+ Carte Blanche
+
+
+
+
+ Diners Club
+
+
+
+
+ Discover
+
+
+
+
+ GE Twinpay Credit
+
+
+
+
+ GECC Private Label Credit
+
+
+
+
+ JCB
+
+
+
+
+ Mastercard
+
+
+
+
+ Visa
+
+
+
+
+ GE Twinpay Debit
+
+
+
+
+ Switch / Solo
+
+
+
+
+ Electronic Check
+
+
+
+
+ Flex Cache
+
+
+
+
+ European Direct Debit
+
+
+
+
+ Bill Me Later
+
+
+
+
+ PINLess Debit
+
+
+
+
+ International Maestro
+
+
+
+
+ ChaseNet Credit
+
+
+
+
+ ChaseNet Signature Debit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Credit Card
+
+
+
+
+ Swith/Solo
+
+
+
+
+ Electronic Check
+
+
+
+
+ PINLess Debit
+
+
+
+
+ European Direct Debit
+
+
+
+
+ International Maestro
+
+
+
+
+ International Maestro
+
+
+
+
+ International Maestro
+
+
+
+
+ International Maestro
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ United States
+
+
+
+
+ Canada
+
+
+
+
+ Germany
+
+
+
+
+ Great Britain
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Yes
+
+
+
+
+ Yes
+
+
+
+
+ No
+
+
+
+
+ No
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ First Recurring Transaction
+
+
+
+
+ Subsequent Recurring Transactions
+
+
+
+
+ First Installment Transaction
+
+
+
+
+ Subsequent Installment Transactions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/schema/vindicia/Vindicia.xsd b/test/schema/vindicia/Vindicia.xsd
deleted file mode 100644
index cd642358c7c..00000000000
--- a/test/schema/vindicia/Vindicia.xsd
+++ /dev/null
@@ -1,1005 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/support/mercury_helper.rb b/test/support/mercury_helper.rb
index e52ad7bb800..a8afafa25e5 100644
--- a/test/support/mercury_helper.rb
+++ b/test/support/mercury_helper.rb
@@ -2,43 +2,43 @@ module MercuryHelper
module BatchClosing
def close_batch
xml = Builder::XmlMarkup.new
- xml.tag! "TStream" do
- xml.tag! "Admin" do
+ xml.tag! 'TStream' do
+ xml.tag! 'Admin' do
xml.tag! 'MerchantID', @options[:login]
- xml.tag! 'TranCode', "BatchSummary"
+ xml.tag! 'TranCode', 'BatchSummary'
end
end
xml = xml.target!
- response = commit("BatchSummary", xml)
+ response = commit('BatchSummary', xml)
xml = Builder::XmlMarkup.new
- xml.tag! "TStream" do
- xml.tag! "Admin" do
+ xml.tag! 'TStream' do
+ xml.tag! 'Admin' do
xml.tag! 'MerchantID', @options[:login]
- xml.tag! 'OperatorID', response.params["operator_id"]
- xml.tag! 'TranCode', "BatchClose"
- xml.tag! 'BatchNo', response.params["batch_no"]
- xml.tag! 'BatchItemCount', response.params["batch_item_count"]
- xml.tag! 'NetBatchTotal', response.params["net_batch_total"]
- xml.tag! 'CreditPurchaseCount', response.params["credit_purchase_count"]
- xml.tag! 'CreditPurchaseAmount', response.params["credit_purchase_amount"]
- xml.tag! 'CreditReturnCount', response.params["credit_return_count"]
- xml.tag! 'CreditReturnAmount', response.params["credit_return_amount"]
- xml.tag! 'DebitPurchaseCount', response.params["debit_purchase_count"]
- xml.tag! 'DebitPurchaseAmount', response.params["debit_purchase_amount"]
- xml.tag! 'DebitReturnCount', response.params["debit_return_count"]
- xml.tag! 'DebitReturnAmount', response.params["debit_return_amount"]
+ xml.tag! 'OperatorID', response.params['operator_id']
+ xml.tag! 'TranCode', 'BatchClose'
+ xml.tag! 'BatchNo', response.params['batch_no']
+ xml.tag! 'BatchItemCount', response.params['batch_item_count']
+ xml.tag! 'NetBatchTotal', response.params['net_batch_total']
+ xml.tag! 'CreditPurchaseCount', response.params['credit_purchase_count']
+ xml.tag! 'CreditPurchaseAmount', response.params['credit_purchase_amount']
+ xml.tag! 'CreditReturnCount', response.params['credit_return_count']
+ xml.tag! 'CreditReturnAmount', response.params['credit_return_amount']
+ xml.tag! 'DebitPurchaseCount', response.params['debit_purchase_count']
+ xml.tag! 'DebitPurchaseAmount', response.params['debit_purchase_amount']
+ xml.tag! 'DebitReturnCount', response.params['debit_return_count']
+ xml.tag! 'DebitReturnAmount', response.params['debit_return_amount']
end
end
xml = xml.target!
- commit("BatchClose", xml)
+ commit('BatchClose', xml)
end
def hashify_xml!(xml, response)
super
doc = REXML::Document.new(xml)
- doc.elements.each("//BatchSummary/*") do |node|
+ doc.elements.each('//BatchSummary/*') do |node|
response[node.name.underscore.to_sym] = node.text
end
end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 6b37b224c42..361857a3afb 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,22 +1,10 @@
-#!/usr/bin/env ruby
$:.unshift File.expand_path('../../lib', __FILE__)
-begin
- require 'rubygems'
- require 'bundler'
- Bundler.setup
-rescue LoadError => e
- puts "Error loading bundler (#{e.message}): \"gem install bundler\" for bundler support."
-end
+require 'bundler/setup'
require 'test/unit'
-require 'money'
-require 'mocha/version'
-if(Mocha::VERSION.split(".")[1].to_i < 12)
- require 'mocha'
-else
- require 'mocha/setup'
-end
+require 'mocha/test_unit'
+
require 'yaml'
require 'json'
require 'active_merchant'
@@ -24,27 +12,7 @@
require 'active_support/core_ext/integer/time'
require 'active_support/core_ext/numeric/time'
-
-begin
- require 'active_support/core_ext/time/acts_like'
-rescue LoadError
-end
-
-begin
- gem 'actionpack'
-rescue LoadError
- raise StandardError, "The view tests need ActionPack installed as gem to run"
-end
-
-require 'action_controller'
-require "action_view/template"
-begin
- require 'active_support/core_ext/module/deprecation'
- require 'action_dispatch/testing/test_process'
-rescue LoadError
- require 'action_controller/test_process'
-end
-require 'active_merchant/billing/integrations/action_view_helper'
+require 'active_support/core_ext/time/acts_like'
ActiveMerchant::Billing::Base.mode = :test
@@ -61,10 +29,9 @@ class SimpleTestGateway < ActiveMerchant::Billing::Gateway
class SubclassGateway < SimpleTestGateway
end
-
module ActiveMerchant
module Assertions
- AssertionClass = RUBY_VERSION > '1.9' ? MiniTest::Assertion : Test::Unit::AssertionFailedError
+ AssertionClass = defined?(Minitest) ? MiniTest::Assertion : Test::Unit::AssertionFailedError
def assert_field(field, value)
clean_backtrace do
@@ -72,7 +39,7 @@ def assert_field(field, value)
end
end
- # Allows the testing of you to check for negative assertions:
+ # Allows testing of negative assertions:
#
# # Instead of
# assert !something_that_is_false
@@ -91,57 +58,80 @@ def assert_false(boolean, message = nil)
end
end
- # A handy little assertion to check for a successful response:
+ # An assertion of a successful response:
#
# # Instead of
- # assert_success response
+ # assert response.success?
#
# # DRY that up with
# assert_success response
#
# A message will automatically show the inspection of the response
# object if things go afoul.
- def assert_success(response)
+ def assert_success(response, message=nil)
clean_backtrace do
- assert response.success?, "Response failed: #{response.inspect}"
+ assert response.success?, build_message(nil, "#{message + "\n" if message}Response expected to succeed: >", response)
end
end
# The negative of +assert_success+
- def assert_failure(response)
+ def assert_failure(response, message=nil)
clean_backtrace do
- assert_false response.success?, "Response expected to fail: #{response.inspect}"
+ assert !response.success?, build_message(nil, "#{message + "\n" if message}Response expected to fail: >", response)
end
end
- def assert_valid(validateable)
+ def assert_valid(model, message=nil)
+ errors = model.validate
+
clean_backtrace do
- assert validateable.valid?, "Expected to be valid"
+ assert_equal({}, errors, (message || 'Expected to be valid'))
end
+
+ errors
end
- def assert_not_valid(validateable)
+ def assert_not_valid(model)
+ errors = model.validate
+
clean_backtrace do
- assert_false validateable.valid?, "Expected to not be valid"
+ assert_not_equal({}, errors, 'Expected to not be valid')
end
+
+ errors
+ end
+
+ def assert_deprecation_warning(message=nil)
+ ActiveMerchant.expects(:deprecated).with(message || anything)
+ yield
end
- def assert_deprecation_warning(message, target)
- target.expects(:deprecated).with(message)
+ def refute(value, message = nil)
+ assert(!value, message)
+ end
+
+ def silence_deprecation_warnings
+ ActiveMerchant.stubs(:deprecated)
yield
end
- def assert_no_deprecation_warning(target)
- target.expects(:deprecated).never
+ def assert_no_deprecation_warning
+ ActiveMerchant.expects(:deprecated).never
yield
end
+ def assert_scrubbed(unexpected_value, transcript)
+ regexp = (Regexp === unexpected_value ? unexpected_value : Regexp.new(Regexp.quote(unexpected_value.to_s)))
+ refute_match regexp, transcript, 'Expected the value to be scrubbed out of the transcript'
+ end
+
private
+
def clean_backtrace(&block)
yield
rescue AssertionClass => e
path = File.expand_path(__FILE__)
- raise AssertionClass, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
+ raise AssertionClass, e.message, (e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ })
end
end
@@ -151,20 +141,53 @@ module Fixtures
DEFAULT_CREDENTIALS = File.join(File.dirname(__FILE__), 'fixtures.yml') unless defined?(DEFAULT_CREDENTIALS)
private
+
+ def default_expiration_date
+ @default_expiration_date ||= Date.new((Time.now.year + 1), 9, 30)
+ end
+
+ def formatted_expiration_date(credit_card)
+ credit_card.expiry_date.expiration.strftime('%Y-%m')
+ end
+
def credit_card(number = '4242424242424242', options = {})
defaults = {
:number => number,
- :month => 9,
- :year => Time.now.year + 1,
+ :month => default_expiration_date.month,
+ :year => default_expiration_date.year,
:first_name => 'Longbob',
:last_name => 'Longsen',
- :verification_value => '123',
+ :verification_value => options[:verification_value] || '123',
:brand => 'visa'
}.update(options)
Billing::CreditCard.new(defaults)
end
+ def credit_card_with_track_data(number = '4242424242424242', options = {})
+ exp_date = default_expiration_date.strftime('%y%m')
+
+ defaults = {
+ :track_data => "%B#{number}^LONGSEN/L. ^#{exp_date}1200000000000000**123******?",
+ }.update(options)
+
+ Billing::CreditCard.new(defaults)
+ end
+
+ def network_tokenization_credit_card(number = '4242424242424242', options = {})
+ defaults = {
+ :number => number,
+ :month => default_expiration_date.month,
+ :year => default_expiration_date.year,
+ :first_name => 'Longbob',
+ :last_name => 'Longsen',
+ :verification_value => '123',
+ :brand => 'visa'
+ }.update(options)
+
+ Billing::NetworkTokenizationCreditCard.new(defaults)
+ end
+
def check(options = {})
defaults = {
:name => 'Jim Smith',
@@ -179,21 +202,73 @@ def check(options = {})
Billing::Check.new(defaults)
end
+ def apple_pay_payment_token(options = {})
+ # apple_pay_json_raw should contain the JSON serialization of the object described here
+ # https://developer.apple.com/library/IOs//documentation/PassKit/Reference/PaymentTokenJSON/PaymentTokenJSON.htm
+ apple_pay_json_raw = '{"version":"EC_v1","data":"","signature":""}'
+ defaults = {
+ payment_data: ActiveSupport::JSON.decode(apple_pay_json_raw),
+ payment_instrument_name: 'Visa 2424',
+ payment_network: 'Visa',
+ transaction_identifier: 'uniqueidentifier123'
+ }.update(options)
+
+ ActiveMerchant::Billing::ApplePayPaymentToken.new(defaults[:payment_data],
+ payment_instrument_name: defaults[:payment_instrument_name],
+ payment_network: defaults[:payment_network],
+ transaction_identifier: defaults[:transaction_identifier]
+ )
+ end
+
def address(options = {})
{
- :name => 'Jim Smith',
- :address1 => '1234 My Street',
- :address2 => 'Apt 1',
- :company => 'Widgets Inc',
- :city => 'Ottawa',
- :state => 'ON',
- :zip => 'K1C2N6',
- :country => 'CA',
- :phone => '(555)555-5555',
- :fax => '(555)555-6666'
+ name: 'Jim Smith',
+ address1: '456 My Street',
+ address2: 'Apt 1',
+ company: 'Widgets Inc',
+ city: 'Ottawa',
+ state: 'ON',
+ zip: 'K1C2N6',
+ country: 'CA',
+ phone: '(555)555-5555',
+ fax: '(555)555-6666'
}.update(options)
end
+ def statement_address(options = {})
+ {
+ address1: '456 My Street',
+ address2: 'Apt 1',
+ city: 'Ottawa',
+ state: 'ON',
+ zip: 'K1C2N6'
+ }.update(options)
+ end
+
+ def stored_credential(*args, **options)
+ id = options.delete(:id) || options.delete(:network_transaction_id)
+
+ stored_credential = {
+ network_transaction_id: id,
+ initial_transaction: false
+ }
+
+ stored_credential[:initial_transaction] = true if args.include?(:initial)
+
+ stored_credential[:reason_type] = 'recurring' if args.include?(:recurring)
+ stored_credential[:reason_type] = 'unscheduled' if args.include?(:unscheduled)
+ stored_credential[:reason_type] = 'installment' if args.include?(:installment)
+
+ stored_credential[:initiator] = 'cardholder' if args.include?(:cardholder)
+ stored_credential[:initiator] = 'merchant' if args.include?(:merchant)
+
+ stored_credential
+ end
+
+ def generate_unique_id
+ SecureRandom.hex(16)
+ end
+
def all_fixtures
@@fixtures ||= load_fixtures
end
@@ -206,8 +281,8 @@ def fixtures(key)
def load_fixtures
[DEFAULT_CREDENTIALS, LOCAL_CREDENTIALS].inject({}) do |credentials, file_name|
- if File.exists?(file_name)
- yaml_data = YAML.load(File.read(file_name))
+ if File.exist?(file_name)
+ yaml_data = YAML.safe_load(File.read(file_name), [], [], true)
credentials.merge!(symbolize_keys(yaml_data))
end
credentials
@@ -218,7 +293,7 @@ def symbolize_keys(hash)
return unless hash.is_a?(Hash)
hash.symbolize_keys!
- hash.each{|k,v| symbolize_keys(v)}
+ hash.each { |k, v| symbolize_keys(v) }
end
end
end
@@ -226,12 +301,28 @@ def symbolize_keys(hash)
Test::Unit::TestCase.class_eval do
include ActiveMerchant::Billing
include ActiveMerchant::Assertions
- include ActiveMerchant::Utils
include ActiveMerchant::Fixtures
+
+ def capture_transcript(gateway)
+ transcript = ''
+ gateway.class.wiredump_device = transcript
+
+ yield
+
+ transcript
+ end
+
+ def dump_transcript_and_fail(gateway, amount, credit_card, params)
+ transcript = capture_transcript(gateway) do
+ gateway.purchase(amount, credit_card, params)
+ end
+
+ File.open('transcript.log', 'w') { |f| f.write(transcript) }
+ assert false, 'A purchase transcript has been written to transcript.log for you to test scrubbing with.'
+ end
end
module ActionViewHelperTestHelper
-
def self.included(base)
base.send(:include, ActiveMerchant::Billing::Integrations::ActionViewHelper)
base.send(:include, ActionView::Helpers::FormHelper)
@@ -255,7 +346,29 @@ def url_for(options, *parameters_for_method_reference)
end
protected
+
def protect_against_forgery?
false
end
end
+
+class MockResponse
+ attr_reader :code, :body, :message
+ attr_accessor :headers
+
+ def self.succeeded(body, message='')
+ MockResponse.new(200, body, message)
+ end
+
+ def self.failed(body, http_status_code=422, message='')
+ MockResponse.new(http_status_code, body, message)
+ end
+
+ def initialize(code, body, message='', headers={})
+ @code, @body, @message, @headers = code, body, message, headers
+ end
+
+ def [](header)
+ @headers[header]
+ end
+end
diff --git a/test/unit/apple_pay_payment_token_test.rb b/test/unit/apple_pay_payment_token_test.rb
new file mode 100644
index 00000000000..b5a40f9dd45
--- /dev/null
+++ b/test/unit/apple_pay_payment_token_test.rb
@@ -0,0 +1,11 @@
+require 'test_helper'
+
+class ApplePayPaymentTokenTest < Test::Unit::TestCase
+ def setup
+ @token = ActiveMerchant::Billing::ApplePayPaymentToken.new(payment_data: {})
+ end
+
+ def test_type
+ assert_equal 'apple_pay', @token.type
+ end
+end
diff --git a/test/unit/avs_result_test.rb b/test/unit/avs_result_test.rb
index 97dd7a20ded..f3b960647b7 100644
--- a/test/unit/avs_result_test.rb
+++ b/test/unit/avs_result_test.rb
@@ -2,9 +2,9 @@
class AVSResultTest < Test::Unit::TestCase
def test_nil
- result = AVSResult.new(nil)
+ AVSResult.new(nil)
end
-
+
def test_no_match
result = AVSResult.new(:code => 'N')
assert_equal 'N', result.code
@@ -12,7 +12,7 @@ def test_no_match
assert_equal 'N', result.postal_match
assert_equal AVSResult.messages['N'], result.message
end
-
+
def test_only_street_match
result = AVSResult.new(:code => 'A')
assert_equal 'A', result.code
@@ -20,7 +20,7 @@ def test_only_street_match
assert_equal 'N', result.postal_match
assert_equal AVSResult.messages['A'], result.message
end
-
+
def test_only_postal_match
result = AVSResult.new(:code => 'W')
assert_equal 'W', result.code
@@ -28,32 +28,32 @@ def test_only_postal_match
assert_equal 'Y', result.postal_match
assert_equal AVSResult.messages['W'], result.message
end
-
+
def test_nil_data
result = AVSResult.new(:code => nil)
assert_nil result.code
assert_nil result.message
end
-
+
def test_empty_data
result = AVSResult.new(:code => '')
assert_nil result.code
assert_nil result.message
end
-
+
def test_to_hash
avs_data = AVSResult.new(:code => 'X').to_hash
assert_equal 'X', avs_data['code']
assert_equal AVSResult.messages['X'], avs_data['message']
end
-
+
def test_street_match
avs_data = AVSResult.new(:street_match => 'Y')
assert_equal 'Y', avs_data.street_match
end
-
+
def test_postal_match
avs_data = AVSResult.new(:postal_match => 'Y')
assert_equal 'Y', avs_data.postal_match
end
-end
\ No newline at end of file
+end
diff --git a/test/unit/base_test.rb b/test/unit/base_test.rb
index 2ec4f4721ac..6e5ac461ab7 100644
--- a/test/unit/base_test.rb
+++ b/test/unit/base_test.rb
@@ -4,11 +4,11 @@ class BaseTest < Test::Unit::TestCase
def setup
ActiveMerchant::Billing::Base.mode = :test
end
-
+
def teardown
ActiveMerchant::Billing::Base.mode = :test
end
-
+
def test_should_return_a_new_gateway_specified_by_symbol_name
assert_equal BogusGateway, Base.gateway(:bogus)
assert_equal MonerisGateway, Base.gateway(:moneris)
@@ -18,52 +18,51 @@ def test_should_return_a_new_gateway_specified_by_symbol_name
assert_equal LinkpointGateway, Base.gateway(:linkpoint)
end
- def test_should_raise_when_invalid_gateway_is_passed
- assert_raise NameError do
- Base.gateway(:nil)
+ def test_should_raise_when_nil_gateway_is_passed
+ e = assert_raise ArgumentError do
+ Base.gateway(nil)
end
+ assert_equal 'A gateway provider must be specified', e.message
+ end
- assert_raise NameError do
+ def test_should_raise_when_empty_gateway_is_passed
+ e = assert_raise ArgumentError do
Base.gateway('')
end
+ assert_equal 'A gateway provider must be specified', e.message
+ end
- assert_raise NameError do
+ def test_should_raise_when_invalid_gateway_symbol_is_passed
+ e = assert_raise ArgumentError do
Base.gateway(:hotdog)
end
+ assert_equal 'The specified gateway is not valid (hotdog)', e.message
end
- def test_should_return_an_integration_by_name
- chronopay = Base.integration(:chronopay)
-
- assert_equal Integrations::Chronopay, chronopay
- assert_instance_of Integrations::Chronopay::Notification, chronopay.notification('name=cody')
+ def test_should_raise_when_invalid_gateway_string_is_passed
+ e = assert_raise ArgumentError do
+ Base.gateway('hotdog')
+ end
+ assert_equal 'The specified gateway is not valid (hotdog)', e.message
end
def test_should_set_modes
Base.mode = :test
assert_equal :test, Base.mode
- assert_equal :test, Base.gateway_mode
- assert_equal :test, Base.integration_mode
Base.mode = :production
assert_equal :production, Base.mode
- assert_equal :production, Base.gateway_mode
- assert_equal :production, Base.integration_mode
- Base.mode = :development
- Base.gateway_mode = :test
- Base.integration_mode = :staging
+ assert_deprecation_warning(Base::GATEWAY_MODE_DEPRECATION_MESSAGE) { Base.gateway_mode = :development }
+ assert_deprecation_warning(Base::GATEWAY_MODE_DEPRECATION_MESSAGE) { assert_equal :development, Base.gateway_mode }
assert_equal :development, Base.mode
- assert_equal :test, Base.gateway_mode
- assert_equal :staging, Base.integration_mode
end
-
+
def test_should_identify_if_test_mode
- Base.gateway_mode = :test
+ Base.mode = :test
assert Base.test?
-
- Base.gateway_mode = :production
+
+ Base.mode = :production
assert_false Base.test?
end
-
end
diff --git a/test/unit/check_test.rb b/test/unit/check_test.rb
index 877a77b3889..e5908553ab9 100644
--- a/test/unit/check_test.rb
+++ b/test/unit/check_test.rb
@@ -4,85 +4,78 @@ class CheckTest < Test::Unit::TestCase
VALID_ABA = '111000025'
INVALID_ABA = '999999999'
MALFORMED_ABA = 'I like fish'
-
+
ACCOUNT_NUMBER = '123456789012'
-
+
def test_validation
- c = Check.new
- assert !c.valid?
- assert !c.errors.empty?
+ assert_not_valid Check.new
end
-
+
def test_first_name_last_name
check = Check.new(:name => 'Fred Bloggs')
assert_equal 'Fred', check.first_name
assert_equal 'Bloggs', check.last_name
assert_equal 'Fred Bloggs', check.name
end
-
+
def test_nil_name
check = Check.new(:name => nil)
assert_nil check.first_name
assert_nil check.last_name
- assert_equal "", check.name
+ assert_equal '', check.name
end
-
+
def test_valid
- c = Check.new(:name => 'Fred Bloggs',
- :routing_number => VALID_ABA,
- :account_number => ACCOUNT_NUMBER,
- :account_holder_type => 'personal',
- :account_type => 'checking')
- assert c.valid?
+ assert_valid Check.new(
+ :name => 'Fred Bloggs',
+ :routing_number => VALID_ABA,
+ :account_number => ACCOUNT_NUMBER,
+ :account_holder_type => 'personal',
+ :account_type => 'checking'
+ )
+ end
+
+ def test_credit_card?
+ assert !check.credit_card?
end
-
+
def test_invalid_routing_number
- c = Check.new(:routing_number => INVALID_ABA)
- assert !c.valid?
- assert_equal c.errors.on(:routing_number), "is invalid"
+ errors = assert_not_valid Check.new(:routing_number => INVALID_ABA)
+ assert_equal ['is invalid'], errors[:routing_number]
end
-
+
def test_malformed_routing_number
- c = Check.new(:routing_number => MALFORMED_ABA)
- assert !c.valid?
- assert_equal c.errors.on(:routing_number), "is invalid"
+ errors = assert_not_valid Check.new(:routing_number => MALFORMED_ABA)
+ assert_equal ['is invalid'], errors[:routing_number]
end
-
+
def test_account_holder_type
c = Check.new
c.account_holder_type = 'business'
- c.valid?
- assert !c.errors.on(:account_holder_type)
-
+ assert !c.validate[:account_holder_type]
+
c.account_holder_type = 'personal'
- c.valid?
- assert !c.errors.on(:account_holder_type)
-
+ assert !c.validate[:account_holder_type]
+
c.account_holder_type = 'pleasure'
- c.valid?
- assert_equal c.errors.on(:account_holder_type), 'must be personal or business'
-
+ assert_equal ['must be personal or business'], c.validate[:account_holder_type]
+
c.account_holder_type = nil
- c.valid?
- assert !c.errors.on(:account_holder_type)
+ assert !c.validate[:account_holder_type]
end
-
+
def test_account_type
c = Check.new
c.account_type = 'checking'
- c.valid?
- assert !c.errors.on(:account_type)
-
+ assert !c.validate[:account_type]
+
c.account_type = 'savings'
- c.valid?
- assert !c.errors.on(:account_type)
-
+ assert !c.validate[:account_type]
+
c.account_type = 'moo'
- c.valid?
- assert_equal c.errors.on(:account_type), "must be checking or savings"
-
+ assert_equal ['must be checking or savings'], c.validate[:account_type]
+
c.account_type = nil
- c.valid?
- assert !c.errors.on(:account_type)
+ assert !c.validate[:account_type]
end
end
diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb
new file mode 100644
index 00000000000..ac8c29a4f1d
--- /dev/null
+++ b/test/unit/connection_test.rb
@@ -0,0 +1,248 @@
+require 'test_helper'
+
+class ConnectionTest < Test::Unit::TestCase
+
+ def setup
+ @ok = stub(:code => 200, :message => 'OK', :body => 'success')
+
+ @endpoint = 'https://example.com/tx.php'
+ @connection = ActiveMerchant::Connection.new(@endpoint)
+ @connection.logger = stub(:info => nil, :debug => nil, :error => nil)
+ end
+
+ def test_connection_endpoint_parses_string_to_uri
+ assert_equal URI.parse(@endpoint), @connection.endpoint
+ end
+
+ def test_connection_endpoint_accepts_uri
+ endpoint = URI.parse(@endpoint)
+ connection = ActiveMerchant::Connection.new(endpoint)
+ assert_equal endpoint, connection.endpoint
+ end
+
+ def test_connection_endpoint_raises_uri_error
+ assert_raises URI::InvalidURIError do
+ ActiveMerchant::Connection.new('not a URI')
+ end
+ end
+
+ def test_connection_passes_env_proxy_by_default
+ spy = Net::HTTP.new('example.com', 443)
+ Net::HTTP.expects(:new).with('example.com', 443, :ENV, nil).returns(spy)
+ spy.expects(:start).returns(true)
+ spy.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok)
+ @connection.request(:get, nil, {})
+ end
+
+ def test_connection_does_pass_requested_proxy
+ @connection.proxy_address = 'proxy.example.com'
+ @connection.proxy_port = 8080
+ spy = Net::HTTP.new('example.com', 443)
+ Net::HTTP.expects(:new).with('example.com', 443, 'proxy.example.com', 8080).returns(spy)
+ spy.expects(:start).returns(true)
+ spy.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok)
+ @connection.request(:get, nil, {})
+ end
+
+ def test_connection_does_not_mutate_headers_argument
+ headers = { 'Content-Type' => 'text/xml' }.freeze
+ Net::HTTP.any_instance.expects(:get).with('/tx.php', headers.merge({'connection' => 'close'})).returns(@ok)
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ @connection.request(:get, nil, headers)
+ assert_equal({ 'Content-Type' => 'text/xml' }, headers)
+ end
+
+ def test_successful_get_request
+ @connection.logger.expects(:info).twice
+ Net::HTTP.any_instance.expects(:get).with('/tx.php', {'connection' => 'close'}).returns(@ok)
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ response = @connection.request(:get, nil, {})
+ assert_equal 'success', response.body
+ end
+
+ def test_successful_post_request
+ Net::HTTP.any_instance.expects(:post).with('/tx.php', 'data', ActiveMerchant::Connection::RUBY_184_POST_HEADERS.merge({'connection' => 'close'})).returns(@ok)
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ response = @connection.request(:post, 'data', {})
+ assert_equal 'success', response.body
+ end
+
+ def test_successful_put_request
+ Net::HTTP.any_instance.expects(:put).with('/tx.php', 'data', {'connection' => 'close'}).returns(@ok)
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ response = @connection.request(:put, 'data', {})
+ assert_equal 'success', response.body
+ end
+
+ def test_successful_delete_request
+ Net::HTTP.any_instance.expects(:delete).with('/tx.php', {'connection' => 'close'}).returns(@ok)
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ response = @connection.request(:delete, nil, {})
+ assert_equal 'success', response.body
+ end
+
+ def test_successful_delete_with_body_request
+ Net::HTTP.any_instance.expects(:request).at_most(3).returns(@ok)
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ response = @connection.request(:delete, 'data', {})
+ assert_equal 'success', response.body
+ end
+
+ def test_get_raises_argument_error_if_passed_data
+ assert_raises(ArgumentError) do
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ @connection.request(:get, 'data', {})
+ end
+ end
+
+ def test_request_raises_when_request_method_not_supported
+ assert_raises(ArgumentError) do
+ Net::HTTP.any_instance.expects(:start).returns(true)
+ @connection.request(:head, nil, {})
+ end
+ end
+
+ def test_override_max_retries
+ refute_equal 1, @connection.max_retries
+ @connection.max_retries = 1
+ assert_equal 1, @connection.max_retries
+ end
+
+ def test_override_ssl_version
+ refute_equal :SSLv3, @connection.ssl_version
+ @connection.ssl_version = :SSLv3
+ assert_equal :SSLv3, @connection.ssl_version
+ end
+
+ def test_override_min_version
+ omit_if Net::HTTP.instance_methods.exclude?(:min_version=)
+
+ refute_equal :TLS1_2, @connection.min_version
+ @connection.min_version = :TLS1_2
+ assert_equal :TLS1_2, @connection.min_version
+ end
+
+ def test_override_max_version
+ omit_if Net::HTTP.instance_methods.exclude?(:min_version=)
+
+ refute_equal :TLS1_2, @connection.max_version
+ @connection.max_version = :TLS1_2
+ assert_equal :TLS1_2, @connection.max_version
+ end
+
+ def test_default_read_timeout
+ assert_equal ActiveMerchant::Connection::READ_TIMEOUT, @connection.read_timeout
+ end
+
+ def test_override_read_timeout
+ @connection.read_timeout = 20
+ assert_equal 20, @connection.read_timeout
+ end
+
+ def test_default_open_timeout
+ @connection.open_timeout = 20
+ assert_equal 20, @connection.open_timeout
+ end
+
+ def test_default_verify_peer
+ assert_equal ActiveMerchant::Connection::VERIFY_PEER, @connection.verify_peer
+ end
+
+ def test_override_verify_peer
+ @connection.verify_peer = false
+ assert_equal false, @connection.verify_peer
+ end
+
+ def test_default_ca_file_exists
+ assert File.exist?(ActiveMerchant::Connection::CA_FILE)
+ end
+
+ def test_default_ca_file
+ assert_equal ActiveMerchant::Connection::CA_FILE, @connection.ca_file
+ assert_equal ActiveMerchant::Connection::CA_FILE, @connection.send(:http).ca_file
+ end
+
+ def test_override_ca_file
+ @connection.ca_file = '/bogus'
+ assert_equal '/bogus', @connection.ca_file
+ assert_equal '/bogus', @connection.send(:http).ca_file
+ end
+
+ def test_default_ca_path
+ assert_equal ActiveMerchant::Connection::CA_PATH, @connection.ca_path
+ assert_equal ActiveMerchant::Connection::CA_PATH, @connection.send(:http).ca_path
+ end
+
+ def test_override_ca_path
+ @connection.ca_path = '/bogus'
+ assert_equal '/bogus', @connection.ca_path
+ assert_equal '/bogus', @connection.send(:http).ca_path
+ end
+
+ def test_unrecoverable_exception
+ @connection.logger.expects(:info).once
+ Net::HTTP.any_instance.expects(:start).raises(EOFError)
+
+ assert_raises(ActiveMerchant::ConnectionError) do
+ @connection.request(:post, '')
+ end
+ end
+
+ def test_failure_then_success_with_recoverable_exception
+ @connection.logger.expects(:info).never
+ Net::HTTP.any_instance.expects(:start).times(2).raises(Errno::ECONNREFUSED).then.returns(true)
+ Net::HTTP.any_instance.expects(:post).returns(@ok)
+
+ @connection.request(:post, '')
+ end
+
+ def test_failure_limit_reached
+ @connection.logger.expects(:info).once
+ Net::HTTP.any_instance.expects(:start).times(ActiveMerchant::Connection::MAX_RETRIES).raises(Errno::ECONNREFUSED)
+
+ assert_raises(ActiveMerchant::ConnectionError) do
+ @connection.request(:post, '')
+ end
+ end
+
+ def test_failure_then_success_with_retry_safe_enabled
+ Net::HTTP.any_instance.expects(:start).times(2).raises(EOFError).then.returns(@ok)
+ Net::HTTP.any_instance.expects(:post).returns(@ok)
+
+ @connection.retry_safe = true
+
+ @connection.request(:post, '')
+ end
+
+ def test_mixture_of_failures_with_retry_safe_enabled
+ Net::HTTP.any_instance.
+ expects(:start).
+ times(3).
+ raises(Errno::ECONNRESET).
+ raises(Errno::ECONNREFUSED).
+ raises(EOFError)
+
+ @connection.retry_safe = true
+
+ assert_raises(ActiveMerchant::ConnectionError) do
+ @connection.request(:post, '')
+ end
+ end
+
+ def test_failure_with_ssl_certificate
+ @connection.logger.expects(:error).once
+ Net::HTTP.any_instance.expects(:start).raises(OpenSSL::X509::CertificateError)
+
+ assert_raises(ActiveMerchant::ClientCertificateError) do
+ @connection.request(:post, '')
+ end
+ end
+
+ def test_wiredump_service_raises_on_frozen_object
+ transcript = ''.freeze
+ assert_raise ArgumentError, "can't wiredump to frozen object" do
+ @connection.wiredump_device = transcript
+ end
+ end
+
+end
diff --git a/test/unit/country_code_test.rb b/test/unit/country_code_test.rb
new file mode 100644
index 00000000000..12ee3ae4ab4
--- /dev/null
+++ b/test/unit/country_code_test.rb
@@ -0,0 +1,31 @@
+require 'test_helper'
+
+class CountryCodeTest < Test::Unit::TestCase
+ def test_alpha2_country_code
+ code = ActiveMerchant::CountryCode.new('CA')
+ assert_equal 'CA', code.value
+ assert_equal 'CA', code.to_s
+ assert_equal :alpha2, code.format
+ end
+
+ def test_lower_alpha2_country_code
+ code = ActiveMerchant::CountryCode.new('ca')
+ assert_equal 'CA', code.value
+ assert_equal 'CA', code.to_s
+ assert_equal :alpha2, code.format
+ end
+
+ def test_alpha3_country_code
+ code = ActiveMerchant::CountryCode.new('CAN')
+ assert_equal :alpha3, code.format
+ end
+
+ def test_numeric_code
+ code = ActiveMerchant::CountryCode.new('004')
+ assert_equal :numeric, code.format
+ end
+
+ def test_invalid_code_format
+ assert_raises(ActiveMerchant::CountryCodeFormatError) { ActiveMerchant::CountryCode.new('Canada') }
+ end
+end
diff --git a/test/unit/country_test.rb b/test/unit/country_test.rb
new file mode 100644
index 00000000000..6251e6fa095
--- /dev/null
+++ b/test/unit/country_test.rb
@@ -0,0 +1,93 @@
+require 'test_helper'
+
+class CountryTest < Test::Unit::TestCase
+ def test_country_from_hash
+ country = ActiveMerchant::Country.new(:name => 'Canada', :alpha2 => 'CA', :alpha3 => 'CAN', :numeric => '124')
+ assert_equal 'CA', country.code(:alpha2).value
+ assert_equal 'CAN', country.code(:alpha3).value
+ assert_equal '124', country.code(:numeric).value
+ assert_equal 'Canada', country.to_s
+ end
+
+ def test_country_for_alpha2_code
+ country = ActiveMerchant::Country.find('CA')
+ assert_equal 'CA', country.code(:alpha2).value
+ assert_equal 'CAN', country.code(:alpha3).value
+ assert_equal '124', country.code(:numeric).value
+ assert_equal 'Canada', country.to_s
+ end
+
+ def test_country_for_alpha3_code
+ country = ActiveMerchant::Country.find('CAN')
+ assert_equal 'Canada', country.to_s
+ end
+
+ def test_country_for_numeric_code
+ country = ActiveMerchant::Country.find('124')
+ assert_equal 'Canada', country.to_s
+ end
+
+ def test_find_country_by_name
+ country = ActiveMerchant::Country.find('Canada')
+ assert_equal 'Canada', country.to_s
+ end
+
+ def test_find_country_by_lowercase_name
+ country = ActiveMerchant::Country.find('bosnia and herzegovina')
+ assert_equal 'Bosnia and Herzegovina', country.to_s
+ end
+
+ def test_find_unknown_country_name
+ assert_raises(ActiveMerchant::InvalidCountryCodeError) do
+ ActiveMerchant::Country.find('Asskickistan')
+ end
+ end
+
+ def test_find_australia
+ country = ActiveMerchant::Country.find('AU')
+ assert_equal 'AU', country.code(:alpha2).value
+
+ country = ActiveMerchant::Country.find('Australia')
+ assert_equal 'AU', country.code(:alpha2).value
+ end
+
+ def test_find_united_kingdom
+ country = ActiveMerchant::Country.find('GB')
+ assert_equal 'GB', country.code(:alpha2).value
+
+ country = ActiveMerchant::Country.find('United Kingdom')
+ assert_equal 'GB', country.code(:alpha2).value
+ end
+
+ def test_find_romania
+ country = ActiveMerchant::Country.find('ROM')
+ assert_equal 'RO', country.code(:alpha2).value
+
+ country = ActiveMerchant::Country.find('ROU')
+ assert_equal 'RO', country.code(:alpha2).value
+
+ country = ActiveMerchant::Country.find('Romania')
+ assert_equal 'ROU', country.code(:alpha3).value
+
+ country = ActiveMerchant::Country.find('Romania')
+ assert_not_equal 'ROM', country.code(:alpha3).value
+ end
+
+ def test_raise_on_nil_name
+ assert_raises(ActiveMerchant::InvalidCountryCodeError) do
+ ActiveMerchant::Country.find(nil)
+ end
+ end
+
+ def test_country_names_are_alphabetized
+ country_names = ActiveMerchant::Country::COUNTRIES.map { |each| each[:name] }
+ assert_equal(country_names.sort, country_names)
+ end
+
+ def test_comparisons
+ assert_equal ActiveMerchant::Country.find('GB'), ActiveMerchant::Country.find('GB')
+ assert_not_equal ActiveMerchant::Country.find('GB'), ActiveMerchant::Country.find('CA')
+ assert_not_equal Object.new, ActiveMerchant::Country.find('GB')
+ assert_not_equal ActiveMerchant::Country.find('GB'), Object.new
+ end
+end
diff --git a/test/unit/credit_card_methods_test.rb b/test/unit/credit_card_methods_test.rb
index 4d3b9a7150e..400dc07c3a7 100644
--- a/test/unit/credit_card_methods_test.rb
+++ b/test/unit/credit_card_methods_test.rb
@@ -2,39 +2,40 @@
class CreditCardMethodsTest < Test::Unit::TestCase
include ActiveMerchant::Billing::CreditCardMethods
-
+
class CreditCard
- include ActiveMerchant::Billing::CreditCardMethods
+ include ActiveMerchant::Billing::CreditCardMethods
end
-
+
def maestro_card_numbers
%w[
- 5000000000000000 5099999999999999 5600000000000000
- 5899999999999999 6000000000000000 6999999999999999
- 6761999999999999 6763000000000000 5038999999999999
+ 5612590000000000 5817500000000000 5818000000000000
+ 6390000000000000 6390700000000000 6390990000000000
+ 6761999999999999 6763000000000000 6799999999999999
]
end
-
+
def non_maestro_card_numbers
%w[
- 4999999999999999 5100000000000000 5599999999999999
- 5900000000000000 5999999999999999 7000000000000000
+ 4999999999999999 5100000000000000 5599999999999999
+ 5612709999999999 5817520000000000 5818019999999999
+ 5912600000000000 6000009999999999 7000000000000000
]
end
-
+
def test_should_be_able_to_identify_valid_expiry_months
assert_false valid_month?(-1)
assert_false valid_month?(13)
assert_false valid_month?(nil)
assert_false valid_month?('')
-
+
1.upto(12) { |m| assert valid_month?(m) }
end
def test_should_be_able_to_identify_valid_expiry_years
assert_false valid_expiry_year?(-1)
assert_false valid_expiry_year?(Time.now.year + 21)
-
+
0.upto(20) { |n| assert valid_expiry_year?(Time.now.year + n) }
end
@@ -42,29 +43,40 @@ def test_should_be_able_to_identify_valid_start_years
assert valid_start_year?(1988)
assert valid_start_year?(2007)
assert valid_start_year?(3000)
-
+
assert_false valid_start_year?(1987)
end
-
+
def test_valid_start_year_can_handle_strings
- assert valid_start_year?("2009")
+ assert valid_start_year?('2009')
end
-
+
def test_valid_month_can_handle_strings
- assert valid_month?("1")
+ assert valid_month?('1')
end
-
+
def test_valid_expiry_year_can_handle_strings
year = Time.now.year + 1
assert valid_expiry_year?(year.to_s)
end
-
+
+ def test_should_validate_card_verification_value
+ assert valid_card_verification_value?(123, 'visa')
+ assert valid_card_verification_value?('123', 'visa')
+ assert valid_card_verification_value?(1234, 'american_express')
+ assert valid_card_verification_value?('1234', 'american_express')
+ assert_false valid_card_verification_value?(12, 'visa')
+ assert_false valid_card_verification_value?(1234, 'visa')
+ assert_false valid_card_verification_value?(123, 'american_express')
+ assert_false valid_card_verification_value?(12345, 'american_express')
+ end
+
def test_should_be_able_to_identify_valid_issue_numbers
assert valid_issue_number?(1)
assert valid_issue_number?(10)
assert valid_issue_number?('12')
assert valid_issue_number?(0)
-
+
assert_false valid_issue_number?(-1)
assert_false valid_issue_number?(123)
assert_false valid_issue_number?('CAT')
@@ -73,123 +85,193 @@ def test_should_be_able_to_identify_valid_issue_numbers
def test_should_ensure_brand_from_credit_card_class_is_not_frozen
assert_false CreditCard.brand?('4242424242424242').frozen?
end
-
+
def test_should_be_dankort_card_brand
assert_equal 'dankort', CreditCard.brand?('5019717010103742')
end
-
+
def test_should_detect_visa_dankort_as_visa
assert_equal 'visa', CreditCard.brand?('4571100000000000')
end
-
+
def test_should_detect_electron_dk_as_visa
assert_equal 'visa', CreditCard.brand?('4175001000000000')
end
-
+
def test_should_detect_diners_club
assert_equal 'diners_club', CreditCard.brand?('36148010000000')
end
-
+
def test_should_detect_diners_club_dk
assert_equal 'diners_club', CreditCard.brand?('30401000000000')
end
-
+
def test_should_detect_maestro_dk_as_maestro
assert_equal 'maestro', CreditCard.brand?('6769271000000000')
end
-
+
def test_should_detect_maestro_cards
- assert_equal 'maestro', CreditCard.brand?('5020100000000000')
-
+ assert_equal 'maestro', CreditCard.brand?('675675000000000')
+
maestro_card_numbers.each { |number| assert_equal 'maestro', CreditCard.brand?(number) }
non_maestro_card_numbers.each { |number| assert_not_equal 'maestro', CreditCard.brand?(number) }
end
-
+
def test_should_detect_mastercard
- assert_equal 'master', CreditCard.brand?('6771890000000000')
+ assert_equal 'master', CreditCard.brand?('2720890000000000')
assert_equal 'master', CreditCard.brand?('5413031000000000')
end
-
+
def test_should_detect_forbrugsforeningen
assert_equal 'forbrugsforeningen', CreditCard.brand?('6007221000000000')
end
-
- def test_should_detect_laser_card
- # 16 digits
- assert_equal 'laser', CreditCard.brand?('6304985028090561')
-
- # 18 digits
- assert_equal 'laser', CreditCard.brand?('630498502809056151')
-
- # 19 digits
- assert_equal 'laser', CreditCard.brand?('6304985028090561515')
-
- # 17 digits
- assert_not_equal 'laser', CreditCard.brand?('63049850280905615')
-
- # 15 digits
- assert_not_equal 'laser', CreditCard.brand?('630498502809056')
-
- # Alternate format
- assert_equal 'laser', CreditCard.brand?('6706950000000000000')
-
- # Alternate format (16 digits)
- assert_equal 'laser', CreditCard.brand?('6706123456789012')
-
- # New format (16 digits)
- assert_equal 'laser', CreditCard.brand?('6709123456789012')
-
- # Ulster bank (Ireland) with 12 digits
- assert_equal 'laser', CreditCard.brand?('677117111234')
- end
-
+
+ def test_should_detect_sodexo_card
+ assert_equal 'sodexo', CreditCard.brand?('6060694495764400')
+ end
+
+ def test_should_detect_vr_card
+ assert_equal 'vr', CreditCard.brand?('6370364495764400')
+ end
+
+ def test_should_detect_elo_card
+ assert_equal 'elo', CreditCard.brand?('5090510000000000')
+ assert_equal 'elo', CreditCard.brand?('5067530000000000')
+ assert_equal 'elo', CreditCard.brand?('6509550000000000')
+ end
+
+ def test_should_detect_alelo_card
+ assert_equal 'alelo', CreditCard.brand?('5067490000000010')
+ assert_equal 'alelo', CreditCard.brand?('5067700000000028')
+ assert_equal 'alelo', CreditCard.brand?('5067600000000036')
+ assert_equal 'alelo', CreditCard.brand?('5067600000000044')
+ end
+
+ def test_should_detect_naranja_card
+ assert_equal 'naranja', CreditCard.brand?('5895627823453005')
+ assert_equal 'naranja', CreditCard.brand?('5895620000000002')
+ assert_equal 'naranja', CreditCard.brand?('5895626746595650')
+ end
+
+ # Alelo BINs beginning with the digit 4 overlap with Visa's range of valid card numbers.
+ # We intentionally misidentify these cards as Visa, which works because transactions with
+ # such cards will run on Visa rails.
+ def test_should_detect_alelo_number_beginning_with_4_as_visa
+ assert_equal 'visa', CreditCard.brand?('4025880000000010')
+ assert_equal 'visa', CreditCard.brand?('4025880000000028')
+ assert_equal 'visa', CreditCard.brand?('4025880000000036')
+ assert_equal 'visa', CreditCard.brand?('4025880000000044')
+ end
+
+ def test_should_detect_cabal_card
+ assert_equal 'cabal', CreditCard.brand?('6044009000000000')
+ assert_equal 'cabal', CreditCard.brand?('5896575500000000')
+ assert_equal 'cabal', CreditCard.brand?('6035224400000000')
+ end
+
def test_should_detect_when_an_argument_brand_does_not_match_calculated_brand
assert CreditCard.matching_brand?('4175001000000000', 'visa')
assert_false CreditCard.matching_brand?('4175001000000000', 'master')
end
-
+
def test_detecting_full_range_of_maestro_card_numbers
- maestro = '50000000000'
-
+ maestro = '63900000000'
+
assert_equal 11, maestro.length
assert_not_equal 'maestro', CreditCard.brand?(maestro)
-
+
while maestro.length < 19
maestro << '0'
- assert_equal 'maestro', CreditCard.brand?(maestro)
+ assert_equal 'maestro', CreditCard.brand?(maestro), "Failed for bin #{maestro}"
end
-
+
assert_equal 19, maestro.length
-
+
maestro << '0'
assert_not_equal 'maestro', CreditCard.brand?(maestro)
end
-
+
def test_matching_discover_card
assert_equal 'discover', CreditCard.brand?('6011000000000000')
assert_equal 'discover', CreditCard.brand?('6500000000000000')
assert_equal 'discover', CreditCard.brand?('6221260000000000')
assert_equal 'discover', CreditCard.brand?('6450000000000000')
-
+
assert_not_equal 'discover', CreditCard.brand?('6010000000000000')
assert_not_equal 'discover', CreditCard.brand?('6600000000000000')
end
-
+
+ def test_matching_invalid_card
+ assert_nil CreditCard.brand?('XXXXXXXXXXXX0000')
+ assert_false CreditCard.valid_number?('XXXXXXXXXXXX0000')
+ assert_false CreditCard.valid_number?(nil)
+ end
+
+ def test_matching_valid_naranja
+ number = '5895627823453005'
+ assert_equal 'naranja', CreditCard.brand?(number)
+ assert CreditCard.valid_number?(number)
+ end
+
def test_16_digit_maestro_uk
number = '6759000000000000'
assert_equal 16, number.length
- assert_equal 'switch', CreditCard.brand?(number)
+ assert_equal 'maestro', CreditCard.brand?(number)
end
-
+
def test_18_digit_maestro_uk
number = '675900000000000000'
assert_equal 18, number.length
- assert_equal 'switch', CreditCard.brand?(number)
+ assert_equal 'maestro', CreditCard.brand?(number)
end
-
+
def test_19_digit_maestro_uk
number = '6759000000000000000'
assert_equal 19, number.length
- assert_equal 'switch', CreditCard.brand?(number)
+ assert_equal 'maestro', CreditCard.brand?(number)
+ end
+
+ def test_carnet_cards
+ numbers = [
+ '5062280000000000',
+ '6046220312312312',
+ '6393889871239871',
+ '5022751231231231'
+ ]
+ numbers.each do |num|
+ assert_equal 16, num.length
+ assert_equal 'carnet', CreditCard.brand?(num)
+ end
+ end
+
+ def test_electron_cards
+ # return the card number so assert failures are easy to isolate
+ electron_test = Proc.new do |card_number|
+ electron = CreditCard.electron?(card_number)
+ card_number if electron
+ end
+
+ CreditCard::ELECTRON_RANGES.each do |range|
+ range.map { |leader| "#{leader}0000000000" }.each do |card_number|
+ assert_equal card_number, electron_test.call(card_number)
+ end
+ end
+
+ # nil check
+ assert_false electron_test.call(nil)
+
+ # Visa range
+ assert_false electron_test.call('4245180000000000')
+ assert_false electron_test.call('4918810000000000')
+
+ # 19 PAN length
+ assert electron_test.call('4249620000000000000')
+
+ # 20 PAN length
+ assert_false electron_test.call('42496200000000000')
+ end
+
+ def test_credit_card?
+ assert credit_card.credit_card?
end
end
diff --git a/test/unit/credit_card_test.rb b/test/unit/credit_card_test.rb
index 4ff2ccdba27..d31748f17bb 100644
--- a/test/unit/credit_card_test.rb
+++ b/test/unit/credit_card_test.rb
@@ -3,8 +3,8 @@
class CreditCardTest < Test::Unit::TestCase
def setup
CreditCard.require_verification_value = false
- @visa = credit_card("4779139500118580", :brand => "visa")
- @solo = credit_card("676700000000000000", :brand => "solo", :issue_number => '01')
+ @visa = credit_card('4779139500118580', brand: 'visa')
+ @maestro = credit_card('676700000000000000', brand: 'maestro', verification_value: '')
end
def teardown
@@ -14,11 +14,11 @@ def teardown
def test_constructor_should_properly_assign_values
c = credit_card
- assert_equal "4242424242424242", c.number
+ assert_equal '4242424242424242', c.number
assert_equal 9, c.month
assert_equal Time.now.year + 1, c.year
- assert_equal "Longbob Longsen", c.name
- assert_equal "visa", c.brand
+ assert_equal 'Longbob Longsen', c.name
+ assert_equal 'visa', c.brand
assert_valid c
end
@@ -26,33 +26,26 @@ def test_new_credit_card_should_not_be_valid
c = CreditCard.new
assert_not_valid c
- assert_false c.errors.empty?
end
def test_should_be_a_valid_visa_card
assert_valid @visa
- assert @visa.errors.empty?
end
- def test_should_be_a_valid_solo_card
- assert_valid @solo
- assert @solo.errors.empty?
+ def test_should_be_a_valid_maestro_card
+ assert_valid @maestro
end
- def test_cards_with_empty_names_should_not_be_valid
- @visa.first_name = ''
- @visa.last_name = ''
-
- assert_not_valid @visa
- assert_false @visa.errors.empty?
+ def test_should_be_a_valid_maestro_card_when_require_cvv_is_true
+ CreditCard.require_verification_value = true
+ assert_valid @maestro
end
- def test_should_be_able_to_access_errors_indifferently
+ def test_cards_with_empty_names_should_not_be_valid
@visa.first_name = ''
+ @visa.last_name = ''
assert_not_valid @visa
- assert @visa.errors.on(:first_name)
- assert @visa.errors.on("first_name")
end
def test_should_be_able_to_liberate_a_bogus_card
@@ -67,45 +60,59 @@ def test_should_be_able_to_identify_invalid_card_numbers
@visa.number = nil
assert_not_valid @visa
- @visa.number = "11112222333344ff"
- assert_not_valid @visa
- assert_false @visa.errors.on(:type)
- assert_false @visa.errors.on(:brand)
- assert @visa.errors.on(:number)
+ @visa.number = '11112222333344ff'
+ errors = assert_not_valid @visa
+ assert !errors[:type]
+ assert !errors[:brand]
+ assert errors[:number]
- @visa.number = "111122223333444"
- assert_not_valid @visa
- assert_false @visa.errors.on(:type)
- assert_false @visa.errors.on(:brand)
- assert @visa.errors.on(:number)
+ @visa.number = '111122223333444'
+ errors = assert_not_valid @visa
+ assert !errors[:type]
+ assert !errors[:brand]
+ assert errors[:number]
- @visa.number = "11112222333344444"
- assert_not_valid @visa
- assert_false @visa.errors.on(:type)
- assert_false @visa.errors.on(:brand)
- assert @visa.errors.on(:number)
+ @visa.number = '11112222333344444'
+ errors = assert_not_valid @visa
+ assert !errors[:type]
+ assert !errors[:brand]
+ assert errors[:number]
end
def test_should_have_errors_with_invalid_card_brand_for_otherwise_correct_number
@visa.brand = 'master'
- assert_not_valid @visa
- assert_not_equal @visa.errors.on(:number), @visa.errors.on(:brand)
+ errors = assert_not_valid @visa
+ assert !errors[:number]
+ assert errors[:brand]
+ assert_equal ['does not match the card number'], errors[:brand]
end
def test_should_be_invalid_when_brand_cannot_be_detected
- @visa.number = nil
@visa.brand = nil
- assert_not_valid @visa
- assert !@visa.errors.on(:brand)
- assert !@visa.errors.on(:type)
- assert @visa.errors.on(:number)
- assert_equal 'is required', @visa.errors.on(:number)
+ @visa.number = nil
+ errors = assert_not_valid @visa
+ assert !errors[:brand]
+ assert !errors[:type]
+ assert errors[:number]
+ assert_equal ['is required'], errors[:number]
+
+ @visa.number = '11112222333344ff'
+ errors = assert_not_valid @visa
+ assert !errors[:type]
+ assert !errors[:brand]
+ assert errors[:number]
+
+ @visa.number = '11112222333344444'
+ errors = assert_not_valid @visa
+ assert !errors[:brand]
+ assert !errors[:type]
+ assert errors[:number]
end
def test_should_be_a_valid_card_number
- @visa.number = "4242424242424242"
+ @visa.number = '4242424242424242'
assert_valid @visa
end
@@ -120,36 +127,36 @@ def test_should_require_a_valid_card_month
def test_should_not_be_valid_with_empty_month
@visa.month = ''
- assert_not_valid @visa
- assert_equal 'is required', @visa.errors.on('month')
+ errors = assert_not_valid @visa
+ assert_equal ['is required'], errors[:month]
end
def test_should_not_be_valid_for_edge_month_cases
@visa.month = 13
@visa.year = Time.now.year
- assert_not_valid @visa
- assert @visa.errors.on('month')
+ errors = assert_not_valid @visa
+ assert errors[:month]
@visa.month = 0
@visa.year = Time.now.year
- assert_not_valid @visa
- assert @visa.errors.on('month')
+ errors = assert_not_valid @visa
+ assert errors[:month]
end
def test_should_be_invalid_with_empty_year
@visa.year = ''
- assert_not_valid @visa
- assert_equal 'is required', @visa.errors.on('year')
+ errors = assert_not_valid @visa
+ assert_equal ['is required'], errors[:year]
end
def test_should_not_be_valid_for_edge_year_cases
@visa.year = Time.now.year - 1
- assert_not_valid @visa
- assert @visa.errors.on('year')
+ errors = assert_not_valid @visa
+ assert errors[:year]
@visa.year = Time.now.year + 21
- assert_not_valid @visa
- assert @visa.errors.on('year')
+ errors = assert_not_valid @visa
+ assert errors[:year]
end
def test_should_be_a_valid_future_year
@@ -159,15 +166,10 @@ def test_should_be_a_valid_future_year
def test_expired_card_should_have_one_error_on_year
@visa.year = Time.now.year - 1
- assert_not_valid @visa
- assert_equal 1, @visa.errors['year'].length
- assert /expired/ =~ @visa.errors['year'].first
- end
-
- def test_should_be_valid_with_start_month_and_year_as_string
- @solo.start_month = '2'
- @solo.start_year = '2007'
- assert_valid @solo
+ errors = assert_not_valid(@visa)
+ assert_not_nil errors[:year]
+ assert_equal 1, errors[:year].size
+ assert_match(/expired/, errors[:year].first)
end
def test_should_identify_wrong_card_brand
@@ -190,6 +192,8 @@ def test_should_display_number
def test_should_correctly_identify_card_brand
assert_equal 'visa', CreditCard.brand?('4242424242424242')
assert_equal 'american_express', CreditCard.brand?('341111111111111')
+ assert_equal 'master', CreditCard.brand?('5105105105105100')
+ (222100..272099).each { |bin| assert_equal 'master', CreditCard.brand?(bin.to_s + '1111111111'), "Failed with BIN #{bin}" }
assert_nil CreditCard.brand?('')
end
@@ -203,66 +207,68 @@ def test_should_not_be_valid_when_requiring_a_verification_value
card = credit_card('4242424242424242', :verification_value => nil)
assert_not_valid card
+ card.verification_value = '1234'
+ errors = assert_not_valid card
+ assert_equal errors[:verification_value], ['should be 3 digits']
+
card.verification_value = '123'
assert_valid card
- end
-
- def test_should_require_valid_start_date_for_solo_or_switch
- @solo.start_month = nil
- @solo.start_year = nil
- @solo.issue_number = nil
- assert_not_valid @solo
- assert @solo.errors.on('start_month')
- assert @solo.errors.on('start_year')
- assert @solo.errors.on('issue_number')
+ card = credit_card('341111111111111', :verification_value => '123', :brand => 'american_express')
+ errors = assert_not_valid card
+ assert_equal errors[:verification_value], ['should be 4 digits']
- @solo.start_month = 2
- @solo.start_year = 2007
- assert_valid @solo
+ card.verification_value = '1234'
+ assert_valid card
end
- def test_should_require_a_valid_issue_number_for_solo_or_switch
- @solo.start_month = nil
- @solo.start_year = 2005
- @solo.issue_number = nil
+ def test_should_be_valid_when_not_requiring_a_verification_value
+ CreditCard.require_verification_value = true
+ card = credit_card('4242424242424242', :verification_value => nil, :require_verification_value => false)
+ assert_valid card
- assert_not_valid @solo
- assert @solo.errors.on('start_month')
- assert_equal "cannot be empty", @solo.errors.on('issue_number')
+ card.verification_value = '1234'
+ errors = assert_not_valid card
+ assert_equal errors[:verification_value], ['should be 3 digits']
- @solo.issue_number = 3
- assert_valid @solo
+ card.verification_value = '123'
+ assert_valid card
end
- def test_should_require_a_validate_non_empty_issue_number_for_solo_or_switch
- @solo.issue_number = "invalid"
-
- assert_not_valid @solo
- assert_equal "is invalid", @solo.errors.on('issue_number')
-
- @solo.issue_number = 3
- assert_valid @solo
+ def test_bogus_cards_are_not_valid_without_verification_value
+ CreditCard.require_verification_value = true
+ card = credit_card('1', brand: 'bogus', verification_value: nil)
+ assert_not_valid card
end
def test_should_return_last_four_digits_of_card_number
- ccn = CreditCard.new(:number => "4779139500118580")
- assert_equal "8580", ccn.last_digits
+ ccn = CreditCard.new(:number => '4779139500118580')
+ assert_equal '8580', ccn.last_digits
end
def test_bogus_last_digits
- ccn = CreditCard.new(:number => "1")
- assert_equal "1", ccn.last_digits
+ ccn = CreditCard.new(:number => '1')
+ assert_equal '1', ccn.last_digits
+ end
+
+ def test_should_return_empty_string_for_first_digits_of_nil_card_number
+ ccn = CreditCard.new
+ assert_equal '', ccn.first_digits
end
-
+
+ def test_should_return_empty_string_for_last_digits_of_nil_card_number
+ ccn = CreditCard.new
+ assert_equal '', ccn.last_digits
+ end
+
def test_should_return_first_four_digits_of_card_number
- ccn = CreditCard.new(:number => "4779139500118580")
- assert_equal "477913", ccn.first_digits
+ ccn = CreditCard.new(:number => '4779139500118580')
+ assert_equal '477913', ccn.first_digits
end
-
+
def test_should_return_first_bogus_digit_of_card_number
- ccn = CreditCard.new(:number => "1")
- assert_equal "1", ccn.first_digits
+ ccn = CreditCard.new(:number => '1')
+ assert_equal '1', ccn.first_digits
end
def test_should_be_true_when_credit_card_has_a_first_name
@@ -292,25 +298,42 @@ def test_should_test_for_a_full_name
def test_should_handle_full_name_when_first_or_last_is_missing
c = CreditCard.new(:first_name => 'James')
assert c.name?
- assert_equal "James", c.name
+ assert_equal 'James', c.name
c = CreditCard.new(:last_name => 'Herdman')
assert c.name?
- assert_equal "Herdman", c.name
+ assert_equal 'Herdman', c.name
end
def test_should_assign_a_full_name
- c = CreditCard.new :name => "James Herdman"
- assert_equal "James", c.first_name
- assert_equal "Herdman", c.last_name
+ c = CreditCard.new :name => 'James Herdman'
+ assert_equal 'James', c.first_name
+ assert_equal 'Herdman', c.last_name
- c = CreditCard.new :name => "Rocket J. Squirrel"
- assert_equal "Rocket J.", c.first_name
- assert_equal "Squirrel", c.last_name
+ c = CreditCard.new :name => 'Rocket J. Squirrel'
+ assert_equal 'Rocket J.', c.first_name
+ assert_equal 'Squirrel', c.last_name
- c = CreditCard.new :name => "Twiggy"
- assert_equal "", c.first_name
- assert_equal "Twiggy", c.last_name
+ c = CreditCard.new :name => 'Twiggy'
+ assert_equal '', c.first_name
+ assert_equal 'Twiggy', c.last_name
+ assert_equal 'Twiggy', c.name
+ end
+
+ def test_should_remove_trailing_whitespace_on_name
+ c = CreditCard.new(:last_name => 'Herdman')
+ assert_equal 'Herdman', c.name
+
+ c = CreditCard.new(:last_name => 'Herdman', first_name: '')
+ assert_equal 'Herdman', c.name
+ end
+
+ def test_should_remove_leading_whitespace_on_name
+ c = CreditCard.new(:first_name => 'James')
+ assert_equal 'James', c.name
+
+ c = CreditCard.new(:last_name => '', first_name: 'James')
+ assert_equal 'James', c.name
end
# The following is a regression for a bug that raised an exception when
@@ -328,31 +351,28 @@ def test_validate_new_card
def test_create_and_validate_credit_card_from_brand
credit_card = CreditCard.new(:brand => CreditCard.brand?('4242424242424242'))
assert_nothing_raised do
- credit_card.valid?
+ credit_card.validate
end
end
def test_autodetection_of_credit_card_brand
credit_card = CreditCard.new(:number => '4242424242424242')
- credit_card.valid?
assert_equal 'visa', credit_card.brand
end
def test_card_brand_should_not_be_autodetected_when_provided
credit_card = CreditCard.new(:number => '4242424242424242', :brand => 'master')
- credit_card.valid?
assert_equal 'master', credit_card.brand
end
def test_detecting_bogus_card
credit_card = CreditCard.new(:number => '1')
- credit_card.valid?
assert_equal 'bogus', credit_card.brand
end
def test_validating_bogus_card
credit_card = credit_card('1', :brand => nil)
- assert credit_card.valid?
+ assert_valid credit_card
end
def test_mask_number
@@ -361,22 +381,61 @@ def test_mask_number
def test_strip_non_digit_characters
card = credit_card('4242-4242 %%%%%%4242......4242')
- assert card.valid?
- assert_equal "4242424242424242", card.number
+ assert_valid card
+ assert_equal '4242424242424242', card.number
end
- def test_before_validate_handles_blank_number
+ def test_validate_handles_blank_number
card = credit_card(nil)
- assert !card.valid?
- assert_equal "", card.number
+ assert_not_valid card
+ assert_nil card.number
end
-
+
+ def test_rails_methods_are_deprecated
+ card = credit_card
+ warning = %(Implicit inclusion of Rails-specific functionality is deprecated. Explicitly require "active_merchant/billing/rails" if you need it.)
+ assert_deprecation_warning(warning) do
+ card.valid?
+ end
+
+ assert_deprecation_warning(warning) do
+ card.errors
+ end
+ end
+
def test_brand_is_aliased_as_type
- assert_deprecation_warning("CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.", CreditCard) do
+ assert_deprecation_warning('CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.') do
assert_equal @visa.type, @visa.brand
end
- assert_deprecation_warning("CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.", CreditCard) do
- assert_equal @solo.type, @solo.brand
+ assert_deprecation_warning('CreditCard#type is deprecated and will be removed from a future release of ActiveMerchant. Please use CreditCard#brand instead.') do
+ assert_equal @maestro.type, @maestro.brand
end
end
+
+ def test_month_and_year_are_immediately_converted_to_integers
+ card = CreditCard.new
+
+ card.month = '1'
+ assert_equal 1, card.month
+ card.year = '1'
+ assert_equal 1, card.year
+
+ card.month = ''
+ assert_nil card.month
+ card.year = ''
+ assert_nil card.year
+
+ card.month = nil
+ assert_nil card.month
+ card.year = nil
+ assert_nil card.year
+ end
+
+ def test_should_report_as_emv_if_icc_data_present
+ assert CreditCard.new(icc_data: 'E480').emv?
+ end
+
+ def test_should_not_report_as_emv_if_icc_data_not_present
+ refute CreditCard.new.emv?
+ end
end
diff --git a/test/unit/cvv_result_test.rb b/test/unit/cvv_result_test.rb
index 12ed48d1e35..282fc97c8a6 100644
--- a/test/unit/cvv_result_test.rb
+++ b/test/unit/cvv_result_test.rb
@@ -6,28 +6,28 @@ def test_nil_data
assert_nil result.code
assert_nil result.message
end
-
+
def test_blank_data
result = CVVResult.new('')
assert_nil result.code
assert_nil result.message
end
-
+
def test_successful_match
result = CVVResult.new('M')
assert_equal 'M', result.code
assert_equal CVVResult.messages['M'], result.message
end
-
+
def test_failed_match
result = CVVResult.new('N')
assert_equal 'N', result.code
assert_equal CVVResult.messages['N'], result.message
end
-
+
def test_to_hash
result = CVVResult.new('M').to_hash
assert_equal 'M', result['code']
assert_equal CVVResult.messages['M'], result['message']
end
-end
\ No newline at end of file
+end
diff --git a/test/unit/expiry_date_test.rb b/test/unit/expiry_date_test.rb
index 9be4b731afc..07a44b26c15 100644
--- a/test/unit/expiry_date_test.rb
+++ b/test/unit/expiry_date_test.rb
@@ -6,27 +6,27 @@ def test_should_be_expired
date = CreditCard::ExpiryDate.new(last_month.month, last_month.year)
assert date.expired?
end
-
+
def test_today_should_not_be_expired
today = Time.now.utc
date = CreditCard::ExpiryDate.new(today.month, today.year)
assert_false date.expired?
end
-
+
def test_dates_in_the_future_should_not_be_expired
next_month = 1.month.from_now
date = CreditCard::ExpiryDate.new(next_month.month, next_month.year)
assert_false date.expired?
end
-
+
def test_invalid_date
expiry = CreditCard::ExpiryDate.new(13, 2009)
assert_equal Time.at(0).utc, expiry.expiration
end
-
+
def test_month_and_year_coerced_to_integer
- expiry = CreditCard::ExpiryDate.new("13", "2009")
+ expiry = CreditCard::ExpiryDate.new('13', '2009')
assert_equal 13, expiry.month
assert_equal 2009, expiry.year
end
-end
\ No newline at end of file
+end
diff --git a/test/unit/fixtures_test.rb b/test/unit/fixtures_test.rb
new file mode 100644
index 00000000000..b72720d928c
--- /dev/null
+++ b/test/unit/fixtures_test.rb
@@ -0,0 +1,11 @@
+require 'test_helper'
+
+class FixturesTest < Test::Unit::TestCase
+ def test_sort
+ keys = YAML.safe_load(File.read(ActiveMerchant::Fixtures::DEFAULT_CREDENTIALS), [], [], true).keys
+ assert_equal(
+ keys,
+ keys.sort
+ )
+ end
+end
diff --git a/test/unit/gateways/adyen_test.rb b/test/unit/gateways/adyen_test.rb
new file mode 100644
index 00000000000..976cedc73b5
--- /dev/null
+++ b/test/unit/gateways/adyen_test.rb
@@ -0,0 +1,860 @@
+require 'test_helper'
+
+class AdyenTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = AdyenGateway.new(
+ username: 'ws@adyenmerchant.com',
+ password: 'password',
+ merchant_account: 'merchantAccount'
+ )
+
+ @credit_card = credit_card('4111111111111111',
+ :month => 8,
+ :year => 2018,
+ :first_name => 'Test',
+ :last_name => 'Card',
+ :verification_value => '737',
+ :brand => 'visa'
+ )
+
+ @elo_credit_card = credit_card('5066 9911 1111 1118',
+ :month => 10,
+ :year => 2020,
+ :first_name => 'John',
+ :last_name => 'Smith',
+ :verification_value => '737',
+ :brand => 'elo'
+ )
+
+ @three_ds_enrolled_card = credit_card('4212345678901237', brand: :visa)
+
+ @apple_pay_card = network_tokenization_credit_card('4111111111111111',
+ :payment_cryptogram => 'YwAAAAAABaYcCMX/OhNRQAAAAAA=',
+ :month => '08',
+ :year => '2018',
+ :source => :apple_pay,
+ :verification_value => nil
+ )
+
+ @amount = 100
+
+ @options = {
+ billing_address: address(),
+ shopper_reference: 'John Smith',
+ order_id: '345123',
+ installments: 2,
+ stored_credential: {reason_type: 'unscheduled'}
+ }
+
+ @normalized_initial_stored_credential = {
+ stored_credential: {
+ initial_transaction: true,
+ reason_type: 'unscheduled'
+ }
+ }
+
+ @normalized_stored_credential = {
+ stored_credential: {
+ initial_transaction: false,
+ reason_type: 'recurring'
+ }
+ }
+
+ @normalized_3ds_2_options = {
+ reference: '345123',
+ shopper_email: 'john.smith@test.com',
+ shopper_ip: '77.110.174.153',
+ shopper_reference: 'John Smith',
+ billing_address: address(),
+ order_id: '123',
+ stored_credential: {reason_type: 'unscheduled'},
+ three_ds_2: {
+ channel: 'browser',
+ browser_info: {
+ accept_header: 'unknown',
+ depth: 100,
+ java: false,
+ language: 'US',
+ height: 1000,
+ width: 500,
+ timezone: '-120',
+ user_agent: 'unknown'
+ }
+ }
+ }
+ end
+
+ # Subdomains are only valid for production gateways, so the test_url check must be manually bypassed for this test to pass.
+ # def test_subdomain_specification
+ # gateway = AdyenGateway.new(
+ # username: 'ws@adyenmerchant.com',
+ # password: 'password',
+ # merchant_account: 'merchantAccount',
+ # subdomain: '123-subdomain'
+ # )
+ #
+ # response = stub_comms(gateway) do
+ # gateway.authorize(@amount, @credit_card, @options)
+ # end.check_request do |endpoint, data, headers|
+ # assert_match("https://123-subdomain-pal-live.adyenpayments.com/pal/servlet/Payment/v18/authorise", endpoint)
+ # end.respond_with(successful_authorize_response)
+ #
+ # assert response
+ # assert_success response
+ # end
+
+ def test_successful_authorize
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+
+ assert_equal '#7914775043909934#', response.authorization
+ assert_equal 'R', response.avs_result['code']
+ assert_equal 'M', response.cvv_result['code']
+ assert response.test?
+ end
+
+ def test_successful_authorize_with_3ds
+ @gateway.expects(:ssl_post).returns(successful_authorize_with_3ds_response)
+
+ response = @gateway.authorize(@amount, @three_ds_enrolled_card, @options.merge(execute_threed: true))
+ assert response.test?
+ refute response.authorization.blank?
+ assert_equal '#8835440446784145#', response.authorization
+ assert_equal response.params['resultCode'], 'RedirectShopper'
+ refute response.params['issuerUrl'].blank?
+ refute response.params['md'].blank?
+ refute response.params['paRequest'].blank?
+ end
+
+ def test_adds_3ds1_standalone_fields
+ eci = '05'
+ cavv = '3q2+78r+ur7erb7vyv66vv\/\/\/\/8='
+ cavv_algorithm = '1'
+ xid = 'ODUzNTYzOTcwODU5NzY3Qw=='
+ directory_response_status = 'C'
+ authentication_response_status = 'Y'
+ options_with_3ds1_standalone = @options.merge(
+ three_d_secure: {
+ eci: eci,
+ cavv: cavv,
+ cavv_algorithm: cavv_algorithm,
+ xid: xid,
+ directory_response_status: directory_response_status,
+ authentication_response_status: authentication_response_status
+ }
+ )
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, options_with_3ds1_standalone)
+ end.check_request do |endpoint, data, headers|
+ assert_equal eci, JSON.parse(data)['mpiData']['eci']
+ assert_equal cavv, JSON.parse(data)['mpiData']['cavv']
+ assert_equal cavv_algorithm, JSON.parse(data)['mpiData']['cavvAlgorithm']
+ assert_equal xid, JSON.parse(data)['mpiData']['xid']
+ assert_equal directory_response_status, JSON.parse(data)['mpiData']['directoryResponse']
+ assert_equal authentication_response_status, JSON.parse(data)['mpiData']['authenticationResponse']
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_adds_3ds2_standalone_fields
+ version = '2.1.0'
+ eci = '02'
+ cavv = 'jJ81HADVRtXfCBATEp01CJUAAAA='
+ ds_transaction_id = '97267598-FAE6-48F2-8083-C23433990FBC'
+ directory_response_status = 'C'
+ authentication_response_status = 'Y'
+ options_with_3ds2_standalone = @options.merge(
+ three_d_secure: {
+ version: version,
+ eci: eci,
+ cavv: cavv,
+ ds_transaction_id: ds_transaction_id,
+ directory_response_status: directory_response_status,
+ authentication_response_status: authentication_response_status
+ }
+ )
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, options_with_3ds2_standalone)
+ end.check_request do |endpoint, data, headers|
+ assert_equal version, JSON.parse(data)['mpiData']['threeDSVersion']
+ assert_equal eci, JSON.parse(data)['mpiData']['eci']
+ assert_equal cavv, JSON.parse(data)['mpiData']['cavv']
+ assert_equal ds_transaction_id, JSON.parse(data)['mpiData']['dsTransID']
+ assert_equal directory_response_status, JSON.parse(data)['mpiData']['directoryResponse']
+ assert_equal authentication_response_status, JSON.parse(data)['mpiData']['authenticationResponse']
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_failed_authorize
+ @gateway.expects(:ssl_post).returns(failed_authorize_response)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_equal 'Expired Card', response.message
+ assert_failure response
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_post).returns(successful_capture_response)
+ response = @gateway.capture(@amount, '7914775043909934')
+ assert_equal '7914775043909934#8814775564188305#', response.authorization
+ assert_success response
+ assert response.test?
+ end
+
+ def test_failed_capture
+ @gateway.expects(:ssl_post).returns(failed_capture_response)
+ response = @gateway.capture(nil, '')
+ assert_nil response.authorization
+ assert_equal 'Original pspReference required for this operation', response.message
+ assert_failure response
+ end
+
+ def test_successful_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options)
+ end.respond_with(successful_authorize_response, successful_capture_response)
+ assert_success response
+ assert_equal '7914775043909934#8814775564188305#', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_purchase_with_elo_card
+ response = stub_comms do
+ @gateway.purchase(@amount, @elo_credit_card, @options)
+ end.respond_with(successful_authorize_with_elo_response, successful_capture_with_elo_repsonse)
+ assert_success response
+ assert_equal '8835511210681145#8835511210689965#', response.authorization
+ assert response.test?
+ end
+
+ def test_successful_maestro_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card, @options.merge({selected_brand: 'maestro', overwrite_brand: 'true'}))
+ end.check_request do |endpoint, data, headers|
+ if endpoint =~ /authorise/
+ assert_match(/"overwriteBrand":true/, data)
+ assert_match(/"selectedBrand":"maestro"/, data)
+ end
+ end.respond_with(successful_authorize_response, successful_capture_response)
+ assert_success response
+ assert_equal '7914775043909934#8814775564188305#', response.authorization
+ assert response.test?
+ end
+
+ def test_3ds_2_fields_sent
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, @normalized_3ds_2_options)
+ end.check_request do |endpoint, data, headers|
+ data = JSON.parse(data)
+ assert_equal 'browser', data['threeDS2RequestData']['deviceChannel']
+ assert_equal 'unknown', data['browserInfo']['acceptHeader']
+ assert_equal 100, data['browserInfo']['colorDepth']
+ assert_equal false, data['browserInfo']['javaEnabled']
+ assert_equal 'US', data['browserInfo']['language']
+ assert_equal 1000, data['browserInfo']['screenHeight']
+ assert_equal 500, data['browserInfo']['screenWidth']
+ assert_equal '-120', data['browserInfo']['timeZoneOffset']
+ assert_equal 'unknown', data['browserInfo']['userAgent']
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_installments_sent
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options)
+ end.check_request do |endpoint, data, headers|
+ assert_equal 2, JSON.parse(data)['installments']['value']
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_custom_routing_sent
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge({custom_routing_flag: 'abcdefg'}))
+ end.check_request do |endpoint, data, headers|
+ assert_equal 'abcdefg', JSON.parse(data)['additionalData']['customRoutingFlag']
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_update_shopper_statement_and_industry_usage_sent
+ stub_comms do
+ @gateway.adjust(@amount, '123', @options.merge({update_shopper_statement: 'statement note', industry_usage: 'DelayedCharge'}))
+ end.check_request do |endpoint, data, headers|
+ assert_equal 'statement note', JSON.parse(data)['additionalData']['updateShopperStatement']
+ assert_equal 'DelayedCharge', JSON.parse(data)['additionalData']['industryUsage']
+ end.respond_with(successful_adjust_response)
+ end
+
+ def test_risk_data_sent
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, @options.merge({risk_data: {'operatingSystem' => 'HAL9000'}}))
+ end.check_request do |endpoint, data, headers|
+ assert_equal 'HAL9000', JSON.parse(data)['additionalData']['riskdata.operatingSystem']
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_risk_data_complex_data
+ stub_comms do
+ risk_data = {
+ 'deliveryMethod' => 'express',
+ 'basket.item.productTitle' => 'Blue T Shirt',
+ 'promotions.promotion.promotionName' => 'Big Sale promotion'
+ }
+ @gateway.authorize(@amount, @credit_card, @options.merge({risk_data: risk_data}))
+ end.check_request do |endpoint, data, headers|
+ parsed = JSON.parse(data)
+ assert_equal 'express', parsed['additionalData']['riskdata.deliveryMethod']
+ assert_equal 'Blue T Shirt', parsed['additionalData']['riskdata.basket.item.productTitle']
+ assert_equal 'Big Sale promotion', parsed['additionalData']['riskdata.promotions.promotion.promotionName']
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_successful_authorize_with_normalized_stored_credentials
+ @credit_card.verification_value = nil
+ stub_comms do
+ @gateway.authorize(50, @credit_card, @options.merge(@normalized_stored_credential))
+ end.check_request do |endpoint, data, headers|
+ assert_match(/"shopperInteraction":"ContAuth"/, data)
+ assert_match(/"recurringProcessingModel":"Subscription"/, data)
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_successful_initial_authorize_with_normalized_stored_credentials
+ stub_comms do
+ @gateway.authorize(50, @credit_card, @options.merge(@normalized_initial_stored_credential))
+ end.check_request do |endpoint, data, headers|
+ assert_match(/"shopperInteraction":"Ecommerce"/, data)
+ assert_match(/"recurringProcessingModel":"CardOnFile"/, data)
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_nonfractional_currency_handling
+ stub_comms do
+ @gateway.authorize(200, @credit_card, @options.merge(currency: 'JPY'))
+ end.check_request do |endpoint, data, headers|
+ assert_match(/"amount\":{\"value\":\"2\",\"currency\":\"JPY\"}/, data)
+ end.respond_with(successful_authorize_response)
+
+ stub_comms do
+ @gateway.authorize(200, @credit_card, @options.merge(currency: 'CLP'))
+ end.check_request do |endpoint, data, headers|
+ assert_match(/"amount\":{\"value\":\"200\",\"currency\":\"CLP\"}/, data)
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_failed_purchase
+ @gateway.expects(:ssl_post).returns(failed_purchase_response)
+
+ response = @gateway.purchase(@amount, credit_card('400111'), @options)
+ assert_failure response
+
+ assert_equal AdyenGateway::STANDARD_ERROR_CODE[:incorrect_number], response.error_code
+ end
+
+ def test_successful_refund
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+ response = @gateway.refund(@amount, '7914775043909934')
+ assert_equal '7914775043909934#8514775559925128#', response.authorization
+ assert_equal '[refund-received]', response.message
+ assert response.test?
+ end
+
+ def test_successful_refund_with_compound_psp_reference
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+ response = @gateway.refund(@amount, '7914775043909934#8514775559000000')
+ assert_equal '7914775043909934#8514775559925128#', response.authorization
+ assert_equal '[refund-received]', response.message
+ assert response.test?
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_post).returns(failed_refund_response)
+ response = @gateway.refund(@amount, '')
+ assert_nil response.authorization
+ assert_equal 'Original pspReference required for this operation', response.message
+ assert_failure response
+ end
+
+ def test_successful_void
+ @gateway.expects(:ssl_post).returns(successful_void_response)
+ response = @gateway.void('7914775043909934')
+ assert_equal '7914775043909934#8614775821628806#', response.authorization
+ assert_equal '[cancel-received]', response.message
+ assert response.test?
+ end
+
+ def test_failed_void
+ @gateway.expects(:ssl_post).returns(failed_void_response)
+ response = @gateway.void('')
+ assert_equal 'Original pspReference required for this operation', response.message
+ assert_failure response
+ end
+
+ def test_successful_adjust
+ @gateway.expects(:ssl_post).returns(successful_adjust_response)
+ response = @gateway.adjust(200, '8835544088660594')
+ assert_equal '8835544088660594#8835544088660594#', response.authorization
+ assert_equal '[adjustAuthorisation-received]', response.message
+ end
+
+ def test_failed_adjust
+ @gateway.expects(:ssl_post).returns(failed_adjust_response)
+ response = @gateway.adjust(200, '')
+ assert_equal 'Original pspReference required for this operation', response.message
+ assert_failure response
+ end
+
+ def test_successful_synchronous_adjust
+ @gateway.expects(:ssl_post).returns(successful_synchronous_adjust_response)
+ response = @gateway.adjust(200, '8835544088660594')
+ assert_equal '8835544088660594#8835574118820108#', response.authorization
+ assert_equal 'Authorised', response.message
+ end
+
+ def test_failed_synchronous_adjust
+ @gateway.expects(:ssl_post).returns(failed_synchronous_adjust_response)
+ response = @gateway.adjust(200, '8835544088660594')
+ assert_equal 'Refused', response.message
+ assert_failure response
+ end
+
+ def test_successful_store
+ response = stub_comms do
+ @gateway.store(@credit_card, @options)
+ end.check_request do |endpoint, data, headers|
+ assert_equal 'CardOnFile', JSON.parse(data)['recurringProcessingModel']
+ end.respond_with(successful_store_response)
+ assert_success response
+ assert_equal '#8835205392522157#8315202663743702', response.authorization
+ end
+
+ def test_failed_store
+ @gateway.expects(:ssl_post).returns(failed_store_response)
+ response = @gateway.store(@credit_card, @options)
+ assert_failure response
+ assert_equal 'Refused', response.message
+ end
+
+ def test_successful_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(successful_verify_response)
+ assert_success response
+ assert_equal '#7914776426645103#', response.authorization
+ assert_equal 'Authorised', response.message
+ assert response.test?
+ end
+
+ def test_failed_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(failed_verify_response)
+ assert_failure response
+ assert_equal '#7914776433387947#', response.authorization
+ assert_equal 'Refused', response.message
+ assert response.test?
+ end
+
+ def test_failed_avs_check_returns_refusal_reason_raw
+ @gateway.expects(:ssl_post).returns(failed_authorize_avs_response)
+
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'Refused | 05 : Do not honor', response.message
+ end
+
+ def test_scrub
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ def test_scrub_network_tokenization_card
+ assert @gateway.supports_scrubbing?
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
+ end
+
+ def test_add_address
+ post = {:card => {:billingAddress => {}}}
+ @options[:billing_address].delete(:address1)
+ @options[:billing_address].delete(:address2)
+ @options[:billing_address].delete(:state)
+ @gateway.send(:add_address, post, @options)
+ assert_equal 'N/A', post[:billingAddress][:street]
+ assert_equal 'N/A', post[:billingAddress][:houseNumberOrName]
+ assert_equal 'N/A', post[:billingAddress][:stateOrProvince]
+ assert_equal @options[:billing_address][:zip], post[:billingAddress][:postalCode]
+ assert_equal @options[:billing_address][:city], post[:billingAddress][:city]
+ assert_equal @options[:billing_address][:country], post[:billingAddress][:country]
+ end
+
+ def test_authorize_with_network_tokenization_credit_card_no_name
+ @apple_pay_card.first_name = nil
+ @apple_pay_card.last_name = nil
+ response = stub_comms do
+ @gateway.authorize(@amount, @apple_pay_card, @options)
+ end.check_request do |endpoint, data, headers|
+ assert_equal 'Not Provided', JSON.parse(data)['card']['holderName']
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_authorize_with_network_tokenization_credit_card
+ response = stub_comms do
+ @gateway.authorize(@amount, @apple_pay_card, @options)
+ end.check_request do |endpoint, data, headers|
+ parsed = JSON.parse(data)
+ assert_equal 'YwAAAAAABaYcCMX/OhNRQAAAAAA=', parsed['mpiData']['cavv']
+ assert_equal '07', parsed['mpiData']['eci']
+ assert_equal 'applepay', parsed['additionalData']['paymentdatasource.type']
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ def test_extended_avs_response
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(extended_avs_response)
+ assert_equal 'Card member\'s name, billing address, and billing postal code match.', response.avs_result['message']
+ end
+
+ def test_optional_idempotency_key_header
+ options = @options.merge(:idempotency_key => 'test123')
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card, options)
+ end.check_request do |endpoint, data, headers|
+ assert headers['Idempotency-Key']
+ end.respond_with(successful_authorize_response)
+ assert_success response
+ end
+
+ private
+
+ def pre_scrubbed
+ <<-PRE_SCRUBBED
+ opening connection to pal-test.adyen.com:443...
+ opened
+ starting SSL for pal-test.adyen.com:443...
+ SSL established
+ <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic d3NfMTYzMjQ1QENvbXBhbnkuRGFuaWVsYmFra2Vybmw6eXU0aD50ZlxIVEdydSU1PDhxYTVMTkxVUw==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.adyen.com\r\nContent-Length: 308\r\n\r\n"
+ <- "{\"merchantAccount\":\"DanielbakkernlNL\",\"reference\":\"345123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"John Smith\",\"number\":\"4111111111111111\",\"cvc\":\"737\"},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\"}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Thu, 27 Oct 2016 11:37:13 GMT\r\n"
+ -> "Server: Apache\r\n"
+ -> "Set-Cookie: JSESSIONID=C0D66C19173B3491D862B8FDBFD72FD7.test3e; Path=/pal/; Secure; HttpOnly\r\n"
+ -> "pspReference: 8514775682339577\r\n"
+ -> "Connection: close\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Content-Type: application/json;charset=utf-8\r\n"
+ -> "\r\n"
+ -> "50\r\n"
+ reading 80 bytes...
+ -> ""
+ -> "{\"pspReference\":\"8514775682339577\",\"resultCode\":\"Authorised\",\"authCode\":\"31845\"}"
+ read 80 bytes
+ reading 2 bytes...
+ -> ""
+ -> "\r\n"
+ read 2 bytes
+ -> "0\r\n"
+ -> "\r\n"
+ Conn close
+ PRE_SCRUBBED
+ end
+
+ def post_scrubbed
+ <<-POST_SCRUBBED
+ opening connection to pal-test.adyen.com:443...
+ opened
+ starting SSL for pal-test.adyen.com:443...
+ SSL established
+ <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]==\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: pal-test.adyen.com\r\nContent-Length: 308\r\n\r\n"
+ <- "{\"merchantAccount\":\"DanielbakkernlNL\",\"reference\":\"345123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"John Smith\",\"number\":\"[FILTERED]\",\"cvc\":\"[FILTERED]\"},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\"}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Thu, 27 Oct 2016 11:37:13 GMT\r\n"
+ -> "Server: Apache\r\n"
+ -> "Set-Cookie: JSESSIONID=C0D66C19173B3491D862B8FDBFD72FD7.test3e; Path=/pal/; Secure; HttpOnly\r\n"
+ -> "pspReference: 8514775682339577\r\n"
+ -> "Connection: close\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Content-Type: application/json;charset=utf-8\r\n"
+ -> "\r\n"
+ -> "50\r\n"
+ reading 80 bytes...
+ -> ""
+ -> "{\"pspReference\":\"8514775682339577\",\"resultCode\":\"Authorised\",\"authCode\":\"31845\"}"
+ read 80 bytes
+ reading 2 bytes...
+ -> ""
+ -> "\r\n"
+ read 2 bytes
+ -> "0\r\n"
+ -> "\r\n"
+ Conn close
+ POST_SCRUBBED
+ end
+
+ def pre_scrubbed_network_tokenization_card
+ <<-PRE_SCRUBBED
+ opening connection to pal-test.adyen.com:443...
+ opened
+ starting SSL for pal-test.adyen.com:443...
+ SSL established
+ I, [2018-06-18T11:53:47.394267 #25363] INFO -- : [ActiveMerchant::Billing::AdyenGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256
+ D, [2018-06-18T11:53:47.394346 #25363] DEBUG -- : {"merchantAccount":"SpreedlyCOM294","reference":"123","amount":{"value":"100","currency":"USD"},"mpiData":{"authenticationResponse":"Y","cavv":"YwAAAAAABaYcCMX/OhNRQAAAAAA=","directoryResponse":"Y","eci":"07"},"card":{"expiryMonth":8,"expiryYear":2018,"holderName":"Longbob Longsen","number":"4111111111111111","billingAddress":{"street":"456 My Street","houseNumberOrName":"Apt 1","postalCode":"K1C2N6","city":"Ottawa","stateOrProvince":"ON","country":"CA"}},"shopperEmail":"john.smith@test.com","shopperIP":"77.110.174.153","shopperReference":"John Smith","selectedBrand":"applepay","shopperInteraction":"Ecommerce"}
+ <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic d3NAQ29tcGFueS5TcHJlZWRseTQ3MTo3c3d6U0p2R1VWViUvP3Q0Uy9bOVtoc0hF\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: pal-test.adyen.com\r\nContent-Length: 618\r\n\r\n"
+ <- "{\"merchantAccount\":\"SpreedlyCOM294\",\"reference\":\"123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"mpiData\":{\"authenticationResponse\":\"Y\",\"cavv\":\"YwAAAAAABaYcCMX/OhNRQAAAAAA=\",\"directoryResponse\":\"Y\",\"eci\":\"07\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"Longbob Longsen\",\"number\":\"4111111111111111\",\"billingAddress\":{\"street\":\"456 My Street\",\"houseNumberOrName\":\"Apt 1\",\"postalCode\":\"K1C2N6\",\"city\":\"Ottawa\",\"stateOrProvince\":\"ON\",\"country\":\"CA\"}},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\",\"selectedBrand\":\"applepay\",\"shopperInteraction\":\"Ecommerce\"}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Mon, 18 Jun 2018 15:53:47 GMT\r\n"
+ -> "Server: Apache\r\n"
+ -> "Set-Cookie: JSESSIONID=06EE78291B761A33ED9E21E46BA54649.test104e; Path=/pal; Secure; HttpOnly\r\n"
+ -> "pspReference: 8835293372276408\r\n"
+ -> "Connection: close\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Content-Type: application/json;charset=utf-8\r\n"
+ -> "\r\n"
+ -> "50\r\n"
+ reading 80 bytes...
+ -> ""
+ -> "{\"pspReference\":\"8835293372276408\",\"resultCode\":\"Authorised\",\"authCode\":\"26056\"}"
+ read 80 bytes
+ reading 2 bytes...
+ -> ""
+ -> "\r\n"
+ read 2 bytes
+ -> "0\r\n"
+ -> "\r\n"
+ Conn close
+ PRE_SCRUBBED
+ end
+
+ def post_scrubbed_network_tokenization_card
+ <<-POST_SCRUBBED
+ opening connection to pal-test.adyen.com:443...
+ opened
+ starting SSL for pal-test.adyen.com:443...
+ SSL established
+ I, [2018-06-18T11:53:47.394267 #25363] INFO -- : [ActiveMerchant::Billing::AdyenGateway] connection_ssl_version=TLSv1.2 connection_ssl_cipher=ECDHE-RSA-AES128-GCM-SHA256
+ D, [2018-06-18T11:53:47.394346 #25363] DEBUG -- : {"merchantAccount":"SpreedlyCOM294","reference":"123","amount":{"value":"100","currency":"USD"},"mpiData":{"authenticationResponse":"Y","cavv":"[FILTERED]","directoryResponse":"Y","eci":"07"},"card":{"expiryMonth":8,"expiryYear":2018,"holderName":"Longbob Longsen","number":"[FILTERED]","billingAddress":{"street":"456 My Street","houseNumberOrName":"Apt 1","postalCode":"K1C2N6","city":"Ottawa","stateOrProvince":"ON","country":"CA"}},"shopperEmail":"john.smith@test.com","shopperIP":"77.110.174.153","shopperReference":"John Smith","selectedBrand":"applepay","shopperInteraction":"Ecommerce"}
+ <- "POST /pal/servlet/Payment/v18/authorise HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Basic [FILTERED]\r\nConnection: close\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: pal-test.adyen.com\r\nContent-Length: 618\r\n\r\n"
+ <- "{\"merchantAccount\":\"SpreedlyCOM294\",\"reference\":\"123\",\"amount\":{\"value\":\"100\",\"currency\":\"USD\"},\"mpiData\":{\"authenticationResponse\":\"Y\",\"cavv\":\"[FILTERED]\",\"directoryResponse\":\"Y\",\"eci\":\"07\"},\"card\":{\"expiryMonth\":8,\"expiryYear\":2018,\"holderName\":\"Longbob Longsen\",\"number\":\"[FILTERED]\",\"billingAddress\":{\"street\":\"456 My Street\",\"houseNumberOrName\":\"Apt 1\",\"postalCode\":\"K1C2N6\",\"city\":\"Ottawa\",\"stateOrProvince\":\"ON\",\"country\":\"CA\"}},\"shopperEmail\":\"john.smith@test.com\",\"shopperIP\":\"77.110.174.153\",\"shopperReference\":\"John Smith\",\"selectedBrand\":\"applepay\",\"shopperInteraction\":\"Ecommerce\"}"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Date: Mon, 18 Jun 2018 15:53:47 GMT\r\n"
+ -> "Server: Apache\r\n"
+ -> "Set-Cookie: JSESSIONID=06EE78291B761A33ED9E21E46BA54649.test104e; Path=/pal; Secure; HttpOnly\r\n"
+ -> "pspReference: 8835293372276408\r\n"
+ -> "Connection: close\r\n"
+ -> "Transfer-Encoding: chunked\r\n"
+ -> "Content-Type: application/json;charset=utf-8\r\n"
+ -> "\r\n"
+ -> "50\r\n"
+ reading 80 bytes...
+ -> ""
+ -> "{\"pspReference\":\"8835293372276408\",\"resultCode\":\"Authorised\",\"authCode\":\"26056\"}"
+ read 80 bytes
+ reading 2 bytes...
+ -> ""
+ -> "\r\n"
+ read 2 bytes
+ -> "0\r\n"
+ -> "\r\n"
+ Conn close
+ POST_SCRUBBED
+ end
+
+ def failed_purchase_response
+ <<-RESPONSE
+ {
+ "status": 422,
+ "errorCode": "101",
+ "message": "Invalid card number",
+ "errorType": "validation",
+ "pspReference": "8514775645144049"
+ }
+ RESPONSE
+ end
+
+ def successful_authorize_with_elo_response
+ <<-RESPONSE
+ {
+ "pspReference":"8835511210681145",
+ "resultCode":"Authorised",
+ "authCode":"98696"
+ }
+ RESPONSE
+ end
+
+ def successful_capture_with_elo_repsonse
+ <<-RESPONSE
+ {
+ "pspReference":"8835511210689965",
+ "response":"[capture-received]"
+ }
+ RESPONSE
+ end
+
+ def successful_authorize_response
+ <<-RESPONSE
+ {
+ "additionalData": {
+ "cvcResult": "1 Matches",
+ "avsResult": "0 Unknown",
+ "cvcResultRaw": "M"
+ },
+ "pspReference":"7914775043909934",
+ "resultCode":"Authorised",
+ "authCode":"50055"
+ }
+ RESPONSE
+ end
+
+ def successful_authorize_with_3ds_response
+ '{"pspReference":"8835440446784145","resultCode":"RedirectShopper","issuerUrl":"https:\\/\\/test.adyen.com\\/hpp\\/3d\\/validate.shtml","md":"djIhcWk3MUhlVFlyQ1h2UC9NWmhpVm10Zz09IfIxi5eDMZgG72AUXy7PEU86esY68wr2cunaFo5VRyNPuWg3ZSvEIFuielSuoYol5WhjCH+R6EJTjVqY8eCTt+0wiqHd5btd82NstIc8idJuvg5OCu2j8dYo0Pg7nYxW\\/2vXV9Wy\\/RYvwR8tFfyZVC\\/U2028JuWtP2WxrBTqJ6nV2mDoX2chqMRSmX8xrL6VgiLoEfzCC\\/c+14r77+whHP0Mz96IGFf4BIA2Qo8wi2vrTlccH\\/zkLb5hevvV6QH3s9h0\\/JibcUrpoXH6M903ulGuikTr8oqVjEB9w8\\/WlUuxukHmqqXqAeOPA6gScehs6SpRm45PLpLysCfUricEIDhpPN1QCjjgw8+qVf3Ja1SzwfjCVocU","paRequest":"eNpVUctuwjAQ\\/BXaD2Dt4JCHFkspqVQOBChwriJnBanIAyepoF9fG5LS+jQz612PZ3F31ETxllSnSeKSmiY90CjPZs+h709cIZgQU88XXLjPEtfRO50lfpFu8qqUfMzGDsJATbtWx7RsJabq\\/LJIJHcmwp0i9BQL0otY7qhp10URqXOXa9IIdxnLtCC5jz6i+VO4rY2v7HSdr5ZOIBBuNVRVV7b6Kn3BEAaCnT7JY9vWIUDTt41VVSDYAsLD1bqzqDGDLnkmV\\/HhO9lt2DLesORTiSR+ZckmsmeGYG9glrYkHcZ97jB35PCQe6HrI9x0TAvrQO638cgkYRz1Atb2nehOuC38FdBEralUwy8GhnSpq5LMDRPpL0Z4mJ6\\/2WBVa7ISzj1azw+YQZ6N+FawU3ITCg9YcBtjCYJthX570G\\/ZoH\\/b\\/wFlSqpp"}'
+ end
+
+ def failed_authorize_response
+ <<-RESPONSE
+ {
+ "pspReference": "8514775559925128",
+ "refusalReason": "Expired Card",
+ "resultCode": "Refused"
+ }
+ RESPONSE
+ end
+
+ def successful_capture_response
+ <<-RESPONSE
+ {
+ "pspReference": "8814775564188305",
+ "response": "[capture-received]"
+ }
+ RESPONSE
+ end
+
+ def failed_capture_response
+ <<-RESPONSE
+ {
+ "status": 422,
+ "errorCode": "167",
+ "message": "Original pspReference required for this operation",
+ "errorType": "validation"
+ }
+ RESPONSE
+ end
+
+ def successful_refund_response
+ <<-RESPONSE
+ {
+ "pspReference": "8514775559925128",
+ "response": "[refund-received]"
+ }
+ RESPONSE
+ end
+
+ def failed_refund_response
+ <<-RESPONSE
+ {
+ "status":422,
+ "errorCode":"167",
+ "message":"Original pspReference required for this operation",
+ "errorType":"validation"
+ }
+ RESPONSE
+ end
+
+ def successful_void_response
+ <<-RESPONSE
+ {
+ "pspReference":"8614775821628806",
+ "response":"[cancel-received]"
+ }
+ RESPONSE
+ end
+
+ def failed_void_response
+ <<-RESPONSE
+ {
+ "status":422,
+ "errorCode":"167",
+ "message":"Original pspReference required for this operation",
+ "errorType":"validation"
+ }
+ RESPONSE
+ end
+
+ def successful_adjust_response
+ <<-RESPONSE
+ {
+ "pspReference": "8835544088660594",
+ "response": "[adjustAuthorisation-received]"
+ }
+ RESPONSE
+ end
+
+ def failed_adjust_response
+ <<-RESPONSE
+ {
+ "status":422,
+ "errorCode":"167",
+ "message":"Original pspReference required for this operation",
+ "errorType":"validation"
+ }
+ RESPONSE
+ end
+
+ def successful_synchronous_adjust_response
+ <<-RESPONSE
+ {\"additionalData\":{\"authCode\":\"70125\",\"adjustAuthorisationData\":\"BQABAQA9NtGnJAkLXKqW1C+VUeCNMzDf4WwzLFBiuQ8iaA2Yvflz41t0cYxtA7XVzG2pzlJPMnkSK75k3eByNS0\\/m0\\/N2+NnnKv\\/9rYPn8Pjq1jc7CapczdqZNl8P9FwqtIa4Kdeq7ZBNeGalx9oH4reutlFggzWCr+4eYXMRqMgQNI2Bu5XvwkqBbXwbDL05CuNPjjEwO64YrCpVBLrxk4vlW4fvCLFR0u8O68C+Y4swmsPDvGUxWpRgwNVqXsTmvt9z8hlej21BErL8fPEy+fJP4Zab8oyfcLrv9FJkHZq03cyzJpOzqX458Ctn9sIwBawXzNEFN5bCt6eT1rgp0yuHeMGEGwrjNl8rijez7Rd\\/vy1WUYAAMfmZFuJMQ73l1+Hkr0VlHv6crlyP\\/FVTY\\/XIUiGMqa1yM08Zu\\/Gur5N7lU8qnMi2WO9QPyHmmdlfo7+AGsrKrzV4wY\\/wISg0pcv8PypBWVq\\/hYoCqlHsGUuIiyGLIW7A8LtG6\\/JqAA9t\\/0EdnQVz0k06IEEYnBzkQoY8Qv3cVszgPQukGstBraB47gQdVDp9vmuQjMstt8Te56SDRxtfcu0z4nQIURVSkJJNj8RYfwXH9OUbz3Vd2vwoR3lCJFTCKIeW8sidNVB3xAZnddBVQ3P\\/QxPnrrRdCcnoWSGoEOBBIxgF00XwNxJ4P7Xj1bB7oq3M7k99dgPnSdZIjyvG6BWKnCQcGyVRB0yOaYBaOCmN66EgWfXoJR5BA4Jo6gnWnESWV62iUC8OCzmis1VagfaBn0A9vWNcqKFkUr\\/68s3w8ixLJFy+WdpAS\\/flzC3bJbvy9YR9nESKAP40XiNGz9iBROCfPI2bSOvdFf831RdTxWaE+ewAC3w9GsgEKAXxzWsVeSODWRZQA0TEVOfX8SaNVa5w3EXLDsRVnmKgUH8yQnEJQBGhDJXg1sEbowE07CzzdAY5Mc=\",\"refusalReasonRaw\":\"AUTHORISED\"},\"pspReference\":\"8835574118820108\",\"response\":\"Authorised\"}
+ RESPONSE
+ end
+
+ def failed_synchronous_adjust_response
+ <<-RESPONSE
+ {\"additionalData\":{\"authCode\":\"90745\",\"refusalReasonRaw\":\"2\"},\"pspReference\":\"8835574120337117\",\"response\":\"Refused\"}
+ RESPONSE
+ end
+
+ def successful_verify_response
+ <<-RESPONSE
+ {
+ "pspReference":"7914776426645103",
+ "resultCode":"Authorised",
+ "authCode":"31265"
+ }
+ RESPONSE
+ end
+
+ def failed_verify_response
+ <<-RESPONSE
+ {
+ "pspReference":"7914776433387947",
+ "refusalReason":"Refused",
+ "resultCode":"Refused"
+ }
+ RESPONSE
+ end
+
+ def failed_authorize_avs_response
+ <<-RESPONSE
+ {\"additionalData\":{\"cvcResult\":\"0 Unknown\",\"fraudResultType\":\"GREEN\",\"avsResult\":\"3 AVS unavailable\",\"fraudManualReview\":\"false\",\"avsResultRaw\":\"U\",\"refusalReasonRaw\":\"05 : Do not honor\",\"authorisationMid\":\"494619000001174\",\"acquirerCode\":\"AdyenVisa_BR_494619\",\"acquirerReference\":\"802320302458\",\"acquirerAccountCode\":\"AdyenVisa_BR_Cabify\"},\"fraudResult\":{\"accountScore\":0,\"results\":[{\"FraudCheckResult\":{\"accountScore\":0,\"checkId\":46,\"name\":\"DistinctCountryUsageByShopper\"}}]},\"pspReference\":\"1715167376763498\",\"refusalReason\":\"Refused\",\"resultCode\":\"Refused\"}
+ RESPONSE
+ end
+
+ def successful_store_response
+ <<-RESPONSE
+ {"additionalData":{"recurring.recurringDetailReference":"8315202663743702","recurring.shopperReference":"John Smith"},"pspReference":"8835205392522157","resultCode":"Authorised","authCode":"94571"}
+ RESPONSE
+ end
+
+ def failed_store_response
+ <<-RESPONSE
+ {"pspReference":"8835205393394754","refusalReason":"Refused","resultCode":"Refused"}
+ RESPONSE
+ end
+
+ def extended_avs_response
+ <<-RESPONSE
+ {\"additionalData\":{\"cvcResult\":\"1 Matches\",\"cvcResultRaw\":\"Y\",\"avsResult\":\"20 Name, address and zip match\",\"avsResultRaw\":\"M\"}}
+ RESPONSE
+ end
+end
diff --git a/test/unit/gateways/allied_wallet_test.rb b/test/unit/gateways/allied_wallet_test.rb
new file mode 100644
index 00000000000..fb291c7c4b4
--- /dev/null
+++ b/test/unit/gateways/allied_wallet_test.rb
@@ -0,0 +1,354 @@
+require 'test_helper'
+
+class AlliedWalletTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ @gateway = AlliedWalletGateway.new(
+ site_id: '1234',
+ merchant_id: '1234',
+ token: 'token'
+ )
+
+ @credit_card = credit_card
+ @amount = 100
+ end
+
+ def test_successful_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+
+ assert_equal '123456', response.authorization
+ assert response.test?
+ end
+
+ def test_failed_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(failed_purchase_response)
+
+ assert_failure response
+ assert_equal 'Declined', response.message
+ assert response.test?
+ end
+
+ def test_successful_authorize_and_capture
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card)
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ assert_equal '123456', response.authorization
+
+ capture = stub_comms do
+ @gateway.capture(@amount, response.authorization)
+ end.respond_with(successful_capture_response)
+
+ assert_success capture
+ end
+
+ def test_failed_authorize
+ response = stub_comms do
+ @gateway.authorize(@amount, @credit_card)
+ end.respond_with(failed_authorize_response)
+
+ assert_failure response
+ assert_equal 'Declined', response.message
+ assert response.test?
+ end
+
+ def test_failed_capture
+ response = stub_comms do
+ @gateway.capture(100, '')
+ end.respond_with(failed_capture_response)
+
+ assert_failure response
+ end
+
+ def test_successful_void
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(successful_authorize_response)
+
+ assert_success response
+ assert_equal '123456', response.authorization
+
+ void = stub_comms do
+ @gateway.void(response.authorization)
+ end.check_request do |endpoint, data, headers|
+ assert_match(/123456/, data)
+ end.respond_with(successful_void_response)
+
+ assert_success void
+ end
+
+ def test_failed_void
+ response = stub_comms do
+ @gateway.void('5d53a33d960c46d00f5dc061947d998c')
+ end.check_request do |endpoint, data, headers|
+ assert_match(/5d53a33d960c46d00f5dc061947d998c/, data)
+ end.respond_with(failed_void_response)
+
+ assert_failure response
+ end
+
+ def test_successful_refund
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(successful_purchase_response)
+
+ assert_success response
+ assert_equal '123456', response.authorization
+
+ refund = stub_comms do
+ @gateway.refund(@amount, response.authorization)
+ end.check_request do |endpoint, data, headers|
+ assert_match(/123456/, data)
+ end.respond_with(successful_refund_response)
+
+ assert_success refund
+ end
+
+ def test_failed_refund
+ response = stub_comms do
+ @gateway.refund(nil, '')
+ end.respond_with(failed_refund_response)
+
+ assert_failure response
+ end
+
+ def test_successful_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card)
+ end.respond_with(successful_authorize_response, failed_void_response)
+ assert_success response
+ assert_equal 'Succeeded', response.message
+ end
+
+ def test_failed_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card)
+ end.respond_with(failed_authorize_response, successful_void_response)
+ assert_failure response
+ assert_equal 'Declined', response.message
+ end
+
+ def test_empty_response_fails
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(empty_purchase_response)
+
+ assert_failure response
+ assert_equal 'Error', response.message
+ end
+
+ def test_invalid_json
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(invalid_json_response)
+
+ assert_failure response
+ assert_match %r{Unparsable response}, response.message
+ end
+
+ def test_transcript_scrubbing
+ assert_equal scrubbed_transcript, @gateway.scrub(transcript)
+ end
+
+ def test_nil_cvv_transcript_scrubbing
+ assert_equal nil_cvv_scrubbed_transcript, @gateway.scrub(nil_cvv_transcript)
+ end
+
+ def test_empty_string_cvv_transcript_scrubbing
+ assert_equal empty_string_cvv_scrubbed_transcript, @gateway.scrub(empty_string_cvv_transcript)
+ end
+
+ private
+
+ def successful_purchase_response
+ %(
+ {
+ "id": "123456",
+ "message": "Success",
+ "state": "Sale",
+ "status": "Successful"
+ }
+ )
+ end
+
+ def failed_purchase_response
+ %(
+ {
+ "id": "123456",
+ "message": "Declined",
+ "state": "Sale",
+ "status": "Declined"
+ }
+ )
+ end
+
+ def successful_authorize_response
+ %(
+ {
+ "id": "123456",
+ "message": "Success",
+ "state": "Authorize",
+ "status": "Successful"
+ }
+ )
+ end
+
+ def failed_authorize_response
+ %(
+ {
+ "id": "123456",
+ "message": "Declined",
+ "state": "Authorize",
+ "status": "Declined"
+ }
+ )
+ end
+
+ def successful_capture_response
+ %(
+ {
+ "id": "123456",
+ "message": "Successful",
+ "state": "Capture",
+ "status": "Successful"
+ }
+ )
+ end
+
+ def failed_capture_response
+ %(
+ {
+ "id": "123456",
+ "message": "Declined",
+ "state": "Capture",
+ "status": "Declined"
+ }
+ )
+ end
+
+ def successful_void_response
+ %(
+ {
+ "id": "123456",
+ "message": "Success",
+ "state": "Void",
+ "status": "Successful"
+ }
+ )
+ end
+
+ def failed_void_response
+ %(
+ {
+ "id": "123456",
+ "message": "Error",
+ "state": "Void",
+ "status": "Error"
+ }
+ )
+ end
+
+ def successful_refund_response
+ %(
+ {
+ "id": "123456",
+ "message": "Success",
+ "state": "Refund",
+ "status": "Successful"
+ }
+ )
+ end
+
+ def failed_refund_response
+ %(
+ {
+ "id": "123456",
+ "message": "Error",
+ "state": "Refund",
+ "status": "Error"
+ }
+ )
+ end
+
+ def empty_purchase_response
+ %(
+ {
+ "id": "123456",
+ "message": "Error",
+ "state": "Purchase",
+ "status": "Error"
+ }
+ )
+ end
+
+ def invalid_json_response
+ %(
+ {
+ "id": "123456",
+ )
+ end
+
+ def transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":\"123\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+
+ def scrubbed_transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":\"[FILTERED]\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+
+ def nil_cvv_transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":null,\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+
+ def nil_cvv_scrubbed_transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":[BLANK],\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+
+ def empty_string_cvv_transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":\"\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+
+ def empty_string_cvv_scrubbed_transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":\"[BLANK]\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+
+ def whitespace_string_cvv_transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer AAEAAHwXaLTYs2APKJW_4TpTDwCw_h9oDx9rA58FR78AFuYCX82Izes1nz9qGBXELGUN_EukKcP5T78Th5guDz4Rw5dQ4Gf0suKw7pz9vWrqa1NpZhrD9Lj9T-SFtOJgfodiwVaBBeSgbJLKA7MOzC9q2dv91HBNP69DygL1oX2L2mtt8fWlKSWhQmtG040E1I43jTueX3L3L9YA7iO6pIwO7CGybE5LnjkQ65KB2K4oYKfXRZosF77hgMJIh-KprFy9cYY3EjfupHeLon9im1BGafrda2N5wj_A_LvdMzfLAD1l1dgj82KlvM_gAzNJ4S19gAicRo9zIbsq36Apt-8jFjS0AQAAAAEAAA9Zr_lVLKMmmtKSo6T_9ulzMCRbYs798EpFD2wMlkb1NCQtA65VrNcM20Ka2FjNQfwOcSMWqDl9zFQhPyFl-npsG1Ww2oyyavA6HSe1HLRLtE_1hNBAlTBPQnLJ6hBf8eR_NTiVa-aQdV2l92-eSwCS59CzrOYGGCY1pLdNMDr_r66kg9l-l94154kRoMBRQSCqZV9iM9M-f3adLJqG6Q79zz1oJpGrH-Zv1kuv8eLaJJNOEFYARb0JbnAC5G1l9-aqxGvBrNkd4sAJIe23XrRx2XJCBIABxuGSQ1xJBTINVlXBXq1mvvd8B1uiYiDNia3c_vIGuSGIjZE0VbUN3oJppfCt1joGdePeUaC2Pyb2vuUN00EBEOaD9RF8IBWMLVJaF9cW2OewDOfBQg94MuOKLdXB_IisRx1ed25VQDVyv0f0CxmkAidvoDN0vvRIJZJr-bgBuL5FZM7gETAeYeiGlh7-Mf2Hzgy7236YNxcC9OnWFEcKEU50nlqog1bJnk8wJgoJWNqG0NUEK4DUzYqknmZ98qQv6rYrg5V-Hey-jAQp_KNf3h-vFHVZdP26Yg\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"4242424242424242\",\"cVVCode\":\" \",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+
+ def whitespace_string_cvv_scrubbed_transcript
+ %(
+ <- "POST /merchants/10090/SALEtransactions HTTP/1.1\r\nContent-Type: application/json\r\nAuthorization: Bearer [FILTERED]\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: api.alliedwallet.com\r\nContent-Length: 464\r\n\r\n"
+ <- "{\"siteId\":\"10118\",\"amount\":\"1.00\",\"trackingId\":\"82b5f6217fa19daa426e226a231d330a\",\"currency\":\"USD\",\"nameOnCard\":\"Longbob Longsen\",\"cardNumber\":\"[FILTERED]\",\"cVVCode\":\"[BLANK]\",\"expirationYear\":\"2016\",\"expirationMonth\":\"09\",\"email\":\"jim_smith@example.com\",\"iPAddress\":\"127.0.0.1\",\"firstName\":\"Jim\",\"lastName\":\"Smith\",\"addressLine1\":\"456 My Street\",\"addressLine2\":\"Apt 1\",\"city\":\"Ottawa\",\"state\":\"ON\",\"countryId\":\"CA\",\"postalCode\":\"K1C2N6\",\"phone\":\"(555)555-5555\"}"
+ )
+ end
+end
diff --git a/test/unit/gateways/authorize_net_arb_test.rb b/test/unit/gateways/authorize_net_arb_test.rb
new file mode 100644
index 00000000000..5c18ff468ff
--- /dev/null
+++ b/test/unit/gateways/authorize_net_arb_test.rb
@@ -0,0 +1,141 @@
+require 'test_helper'
+
+class AuthorizeNetArbTest < Test::Unit::TestCase
+ include CommStub
+
+ def setup
+ ActiveMerchant.expects(:deprecated).with('ARB functionality in ActiveMerchant is deprecated and will be removed in a future version. Please contact the ActiveMerchant maintainers if you have an interest in taking ownership of a separate gem that continues support for it.')
+ @gateway = AuthorizeNetArbGateway.new(
+ :login => 'X',
+ :password => 'Y'
+ )
+ @amount = 100
+ @credit_card = credit_card
+ @subscription_id = '100748'
+ @subscription_status = 'active'
+ end
+
+ def test_successful_recurring
+ @gateway.expects(:ssl_post).returns(successful_recurring_response)
+
+ response = @gateway.recurring(@amount, @credit_card,
+ :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'),
+ :interval => {
+ :length => 10,
+ :unit => :days
+ },
+ :duration => {
+ :start_date => Time.now.strftime('%Y-%m-%d'),
+ :occurrences => 30
+ }
+ )
+
+ assert_instance_of Response, response
+ assert response.success?
+ assert response.test?
+ assert_equal @subscription_id, response.authorization
+ end
+
+ def test_successful_update_recurring
+ @gateway.expects(:ssl_post).returns(successful_update_recurring_response)
+
+ response = @gateway.update_recurring(:subscription_id => @subscription_id, :amount => @amount * 2)
+
+ assert_instance_of Response, response
+ assert response.success?
+ assert response.test?
+ assert_equal @subscription_id, response.authorization
+ end
+
+ def test_successful_cancel_recurring
+ @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response)
+
+ response = @gateway.cancel_recurring(@subscription_id)
+
+ assert_instance_of Response, response
+ assert response.success?
+ assert response.test?
+ assert_equal @subscription_id, response.authorization
+ end
+
+ def test_successful_status_recurring
+ @gateway.expects(:ssl_post).returns(successful_status_recurring_response)
+
+ response = @gateway.status_recurring(@subscription_id)
+ assert_instance_of Response, response
+ assert response.success?
+ assert response.test?
+ assert_equal @subscription_status, response.params['status']
+ end
+
+ def test_expdate_formatting
+ assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009'))
+ assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => '11', :year => '2013'))
+ end
+
+ private
+
+ def successful_recurring_response
+ <<-XML
+
+ Sample
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ #{@subscription_id}
+
+ XML
+ end
+
+ def successful_update_recurring_response
+ <<-XML
+
+ Sample
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ #{@subscription_id}
+
+ XML
+ end
+
+ def successful_cancel_recurring_response
+ <<-XML
+
+ Sample
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ #{@subscription_id}
+
+ XML
+ end
+
+ def successful_status_recurring_response
+ <<-XML
+
+ Sample
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ #{@subscription_status}
+
+ XML
+ end
+end
diff --git a/test/unit/gateways/authorize_net_cim_test.rb b/test/unit/gateways/authorize_net_cim_test.rb
index e138e383824..1d9bd462192 100644
--- a/test/unit/gateways/authorize_net_cim_test.rb
+++ b/test/unit/gateways/authorize_net_cim_test.rb
@@ -1,6 +1,8 @@
require 'test_helper'
class AuthorizeNetCimTest < Test::Unit::TestCase
+ include CommStub
+
def setup
@gateway = AuthorizeNetCimGateway.new(
:login => 'X',
@@ -44,8 +46,8 @@ def setup
end
def test_expdate_formatting
- assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => "9", :year => "2009"))
- assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => "11", :year => "2013"))
+ assert_equal '2009-09', @gateway.send(:expdate, credit_card('4111111111111111', :month => '9', :year => '2009'))
+ assert_equal '2013-11', @gateway.send(:expdate, credit_card('4111111111111111', :month => '11', :year => '2013'))
assert_equal 'XXXX', @gateway.send(:expdate, credit_card('XXXX1234', :month => nil, :year => nil))
end
@@ -56,7 +58,7 @@ def test_should_create_customer_profile_request
assert_instance_of Response, response
assert_success response
assert_equal @customer_profile_id, response.authorization
- assert_equal "Successful.", response.message
+ assert_equal 'Successful.', response.message
end
def test_should_create_customer_payment_profile_request
@@ -74,7 +76,7 @@ def test_should_create_customer_payment_profile_request
assert_instance_of Response, response
assert_success response
assert_equal @customer_payment_profile_id, response.params['customer_payment_profile_id']
- assert_equal "This output is only present if the ValidationMode input parameter is passed with a value of testMode or liveMode", response.params['validation_direct_response']
+ assert_equal 'This output is only present if the ValidationMode input parameter is passed with a value of testMode or liveMode', response.params['validation_direct_response']
end
def test_should_create_customer_shipping_address_request
@@ -260,7 +262,7 @@ def test_should_create_customer_profile_transaction_auth_capture_request_for_ver
assert_equal response.authorization, response.params['direct_response']['transaction_id']
assert_equal 'This transaction has been approved.', response.params['direct_response']['message']
assert_equal 'auth_capture', response.params['direct_response']['transaction_type']
- assert_equal 'CSYM0K', approval_code = response.params['direct_response']['approval_code']
+ assert_equal 'CSYM0K', response.params['direct_response']['approval_code']
assert_equal '2163585627', response.params['direct_response']['transaction_id']
assert_equal '1', response.params['direct_response']['response_code']
@@ -366,12 +368,14 @@ def test_should_get_customer_payment_profile_request
assert response = @gateway.get_customer_payment_profile(
:customer_profile_id => @customer_profile_id,
- :customer_payment_profile_id => @customer_payment_profile_id
+ :customer_payment_profile_id => @customer_payment_profile_id,
+ :unmask_expiration_date => true
)
assert_instance_of Response, response
assert_success response
assert_nil response.authorization
assert_equal @customer_payment_profile_id, response.params['profile']['payment_profiles']['customer_payment_profile_id']
+ assert_equal formatted_expiration_date(@credit_card), response.params['profile']['payment_profiles']['payment']['credit_card']['expiration_date']
end
def test_should_get_customer_shipping_address_request
@@ -415,6 +419,30 @@ def test_should_update_customer_payment_profile_request
assert_nil response.authorization
end
+ def test_should_update_customer_payment_profile_request_with_last_four_digits
+ last_four_credit_card = ActiveMerchant::Billing::CreditCard.new(:number => '4242') # Credit card with only last four digits
+
+ response = stub_comms do
+ @gateway.update_customer_payment_profile(
+ :customer_profile_id => @customer_profile_id,
+ :payment_profile => {
+ :customer_payment_profile_id => @customer_payment_profile_id,
+ :bill_to => address(:address1 => '345 Avenue B',
+ :address2 => 'Apt 101'),
+ :payment => {
+ :credit_card => last_four_credit_card
+ }
+ }
+ )
+ end.check_request do |endpoint, data, headers|
+ assert_match %r{XXXX4242}, data
+ end.respond_with(successful_update_customer_payment_profile_response)
+
+ assert_instance_of Response, response
+ assert_success response
+ assert_nil response.authorization
+ end
+
def test_should_update_customer_shipping_address_request
@gateway.expects(:ssl_post).returns(successful_update_customer_shipping_address_response)
@@ -481,6 +509,8 @@ def test_should_create_customer_profile_transaction_auth_capture_and_then_refund
# http://www.modernbill.com/support/manual/old/v4/adminhelp/english/Configuration/Payment_Settings/Gateway_API/AuthorizeNet/Module_Authorize.net.htm
assert_failure response
assert_equal 'The referenced transaction does not meet the criteria for issuing a credit.', response.params['direct_response']['message']
+ assert_equal 'The transaction was unsuccessful.', response.message
+ assert_equal 'E00027', response.error_code
return response
end
@@ -545,8 +575,8 @@ def test_should_create_customer_profile_transaction_for_refund_request
assert response = @gateway.create_customer_profile_transaction_for_refund(
:transaction => {
:trans_id => 1,
- :amount => "1.00",
- :credit_card_number_masked => "XXXX1234"
+ :amount => '1.00',
+ :credit_card_number_masked => 'XXXX1234'
}
)
assert_instance_of Response, response
@@ -555,6 +585,57 @@ def test_should_create_customer_profile_transaction_for_refund_request
assert_equal 'This transaction has been approved.', response.params['direct_response']['message']
end
+ def test_should_create_customer_profile_transaction_passing_recurring_flag
+ response = stub_comms do
+ @gateway.create_customer_profile_transaction(
+ :transaction => {
+ :customer_profile_id => @customer_profile_id,
+ :customer_payment_profile_id => @customer_payment_profile_id,
+ :type => :auth_capture,
+ :order => {
+ :invoice_number => '1234',
+ :description => 'Test Order Description',
+ :purchase_order_number => '4321'
+ },
+ :amount => @amount,
+ :card_code => '123',
+ :recurring_billing => true
+ }
+ )
+ end.check_request do |endpoint, data, headers|
+ assert_match %r{true}, data
+ end.respond_with(successful_create_customer_profile_transaction_response(:auth_capture))
+
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal 'M', response.params['direct_response']['card_code'] # M => match
+ assert_equal response.authorization, response.params['direct_response']['transaction_id']
+ assert_equal 'This transaction has been approved.', response.params['direct_response']['message']
+ end
+
+ def test_full_or_masked_card_number
+ assert_equal nil, @gateway.send(:full_or_masked_card_number, nil)
+ assert_equal '', @gateway.send(:full_or_masked_card_number, '')
+ assert_equal '4242424242424242', @gateway.send(:full_or_masked_card_number, @credit_card.number)
+ assert_equal 'XXXX1234', @gateway.send(:full_or_masked_card_number, '1234')
+ end
+
+ def test_multiple_errors_when_creating_customer_profile
+ @gateway.expects(:ssl_post).returns(unsuccessful_create_customer_profile_transaction_response_with_multiple_errors(:refund))
+ assert response = @gateway.create_customer_profile_transaction(
+ :transaction => {
+ :type => :refund,
+ :amount => 1,
+
+ :customer_profile_id => @customer_profile_id,
+ :customer_payment_profile_id => @customer_payment_profile_id,
+ :trans_id => 1
+ }
+ )
+ assert_equal 'The transaction was unsuccessful.', response.message
+ assert_equal 'E00027', response.error_code
+ end
+
private
def get_auth_only_response
@@ -573,7 +654,7 @@ def get_auth_only_response
assert_nil response.authorization
assert_equal 'This transaction has been approved.', response.params['direct_response']['message']
assert_equal 'auth_only', response.params['direct_response']['transaction_type']
- assert_equal 'Gw4NGI', approval_code = response.params['direct_response']['approval_code']
+ assert_equal 'Gw4NGI', response.params['direct_response']['approval_code']
return response
end
@@ -993,4 +1074,26 @@ def unsuccessful_create_customer_profile_transaction_response(transaction_type)
XML
end
+ def unsuccessful_create_customer_profile_transaction_response_with_multiple_errors(transaction_type)
+ <<-XML
+
+
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+ E00001
+ An error occurred during processing. Please try again.
+
+
+ #{UNSUCCESSUL_DIRECT_RESPONSE[transaction_type]}
+
+ XML
+ end
end
diff --git a/test/unit/gateways/authorize_net_test.rb b/test/unit/gateways/authorize_net_test.rb
index db70bb2abfe..9c444ad6b3a 100644
--- a/test/unit/gateways/authorize_net_test.rb
+++ b/test/unit/gateways/authorize_net_test.rb
@@ -3,213 +3,906 @@
class AuthorizeNetTest < Test::Unit::TestCase
include CommStub
+ BAD_TRACK_DATA = '%B378282246310005LONGSONLONGBOB1705101130504392?'
+ TRACK1_DATA = '%B378282246310005^LONGSON/LONGBOB^1705101130504392?'
+ TRACK2_DATA = ';4111111111111111=1803101000020000831?'
+
def setup
@gateway = AuthorizeNetGateway.new(
- :login => 'X',
- :password => 'Y'
+ login: 'X',
+ password: 'Y'
)
+
@amount = 100
@credit_card = credit_card
- @subscription_id = '100748'
- @subscription_status = 'active'
@check = check
+ @apple_pay_payment_token = ActiveMerchant::Billing::ApplePayPaymentToken.new(
+ {data: 'encoded_payment_data'},
+ payment_instrument_name: 'SomeBank Visa',
+ payment_network: 'Visa',
+ transaction_identifier: 'transaction123'
+ )
+
+ @options = {
+ order_id: '1',
+ billing_address: address,
+ description: 'Store Purchase'
+ }
+
+ @level_3_options = {
+ ship_from_address: {
+ zip: 'origin27701',
+ country: 'originUS'
+ },
+ summary_commodity_code: 'CODE'
+ }
+
+ @additional_options = {
+ line_items: [
+ {
+ item_id: '1',
+ name: 'mug',
+ description: 'coffee',
+ quantity: '100',
+ unit_price: '10'
+ },
+ {
+ item_id: '2',
+ name: 'vase',
+ description: 'floral',
+ quantity: '200',
+ unit_price: '20'
+ }
+ ]
+ }
+
+ @level_3_line_item_options = {
+ line_items: [
+ {
+ item_id: '1',
+ name: 'mug',
+ description: 'coffee',
+ quantity: '100',
+ unit_price: '10',
+ unit_of_measure: 'yards',
+ total_amount: '1000',
+ product_code: 'coupon'
+ }
+ ]
+ }
+ end
+
+ def test_add_swipe_data_with_bad_data
+ @credit_card.track_data = BAD_TRACK_DATA
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_nil doc.at_xpath('//track1')
+ assert_nil doc.at_xpath('//track2')
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_add_swipe_data_with_track_1
+ @credit_card.track_data = TRACK1_DATA
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal '%B378282246310005^LONGSON/LONGBOB^1705101130504392?', doc.at_xpath('//track1').content
+ assert_nil doc.at_xpath('//track2')
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_add_swipe_data_with_track_2
+ @credit_card.track_data = TRACK2_DATA
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_nil doc.at_xpath('//track1')
+ assert_equal ';4111111111111111=1803101000020000831?', doc.at_xpath('//track2').content
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_retail_market_type_device_type_included_in_swipe_transactions_with_valid_track_data
+ [BAD_TRACK_DATA, nil].each do |track|
+ @credit_card.track_data = track
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_nil doc.at_xpath('//retail')
+ end
+ end.respond_with(successful_purchase_response)
+ end
+
+ [TRACK1_DATA, TRACK2_DATA].each do |track|
+ @credit_card.track_data = track
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//retail')
+ assert_equal '2', doc.at_xpath('//retail/marketType').content
+ assert_equal '7', doc.at_xpath('//retail/deviceType').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+ end
+
+ def test_device_type_used_from_options_if_included_with_valid_track_data
+ [TRACK1_DATA, TRACK2_DATA].each do |track|
+ @credit_card.track_data = track
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, {device_type: 1})
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//retail')
+ assert_equal '2', doc.at_xpath('//retail/marketType').content
+ assert_equal '1', doc.at_xpath('//retail/deviceType').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+ end
+
+ def test_market_type_not_included_for_apple_pay_or_echeck
+ [@check, @apple_pay_payment_token].each do |payment|
+ stub_comms do
+ @gateway.purchase(@amount, payment)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_nil doc.at_xpath('//retail')
+ end
+ end.respond_with(successful_purchase_response)
+ end
+ end
+
+ def test_moto_market_type_included_when_card_is_entered_manually
+ @credit_card.manual_entry = true
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//retail')
+ assert_equal '1', doc.at_xpath('//retail/marketType').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_market_type_can_be_specified
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, market_type: 0)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal '0', doc.at_xpath('//retail/marketType').content
+ end
+ end.respond_with(successful_purchase_response)
end
def test_successful_echeck_authorization
response = stub_comms do
@gateway.authorize(@amount, @check)
end.check_request do |endpoint, data, headers|
- assert_match(/x_method=ECHECK/, data)
- assert_match(/x_bank_aba_code=244183602/, data)
- assert_match(/x_bank_acct_num=15378535/, data)
- assert_match(/x_bank_name=Bank\+of\+Elbonia/, data)
- assert_match(/x_bank_acct_name=Jim\+Smith/, data)
- assert_match(/x_echeck_type=WEB/, data)
- assert_match(/x_bank_check_number=1/, data)
- assert_match(/x_recurring_billing=FALSE/, data)
- end.respond_with(successful_authorization_response)
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//payment/bankAccount')
+ assert_equal '244183602', doc.at_xpath('//routingNumber').content
+ assert_equal '15378535', doc.at_xpath('//accountNumber').content
+ assert_equal 'Bank of Elbonia', doc.at_xpath('//bankName').content
+ assert_equal 'Jim Smith', doc.at_xpath('//nameOnAccount').content
+ assert_equal '1', doc.at_xpath('//checkNumber').content
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_authorize_response)
assert response
assert_instance_of Response, response
assert_success response
- assert_equal '508141794', response.authorization
+ assert_equal '508141794', response.authorization.split('#')[0]
end
def test_successful_echeck_purchase
response = stub_comms do
@gateway.purchase(@amount, @check)
end.check_request do |endpoint, data, headers|
- assert_match(/x_method=ECHECK/, data)
- assert_match(/x_bank_aba_code=244183602/, data)
- assert_match(/x_bank_acct_num=15378535/, data)
- assert_match(/x_bank_name=Bank\+of\+Elbonia/, data)
- assert_match(/x_bank_acct_name=Jim\+Smith/, data)
- assert_match(/x_echeck_type=WEB/, data)
- assert_match(/x_bank_check_number=1/, data)
- assert_match(/x_recurring_billing=FALSE/, data)
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//payment/bankAccount')
+ assert_equal '244183602', doc.at_xpath('//routingNumber').content
+ assert_equal '15378535', doc.at_xpath('//accountNumber').content
+ assert_equal 'Bank of Elbonia', doc.at_xpath('//bankName').content
+ assert_equal 'Jim Smith', doc.at_xpath('//nameOnAccount').content
+ assert_equal '1', doc.at_xpath('//checkNumber').content
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
end.respond_with(successful_purchase_response)
assert response
assert_instance_of Response, response
assert_success response
- assert_equal '508141795', response.authorization
+ assert_equal '508141795', response.authorization.split('#')[0]
end
def test_echeck_passing_recurring_flag
response = stub_comms do
- @gateway.purchase(@amount, @check, :recurring => true)
+ @gateway.purchase(@amount, @check, recurring: true)
end.check_request do |endpoint, data, headers|
- assert_match(/x_recurring_billing=TRUE/, data)
+ assert_equal settings_from_doc(parse(data))['recurringBilling'], 'true'
end.respond_with(successful_purchase_response)
assert_success response
end
def test_failed_echeck_authorization
- @gateway.expects(:ssl_post).returns(failed_authorization_response)
+ @gateway.expects(:ssl_post).returns(failed_authorize_response)
- assert response = @gateway.authorize(@amount, @check)
- assert_instance_of Response, response
+ response = @gateway.authorize(@amount, @check)
assert_failure response
- assert_equal '508141794', response.authorization
end
- def test_successful_authorization
- @gateway.expects(:ssl_post).returns(successful_authorization_response)
+ def test_successful_apple_pay_authorization
+ response = stub_comms do
+ @gateway.authorize(@amount, @apple_pay_payment_token)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal @gateway.class::APPLE_PAY_DATA_DESCRIPTOR, doc.at_xpath('//opaqueData/dataDescriptor').content
+ assert_equal Base64.strict_encode64(@apple_pay_payment_token.payment_data.to_json), doc.at_xpath('//opaqueData/dataValue').content
+ end
+ end.respond_with(successful_authorize_response)
+
+ assert response
+ assert_instance_of Response, response
+ assert_success response
+ assert_equal '508141794', response.authorization.split('#')[0]
+ end
- assert response = @gateway.authorize(@amount, @credit_card)
+ def test_successful_apple_pay_purchase
+ response = stub_comms do
+ @gateway.purchase(@amount, @apple_pay_payment_token)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal @gateway.class::APPLE_PAY_DATA_DESCRIPTOR, doc.at_xpath('//opaqueData/dataDescriptor').content
+ assert_equal Base64.strict_encode64(@apple_pay_payment_token.payment_data.to_json), doc.at_xpath('//opaqueData/dataValue').content
+ end
+ end.respond_with(successful_purchase_response)
+
+ assert response
assert_instance_of Response, response
assert_success response
- assert_equal '508141794', response.authorization
+ assert_equal '508141795', response.authorization.split('#')[0]
+ end
+
+ def test_successful_authorization
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+
+ response = @gateway.authorize(@amount, @credit_card)
+ assert_success response
+
+ assert_equal 'M', response.cvv_result['code']
+ assert_equal 'CVV matches', response.cvv_result['message']
+ assert_equal 'I00001', response.params['full_response_code']
+
+ assert_equal '508141794', response.authorization.split('#')[0]
+ assert response.test?
end
def test_successful_purchase
@gateway.expects(:ssl_post).returns(successful_purchase_response)
- assert response = @gateway.purchase(@amount, @credit_card)
- assert_instance_of Response, response
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_success response
+
+ assert_equal '508141795', response.authorization.split('#')[0]
+ assert response.test?
+ assert_equal 'Y', response.avs_result['code']
+ assert response.avs_result['street_match']
+ assert response.avs_result['postal_match']
+ assert_equal 'Street address and 5-digit postal code match.', response.avs_result['message']
+ assert_equal 'P', response.cvv_result['code']
+ assert_equal 'CVV not processed', response.cvv_result['message']
+ end
+
+ def test_successful_purchase_with_utf_character
+ stub_comms do
+ @gateway.purchase(@amount, credit_card('4000100011112224', last_name: 'Wåhlin'))
+ end.check_request do |endpoint, data, headers|
+ assert_match(/Wåhlin/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_passes_partial_auth
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, disable_partial_auth: true)
+ end.check_request do |endpoint, data, headers|
+ assert_match(/allowPartialAuth<\/settingName>/, data)
+ assert_match(/false<\/settingValue>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_passes_email_customer
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, email_customer: true)
+ end.check_request do |endpoint, data, headers|
+ assert_match(/emailCustomer<\/settingName>/, data)
+ assert_match(/true<\/settingValue>/, data)
+ end.respond_with(successful_purchase_response)
+
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, email_customer: false)
+ end.check_request do |endpoint, data, headers|
+ assert_match(/emailCustomer<\/settingName>/, data)
+ assert_match(/false<\/settingValue>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_passes_header_email_receipt
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, header_email_receipt: 'yet another field')
+ end.check_request do |endpoint, data, headers|
+ assert_match(/headerEmailReceipt<\/settingName>/, data)
+ assert_match(/yet another field<\/settingValue>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_passes_level_3_options
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, @options.merge(@level_3_options))
+ end.check_request do |endpoint, data, headers|
+ assert_match(//, data)
+ assert_match(/#{@level_3_options[:summary_commodity_code]}<\/summaryCommodityCode>/, data)
+ assert_match(/<\/order>/, data)
+ assert_match(//, data)
+ assert_match(/#{@level_3_options[:ship_from_address][:zip]}<\/zip>/, data)
+ assert_match(/#{@level_3_options[:ship_from_address][:country]}<\/country>/, data)
+ assert_match(/<\/shipFrom>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_passes_line_items
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, @options.merge(@additional_options))
+ end.check_request do |endpoint, data, headers|
+ assert_match(//, data)
+ assert_match(//, data)
+ assert_match(/#{@additional_options[:line_items][0][:item_id]}<\/itemId>/, data)
+ assert_match(/#{@additional_options[:line_items][0][:name]}<\/name>/, data)
+ assert_match(/#{@additional_options[:line_items][0][:description]}<\/description>/, data)
+ assert_match(/#{@additional_options[:line_items][0][:quantity]}<\/quantity>/, data)
+ assert_match(/#{@additional_options[:line_items][0][:unit_price]}<\/unitPrice>/, data)
+ assert_match(/<\/lineItem>/, data)
+ assert_match(/#{@additional_options[:line_items][1][:item_id]}<\/itemId>/, data)
+ assert_match(/#{@additional_options[:line_items][1][:name]}<\/name>/, data)
+ assert_match(/#{@additional_options[:line_items][1][:description]}<\/description>/, data)
+ assert_match(/#{@additional_options[:line_items][1][:quantity]}<\/quantity>/, data)
+ assert_match(/#{@additional_options[:line_items][1][:unit_price]}<\/unitPrice>/, data)
+ assert_match(/<\/lineItems>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_passes_level_3_line_items
+ stub_comms do
+ @gateway.purchase(@amount, credit_card, @options.merge(@level_3_line_item_options))
+ end.check_request do |endpoint, data, headers|
+ assert_match(//, data)
+ assert_match(//, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:item_id]}<\/itemId>/, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:name]}<\/name>/, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:description]}<\/description>/, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:quantity]}<\/quantity>/, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:unit_price]}<\/unitPrice>/, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:unit_of_measure]}<\/unitOfMeasure>/, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:total_amount]}<\/totalAmount>/, data)
+ assert_match(/#{@level_3_line_item_options[:line_items][0][:product_code]}<\/productCode>/, data)
+ assert_match(/<\/lineItems>/, data)
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_failed_purchase
+ @gateway.expects(:ssl_post).returns(failed_purchase_response)
+
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'incorrect_number', response.error_code
+ end
+
+ def test_live_gateway_cannot_use_test_mode_on_auth_dot_net_server
+ test_gateway = AuthorizeNetGateway.new(
+ login: 'X',
+ password: 'Y',
+ test: true
+ )
+ test_gateway.stubs(:ssl_post).returns(successful_purchase_response_test_mode)
+
+ response = test_gateway.purchase(@amount, @credit_card)
+ assert_success response
+
+ real_gateway = AuthorizeNetGateway.new(
+ login: 'X',
+ password: 'Y',
+ test: false
+ )
+ real_gateway.stubs(:ssl_post).returns(successful_purchase_response_test_mode)
+ response = real_gateway.purchase(@amount, @credit_card)
+ assert_failure response
+ assert_equal 'Using a live Authorize.net account in Test Mode is not permitted.', response.message
+ end
+
+ def test_successful_purchase_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response)
+
+ response = @gateway.purchase(@amount, store.authorization)
+ assert_success response
+
+ assert_equal '2235700270#XXXX2224#cim_purchase', response.authorization
+ assert_equal 'Y', response.avs_result['code']
+ assert response.avs_result['street_match']
+ assert response.avs_result['postal_match']
+ assert_equal 'Street address and 5-digit postal code match.', response.avs_result['message']
+ end
+
+ def test_successful_purchase_using_stored_card_and_custom_delimiter
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response_with_pipe_delimiter)
+
+ response = @gateway.purchase(@amount, store.authorization, {delimiter: '|', description: 'description, with, commas'})
assert_success response
- assert_equal '508141795', response.authorization
+
+ assert_equal '2235700270#XXXX2224#cim_purchase', response.authorization
+ assert_equal 'Y', response.avs_result['code']
+ assert response.avs_result['street_match']
+ assert response.avs_result['postal_match']
+ assert_equal 'Street address and 5-digit postal code match.', response.avs_result['message']
+ assert_equal 'description, with, commas', response.params['order_description']
+ end
+
+ def test_failed_purchase_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ @gateway.expects(:ssl_post).returns(failed_purchase_using_stored_card_response)
+
+ response = @gateway.purchase(@amount, store.authorization)
+ assert_failure response
+ assert_equal 'The credit card number is invalid.', response.message
+ assert_equal '6', response.params['response_reason_code']
end
- def test_failed_authorization
- @gateway.expects(:ssl_post).returns(failed_authorization_response)
+ def test_failed_authorize
+ @gateway.expects(:ssl_post).returns(failed_authorize_response)
- assert response = @gateway.authorize(@amount, @credit_card)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_failure response
+ assert_equal 'incorrect_number', response.error_code
+ end
+
+ def test_successful_authorize_and_capture_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+
+ @gateway.expects(:ssl_post).returns(successful_authorize_using_stored_card_response)
+ auth = @gateway.authorize(@amount, store.authorization)
+ assert_success auth
+ assert_equal 'This transaction has been approved.', auth.message
+
+ @gateway.expects(:ssl_post).returns(successful_capture_using_stored_card_response)
+
+ capture = @gateway.capture(@amount, auth.authorization)
+ assert_success capture
+ assert_equal 'This transaction has been approved.', capture.message
+ end
+
+ def test_failed_authorize_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+
+ @gateway.expects(:ssl_post).returns(failed_authorize_using_stored_card_response)
+ response = @gateway.authorize(@amount, store.authorization)
+ assert_failure response
+ assert_equal 'The credit card number is invalid.', response.message
+ assert_equal '6', response.params['response_reason_code']
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_post).returns(successful_capture_response)
+
+ capture = @gateway.capture(@amount, '2214269051#XXXX1234', @options)
+ assert_success capture
+ assert_equal nil, capture.error_code
+ end
+
+ def test_failed_capture
+ @gateway.expects(:ssl_post).returns(failed_capture_response)
+
+ assert capture = @gateway.capture(@amount, '2214269051#XXXX1234')
+ assert_failure capture
+ end
+
+ def test_failed_already_actioned_capture
+ @gateway.expects(:ssl_post).returns(already_actioned_capture_response)
+
+ response = @gateway.capture(50, '123456789')
assert_instance_of Response, response
assert_failure response
- assert_equal '508141794', response.authorization
end
- def test_add_address_outsite_north_america
- result = {}
+ def test_failed_capture_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
- @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'DE', :state => ''} )
+ @gateway.expects(:ssl_post).returns(successful_authorize_using_stored_card_response)
+ auth = @gateway.authorize(@amount, store.authorization)
- assert_equal ["address", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort
- assert_equal 'n/a', result[:state]
- assert_equal '164 Waverley Street', result[:address]
- assert_equal 'DE', result[:country]
+ @gateway.expects(:ssl_post).returns(failed_capture_using_stored_card_response)
+ capture = @gateway.capture(@amount, auth.authorization)
+ assert_failure capture
+ assert_match(/The amount requested for settlement cannot be greater/, capture.message)
end
- def test_add_address
- result = {}
+ def test_successful_void
+ @gateway.expects(:ssl_post).returns(successful_void_response)
- @gateway.send(:add_address, result, :billing_address => {:address1 => '164 Waverley Street', :country => 'US', :state => 'CO'} )
+ assert void = @gateway.void('')
+ assert_success void
+ end
- assert_equal ["address", "city", "company", "country", "phone", "state", "zip"], result.stringify_keys.keys.sort
- assert_equal 'CO', result[:state]
- assert_equal '164 Waverley Street', result[:address]
- assert_equal 'US', result[:country]
+ def test_failed_void
+ @gateway.expects(:ssl_post).returns(failed_void_response)
+ response = @gateway.void('')
+ assert_failure response
end
- def test_add_invoice
- result = {}
- @gateway.send(:add_invoice, result, :order_id => '#1001')
- assert_equal '#1001', result[:invoice_num]
+ def test_successful_void_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+
+ @gateway.expects(:ssl_post).returns(successful_authorize_using_stored_card_response)
+ auth = @gateway.authorize(@amount, store.authorization)
+
+ @gateway.expects(:ssl_post).returns(successful_void_using_stored_card_response)
+ void = @gateway.void(auth.authorization)
+ assert_success void
+ assert_equal 'This transaction has been approved.', void.message
end
- def test_add_description
- result = {}
- @gateway.send(:add_invoice, result, :description => 'My Purchase is great')
- assert_equal 'My Purchase is great', result[:description]
+ def test_failed_void_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+
+ @gateway.expects(:ssl_post).returns(failed_authorize_using_stored_card_response)
+ auth = @gateway.authorize(@amount, store.authorization)
+
+ @gateway.expects(:ssl_post).returns(failed_void_using_stored_card_response)
+ void = @gateway.void(auth.authorization)
+ assert_failure void
+ assert_equal 'This transaction has already been voided.', void.message
end
- def test_add_duplicate_window_without_duplicate_window
- result = {}
- @gateway.class.duplicate_window = nil
- @gateway.send(:add_duplicate_window, result)
- assert_nil result[:duplicate_window]
+ def test_successful_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card)
+ end.respond_with(successful_authorize_response, successful_void_response)
+ assert_success response
end
- def test_add_duplicate_window_with_duplicate_window
- result = {}
- @gateway.class.duplicate_window = 0
- @gateway.send(:add_duplicate_window, result)
- assert_equal 0, result[:duplicate_window]
+ def test_successful_verify_failed_void
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(successful_authorize_response, failed_void_response)
+ assert_success response
+ assert_match %r{This transaction has been approved}, response.message
end
- def test_purchase_is_valid_csv
- params = { :amount => '1.01' }
+ def test_unsuccessful_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(failed_authorize_response, successful_void_response)
+ assert_failure response
+ assert_not_nil response.message
+ end
+
+ def test_failed_refund_using_stored_card
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+
+ @gateway.expects(:ssl_post).returns(successful_purchase_using_stored_card_response)
- @gateway.send(:add_creditcard, params, @credit_card)
+ purchase = @gateway.purchase(@amount, store.authorization)
+ assert_success purchase
- assert data = @gateway.send(:post_data, 'AUTH_ONLY', params)
- assert_equal post_data_fixture.size, data.size
+ @gateway.expects(:ssl_post).returns(failed_refund_using_stored_card_response)
+ refund = @gateway.refund(@amount, purchase.authorization)
+ assert_failure refund
+ assert_equal 'The record cannot be found', refund.message
end
- def test_purchase_meets_minimum_requirements
- params = {
- :amount => "1.01",
- }
+ def test_failed_refund_due_to_unsettled_payment
+ @gateway.expects(:ssl_post).returns(failed_refund_for_unsettled_payment_response)
+ @gateway.expects(:void).never
- @gateway.send(:add_creditcard, params, @credit_card)
+ @gateway.refund(36.40, '2214269051#XXXX1234')
+ end
- assert data = @gateway.send(:post_data, 'AUTH_ONLY', params)
- minimum_requirements.each do |key|
- assert_not_nil(data =~ /x_#{key}=/)
- end
+ def test_failed_full_refund_due_to_unsettled_payment_forces_void
+ @gateway.expects(:ssl_post).returns(failed_refund_for_unsettled_payment_response)
+ @gateway.expects(:void).once
+
+ @gateway.refund(36.40, '2214269051#XXXX1234', force_full_refund_if_unsettled: true)
+ end
+
+ def test_failed_full_refund_returns_failed_response_if_reason_code_is_not_unsettled_error
+ @gateway.expects(:ssl_post).returns(failed_refund_response)
+ @gateway.expects(:void).never
+
+ response = @gateway.refund(36.40, '2214269051#XXXX1234', force_full_refund_if_unsettled: true)
+ assert response.present?
+ assert_failure response
+ end
+
+ def test_successful_store
+ @gateway.expects(:ssl_post).returns(successful_store_response)
+
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert_equal 'Successful', store.message
+ assert_equal '35959426', store.params['customer_profile_id']
+ assert_equal '32506918', store.params['customer_payment_profile_id']
+ end
+
+ def test_failed_store
+ @gateway.expects(:ssl_post).returns(failed_store_response)
+
+ store = @gateway.store(@credit_card, @options)
+ assert_failure store
+ assert_match(/The field length is invalid/, store.message)
+ assert_equal('15', store.params['message_code'])
+ end
+
+ def test_successful_unstore
+ response = stub_comms do
+ @gateway.unstore('35959426#32506918#cim_store')
+ end.check_request do |endpoint, data, headers|
+ doc = parse(data)
+ assert_equal '35959426', doc.at_xpath('//deleteCustomerProfileRequest/customerProfileId').content
+ end.respond_with(successful_unstore_response)
+
+ assert_success response
+ assert_equal 'Successful', response.message
+ end
+
+ def test_failed_unstore
+ @gateway.expects(:ssl_post).returns(failed_unstore_response)
+
+ unstore = @gateway.unstore('35959426#32506918#cim_store')
+ assert_failure unstore
+ assert_match(/The record cannot be found/, unstore.message)
+ assert_equal('40', unstore.params['message_code'])
+ end
+
+ def test_successful_store_new_payment_profile
+ @gateway.expects(:ssl_post).returns(successful_store_new_payment_profile_response)
+
+ store = @gateway.store(@credit_card, @options)
+ assert_success store
+ assert_equal 'Successful', store.message
+ assert_equal '38392170', store.params['customer_profile_id']
+ assert_equal '34896759', store.params['customer_payment_profile_id']
+ end
+
+ def test_failed_store_new_payment_profile
+ @gateway.expects(:ssl_post).returns(failed_store_new_payment_profile_response)
+
+ store = @gateway.store(@credit_card, @options)
+ assert_failure store
+ assert_equal 'A duplicate customer payment profile already exists', store.message
+ assert_equal '38392767', store.params['customer_profile_id']
+ assert_equal '34897359', store.params['customer_payment_profile_id']
end
- def test_action_included_in_params
- @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ def test_address
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444'})
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'CO', doc.at_xpath('//billTo/state').content, data
+ assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data
+ assert_equal 'US', doc.at_xpath('//billTo/country').content, data
+ assert_equal '(555)555-5555', doc.at_xpath('//billTo/phoneNumber').content
+ assert_equal '(555)555-4444', doc.at_xpath('//billTo/faxNumber').content
+ end
+ end.respond_with(successful_authorize_response)
+ end
- response = @gateway.capture(50, '123456789')
- assert_equal('PRIOR_AUTH_CAPTURE', response.params['action'] )
+ def test_address_with_empty_billing_address
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal '', doc.at_xpath('//billTo/address').content, data
+ assert_equal '', doc.at_xpath('//billTo/city').content, data
+ assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data
+ assert_equal '', doc.at_xpath('//billTo/zip').content, data
+ assert_equal '', doc.at_xpath('//billTo/country').content, data
+ end
+ end.respond_with(successful_authorize_response)
end
- def test_authorization_code_included_in_params
- @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ def test_address_with_address2_present
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', address2: 'Apt 1234', country: 'US', state: 'CO', phone: '(555)555-5555', fax: '(555)555-4444'})
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'CO', doc.at_xpath('//billTo/state').content, data
+ assert_equal '164 Waverley Street Apt 1234', doc.at_xpath('//billTo/address').content, data
+ assert_equal 'US', doc.at_xpath('//billTo/country').content, data
+ assert_equal '(555)555-5555', doc.at_xpath('//billTo/phoneNumber').content
+ assert_equal '(555)555-4444', doc.at_xpath('//billTo/faxNumber').content
+ end
+ end.respond_with(successful_authorize_response)
+ end
- response = @gateway.capture(50, '123456789')
- assert_equal('d1GENk', response.params['authorization_code'] )
+ def test_address_north_america_with_defaults
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'US'})
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'NC', doc.at_xpath('//billTo/state').content, data
+ assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data
+ assert_equal 'US', doc.at_xpath('//billTo/country').content, data
+ end
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_address_outsite_north_america
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', country: 'DE'})
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data
+ assert_equal '164 Waverley Street', doc.at_xpath('//billTo/address').content, data
+ assert_equal 'DE', doc.at_xpath('//billTo/country').content, data
+ end
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_address_outsite_north_america_with_address2_present
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card, billing_address: {address1: '164 Waverley Street', address2: 'Apt 1234', country: 'DE'})
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'n/a', doc.at_xpath('//billTo/state').content, data
+ assert_equal '164 Waverley Street Apt 1234', doc.at_xpath('//billTo/address').content, data
+ assert_equal 'DE', doc.at_xpath('//billTo/country').content, data
+ end
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_duplicate_window
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, duplicate_window: 0)
+ end.check_request do |endpoint, data, headers|
+ assert_equal settings_from_doc(parse(data))['duplicateWindow'], '0'
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_duplicate_window_class_attribute_deprecated
+ @gateway.class.duplicate_window = 0
+ assert_deprecation_warning('Using the duplicate_window class_attribute is deprecated. Use the transaction options hash instead.') do
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(successful_purchase_response)
+ end
+ ensure
+ @gateway.class.duplicate_window = nil
+ end
+
+ def test_add_cardholder_authentication_value
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, cardholder_authentication_value: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', authentication_indicator: '2')
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content
+ assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_alternative_three_d_secure_options
+ three_d_secure_opts = { cavv: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', eci: '2' }
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, three_d_secure: three_d_secure_opts)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content
+ assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_purchase_response)
+ end
+
+ def test_prioritize_authentication_value_params
+ three_d_secure_opts = { cavv: 'fake', eci: 'fake' }
+ stub_comms do
+ @gateway.purchase(
+ @amount,
+ @credit_card,
+ cardholder_authentication_value: 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=',
+ authentication_indicator: '2',
+ three_d_secure: three_d_secure_opts
+ )
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'E0Mvq8AAABEiMwARIjNEVWZ3iJk=', doc.at_xpath('//cardholderAuthentication/cardholderAuthenticationValue').content
+ assert_equal '2', doc.at_xpath('//cardholderAuthentication/authenticationIndicator').content
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_purchase_response)
end
def test_capture_passing_extra_info
response = stub_comms do
- @gateway.capture(50, '123456789', :description => "Yo", :order_id => "Sweetness")
+ @gateway.capture(50, '123456789', description: 'Yo', order_id: 'Sweetness')
end.check_request do |endpoint, data, headers|
- assert_match(/x_description=Yo/, data)
- assert_match(/x_invoice_num=Sweetness/, data)
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//order/description'), data
+ assert_equal 'Yo', doc.at_xpath('//order/description').content, data
+ assert_equal 'Sweetness', doc.at_xpath('//order/invoiceNumber').content, data
+ assert_equal '0.50', doc.at_xpath('//transactionRequest/amount').content
+ end
end.respond_with(successful_capture_response)
assert_success response
end
def test_successful_refund
- @gateway.expects(:ssl_post).returns(successful_purchase_response)
- assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number)
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+
+ assert refund = @gateway.refund(36.40, '2214269051#XXXX1234')
+ assert_success refund
+ assert_equal 'This transaction has been approved', refund.message
+ assert_equal '2214602071#2224#refund', refund.authorization
+ end
+
+ def test_successful_bank_refund
+ response = stub_comms do
+ @gateway.refund(50, '12345667', account_type: 'checking', routing_number: '123450987', account_number: '12345667', first_name: 'Louise', last_name: 'Belcher')
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'checking', doc.at_xpath('//transactionRequest/payment/bankAccount/accountType').content
+ assert_equal '123450987', doc.at_xpath('//transactionRequest/payment/bankAccount/routingNumber').content
+ assert_equal '12345667', doc.at_xpath('//transactionRequest/payment/bankAccount/accountNumber').content
+ assert_equal 'Louise Belcher', doc.at_xpath('//transactionRequest/payment/bankAccount/nameOnAccount').content
+ end
+ end.respond_with(successful_refund_response)
assert_success response
- assert_equal 'This transaction has been approved', response.message
end
def test_refund_passing_extra_info
response = stub_comms do
- @gateway.refund(50, '123456789', :card_number => @credit_card.number, :first_name => "Bob", :last_name => "Smith", :zip => "12345")
+ @gateway.refund(50, '123456789', card_number: @credit_card.number, first_name: 'Bob', last_name: 'Smith', zip: '12345', order_id: '1', description: 'Refund for order 1')
end.check_request do |endpoint, data, headers|
- assert_match(/x_first_name=Bob/, data)
- assert_match(/x_last_name=Smith/, data)
- assert_match(/x_zip=12345/, data)
+ parse(data) do |doc|
+ assert_equal 'Bob', doc.at_xpath('//billTo/firstName').content, data
+ assert_equal 'Smith', doc.at_xpath('//billTo/lastName').content, data
+ assert_equal '12345', doc.at_xpath('//billTo/zip').content, data
+ assert_equal '0.50', doc.at_xpath('//transactionRequest/amount').content
+ assert_equal '1', doc.at_xpath('//transactionRequest/order/invoiceNumber').content
+ assert_equal 'Refund for order 1', doc.at_xpath('//transactionRequest/order/description').content
+ end
end.respond_with(successful_purchase_response)
assert_success response
end
@@ -217,41 +910,52 @@ def test_refund_passing_extra_info
def test_failed_refund
@gateway.expects(:ssl_post).returns(failed_refund_response)
- assert response = @gateway.refund(@amount, '123456789', :card_number => @credit_card.number)
- assert_failure response
- assert_equal 'The referenced transaction does not meet the criteria for issuing a credit', response.message
+ refund = @gateway.refund(nil, '')
+ assert_failure refund
+ assert_equal 'The sum of credits against the referenced transaction would exceed original debit amount', refund.message
+ assert_equal '0#2224#refund', refund.authorization
end
- def test_deprecated_credit
- @gateway.expects(:ssl_post).returns(successful_purchase_response)
- assert_deprecation_warning(Gateway::CREDIT_DEPRECATION_MESSAGE, @gateway) do
- assert response = @gateway.credit(@amount, '123456789', :card_number => @credit_card.number)
- assert_success response
- assert_equal 'This transaction has been approved', response.message
- end
+ def test_successful_credit
+ @gateway.expects(:ssl_post).returns(successful_credit_response)
+
+ response = @gateway.credit(@amount, @credit_card)
+ assert_success response
+
+ assert_equal '2230004436', response.authorization.split('#')[0]
+ assert_equal 'This transaction has been approved', response.message
+ end
+
+ def test_failed_credit
+ @gateway.expects(:ssl_post).returns(failed_credit_response)
+
+ response = @gateway.credit(@amount, @credit_card)
+ assert_failure response
+ assert_equal 'The credit card number is invalid', response.message
end
def test_supported_countries
- assert_equal ['US', 'CA', 'GB'], AuthorizeNetGateway.supported_countries
+ assert_equal 4, (['US', 'CA', 'AU', 'VA'] & AuthorizeNetGateway.supported_countries).size
end
def test_supported_card_types
- assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb], AuthorizeNetGateway.supported_cardtypes
+ assert_equal [:visa, :master, :american_express, :discover, :diners_club, :jcb, :maestro], AuthorizeNetGateway.supported_cardtypes
end
def test_failure_without_response_reason_text
- assert_nothing_raised do
- assert_equal '', @gateway.send(:message_from, {})
- end
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(no_message_response)
+ assert_equal '', response.message
end
def test_response_under_review_by_fraud_service
@gateway.expects(:ssl_post).returns(fraud_review_response)
response = @gateway.purchase(@amount, @credit_card)
- assert_failure response
+ assert_success response
assert response.fraud_review?
- assert_equal "Thank you! For security reasons your order is currently being reviewed", response.message
+ assert_equal 'Thank you! For security reasons your order is currently being reviewed', response.message
end
def test_avs_result
@@ -259,6 +963,15 @@ def test_avs_result
response = @gateway.purchase(@amount, @credit_card)
assert_equal 'X', response.avs_result['code']
+ assert_equal 'Y', response.avs_result['street_match']
+ assert_equal 'Y', response.avs_result['postal_match']
+
+ @gateway.expects(:ssl_post).returns(address_not_provided_avs_response)
+
+ response = @gateway.purchase(@amount, @credit_card)
+ assert_equal 'I', response.avs_result['code']
+ assert_equal nil, response.avs_result['street_match']
+ assert_equal nil, response.avs_result['postal_match']
end
def test_cvv_result
@@ -268,223 +981,1482 @@ def test_cvv_result
assert_equal 'M', response.cvv_result['code']
end
- def test_message_from
- @gateway.class_eval {
- public :message_from
- }
- result = {
- :response_code => 2,
- :card_code => 'N',
- :avs_result_code => 'A',
- :response_reason_code => '27',
- :response_reason_text => 'Failure.',
- }
- assert_equal "No Match", @gateway.message_from(result)
+ def test_message
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(no_match_cvv_response)
+ assert_equal 'CVV does not match', response.message
+
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(no_match_avs_response)
+ assert_equal 'Street address matches, but postal code does not match.', response.message
- result[:card_code] = 'M'
- assert_equal "Street address matches, but 5-digit and 9-digit postal code do not match.", @gateway.message_from(result)
+ response = stub_comms do
+ @gateway.purchase(@amount, @credit_card)
+ end.respond_with(failed_purchase_response)
+ assert_equal 'The credit card number is invalid', response.message
+ end
- result[:response_reason_code] = '22'
- assert_equal "Failure", @gateway.message_from(result)
+ def test_solution_id_is_added_to_post_data_parameters
+ @gateway.class.application_id = 'A1000000'
+ stub_comms do
+ @gateway.authorize(@amount, @credit_card)
+ end.check_request do |endpoint, data, headers|
+ doc = parse(data)
+ assert_equal 'A1000000', fields_from_doc(doc)['x_solution_id'], data
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end.respond_with(successful_authorize_response)
+ ensure
+ @gateway.class.application_id = nil
end
- # ARB Unit Tests
+ def test_alternate_currency
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
- def test_successful_recurring
- @gateway.expects(:ssl_post).returns(successful_recurring_response)
+ response = @gateway.purchase(@amount, @credit_card, currency: 'GBP')
+ assert_success response
+ end
- response = @gateway.recurring(@amount, @credit_card,
- :billing_address => address.merge(:first_name => 'Jim', :last_name => 'Smith'),
- :interval => {
- :length => 10,
- :unit => :days
- },
- :duration => {
- :start_date => Time.now.strftime("%Y-%m-%d"),
- :occurrences => 30
- }
- )
+ def assert_no_has_customer_id(data)
+ assert_no_match %r{x_cust_id}, data
+ end
- assert_instance_of Response, response
- assert response.success?
- assert response.test?
- assert_equal @subscription_id, response.authorization
+ def test_include_cust_id_for_numeric_values
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, customer: '123')
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//customer/id'), data
+ assert_equal '123', doc.at_xpath('//customer/id').content, data
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_authorize_response)
end
- def test_successful_update_recurring
- @gateway.expects(:ssl_post).returns(successful_update_recurring_response)
+ def test_include_cust_id_for_word_character_values
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, customer: '4840_TT')
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_not_nil doc.at_xpath('//customer/id'), data
+ assert_equal '4840_TT', doc.at_xpath('//customer/id').content, data
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end
+ end.respond_with(successful_authorize_response)
+ end
- response = @gateway.update_recurring(:subscription_id => @subscription_id, :amount => @amount * 2)
+ def test_dont_include_cust_id_for_email_addresses
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, customer: 'bob@test.com')
+ end.check_request do |endpoint, data, headers|
+ doc = parse(data)
+ assert !doc.at_xpath('//customer/id'), data
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end.respond_with(successful_authorize_response)
+ end
- assert_instance_of Response, response
- assert response.success?
- assert response.test?
- assert_equal @subscription_id, response.authorization
+ def test_dont_include_cust_id_for_phone_numbers
+ stub_comms do
+ @gateway.purchase(@amount, @credit_card, customer: '111-123-1231')
+ end.check_request do |endpoint, data, headers|
+ doc = parse(data)
+ assert !doc.at_xpath('//customer/id'), data
+ assert_equal '1.00', doc.at_xpath('//transactionRequest/amount').content
+ end.respond_with(successful_authorize_response)
end
- def test_successful_cancel_recurring
- @gateway.expects(:ssl_post).returns(successful_cancel_recurring_response)
+ def test_includes_shipping_name_when_different_from_billing_name
+ card = credit_card('4242424242424242',
+ first_name: 'billing',
+ last_name: 'name')
- response = @gateway.cancel_recurring(@subscription_id)
+ options = {
+ order_id: 'a' * 21,
+ billing_address: address(name: 'billing name'),
+ shipping_address: address(name: 'shipping lastname')
+ }
- assert_instance_of Response, response
- assert response.success?
- assert response.test?
- assert_equal @subscription_id, response.authorization
+ stub_comms do
+ @gateway.purchase(@amount, card, options)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'billing', doc.at_xpath('//billTo/firstName').text
+ assert_equal 'name', doc.at_xpath('//billTo/lastName').text
+ assert_equal 'shipping', doc.at_xpath('//shipTo/firstName').text
+ assert_equal 'lastname', doc.at_xpath('//shipTo/lastName').text
+ end
+ end.respond_with(successful_purchase_response)
end
- def test_successful_status_recurring
- @gateway.expects(:ssl_post).returns(successful_status_recurring_response)
+ def test_includes_shipping_name_when_passed_as_options
+ card = credit_card('4242424242424242',
+ first_name: 'billing',
+ last_name: 'name')
+
+ shipping_address = address(first_name: 'shipping', last_name: 'lastname')
+ shipping_address.delete(:name)
+ options = {
+ order_id: 'a' * 21,
+ billing_address: address(name: 'billing name'),
+ shipping_address: shipping_address
+ }
- response = @gateway.status_recurring(@subscription_id)
- assert_instance_of Response, response
- assert response.success?
- assert response.test?
- assert_equal @subscription_status, response.params['status']
+ stub_comms do
+ @gateway.purchase(@amount, card, options)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'billing', doc.at_xpath('//billTo/firstName').text
+ assert_equal 'name', doc.at_xpath('//billTo/lastName').text
+ assert_equal 'shipping', doc.at_xpath('//shipTo/firstName').text
+ assert_equal 'lastname', doc.at_xpath('//shipTo/lastName').text
+ end
+ end.respond_with(successful_purchase_response)
end
- def test_expdate_formatting
- assert_equal '2009-09', @gateway.send(:arb_expdate, credit_card('4111111111111111', :month => "9", :year => "2009"))
- assert_equal '2013-11', @gateway.send(:arb_expdate, credit_card('4111111111111111', :month => "11", :year => "2013"))
+ def test_truncation
+ card = credit_card('4242424242424242',
+ first_name: 'a' * 51,
+ last_name: 'a' * 51
+ )
+
+ options = {
+ order_id: 'a' * 21,
+ description: 'a' * 256,
+ billing_address: address(
+ company: 'a' * 51,
+ address1: 'a' * 61,
+ city: 'a' * 41,
+ state: 'a' * 41,
+ zip: 'a' * 21,
+ country: 'a' * 61
+ ),
+ shipping_address: address(
+ name: ['a' * 51, 'a' * 51].join(' '),
+ company: 'a' * 51,
+ address1: 'a' * 61,
+ city: 'a' * 41,
+ state: 'a' * 41,
+ zip: 'a' * 21,
+ country: 'a' * 61
+ )
+ }
+
+ stub_comms do
+ @gateway.purchase(@amount, card, options)
+ end.check_request do |endpoint, data, headers|
+ assert_truncated(data, 20, '//refId')
+ assert_truncated(data, 255, '//description')
+ assert_address_truncated(data, 50, 'firstName')
+ assert_address_truncated(data, 50, 'lastName')
+ assert_address_truncated(data, 50, 'company')
+ assert_address_truncated(data, 60, 'address')
+ assert_address_truncated(data, 40, 'city')
+ assert_address_truncated(data, 40, 'state')
+ assert_address_truncated(data, 20, 'zip')
+ assert_address_truncated(data, 60, 'country')
+ end.respond_with(successful_purchase_response)
end
- def test_solution_id_is_added_to_post_data_parameters
- assert !@gateway.send(:post_data, 'AUTH_ONLY').include?("x_solution_ID=A1000000")
- ActiveMerchant::Billing::AuthorizeNetGateway.application_id = 'A1000000'
- assert @gateway.send(:post_data, 'AUTH_ONLY').include?("x_solution_ID=A1000000")
- ensure
- ActiveMerchant::Billing::AuthorizeNetGateway.application_id = nil
+ def test_invalid_cvv
+ invalid_cvvs = ['47', '12345', '']
+ invalid_cvvs.each do |cvv|
+ card = credit_card(@credit_card.number, { verification_value: cvv })
+ stub_comms do
+ @gateway.purchase(@amount, card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) { |doc| assert_nil doc.at_xpath('//cardCode') }
+ end.respond_with(successful_purchase_response)
+ end
end
- def test_bad_currency
- @gateway.expects(:ssl_post).returns(bad_currency_response)
+ def test_card_number_truncation
+ card = credit_card(@credit_card.number + '0123456789')
+ stub_comms do
+ @gateway.purchase(@amount, card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal @credit_card.number, doc.at_xpath('//cardNumber').text
+ end
+ end.respond_with(successful_purchase_response)
+ end
- response = @gateway.purchase(@amount, @credit_card, {:currency => "XYZ"})
- assert_failure response
- assert_equal 'The supplied currency code is either invalid, not supported, not allowed for this merchant or doesn\'t have an exchange rate', response.message
+ def test_scrub
+ assert_equal @gateway.scrub(pre_scrubbed), post_scrubbed
end
- def test_alternate_currency
- @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ def test_supports_scrubbing?
+ assert @gateway.supports_scrubbing?
+ end
+
+ def test_successful_apple_pay_authorization_with_network_tokenization
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ :payment_cryptogram => '111111111100cryptogram'
+ )
- response = @gateway.purchase(@amount, @credit_card, {:currency => "GBP"})
+ response = stub_comms do
+ @gateway.authorize(@amount, credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal credit_card.payment_cryptogram, doc.at_xpath('//creditCard/cryptogram').content
+ assert_equal credit_card.number, doc.at_xpath('//creditCard/cardNumber').content
+ end
+ end.respond_with(successful_authorize_response)
+
+ assert response
+ assert_instance_of Response, response
assert_success response
+ assert_equal '508141794', response.authorization.split('#')[0]
end
- def test_include_cust_id_for_numeric_values
- stub_comms do
- @gateway.purchase(@amount, @credit_card, {:customer => "123"})
- end.check_request do |method, data|
- assert data =~ /x_cust_id=123/
- end.respond_with(successful_authorization_response)
+ def test_failed_apple_pay_authorization_with_network_tokenization_not_supported
+ credit_card = network_tokenization_credit_card('4242424242424242',
+ :payment_cryptogram => '111111111100cryptogram'
+ )
+
+ response = stub_comms do
+ @gateway.authorize(@amount, credit_card)
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal credit_card.payment_cryptogram, doc.at_xpath('//creditCard/cryptogram').content
+ assert_equal credit_card.number, doc.at_xpath('//creditCard/cardNumber').content
+ end
+ end.respond_with(network_tokenization_not_supported_response)
+
+ assert_equal Gateway::STANDARD_ERROR_CODE[:unsupported_feature], response.error_code
+ end
+
+ def test_supports_network_tokenization_true
+ response = stub_comms do
+ @gateway.supports_network_tokenization?
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'authOnlyTransaction', doc.at_xpath('//transactionType').content
+ assert_equal '0.01', doc.at_xpath('//amount').content
+ assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', doc.at_xpath('//creditCard/cryptogram').content
+ assert_equal '4111111111111111', doc.at_xpath('//creditCard/cardNumber').content
+ end
+ end.respond_with(successful_authorize_response)
+
+ assert_instance_of TrueClass, response
end
- def test_dont_include_cust_id_for_non_numeric_values
- stub_comms do
- @gateway.purchase(@amount, @credit_card, {:customer => "bob@test.com"})
- end.check_request do |method, data|
- assert data !~ /x_cust_id/
- end.respond_with(successful_authorization_response)
+ def test_supports_network_tokenization_false
+ response = stub_comms do
+ @gateway.supports_network_tokenization?
+ end.check_request do |endpoint, data, headers|
+ parse(data) do |doc|
+ assert_equal 'authOnlyTransaction', doc.at_xpath('//transactionType').content
+ assert_equal '0.01', doc.at_xpath('//amount').content
+ assert_equal 'EHuWW9PiBkWvqE5juRwDzAUFBAk=', doc.at_xpath('//creditCard/cryptogram').content
+ assert_equal '4111111111111111', doc.at_xpath('//creditCard/cardNumber').content
+ end
+ end.respond_with(network_tokenization_not_supported_response)
+
+ assert_instance_of FalseClass, response
+ end
+
+ def test_verify_good_credentials
+ @gateway.expects(:ssl_post).returns(credentials_are_legit_response)
+ assert @gateway.verify_credentials
+ end
+
+ def test_verify_bad_credentials
+ @gateway.expects(:ssl_post).returns(credentials_are_bogus_response)
+ assert !@gateway.verify_credentials
end
private
- def post_data_fixture
- 'x_encap_char=%24&x_card_num=4242424242424242&x_exp_date=0806&x_card_code=123&x_type=AUTH_ONLY&x_first_name=Longbob&x_version=3.1&x_login=X&x_last_name=Longsen&x_tran_key=Y&x_relay_response=FALSE&x_delim_data=TRUE&x_delim_char=%2C&x_amount=1.01'
+
+ def pre_scrubbed
+ <<-PRE_SCRUBBED
+ opening connection to apitest.authorize.net:443...
+ opened
+ starting SSL for apitest.authorize.net:443...
+ SSL established
+ <- "POST /xml/v1/request.api HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: apitest.authorize.net\r\nContent-Length: 1306\r\n\r\n"
+ <- "\n\n\n5KP3u95bQpv\n4Ktq966gC55GAX7S\n\n1\n\nauthCaptureTransaction\n1.00\n\n\n4000100011112224\n09/2016\n123\nEHuWW9PiBkWvqE5juRwDzAUFBAk=\n\n\n\n1\nStore Purchase\n\n\n\nLongbob\nLongsen\nWidgets Inc\n1234 My Street\nOttawa\nON\nK1C2N6\nCA\n(555)555-5555\n(555)555-6666\n\n\n\n\n\n\n\nduplicateWindow\n0\n\n\n\n\nx_currency_code\nUSD\n\n\n\n123456\n78910\n\n\n"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Cache-Control: private\r\n"
+ -> "Content-Length: 973\r\n"
+ -> "Content-Type: application/xml; charset=utf-8\r\n"
+ -> "Server: Microsoft-IIS/7.5\r\n"
+ -> "X-AspNet-Version: 2.0.50727\r\n"
+ -> "X-Powered-By: ASP.NET\r\n"
+ -> "Access-Control-Allow-Origin: *\r\n"
+ -> "Access-Control-Allow-Methods: GET,POST,OPTIONS\r\n"
+ -> "Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method\r\n"
+ -> "Date: Mon, 26 Jan 2015 16:29:30 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "\r\n"
+ reading 973 bytes...
+ -> "\xEF\xBB\xBF1OkI00001Successful.1H6K4BUYP22227534280FE7A5BA8F209227CE1EC4B07C4A1BB810XXXX2224Visa1This transaction has been approved.x_currency_codeUSD"
+ read 973 bytes
+ Conn close
+ PRE_SCRUBBED
end
- def minimum_requirements
- %w(version delim_data relay_response login tran_key amount card_num exp_date type)
+ def post_scrubbed
+ <<-PRE_SCRUBBED
+ opening connection to apitest.authorize.net:443...
+ opened
+ starting SSL for apitest.authorize.net:443...
+ SSL established
+ <- "POST /xml/v1/request.api HTTP/1.1\r\nContent-Type: text/xml\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nConnection: close\r\nHost: apitest.authorize.net\r\nContent-Length: 1306\r\n\r\n"
+ <- "\n\n\n5KP3u95bQpv\n[FILTERED]\n\n1\n\nauthCaptureTransaction\n1.00\n\n\n[FILTERED]\n09/2016\n[FILTERED]\n[FILTERED]\n\n\n\n1\nStore Purchase\n\n\n\nLongbob\nLongsen\nWidgets Inc\n1234 My Street\nOttawa\nON\nK1C2N6\nCA\n(555)555-5555\n(555)555-6666\n\n\n\n\n\n\n\nduplicateWindow\n0\n\n\n\n\nx_currency_code\nUSD\n\n\n\n[FILTERED]\n[FILTERED]\n\n\n"
+ -> "HTTP/1.1 200 OK\r\n"
+ -> "Cache-Control: private\r\n"
+ -> "Content-Length: 973\r\n"
+ -> "Content-Type: application/xml; charset=utf-8\r\n"
+ -> "Server: Microsoft-IIS/7.5\r\n"
+ -> "X-AspNet-Version: 2.0.50727\r\n"
+ -> "X-Powered-By: ASP.NET\r\n"
+ -> "Access-Control-Allow-Origin: *\r\n"
+ -> "Access-Control-Allow-Methods: GET,POST,OPTIONS\r\n"
+ -> "Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method\r\n"
+ -> "Date: Mon, 26 Jan 2015 16:29:30 GMT\r\n"
+ -> "Connection: close\r\n"
+ -> "\r\n"
+ reading 973 bytes...
+ -> "\xEF\xBB\xBF1OkI00001Successful.1H6K4BUYP22227534280FE7A5BA8F209227CE1EC4B07C4A1BB810[FILTERED]Visa1This transaction has been approved.x_currency_codeUSD"
+ read 973 bytes
+ Conn close
+ PRE_SCRUBBED
end
- def failed_refund_response
- '$3$,$2$,$54$,$The referenced transaction does not meet the criteria for issuing a credit.$,$$,$P$,$0$,$$,$$,$1.00$,$CC$,$credit$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$39265D8BA0CDD4F045B5F4129B2AAA01$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$'
+ def parse(data)
+ Nokogiri::XML(data).tap do |doc|
+ doc.remove_namespaces!
+ yield(doc) if block_given?
+ end
+ end
+
+ def fields_from_doc(doc)
+ assert_not_nil doc.at_xpath('//userFields/userField/name')
+ doc.xpath('//userFields/userField').inject({}) do |hash, element|
+ hash[element.at_xpath('name').content] = element.at_xpath('value').content
+ hash
+ end
+ end
+
+ def settings_from_doc(doc)
+ assert_not_nil doc.at_xpath('//transactionSettings/setting/settingName')
+ doc.xpath('//transactionSettings/setting').inject({}) do |hash, element|
+ hash[element.at_xpath('settingName').content] = element.at_xpath('settingValue').content
+ hash
+ end
+ end
+
+ def assert_truncated(data, expected_size, field)
+ assert_equal ('a' * expected_size), parse(data).at_xpath(field).text, data
end
- def successful_authorization_response
- '$1$,$1$,$1$,$This transaction has been approved.$,$advE7f$,$Y$,$508141794$,$5b3fe66005f3da0ebe51$,$$,$1.00$,$CC$,$auth_only$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$2860A297E0FE804BCB9EF8738599645C$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$'
+ def assert_address_truncated(data, expected_size, field)
+ assert_truncated(data, expected_size, "//billTo/#{field}")
+ assert_truncated(data, expected_size, "//shipTo/#{field}")
end
def successful_purchase_response
- '$1$,$1$,$1$,$This transaction has been approved.$,$d1GENk$,$Y$,$508141795$,$32968c18334f16525227$,$Store purchase$,$1.00$,$CC$,$auth_capture$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$269862C030129C1173727CC10B1935ED$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$'
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 1
+ GSOFTZ
+ Y
+ P
+ 2
+ 508141795
+
+ 655D049EE60E1766C9C28EB47CFAA389
+ 0
+ XXXX2224
+ Visa
+
+
+ 1
+ This transaction has been approved.
+
+
+
+
+ eos
+ end
+
+ def fraud_review_response
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 4
+ GSOFTZ
+ X
+ M
+ 2
+ 508141795
+
+ 655D049EE60E1766C9C28EB47CFAA389
+ 0
+ XXXX2224
+ Visa
+
+
+ 1
+ Thank you! For security reasons your order is currently being reviewed
+
+
+
+
+ eos
+ end
+
+ def address_not_provided_avs_response
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 4
+ GSOFTZ
+ B
+ M
+ 2
+ 508141795
+
+ 655D049EE60E1766C9C28EB47CFAA389
+ 0
+ XXXX2224
+ Visa
+
+
+ 1
+ Thank you! For security reasons your order is currently being reviewed
+
+
+
+
+ eos
+ end
+
+ def no_match_cvv_response
+ <<-eos
+
+
+ 1
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 2
+ GSOFTZ
+ A
+ N
+ 2
+ 508141795
+
+ 655D049EE60E1766C9C28EB47CFAA389
+ 0
+ XXXX2224
+ Visa
+
+
+ 1
+ Thank you! For security reasons your order is currently being reviewed
+
+
+
+
+ eos
+ end
+
+ def no_match_avs_response
+ <<-eos
+
+
+ 1
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 2
+ GSOFTZ
+ A
+ M
+ 2
+ 508141795
+
+ 655D049EE60E1766C9C28EB47CFAA389
+ 0
+ XXXX2224
+ Visa
+
+
+ 27
+ The transaction cannot be found.
+
+
+
+
+ eos
+ end
+
+ def failed_purchase_response
+ <<-eos
+
+
+ 1234567
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 3
+
+ P
+
+
+ 0
+
+ 7F9A0CB845632DCA5833D2F30ED02677
+ 0
+ XXXX0001
+
+
+
+ 6
+ The credit card number is invalid.
+
+
+
+
+ MerchantDefinedFieldName1
+ MerchantDefinedFieldValue1
+
+
+ favorite_color
+ blue
+
+
+
+
+ eos
+ end
+
+ def no_message_response
+ <<-eos
+
+
+ 1234567
+
+ Error
+
+
+ 3
+
+ P
+
+
+ 0
+
+ 7F9A0CB845632DCA5833D2F30ED02677
+ 0
+ XXXX0001
+
+
+
+ MerchantDefinedFieldName1
+ MerchantDefinedFieldValue1
+
+
+ favorite_color
+ blue
+
+
+
+
+ eos
+ end
+
+ def successful_purchase_response_test_mode
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 1
+ GSOFTZ
+ Y
+ P
+ 2
+ 508141795
+
+ 655D049EE60E1766C9C28EB47CFAA389
+ 1
+ XXXX2224
+ Visa
+
+
+ 1
+ This transaction has been approved.
+
+
+
+
+ eos
+ end
+
+ def successful_authorize_response
+ <<-eos
+
+
+ 123456
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 1
+ A88MS0
+ Y
+ M
+ 2
+ 508141794
+
+ D0EFF3F32E5ABD14A7CE6ADF32736D57
+ 0
+ XXXX0015
+ MasterCard
+
+
+ 1
+ This transaction has been approved.
+
+
+
+
+ MerchantDefinedFieldName1
+ MerchantDefinedFieldValue1
+
+
+ favorite_color
+ blue
+
+
+
+
+ eos
+ end
+
+ def failed_authorize_response
+ <<-eos
+
+
+ 123456
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 3
+
+ P
+
+
+ 0
+
+ DA56E64108957174C5AE9BE466914741
+ 0
+ XXXX0001
+
+
+
+ 6
+ The credit card number is invalid.
+
+
+
+
+ MerchantDefinedFieldName1
+ MerchantDefinedFieldValue1
+
+
+ favorite_color
+ blue
+
+
+
+
+ eos
end
def successful_capture_response
- '$1$,$1$,$1$,$This transaction has been approved.$,$d1GENk$,$Y$,$508141795$,$32968c18334f16525227$,$Store purchase$,$1.00$,$CC$,$auth_capture$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$269862C030129C1173727CC10B1935ED$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$'
+ <<-eos
+
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 1
+ UTDVHP
+ P
+
+
+ 2214675515
+ 2214675515
+ 6D739029E129D87F6CEFE3B3864F6D61
+ 0
+ XXXX2224
+ Visa
+
+
+ 1
+ This transaction has been approved.
+
+
+
+
+ eos
end
- def failed_authorization_response
- '$2$,$1$,$1$,$This transaction was declined.$,$advE7f$,$Y$,$508141794$,$5b3fe66005f3da0ebe51$,$$,$1.00$,$CC$,$auth_only$,$$,$Longbob$,$Longsen$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$2860A297E0FE804BCB9EF8738599645C$,$P$,$2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$'
+ def already_actioned_capture_response
+ <<-eos
+
+
+
+
+ Ok
+
+ I00001
+ This transaction has already been captured.
+
+
+
+ 1
+ UTDVHP
+ P
+
+
+ 2214675515
+ 2214675515
+ 6D739029E129D87F6CEFE3B3864F6D61
+ 0
+ XXXX2224
+ Visa
+
+
+ 311
+ This transaction has already been captured.
+
+
+
+
+ eos
end
- def fraud_review_response
- "$4$,$$,$253$,$Thank you! For security reasons your order is currently being reviewed.$,$$,$X$,$0$,$$,$$,$1.00$,$$,$auth_capture$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$207BCBBF78E85CF174C87AE286B472D2$,$M$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$"
- end
-
- def bad_currency_response
- "$3$,$1$,$39$,$The supplied currency code is either invalid, not supported, not allowed for this merchant or doesn't have an exchange rate.$,$$,$P$,$0$,$$,$$,$1.00$,$$,$auth_capture$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$207BCBBF78E85CF174C87AE286B472D2$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$,$$"
- end
-
- def successful_recurring_response
- <<-XML
-
- Sample
-
- Ok
-
- I00001
- Successful.
-
-
- #{@subscription_id}
-
- XML
- end
-
- def successful_update_recurring_response
- <<-XML
-
- Sample
-
- Ok
-
- I00001
- Successful.
-
-
- #{@subscription_id}
-
- XML
- end
-
- def successful_cancel_recurring_response
- <<-XML
-
- Sample
-
- Ok
-
- I00001
- Successful.
-
-
- #{@subscription_id}
-
- XML
- end
-
- def successful_status_recurring_response
- <<-XML
-
- Sample
-
- Ok
-
- I00001
- Successful.
-
-
- #{@subscription_status}
-
- XML
+ def failed_capture_response
+ <<-eos
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+ 3
+
+ P
+
+
+ 0
+ 23124
+ D99CC43D1B34F0DAB7F430F8F8B3249A
+ 0
+
+
+
+
+ 16
+ The transaction cannot be found.
+
+
+
+
+
+ eos
+ end
+
+ def successful_refund_response
+ <<-eos
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 1
+
+ P
+
+
+ 2214602071
+ 2214269051
+ A3E5982FB6789092985F2D618196A268
+ 0
+ XXXX2224
+ Visa
+
+
+ 1
+ This transaction has been approved.
+
+
+
+
+ eos
+ end
+
+ def failed_refund_response
+ <<-eos
+
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 3
+
+ P
+
+
+ 0
+ 2214269051
+ 63E03F4968F0874E1B41FCD79DD54717
+ 0
+ XXXX2224
+ Visa
+
+
+ 55
+ The sum of credits against the referenced transaction would exceed original debit amount.
+
+
+
+
+ eos
+ end
+
+ def successful_void_response
+ <<-eos
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 1
+ GYEB3
+ P
+
+
+ 2213755822
+ 2213755822
+ 3383BBB85FF98057D61B2D9B9A2DA79F
+ 0
+ XXXX0015
+ MasterCard
+
+
+ 1
+ This transaction has been approved.
+
+
+
+
+ eos
+ end
+
+ def failed_void_response
+ <<-eos
+
+
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 3
+
+ P
+
+
+ 0
+ 2213755821
+ 39DC95085A313FEF7278C40EA8A66B16
+ 0
+
+
+
+
+ 16
+ The transaction cannot be found.
+
+
+
+
+
+ eos
+ end
+
+ def successful_credit_response
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ 1
+
+ P
+
+
+ 2230004436
+
+ BF2ADA32B70495EE035C6A5ADC635047
+ 0
+ XXXX2224
+ Visa
+
+
+ 1
+ This transaction has been approved.
+
+
+
+
+ x_currency_code
+ USD
+
+
+
+
+ eos
+ end
+
+ def failed_credit_response
+ <<-eos
+
+
+ 1
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 3
+
+ P
+
+
+ 0
+
+ 0FFA5F1B4CA8DC9643BC117DAFB45770
+ 0
+ XXXX1222
+
+
+
+ 6
+ The credit card number is invalid.
+
+
+
+
+ x_currency_code
+ USD
+
+
+
+
+ eos
+ end
+
+ def successful_store_response
+ <<-eos
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 35959426
+
+ 32506918
+
+
+
+
+ eos
+ end
+
+ def failed_store_response
+ <<-eos
+
+
+
+ Error
+
+ E00015
+ The field length is invalid for Card Number.
+
+
+
+
+
+
+ eos
+ end
+
+ def successful_unstore_response
+ <<-eos
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ eos
+ end
+
+ def failed_unstore_response
+ <<-eos
+
+
+
+ Error
+
+ E00040
+ The record cannot be found.
+
+
+
+ eos
+ end
+
+ def successful_store_new_payment_profile_response
+ <<-eos
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 38392170
+ 34896759
+
+ eos
+ end
+
+ def failed_store_new_payment_profile_response
+ <<-eos
+
+
+
+ Error
+
+ E00039
+ A duplicate customer payment profile already exists.
+
+
+ 38392767
+ 34897359
+
+ eos
+ end
+
+ def successful_purchase_using_stored_card_response
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 1,1,1,This transaction has been approved.,8HUT72,Y,2235700270,1,Store Purchase,1.01,CC,auth_capture,e385c780422f4bd182c4,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,4A20EEAF89018FF075899DDB332E9D35,,2,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def successful_purchase_using_stored_card_response_with_pipe_delimiter
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 1|1|1|This transaction has been approved.|8HUT72|Y|2235700270|1|description, with, commas|1.01|CC|auth_capture|e385c780422f4bd182c4|Longbob|Longsen||||n/a|||||||||||||||||||4A20EEAF89018FF075899DDB332E9D35||2|||||||||||XXXX2224|Visa||||||||||||||||
+
+ eos
+ end
+
+ def failed_purchase_using_stored_card_response
+ <<-eos
+
+
+ 1
+
+ Error
+
+ E00027
+ The credit card number is invalid.
+
+
+ 3,1,6,The credit card number is invalid.,,P,0,1,Store Purchase,1.01,CC,auth_capture,2da01d7b583c706106e2,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,13BA28EEA3593C13E2E3BC109D16E5D2,,,,,,,,,,,,,XXXX1222,,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def successful_authorize_using_stored_card_response
+ <<-eos
+
+
+ 1
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 1,1,1,This transaction has been approved.,GGHQ5R,Y,2235700640,1,Store Purchase,1.01,CC,auth_only,0bde9d39f8eb9443f2af,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,E47E5CA4F1239B00D39A7F8C147215D3,,2,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def failed_authorize_using_stored_card_response
+ <<-eos
+
+
+ 1
+
+ Error
+
+ E00027
+ The credit card number is invalid.
+
+
+ 3,1,6,The credit card number is invalid.,,P,0,1,Store Purchase,1.01,CC,auth_only,f632442ce056f9f82ee9,Longbob,Longsen,,,,n/a,,,,,,,,,,,,,,,,,,,13BA28EEA3593C13E2E3BC109D16E5D2,,,,,,,,,,,,,XXXX1222,,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def successful_capture_using_stored_card_response
+ <<-eos
+
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 1,1,1,This transaction has been approved.,GGHQ5R,P,2235700640,1,,1.01,CC,prior_auth_capture,0bde9d39f8eb9443f2af,,,,,,,,,,,,,,,,,,,,,,,,,E47E5CA4F1239B00D39A7F8C147215D3,,,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def failed_capture_using_stored_card_response
+ <<-eos
+
+
+
+
+ Error
+
+ E00027
+ The amount requested for settlement cannot be greater than the original amount authorized.
+
+
+ 3,2,47,The amount requested for settlement cannot be greater than the original amount authorized.,,P,0,,,41.01,CC,prior_auth_capture,,,,,,,,,,,,,,,,,,,,,,,,,,8A556B125A1DA070AF5A84B798B7FBF7,,,,,,,,,,,,,,Visa,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def failed_refund_using_stored_card_response
+ <<-eos
+
+
+ 1
+
+ Error
+
+ E00040
+ The record cannot be found.
+
+
+
+ eos
+ end
+
+ def successful_void_using_stored_card_response
+ <<-eos
+
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 1,1,1,This transaction has been approved.,3R9YE2,P,2235701141,1,,0.00,CC,void,becdb509b35a32c30e97,,,,,,,,,,,,,,,,,,,,,,,,,C3C4B846B9D5A37D14462C2BF5B924FD,,,,,,,,,,,,,XXXX2224,Visa,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def failed_void_using_stored_card_response
+ <<-eos
+
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+ 1,1,310,This transaction has already been voided.,,P,0,,,0.00,CC,void,,,,,,,,,,,,,,,,,,,,,,,,,,FD9FAE70BEF461997A6C15D7D597658D,,,,,,,,,,,,,,Visa,,,,,,,,,,,,,,,,
+
+ eos
+ end
+
+ def network_tokenization_not_supported_response
+ <<-eos
+
+
+ 123456
+
+ Error
+
+ E00155
+ This processor does not support this method of submitting payment data
+
+
+
+ 3
+
+ P
+
+
+ 0
+
+ DA56E64108957174C5AE9BE466914741
+ 0
+ XXXX0001
+
+
+
+ 155
+ This processor does not support this method of submitting payment data
+
+
+
+
+ MerchantDefinedFieldName1
+ MerchantDefinedFieldValue1
+
+
+ favorite_color
+ blue
+
+
+
+
+ eos
+ end
+
+ def credentials_are_legit_response
+ <<-eos
+
+
+
+ Ok
+
+ I00001
+ Successful.
+
+
+
+ eos
+ end
+
+ def credentials_are_bogus_response
+ <<-eos
+
+
+
+ Error
+
+ E00007
+ User authentication failed due to invalid authentication values.
+
+
+
+ eos
+ end
+
+ def failed_refund_for_unsettled_payment_response
+ <<-eos
+
+
+
+ Error
+
+ E00027
+ The transaction was unsuccessful.
+
+
+
+ 3
+
+ P
+
+
+ 0
+
+
+ 0
+ XXXX0001
+ Visa
+
+
+ 54
+ The referenced transaction does not meet the criteria for issuing a credit.
+
+
+
+
+
+
+ eos
end
end
diff --git a/test/unit/gateways/axcessms_test.rb b/test/unit/gateways/axcessms_test.rb
new file mode 100644
index 00000000000..e87a092498b
--- /dev/null
+++ b/test/unit/gateways/axcessms_test.rb
@@ -0,0 +1,440 @@
+require 'test_helper'
+
+class AxcessmsTest < Test::Unit::TestCase
+ include CommStub
+
+ TEST_AUTHORIZATION = '8a8294494830a3bb01483174f1827b9a'
+ TEST_PURCHASE = '8a82944a4830c4810148350aeeec5e58'
+
+ def setup
+ @gateway = AxcessmsGateway.new(fixtures(:axcessms))
+
+ @amount = 1500
+ @credit_card = credit_card('4200000000000000', month: 05, year: 2022)
+ @declined_card = credit_card('4444444444444444', month: 05, year: 2022)
+ @mode = 'CONNECTOR_TEST'
+
+ @options = {
+ order_id: generate_unique_id,
+ email: 'customer@example.com',
+ description: "Order Number #{Time.now.to_f.divmod(2473)[1]}",
+ ip: '0.0.0.0',
+ mode: @mode,
+ billing_address: {
+ :address1 => '10 Marklar St',
+ :address2 => 'Musselburgh',
+ :city => 'Dunedin',
+ :zip => '9013',
+ :state => 'Otago',
+ :country => 'NZ'
+ }
+ }
+ end
+
+ def test_successful_authorize
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ response = @gateway.authorize(@amount, @credit_card, @options)
+ assert_success response
+ assert response.test?
+ assert_equal TEST_AUTHORIZATION, response.authorization
+ end
+
+ def test_successful_purchase
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ response = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success response
+ assert_equal TEST_PURCHASE, response.authorization
+ end
+
+ def test_successful_purchase_with_minimal_options
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ response = @gateway.purchase(@amount, @credit_card, billing_address: address)
+ assert_success response
+ assert_equal TEST_PURCHASE, response.authorization
+ end
+
+ def test_successful_reference_purchase
+ @gateway.expects(:ssl_post).returns(successful_purchase_response)
+ response = @gateway.purchase(@amount, '12345', @options)
+ assert_success response
+ assert_equal TEST_PURCHASE, response.authorization
+ end
+
+ def test_successful_reference_authorize
+ @gateway.expects(:ssl_post).returns(successful_authorize_response)
+ response = @gateway.authorize(@amount, '12345', @options)
+ assert_success response
+ assert_equal TEST_AUTHORIZATION, response.authorization
+ end
+
+ def test_successful_capture
+ @gateway.expects(:ssl_post).returns(successful_capture_response)
+ response = @gateway.capture(@amount, TEST_AUTHORIZATION, @options)
+ assert_success response
+ assert response.params['reference_id'], TEST_AUTHORIZATION
+ end
+
+ def test_successful_refund
+ @gateway.expects(:ssl_post).returns(successful_refund_response)
+ response = @gateway.refund(@amount - 30, TEST_PURCHASE, @options)
+ assert_success response
+ assert response.params['reference_id'], TEST_PURCHASE
+ end
+
+ def test_successful_void
+ @gateway.expects(:ssl_post).returns(successful_void_response)
+ response = @gateway.void(TEST_AUTHORIZATION, @options)
+ assert_success response
+ assert response.params['reference_id'], TEST_AUTHORIZATION
+ end
+
+ def test_unauthorized_capture
+ @gateway.expects(:ssl_post).returns(failed_capture_response)
+ response = @gateway.capture(@amount, 'authorization', @options)
+ assert_failure response
+ assert_equal 'Reference Error - capture needs at least one successful transaction of type (PA)', response.message
+ end
+
+ def test_failed_refund
+ @gateway.expects(:ssl_post).returns(failed_refund_response)
+ response = @gateway.refund(@amount - 30, 'authorization', @options)
+ assert_failure response
+ assert_equal 'Configuration Validation - Invalid payment data. You are not configured for this currency or sub type (country or brand)', response.message
+ end
+
+ def test_failed_void
+ @gateway.expects(:ssl_post).returns(failed_void_response)
+ response = @gateway.refund(@amount - 30, 'authorization', @options)
+ assert_failure response
+ assert_equal 'Reference Error - reversal needs at least one successful transaction of type (CP or DB or RB or PA)', response.message
+ end
+
+ def test_authorize_using_reference_sets_proper_elements
+ stub_comms do
+ @gateway.authorize(@amount, 'MY_AUTHORIZE_VALUE', @options)
+ end.check_request do |endpoint, body, headers|
+ assert_xpath_text(body, '//ReferenceID', 'MY_AUTHORIZE_VALUE')
+ assert_no_match(//, body)
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_purchase_using_reference_sets_proper_elements
+ stub_comms do
+ @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', @options)
+ end.check_request do |endpoint, body, headers|
+ assert_xpath_text(body, '//ReferenceID', 'MY_AUTHORIZE_VALUE')
+ assert_no_match(//, body)
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_setting_mode_sets_proper_element
+ stub_comms do
+ @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', {mode: 'CRAZY_TEST_MODE'})
+ end.check_request do |endpoint, body, headers|
+ assert_xpath_text(body, '//Transaction/@mode', 'CRAZY_TEST_MODE')
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_defaults_to_integrator_test
+ stub_comms do
+ @gateway.purchase(@amount, 'MY_AUTHORIZE_VALUE', {})
+ end.check_request do |endpoint, body, headers|
+ assert_xpath_text(body, '//Transaction/@mode', 'INTEGRATOR_TEST')
+ end.respond_with(successful_authorize_response)
+ end
+
+ def test_successful_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(successful_authorize_response, successful_void_response)
+ assert_success response
+ end
+
+ def test_successful_verify_with_failed_void
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(successful_authorize_response, failed_void_response)
+ assert_success response
+ end
+
+ def test_failed_verify
+ response = stub_comms do
+ @gateway.verify(@credit_card, @options)
+ end.respond_with(failed_authorize_response, successful_void_response)
+ assert_failure response
+ end
+
+ private
+
+ def assert_xpath_text(xml, xpath, expected_text)
+ xml = CGI.unescape(xml.gsub('load=', ''))
+ root = REXML::Document.new(xml).root
+ element = REXML::XPath.first(root, xpath)
+ actual_text = xpath.include?('@') ? element.value : element.text
+ assert_equal expected_text, actual_text, %{Expected to find the text "#{expected_text}" within the XML element with path "#{xpath}", but instead found the text "#{actual_text}" in the following XML:\n#{xml}}
+ end
+
+ def successful_authorize_response
+ <<-XML
+
+
+
+
+ 7159.9714.3714
+ 8a8294494830a3bb01483174f1827b9a
+ 64ac051ce71ede9fbf6d9d9f17dd43db
+
+
+
+ 10.00
+ GBP
+ 7159.9714.3714 Recurring
+ 1.0
+ INTERN
+ 2014-09-01 13:43:40
+
+
+
+ 2014-09-01 13:43:40
+ ACK
+ NEW
+ Successful Processing
+ Request successfully processed in 'Merchant in Connector Test Mode'
+
+
+
+
+ XML
+ end
+
+ def successful_purchase_response
+ <<-XML
+
+
+
+
+ 5120.3652.8802
+ 8a82944a4830c4810148350aeeec5e58
+ 38a96c022cafb4ef2e308c119ae34452
+
+
+
+ 10.00
+ GBP
+ 5120.3652.8802 Recurring
+ 1.0
+ INTERN
+ 2014-09-02 06:26:22
+
+
+
+ 2014-09-02 06:26:22
+ ACK
+ NEW
+ Successful Processing
+ Request successfully processed in 'Merchant in Integrator Test Mode'
+
+
+
+
+ XML
+ end
+
+ def successful_capture_response
+ <<-XML
+
+
+
+
+ 9318.6958.1986
+ 8a8294494830a3bb0148351b6546087a
+ 2a45729f95aec98e263461e124593a50
+ 8a8294494830a3bb0148351b5c9d086e
+
+
+
+ 10.00
+ GBP
+ 9318.6958.1986 Recurring
+ 1.0
+ INTERN
+ 2014-09-02 06:44:21
+
+
+
+ 2014-09-02 06:44:21
+ ACK
+ NEW
+ Successful Processing
+ Request successfully processed in 'Merchant in Integrator Test Mode'
+
+
+
+
+ XML
+ end
+
+ def successful_refund_response
+ <<-XML
+
+
+
+
+ 8073.1553.0402
+ 8a82944a4830c4810148350aeeec5e58
+ 94e8c527be2ffb1c120188b4c9bec521
+ 8a82944a4830c4810148352533ef630f
+
+
+
+ 10.00
+ GBP
+ 8073.1553.0402 Recurring
+ 1.0
+ INTERN
+ 2014-09-02 06:55:06
+
+
+
+ 2014-09-02 06:55:06
+ ACK
+ NEW
+ Successful Processing
+ Request successfully processed in 'Merchant in Integrator Test Mode'
+
+
+
+ XML
+ end
+
+ def successful_void_response
+ <<-XML
+
+
+
+
+ 7729.5579.2034
+ 8a8294494830a3bb01483528826e0adf
+ c3238be8ddc9dc269766add6d3d1b48f
+ 8a82944a4830c481014835287b136390
+
+
+
+ 10.00
+ GBP
+ 7729.5579.2034 Recurring
+ 1.0
+ INTERN
+ 2014-09-02 06:58:40
+
+
+
+ 2014-09-02 06:58:40
+ ACK
+ NEW
+ Successful Processing
+ Request successfully processed in 'Merchant in Integrator Test Mode'
+
+
+
+
+ XML
+ end
+
+ def failed_authorize_response
+ <<-XML
+
+
+
+
+ 4272.9698.1666
+ 8a82944a4bc0dc29014bc2c9aaff5d30
+ 72229d5b6311fddfaf5203c9af083cfa
+
+
+
+ 2015-02-25 22:09:31
+ NOK
+ REJECTED_VALIDATION
+ Account Validation
+ invalid creditcard, bank account number or bank name
+
+
+
+ XML
+ end
+
+ def failed_capture_response
+ <<-XML
+
+
+
+
+ 6526.9670.7746
+ 8a8294494830a3bb0148352d46c90bdf
+ 0c99fae829450624ba6151bed0957f05
+ authorization
+
+
+
+ 2014-09-02 07:03:52
+ NOK
+ REJECTED_VALIDATION
+ Reference Error
+ capture needs at least one successful transaction of type (PA)
+
+
+
+
+ XML
+ end
+
+ def failed_refund_response
+ <<-XML
+
+
+
+
+ 3864.0873.5394
+ 8a8294494830a3bb0148353097580ca5
+ 2016aa53f62f498a6452ff0be1fbd060
+ authorization
+
+
+
+ 2014-09-02 07:07:30
+ NOK
+ REJECTED_VALIDATION
+ Configuration Validation
+ Invalid payment data. You are not configured for this currency or sub type (country or brand)
+
+
+
+ XML
+ end
+
+ def failed_void_response
+ <<-XML
+
+
+
+
+ 8395.2778.5122
+ 8a82944a4830c4810148353115b96501
+ abe0ea6036cbf36b1bed45031b3263a7
+ authorization
+
+
+
+ 2014-09-02 07:08:02
+ NOK
+ REJECTED_VALIDATION
+ Reference Error
+ reversal needs at least one successful transaction of type (CP or DB or RB or PA)
+
+
+
+
+ XML
+ end
+end
diff --git a/test/unit/gateways/balanced_test.rb b/test/unit/gateways/balanced_test.rb
index 174d12998ff..68e98b4f0d4 100644
--- a/test/unit/gateways/balanced_test.rb
+++ b/test/unit/gateways/balanced_test.rb
@@ -4,684 +4,866 @@ class BalancedTest < Test::Unit::TestCase
include CommStub
def setup
- @marketplace_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO'
-
- marketplace_uris = {
- 'uri' => @marketplace_uri,
- 'holds_uri' => @marketplace_uri + '/holds',
- 'debits_uri' => @marketplace_uri + '/debits',
- 'cards_uri' => @marketplace_uri + '/cards',
- 'accounts_uri' => @marketplace_uri + '/accounts',
- 'refunds_uri' => @marketplace_uri + '/refunds',
- }
-
@gateway = BalancedGateway.new(
- :login => 'e1c5ad38d1c711e1b36c026ba7e239a9',
- :marketplace => marketplace_uris
+ login: 'e1c5ad38d1c711e1b36c026ba7e239a9'
)
- @credit_card = credit_card
@amount = 100
+ @credit_card = credit_card('4111111111111111')
@options = {
- :email => 'john.buyer@example.org',
- :billing_address => address,
- :description => 'Shopify Purchase'
+ email: 'john.buyer@example.org',
+ billing_address: address,
+ description: 'Shopify Purchase'
}
end
def test_successful_purchase
- @gateway.expects(:ssl_request).times(4).returns(
- successful_account_response
- ).then.returns(
- successful_card_response
+ @gateway.expects(:ssl_request).times(2).returns(
+ cards_response
).then.returns(
- successful_account_response
- ).then.returns(
- successful_purchase_response
+ debits_response
)
-
assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_instance_of Response, response
assert_success response
- assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization
- assert response.test?
+ assert_equal 'Success', response.message
+ assert_equal @amount, response.params['debits'][0]['amount']
end
- def test_successful_purchase_with_existing_account
- @gateway.expects(:ssl_request).times(4).returns(
- failed_account_response
- ).then.returns(
- successful_card_response
- ).then.returns(
- successful_account_response
- ).then.returns(
- successful_purchase_response
- )
+ def test_successful_purchase_with_outside_token
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, '/cards/CCVOX2d7Ar6Ze5TOxHsebeH', @options)
+ end.check_request do |method, endpoint, data, headers|
+ assert_equal('https://api.balancedpayments.com/cards/CCVOX2d7Ar6Ze5TOxHsebeH/debits', endpoint)
+ end.respond_with(debits_response)
- assert response = @gateway.purchase(@amount, @credit_card, @options)
- assert_instance_of Response, response
assert_success response
- assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization
- assert response.test?
+ assert_equal 'Success', response.message
+ assert_equal @amount, response.params['debits'][0]['amount']
end
- def test_successful_purchase_with_existing_account_uri
- @gateway.expects(:ssl_request).times(3).returns(
- successful_card_response
- ).then.returns(
- successful_account_response
+ def test_invalid_card
+ @gateway.expects(:ssl_request).times(2).returns(
+ cards_response
).then.returns(
- successful_purchase_response
+ declined_response
)
- options = @options.clone
- options[:account_uri] = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC58ZKWuGoyQEnDy9ENGRGSq'
- assert response = @gateway.purchase(@amount, @credit_card, options)
- assert_instance_of Response, response
- assert_success response
- assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization
- assert response.test?
+ assert response = @gateway.purchase(@amount, credit_card('4222222222222220'), @options)
+ assert_failure response
+ assert_match %r{Customer call bank}i, response.message
end
- def test_successful_purchase_with_existing_card
- @gateway.expects(:ssl_request).times(1).returns(
- successful_purchase_response
+ def test_invalid_email
+ @gateway.expects(:ssl_request).returns(
+ bad_email_response
)
- options = @options.clone
- credit_card = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/cards
-/CC6r6kLUcxW3MxG3AmZoiuTf'
- options[:account_uri] = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC58ZKWuGoyQEnDy9ENGRGSq'
- assert response = @gateway.purchase(@amount, credit_card, options)
- assert_instance_of Response, response
- assert_success response
- assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization
- assert response.test?
- end
-
- def test_bad_email
- @gateway.stubs(:ssl_request).returns(failed_account_response_bad_email).then.returns(successful_card_response)
-
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway.purchase(@amount, @credit_card, @options.merge(email: 'invalid_email'))
assert_failure response
- assert response.test?
- assert_match /must be a valid email address/, response.message
+ assert_match %r{Invalid field.*email}i, response.message
end
def test_unsuccessful_purchase
- @gateway.expects(:ssl_request).times(4).returns(
- successful_account_response
- ).then.returns(
- successful_card_response
+ @gateway.expects(:ssl_request).times(2).returns(
+ cards_response
).then.returns(
- successful_account_response
- ).then.returns(
- failed_purchase_response
+ account_frozen_response
)
- assert response = @gateway.purchase(@amount, @credit_card, @options)
+ assert response = @gateway.purchase(@amount, credit_card('4444444444444448'), @options)
assert_failure response
- assert response.test?
+ assert_match %r{Account Frozen}i, response.message
end
- def test_successful_authorization
- @gateway.expects(:ssl_request).times(4).returns(
- successful_account_response
+ def test_passing_appears_on_statement
+ @gateway.expects(:ssl_request).times(2).returns(
+ cards_response
).then.returns(
- successful_card_response
- ).then.returns(successful_account_response).then.returns(
- successful_hold_response
+ appears_on_response
)
- assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_instance_of Response, response
+ options = @options.merge(appears_on_statement_as: 'Homer Electric')
+ assert response = @gateway.purchase(@amount, @credit_card, options)
+
assert_success response
- assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5', response.authorization
- assert response.test?
+ assert_equal 'BAL*Homer Electric', response.params['debits'][0]['appears_on_statement_as']
end
- def test_unsuccessful_authorization
- @gateway.expects(:ssl_request).times(4).returns(
- successful_account_response
- ).then.returns(
- successful_card_response
+ def test_authorize_and_capture
+ @gateway.expects(:ssl_request).times(3).returns(
+ cards_response
).then.returns(
- successful_account_response
+ holds_response
).then.returns(
- failed_purchase_response
+ authorized_debits_response
)
- assert response = @gateway.authorize(@amount, @credit_card, @options)
- assert_instance_of Response, response
- assert_failure response
- assert response.test?
- end
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Success', auth.message
- def test_successful_authorization_capture
- @gateway.expects(:ssl_request).times(1).returns(
- successful_purchase_response
- )
- hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
- assert response = @gateway.capture(nil, hold_uri) # captures the full amount
- assert_instance_of Response, response
- assert_success response
- assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization
- assert response.test?
+ assert capture = @gateway.capture(amount, auth.authorization)
+ assert_success capture
+ assert_equal amount, capture.params['debits'][0]['amount']
end
- def test_successful_authorization_capture_with_on_behalf_of_uri
- @gateway.expects(:ssl_request).times(1).returns(
- successful_purchase_response
+ def test_authorize_and_capture_partial
+ @gateway.expects(:ssl_request).times(3).returns(
+ cards_response
+ ).then.returns(
+ holds_response
+ ).then.returns(
+ authorized_partial_debits_response
)
- hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
- on_behalf_of_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/accounts/AC73SN17anKkjk6Y1sVe2uaq'
-
- response = stub_comms do
- @gateway.capture(nil, hold_uri, :on_behalf_of_uri => on_behalf_of_uri)
- end.check_request do |endpoint, data, headers|
- assert_match(/on_behalf_of_uri=\/v1\/marketplaces\/TEST-MP73SaFdpQePv9dOaG5wXOGO\/accounts\/AC73SN17anKkjk6Y1sVe2uaq/, data)
- end.respond_with(successful_purchase_response)
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ assert_equal 'Success', auth.message
- assert_instance_of Response, response
- assert_success response
- assert_equal '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO', response.authorization
- assert response.test?
+ assert capture = @gateway.capture(amount / 2, auth.authorization)
+ assert_success capture
+ assert_equal amount / 2, capture.params['debits'][0]['amount']
end
- def test_unsuccessful_authorization_capture
- @gateway.expects(:ssl_request).times(1).returns(
- failed_purchase_response
+ def test_failed_capture
+ @gateway.expects(:ssl_request).returns(
+ method_not_allowed_response
)
- hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
- assert response = @gateway.capture(0, hold_uri)
- assert_instance_of Response, response
+
+ assert response = @gateway.capture(@amount, 'bogus')
assert_failure response
- assert response.test?
end
- def test_void_authorization_success
- @gateway.expects(:ssl_request).times(1).returns(
- void_hold_response
+ def test_void_authorization
+ @gateway.expects(:ssl_request).times(3).returns(
+ cards_response
+ ).then.returns(
+ holds_response
+ ).then.returns(
+ voided_hold_response
)
- hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
- response = @gateway.void(hold_uri)
- assert_instance_of Response, response
- assert_success response
- assert_equal hold_uri, response.authorization
- assert response.test?
- end
- def test_void_authorization_failure
- @gateway.expects(:ssl_request).times(1).returns(
- void_hold_response_failure
- )
- hold_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
- assert response = @gateway.void(hold_uri)
- assert_instance_of Response, response
- assert_failure response
- assert response.test?
+ amount = @amount
+ assert auth = @gateway.authorize(amount, @credit_card, @options)
+ assert_success auth
+ number = auth.params['card_holds'][0]['href']
+ assert void = @gateway.void(number)
+ assert_success void
+ assert void.params['card_holds'][0]['voided_at']
end
def test_refund_purchase
- @gateway.expects(:ssl_request).times(1).returns(
- successful_refund_response
+ @gateway.expects(:ssl_request).times(3).returns(
+ cards_response
+ ).then.returns(
+ debits_response
+ ).then.returns(
+ refunds_response
)
- debit_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh'
- refund_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/refunds/RF3GhhG5I3AgrjjXsdkRFQDA'
- assert refund = @gateway.refund(@amount, debit_uri)
- assert_instance_of Response, refund
+ assert debit = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success debit
+
+ assert refund = @gateway.refund(@amount, debit.authorization)
assert_success refund
- assert refund.test?
- assert_equal refund.authorization, refund_uri
+ assert_equal @amount, refund.params['refunds'][0]['amount']
end
- def test_refund_purchase_failure
- @gateway.expects(:ssl_request).times(1).returns(
- failed_refund_response
+ def test_refund_partial_purchase
+ @gateway.expects(:ssl_request).times(3).returns(
+ cards_response
+ ).then.returns(
+ debits_response
+ ).then.returns(
+ partial_refunds_response
)
- debit_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh'
- assert refund = @gateway.refund(@amount, debit_uri)
- assert_instance_of Response, refund
- assert_failure refund
- assert refund.test?
- end
-
- def test_deprecated_refund_purchase
- assert_deprecation_warning("Calling the refund method without an amount parameter is deprecated and will be removed in a future version.", @gateway) do
- @gateway.expects(:ssl_request).times(1).returns(
- successful_refund_response
- )
-
- debit_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/debits/WD2Nkre6GkWAV1A52YgLWEkh'
- refund_uri = '/v1/marketplaces/TEST-MP6IEymJ6ynwnSoqJQnUTacN/refunds/RF3GhhG5I3AgrjjXsdkRFQDA'
- assert refund = @gateway.refund(debit_uri)
- assert_instance_of Response, refund
- assert_success refund
- assert refund.test?
- assert_equal refund.authorization, refund_uri
- end
+ assert debit = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success debit
+
+ assert refund = @gateway.refund(@amount / 2, debit.authorization)
+ assert_success refund
+ assert_equal @amount / 2, refund.params['refunds'][0]['amount']
end
- def test_refund_with_nil_debit_uri
- @gateway.expects(:ssl_request).times(1).returns(
- failed_refund_response
+ def test_refund_pending_status
+ @gateway.expects(:ssl_request).times(3).returns(
+ cards_response
+ ).then.returns(
+ debits_response
+ ).then.returns(
+ refunds_pending_response
)
- assert refund = @gateway.refund(nil, nil)
- assert_instance_of Response, refund
- assert_failure refund
+ assert debit = @gateway.purchase(@amount, @credit_card, @options)
+ assert_success debit
+
+ assert refund = @gateway.refund(@amount, debit.authorization)
+ assert_success refund
+ assert_equal 'pending', refund.params['refunds'][0]['status']
+ assert_equal @amount, refund.params['refunds'][0]['amount']
end
def test_store
- @gateway.expects(:ssl_request).times(3).returns(
- successful_account_response # create account
- ).then.returns(
- successful_card_response # create card
- ).then.returns(
- successful_account_response # associate card to account
+ @gateway.expects(:ssl_request).returns(
+ cards_response
)
- card_uri = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/cards/CC6r6kLUcxW3MxG3AmZoiuTf'
+ new_email_address = '%d@example.org' % Time.now
assert response = @gateway.store(@credit_card, {
- :email=>'john.buyer@example.org'
+ email: new_email_address
})
- assert_instance_of String, response
- assert_equal card_uri, response
+ assert_instance_of String, response.authorization
+ end
+
+ def test_successful_purchase_with_legacy_outside_token
+ legacy_outside_token = '/v1/marketplaces/MP6oR9hHNlu2BLVsRRoQL3Gg/cards/CC7m1Mtqk6rVJo5tcD1qitAC'
+
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, legacy_outside_token, @options)
+ end.check_request do |method, endpoint, data, headers|
+ assert_equal('https://api.balancedpayments.com/cards/CC7m1Mtqk6rVJo5tcD1qitAC/debits', endpoint)
+ end.respond_with(debits_response)
+
+ assert_success response
+ assert_equal 'Success', response.message
+ assert_equal @amount, response.params['debits'][0]['amount']
end
- def test_ensure_does_not_respond_to_credit
- assert !@gateway.respond_to?(:credit)
+ def test_capturing_legacy_authorizations
+ v1_authorization = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
+ v11_authorization = '/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5/debits||/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
+
+ [v1_authorization, v11_authorization].each do |authorization|
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.capture(@amount, authorization)
+ end.check_request do |method, endpoint, data, headers|
+ assert_equal('https://api.balancedpayments.com/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5/debits', endpoint)
+ end.respond_with(authorized_debits_response)
+ end
end
- def test_ensure_does_not_respond_to_unstore
- assert !@gateway.respond_to?(:unstore)
+ def test_voiding_legacy_authorizations
+ v1_authorization = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
+ v11_authorization = '/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5/debits||/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5'
+
+ [v1_authorization, v11_authorization].each do |authorization|
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.void(authorization)
+ end.check_request do |method, endpoint, data, headers|
+ assert_equal :put, method
+ assert_equal('https://api.balancedpayments.com/card_holds/HL7dYMhpVBcqAYqxLF5mZtQ5', endpoint)
+ assert_match %r{\bis_void=true\b}, data
+ end.respond_with(voided_hold_response)
+ end
+ end
+
+ def test_refunding_legacy_purchases
+ v1_authorization = '/v1/marketplaces/TEST-MP73SaFdpQePv9dOaG5wXOGO/debits/WD2x6vLS7RzHYEcdymqRyNAO'
+ v11_authorization = '|/debits/WD2x6vLS7RzHYEcdymqRyNAO/refunds|'
+
+ [v1_authorization, v11_authorization].each do |authorization|
+ stub_comms(@gateway, :ssl_request) do
+ @gateway.refund(nil, authorization)
+ end.check_request do |method, endpoint, data, headers|
+ assert_equal('https://api.balancedpayments.com/debits/WD2x6vLS7RzHYEcdymqRyNAO/refunds', endpoint)
+ end.respond_with(refunds_response)
+ end
+ end
+
+ def test_passing_address
+ a = address
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, address: a)
+ end.check_request do |method, endpoint, data, headers|
+ next if endpoint =~ /debits/
+ clean = proc { |s| Regexp.escape(CGI.escape(s)) }
+ assert_match(%r{address\[line1\]=#{clean[a[:address1]]}}, data)
+ assert_match(%r{address\[line2\]=#{clean[a[:address2]]}}, data)
+ assert_match(%r{address\[city\]=#{clean[a[:city]]}}, data)
+ assert_match(%r{address\[state\]=#{clean[a[:state]]}}, data)
+ assert_match(%r{address\[postal_code\]=#{clean[a[:zip]]}}, data)
+ assert_match(%r{address\[country_code\]=#{clean[a[:country]]}}, data)
+ end.respond_with(cards_response, debits_response)
+
+ assert_success response
+ end
+
+ def test_passing_address_without_zip
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, address: address(zip: nil))
+ end.check_request do |method, endpoint, data, headers|
+ next if endpoint =~ /debits/
+ assert_no_match(%r{address}, data)
+ end.respond_with(cards_response, debits_response)
+
+ assert_success response
+ end
+
+ def test_passing_address_with_blank_zip
+ response = stub_comms(@gateway, :ssl_request) do
+ @gateway.purchase(@amount, @credit_card, address: address(zip: ' '))
+ end.check_request do |method, endpoint, data, headers|
+ next if endpoint =~ /debits/
+ assert_no_match(%r{address}, data)
+ end.respond_with(cards_response, debits_response)
+
+ assert_success response
end
private
+ def invalid_login_response
+ %(
+{
+ "errors": [
+ {
+ "status": "Unauthorized",
+ "category_code": "authentication-required",
+ "description": "
The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.
In case you are allowed to request the document, please check your user-id and password and try again.
The server could not verify that you are authorized to access the URL requested. You either supplied the wrong credentials (e.g. a bad password), or your browser doesn't understand how to supply the credentials required.
In case you are allowed to request the document, please check your user-id and password and try again.