lib/active_merchant/billing/gateways/cyber_source.rb in activemerchant-1.21.0 vs lib/active_merchant/billing/gateways/cyber_source.rb in activemerchant-1.22.0
- old
+ new
@@ -1,39 +1,41 @@
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
+ #
+ # 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.
+ # Center for the full error messages including field names.
#
# Important Notes
- # * AVS and CVV only work against the production server. You will always get back X for AVS and no response for CVV against the test server.
- # * Nexus is the list of states or provinces where you have a physical presence. Nexus is used to calculate tax. Leave blank to tax everyone.
- # * If you want to calculate VAT for overseas customers you must supply a registration number in the options hash as vat_reg_number.
+ # * AVS and CVV only work against the production server. You will always get back X for AVS and no response for CVV against the test server.
+ # * Nexus is the list of states or provinces where you have a physical presence. Nexus is used to calculate tax. Leave blank to tax everyone.
+ # * If you want to calculate VAT for overseas customers you must supply a registration number in the options hash as vat_reg_number.
# * productCode is a value in the line_items hash that is used to tell CyberSource what kind of item you are selling. It is used when calculating tax/VAT.
# * All transactions use dollar values.
class CyberSourceGateway < Gateway
TEST_URL = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor'
LIVE_URL = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor'
-
+
+ XSD_VERSION = "1.69"
+
# visa, master, american_express, discover
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.supported_countries = ['US']
self.default_currency = 'USD'
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'
- }
+ }
# map response codes to something humans can read
@@response_codes = {
:r100 => "Successful transaction",
:r101 => "Request is missing one or more required fields" ,
@@ -41,21 +43,21 @@
: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",
+ :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",
@@ -70,48 +72,48 @@
: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"
+ :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 Gateway object.
- #
- # :login => your username
#
- # :password => the transaction key you generated in the Business Center
+ # :login => your username
#
+ # :password => the transaction key you generated in the Business Center
+ #
# :test => true sets the gateway to test mode
#
- # :vat_reg_number => your VAT registration number
+ # :vat_reg_number => your VAT registration number
#
# :nexus => "WI CA QC" sets the states/provinces where you have a physical presense for tax purposes
#
- # :ignore_avs => true don't want to use AVS so continue processing even if AVS would have failed
+ # :ignore_avs => true don't want to use AVS so continue processing even if AVS would have failed
#
- # :ignore_cvv => true don't want to use CVV so continue processing even if CVV would have failed
+ # :ignore_cvv => true don't want to use CVV so continue processing even if CVV would have failed
def initialize(options = {})
requires!(options, :login, :password)
@options = options
super
- end
+ end
# Should run against the test servers or not?
def test?
@options[:test] || Base.gateway_mode == :test
end
-
- # Request an authorization for an amount from CyberSource
+
+ # Request an authorization for an amount from CyberSource
#
- # You must supply an :order_id in the options hash
+ # You must supply an :order_id in the options hash
def authorize(money, creditcard, options = {})
requires!(options, :order_id, :email)
setup_address_hash(options)
commit(build_auth_request(money, creditcard, options), options )
end
-
+
def auth_reversal(money, identification, options = {})
commit(build_auth_reversal_request(money, identification, options), options)
end
# Capture an authorization that has previously been requested
@@ -119,35 +121,103 @@
setup_address_hash(options)
commit(build_capture_request(money, authorization, options), options)
end
# Purchase is an auth followed by a capture
- # You must supply an order_id in the options hash
- def purchase(money, creditcard, options = {})
+ # You must supply an order_id in the options hash
+ def purchase(money, payment_source, options = {})
requires!(options, :order_id, :email)
setup_address_hash(options)
- commit(build_purchase_request(money, creditcard, options), options)
+ if payment_source.is_a?(String)
+ requires!(options, [:type, :credit_card, :check])
+ commit(build_subscription_purchase_request(money, payment_source, options), options)
+ else
+ commit(build_purchase_request(money, payment_source, options), options)
+ end
end
-
+
def void(identification, options = {})
commit(build_void_request(identification, options), options)
end
def refund(money, identification, options = {})
commit(build_credit_request(money, identification, options), options)
end
-
+
def credit(money, identification, options = {})
deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
+ # Creates or updates a cybersource customer profile, aka a subscription with type "on-demand"
+ # to charge the card while creating a profile, pass options[:setup_fee] => money
+ def store(credit_card_or_reference, options = {})
+
+ requires!(options, :order_id)
+ setup_address_hash(options)
+
+ if credit_card_or_reference.respond_to?(:number)
+ # create subscription
+ requires!(options, :billing_address, :email)
+ requires!(options[:billing_address], :first_name, :last_name)
+
+ # set subscription options for storing the credit card
+ options[:subscription] ||={}
+ options[:subscription].merge!(:frequency => "on-demand", :amount => 0, :auto_renew => false)
+
+ setup_address_hash(options)
+ request = build_create_subscription_request(credit_card_or_reference, options)
+ else
+ # update subscription
+ request = build_update_subscription_request(credit_card_or_reference, options)
+ end
+
+ commit(request, options)
+ end
+
+ # retrieves a customer subscription/profile
+ def retrieve(reference, options = {})
+ requires!(options, :order_id)
+ commit(build_retrieve_subscription_request(reference, options), options)
+ end
+
+ # removes a customer subscription/profile
+ def unstore(reference, options = {})
+ requires!(options, :order_id)
+ commit(build_delete_subscription_request(reference, options), options)
+ end
+
+ # Creates or updates a Cybersource recurring payment profile/subscription
+ def recurring(money, credit_card_or_reference, options = {})
+ requires!(options, :order_id, :subscription)
+ requires!(options[:subscription], [:frequency, "on-demand", "weekly", "bi-weekly", "semi-monthly", "quarterly", "quad-weekly", "semi-annually", "annually"])
+
+ options[:subscription].merge!(:amount => money)
+
+ setup_address_hash(options)
+
+ if credit_card_or_reference.respond_to?(:number)
+ # create subscription
+ requires!(options, :billing_address, :email)
+ requires!(options[:billing_address], :first_name, :last_name)
+
+ setup_address_hash(options)
+ request = build_create_subscription_request(credit_card_or_reference, options)
+ else
+ # update subscription
+ request = build_update_subscription_request(credit_card_or_reference, options)
+ end
+
+ commit(request, options)
+ end
+
+
# CyberSource requires that you provide line item information for tax calculations
# If you do not have prices for each item or want to simplify the situation then pass in one fake line item that costs the subtotal of the order
#
- # The line_item hash goes in the options hash and should look like
- #
+ # The line_item hash goes in the options hash and should look like
+ #
# :line_items => [
# {
# :declared_value => '1',
# :quantity => '2',
# :code => 'default',
@@ -163,69 +233,70 @@
# }
# ]
#
# This functionality is only supported by this particular gateway may
# be changed at any time
- def calculate_tax(creditcard, options)
+ def calculate_tax(options)
requires!(options, :line_items)
+
setup_address_hash(options)
- commit(build_tax_calculation_request(creditcard, options), options)
+ commit(build_tax_calculation_request(options), options)
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
+
+ private
+ # Create all address hash key value pairs so that we still function if we were only provided with one or two of them
def setup_address_hash(options)
options[:billing_address] = options[:billing_address] || options[:address] || {}
options[:shipping_address] = options[:shipping_address] || {}
end
-
+
def build_auth_request(money, creditcard, options)
xml = Builder::XmlMarkup.new :indent => 2
- add_address(xml, creditcard, options[:billing_address], options)
+ add_address(xml, options[:billing_address], options)
add_purchase_data(xml, money, true, options)
add_creditcard(xml, creditcard)
add_auth_service(xml)
add_business_rules_data(xml)
xml.target!
end
- def build_tax_calculation_request(creditcard, options)
+ def build_tax_calculation_request(options)
xml = Builder::XmlMarkup.new :indent => 2
- add_address(xml, creditcard, options[:billing_address], options, false)
- add_address(xml, creditcard, options[:shipping_address], options, true)
+ add_address(xml, options[:billing_address], options, false)
+ add_address(xml, options[:shipping_address], options, true) unless options[:shipping_address].empty?
add_line_item_data(xml, options)
add_purchase_data(xml, 0, false, options)
- add_tax_service(xml)
+ add_tax_service(xml, options)
add_business_rules_data(xml)
xml.target!
end
-
+
def build_capture_request(money, authorization, options)
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)
xml.target!
- end
+ end
- def build_purchase_request(money, creditcard, options)
+ def build_purchase_request(money, payment_source, options)
xml = Builder::XmlMarkup.new :indent => 2
- add_address(xml, creditcard, options[:billing_address], options)
+ add_address(xml, options[:billing_address], options)
add_purchase_data(xml, money, true, options)
- add_creditcard(xml, creditcard)
- add_purchase_service(xml, options)
+ add_payment_source(xml, payment_source)
+ add_purchase_service(xml, payment_source, options)
add_business_rules_data(xml)
xml.target!
end
-
+
def build_void_request(identification, options)
order_id, request_id, request_token = identification.split(";")
options[:order_id] = order_id
-
+
xml = Builder::XmlMarkup.new :indent => 2
add_void_service(xml, request_id, request_token)
xml.target!
end
@@ -239,99 +310,213 @@
end
def build_credit_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_credit_service(xml, request_id, request_token)
-
+
xml.target!
end
+ def build_create_subscription_request(payment_source, options)
+ xml = Builder::XmlMarkup.new :indent => 2
+ add_address(xml, options[:billing_address], options)
+ add_purchase_data(xml, options[:setup_fee], true, options)
+
+
+ case determine_funding_source(payment_source)
+ when :credit_card then add_creditcard(xml, payment_source)
+ when :check then add_check(xml, payment_source)
+ else raise ArgumentError, "Unsupported funding source provided"
+ end
+
+ add_subscription(xml, options, payment_source)
+ add_subscription_create_service(xml, options)
+ add_business_rules_data(xml)
+ xml.target!
+ end
+
+ def build_update_subscription_request(identification, options)
+ reference_code, subscription_id, request_token = identification.split(";")
+ options[:subscription] ||= {}
+ options[:subscription][:subscription_id] = subscription_id
+
+ xml = Builder::XmlMarkup.new :indent => 2
+ add_address(xml, options[:billing_address], options) unless options[:billing_address].blank?
+ add_purchase_data(xml, options[:setup_fee], true, options) unless options[:setup_fee].blank?
+ add_creditcard(xml, options[:credit_card]) if options[:credit_card]
+ add_subscription(xml, options)
+ add_subscription_update_service(xml, options)
+ add_business_rules_data(xml)
+ xml.target!
+ end
+
+ def build_retrieve_subscription_request(identification, options)
+ reference_code, subscription_id, request_token = identification.split(";")
+ options[:subscription] ||= {}
+ options[:subscription][:subscription_id] = subscription_id
+
+ xml = Builder::XmlMarkup.new :indent => 2
+ add_subscription(xml, options)
+ add_subscription_retrieve_service(xml, options)
+ xml.target!
+ end
+
+ def build_delete_subscription_request(identification, options)
+ reference_code, subscription_id, request_token = identification.split(";")
+ options[:subscription] ||= {}
+ options[:subscription][:subscription_id] = subscription_id
+
+ xml = Builder::XmlMarkup.new :indent => 2
+ add_subscription(xml, options)
+ add_subscription_delete_service(xml, options)
+ xml.target!
+ end
+
+ def build_subscription_purchase_request(money, identification, options)
+ reference_code, subscription_id, request_token = identification.split(";")
+ options[:subscription] ||= {}
+ options[:subscription][:subscription_id] = subscription_id
+
+ xml = Builder::XmlMarkup.new :indent => 2
+ add_purchase_data(xml, money, true, options)
+ add_subscription(xml, options)
+
+ case options[:type]
+ when :credit_card then add_cc_purchase_service(xml, options)
+ when :check then add_check_service(xml)
+ end
+
+ add_business_rules_data(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]
- end
+ end
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', amount(value[:declared_value])
xml.tag! 'quantity', value[:quantity]
xml.tag! 'productCode', value[:code] || 'shipping_only'
xml.tag! 'productName', value[:description]
xml.tag! 'productSKU', value[:sku]
end
end
end
-
+
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! 'clientLibraryVersion', '1.0'
- xml.tag! 'clientEnvironment' , 'Linux'
+ xml.tag! 'clientLibraryVersion', VERSION
+ xml.tag! 'clientEnvironment' , RUBY_PLATFORM
end
- def add_purchase_data(xml, money = 0, include_grand_total = false, options={})
+ def add_payment_source(xml, source, options={})
+ case determine_funding_source(source)
+ #when :subscription then add_customer_vault_id(params, source)
+ when :credit_card then add_creditcard(xml, source)
+ when :check then add_check(xml, source)
+ end
+ end
+
+ def add_purchase_data(xml, money, include_grand_total = false, options={})
+ money ||=0
xml.tag! 'purchaseTotals' do
xml.tag! 'currency', options[:currency] || currency(money)
- xml.tag!('grandTotalAmount', amount(money)) if include_grand_total
+ xml.tag!('grandTotalAmount', amount(money)) if include_grand_total
end
end
- def add_address(xml, creditcard, address, options, shipTo = false)
+ def add_address(xml, address, options, shipTo = false)
xml.tag! shipTo ? 'shipTo' : 'billTo' do
- xml.tag! 'firstName', creditcard.first_name
- xml.tag! 'lastName', creditcard.last_name
- xml.tag! 'street1', address[:address1]
- xml.tag! 'street2', address[:address2]
- xml.tag! 'city', address[:city]
- xml.tag! 'state', address[:state]
- xml.tag! 'postalCode', address[:zip]
- xml.tag! 'country', address[:country]
- xml.tag! 'email', options[:email]
- end
+ xml.tag! 'firstName', address[:first_name]
+ xml.tag! 'lastName', address[:last_name]
+ xml.tag! 'street1', address[:address1]
+ xml.tag! 'street2', address[:address2]
+ xml.tag! 'city', address[:city]
+ xml.tag! 'state', address[:state]
+ xml.tag! 'postalCode', address[:zip]
+ xml.tag! 'country', address[:country]
+ 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] unless options[:email].blank?
+ 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
end
- def add_creditcard(xml, creditcard)
+ def add_creditcard(xml, creditcard)
xml.tag! 'card' do
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! 'cardType', @@credit_card_codes[card_brand(creditcard).to_sym]
end
end
- def add_tax_service(xml)
+ def add_check(xml, check)
+ #convert check object account type into cybs account type code
+ if check.account_type == "checking"
+ accountType = check.account_holder_type == "business" ? 'X' : 'C'
+ else
+ accountType = 'S'
+ end
+
+ xml.tag! 'check' do
+ xml.tag! 'accountNumber', check.account_number
+ xml.tag! 'accountType', accountType
+ xml.tag! 'bankTransitNumber', check.routing_number
+ xml.tag! 'checkNumber', check.number if check.number
+ end
+ end
+
+ def add_check_service(xml)
+ xml.tag! 'ecDebitService', {'run' => 'true'}
+ end
+
+ def add_tax_service(xml, options)
xml.tag! 'taxService', {'run' => 'true'} do
- xml.tag!('nexus', @options[:nexus]) unless @options[:nexus].blank?
- xml.tag!('sellerRegistration', @options[:vat_reg_number]) unless @options[:vat_reg_number].blank?
+ xml.tag!('nexus', options[:nexus]) unless options[:nexus].blank?
+ xml.tag!('sellerRegistration', @ptions[:vat_reg_number]) unless options[:vat_reg_number].blank?
end
end
def add_auth_service(xml)
- xml.tag! 'ccAuthService', {'run' => 'true'}
+ xml.tag! 'ccAuthService', {'run' => 'true'}
end
def add_capture_service(xml, request_id, request_token)
xml.tag! 'ccCaptureService', {'run' => 'true'} do
xml.tag! 'authRequestID', request_id
xml.tag! 'authRequestToken', request_token
end
end
- def add_purchase_service(xml, options)
+ def add_purchase_service(xml, source, options)
+ case determine_funding_source(source)
+ when :credit_card then add_cc_purchase_service(xml, options)
+ when :check then add_check_service(xml)
+ end
+ end
+
+ def add_cc_purchase_service(xml, options)
xml.tag! 'ccAuthService', {'run' => 'true'}
xml.tag! 'ccCaptureService', {'run' => 'true'}
end
-
+
def add_void_service(xml, request_id, request_token)
xml.tag! 'voidService', {'run' => 'true'} do
xml.tag! 'voidRequestID', request_id
xml.tag! 'voidRequestToken', request_token
end
@@ -349,11 +534,51 @@
xml.tag! 'captureRequestID', request_id
xml.tag! 'captureRequestToken', request_token
end
end
-
+
+ def add_subscription_create_service(xml, options)
+ add_cc_purchase_service(xml, options) if options[:setup_fee]
+ xml.tag! 'paySubscriptionCreateService', {'run' => 'true'}
+ end
+
+ def add_subscription_update_service(xml, options)
+ add_cc_purchase_service(xml, options) if options[:setup_fee]
+ xml.tag! 'paySubscriptionUpdateService', {'run' => 'true'}
+ end
+
+ def add_subscription_retrieve_service(xml, options)
+ xml.tag! 'paySubscriptionRetrieveService', {'run' => 'true'}
+ end
+
+ def add_subscription_delete_service(xml, options)
+ xml.tag! 'paySubscriptionDeleteService', {'run' => 'true'}
+ end
+
+ def add_subscription(xml, options, payment_source=nil)
+ if payment_source
+ xml.tag! 'subscription' do
+ xml.tag! 'paymentMethod', determine_funding_source(payment_source).to_s.gsub(/_/, " ")
+ end
+ end
+
+ xml.tag! 'recurringSubscriptionInfo' do
+ xml.tag! 'subscriptionID', options[:subscription][:subscription_id]
+ xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status]
+ xml.tag! 'amount', options[:subscription][:amount] if options[:subscription][:amount]
+ xml.tag! 'numberOfPayments', options[:subscription][:occurrences] if options[:subscription][:occurrences]
+ xml.tag! 'automaticRenew', options[:subscription][:auto_renew] if options[:subscription][:auto_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! '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]
+ end
+ end
+
# Where we actually build the full SOAP request using builder
def build_request(body, options)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do
@@ -364,67 +589,78 @@
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
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-1.32'} 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!
+ 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? ? TEST_URL : LIVE_URL, build_request(request, options)))
-
+ request = build_request(request, options)
+ post_response = ssl_post(test? ? TEST_URL : LIVE_URL, request)
+ response = parse(post_response)
success = response[:decision] == "ACCEPT"
- message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
+ message = @@response_codes[('r' + response[:reasonCode]).to_sym] rescue response[:message]
authorization = success ? [ options[:order_id], response[:requestID], response[:requestToken] ].compact.join(";") : nil
-
- Response.new(success, message, response,
- :test => test?,
+
+ Response.new(success, message, response,
+ :test => test?,
:authorization => authorization,
:avs_result => { :code => response[:avsCode] },
:cvv_result => response[:cvCode]
)
end
-
+
# Parse the SOAP response
# Technique inspired by the Paypal Gateway
def parse(xml)
reply = {}
xml = REXML::Document.new(xml)
if root = REXML::XPath.first(xml, "//c:replyMessage")
root.elements.to_a.each do |node|
- case node.name
+ case node.name
when 'c:reasonCode'
reply[:message] = reply(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
return reply
- end
+ end
def parse_element(reply, node)
if node.has_elements?
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
+ else
reply[node.name.to_sym] = node.text
end
end
return reply
end
- end
- end
-end
+
+ def determine_funding_source(source)
+ case
+ when source.is_a?(String) then :subscription
+ when CreditCard.card_companies.keys.include?(card_brand(source)) then :credit_card
+ when card_brand(source) == 'check' then :check
+ else raise ArgumentError, "Unsupported funding source provided"
+ end
+ end
+
+ end
+ end
+end