lib/active_merchant/billing/gateways/cyber_source.rb in activemerchant-1.60.0 vs lib/active_merchant/billing/gateways/cyber_source.rb in activemerchant-1.61.0

- old
+ new

@@ -1,19 +1,10 @@ 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. - # # Important Notes # * For checks you can purchase and store. # * 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 @@ -33,26 +24,26 @@ 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.121" - # 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' } - # 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", @@ -114,42 +105,32 @@ def initialize(options = {}) requires!(options, :login, :password) 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 = {}) setup_address_hash(options) - commit(build_auth_request(money, creditcard_or_reference, options), options ) + commit(build_auth_request(money, creditcard_or_reference, options), :authorize, money, 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 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 = {}) 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 def verify(payment, options = {}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, payment, options) } @@ -157,38 +138,38 @@ end end # Adds credit to a subscription (stand alone credit). def credit(money, reference, options = {}) - commit(build_credit_request(money, reference, options), options) + commit(build_credit_request(money, 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 = {}) 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 # 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 @@ -216,17 +197,17 @@ # This functionality is only supported by this particular gateway may # be changed at any time 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 @@ -243,10 +224,15 @@ 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 or even none def setup_address_hash(options) @@ -306,27 +292,23 @@ end 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) + 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_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) - xml.target! - end - def build_refund_request(money, identification, options) order_id, request_id, request_token = identification.split(";") options[:order_id] = order_id xml = Builder::XmlMarkup.new :indent => 2 @@ -427,11 +409,11 @@ 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] xml.tag! 'productSKU', value[:sku] end @@ -447,11 +429,11 @@ 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 def add_address(xml, payment_method, address, options, shipTo = false) xml.tag! shipTo ? 'shipTo' : 'billTo' do @@ -460,11 +442,11 @@ xml.tag! 'street1', address[:address1] xml.tag! 'street2', address[:address2] unless address[:address2].blank? 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] unless address[:phone].blank? xml.tag! 'email', options[:email] || 'null@cybersource.com' xml.tag! 'ipAddress', options[:ip] unless options[:ip].blank? || shipTo @@ -490,11 +472,11 @@ end end def add_mdd_fields(xml, options) xml.tag! 'merchantDefinedData' do - (1..20).each do |each| + (1..100).each do |each| key = "mdd_field_#{each}".to_sym xml.tag!("field#{each}", options[key]) if options[key] end end end @@ -623,11 +605,11 @@ _, subscription_id, _ = reference.split(";") xml.tag! 'subscriptionID', subscription_id end xml.tag! 'status', options[:subscription][:status] if options[:subscription][:status] - xml.tag! 'amount', 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] @@ -667,10 +649,15 @@ def add_validate_pinless_debit_service(xml) xml.tag!'pinlessDebitValidateService', {'run' => 'true'} end + def lookup_country_code(country_field) + country_code = Country.find(country_field) rescue nil + country_code.code(:alpha2) + 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 @@ -692,19 +679,19 @@ xml.target! end # Contact CyberSource, make the SOAP request, and parse the reply into a # Response object - def commit(request, options) + def commit(request, action, amount, options) begin response = parse(ssl_post(test? ? self.test_url : self.live_url, build_request(request, options))) rescue ResponseError => e response = parse(e.response.body) 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 + authorization = success ? [ options[:order_id], response[:requestID], response[:requestToken], action, amount, options[:currency]].compact.join(";") : nil Response.new(success, message, response, :test => test?, :authorization => authorization, :avs_result => { :code => response[:avsCode] },