lib/active_merchant/billing/gateways/sage_pay.rb in activemerchant-1.133.0 vs lib/active_merchant/billing/gateways/sage_pay.rb in activemerchant-1.137.0

- old
+ new

@@ -4,12 +4,12 @@ cattr_accessor :simulate self.simulate = false class_attribute :simulator_url - self.test_url = 'https://test.sagepay.com/gateway/service' - self.live_url = 'https://live.sagepay.com/gateway/service' + self.test_url = 'https://sandbox.opayo.eu.elavon.com/gateway/service' + self.live_url = 'https://live.opayo.eu.elavon.com/gateway/service' self.simulator_url = 'https://test.sagepay.com/Simulator' APPROVED = 'OK' TRANSACTIONS = { @@ -76,18 +76,22 @@ self.homepage_url = 'http://www.sagepay.com' self.display_name = 'SagePay' def initialize(options = {}) requires!(options, :login) + @protocol_version = options.fetch(:protocol_version, '3.00') super end def purchase(money, payment_method, options = {}) requires!(options, :order_id) post = {} + add_override_protocol_version(options) + add_three_ds_data(post, options) + add_stored_credentials_data(post, options) add_amount(post, money, options) add_invoice(post, options) add_payment_method(post, payment_method, options) add_address(post, options) add_customer_data(post, options) @@ -99,10 +103,13 @@ def authorize(money, payment_method, options = {}) requires!(options, :order_id) post = {} + add_three_ds_data(post, options) + add_stored_credentials_data(post, options) + add_override_protocol_version(options) add_amount(post, money, options) add_invoice(post, options) add_payment_method(post, payment_method, options) add_address(post, options) add_customer_data(post, options) @@ -113,19 +120,21 @@ # You can only capture a transaction once, even if you didn't capture the full amount the first time. def capture(money, identification, options = {}) post = {} + add_override_protocol_version(options) add_reference(post, identification) add_release_amount(post, money, options) commit(:capture, post) end def void(identification, options = {}) post = {} + add_override_protocol_version(options) add_reference(post, identification) action = abort_or_void_from(identification) commit(action, post) end @@ -134,10 +143,11 @@ def refund(money, identification, options = {}) requires!(options, :order_id, :description) post = {} + add_override_protocol_version(options) add_related_reference(post, identification) add_amount(post, money, options) add_invoice(post, options) commit(:credit, post) @@ -148,18 +158,20 @@ refund(money, identification, options) end def store(credit_card, options = {}) post = {} + add_override_protocol_version(options) add_credit_card(post, credit_card) add_currency(post, 0, options) commit(:store, post) end def unstore(token, options = {}) post = {} + add_override_protocol_version(options) add_token(post, token) commit(:unstore, post) end def verify(credit_card, options = {}) @@ -180,10 +192,62 @@ gsub(%r((&?CV2=)\d+(&?)), '\1[FILTERED]\2') end private + def add_override_protocol_version(options) + @protocol_version = options[:protocol_version] if options[:protocol_version] + end + + def add_three_ds_data(post, options) + return unless @protocol_version == '4.00' + return unless three_ds_2_options = options[:three_ds_2] + + add_pair(post, :ThreeDSNotificationURL, three_ds_2_options[:notification_url]) + return unless three_ds_2_options[:browser_info] + + add_browser_info(post, three_ds_2_options[:browser_info]) + end + + def add_browser_info(post, browser_info) + add_pair(post, :BrowserAcceptHeader, browser_info[:accept_header]) + add_pair(post, :BrowserColorDepth, browser_info[:depth]) + add_pair(post, :BrowserJavascriptEnabled, format_boolean(browser_info[:java])) + add_pair(post, :BrowserJavaEnabled, format_boolean(browser_info[:java])) + add_pair(post, :BrowserLanguage, browser_info[:language]) + add_pair(post, :BrowserScreenHeight, browser_info[:height]) + add_pair(post, :BrowserScreenWidth, browser_info[:width]) + add_pair(post, :BrowserTZ, browser_info[:timezone]) + add_pair(post, :BrowserUserAgent, browser_info[:user_agent]) + add_pair(post, :ChallengeWindowSize, browser_info[:browser_size]) + end + + def add_stored_credentials_data(post, options) + return unless @protocol_version == '4.00' + return unless stored_credential = options[:stored_credential] + + initiator = stored_credential[:initiator] == 'cardholder' ? 'CIT' : 'MIT' + cof_usage = if stored_credential[:initial_transaction] && initiator == 'CIT' + 'FIRST' + elsif !stored_credential[:initial_transaction] && initiator == 'MIT' + 'SUBSEQUENT' + end + + add_pair(post, :COFUsage, cof_usage) if cof_usage + add_pair(post, :InitiatedTYPE, initiator) + add_pair(post, :SchemeTraceID, stored_credential[:network_transaction_id]) if stored_credential[:network_transaction_id] + + reasoning = stored_credential[:reason_type] == 'installment' ? 'instalment' : stored_credential[:reason_type] + add_pair(post, :MITType, reasoning.upcase) + + if %w(instalment recurring).any?(reasoning) + add_pair(post, :RecurringExpiry, options[:recurring_expiry]) + add_pair(post, :RecurringFrequency, options[:recurring_frequency]) + add_pair(post, :PurchaseInstalData, options[:installment_data]) + end + end + 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 @@ -344,18 +408,22 @@ end def commit(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_CODE[response['AddressResult']], postal_match: AVS_CODE[response['PostCodeResult']] }, - cvv_result: CVV_CODE[response['CV2Result']]) + cvv_result: CVV_CODE[response['CV2Result']] + ) end def authorization_from(response, params, action) case action when :store @@ -399,15 +467,21 @@ def post_data(action, parameters = {}) parameters.update( Vendor: @options[:login], TxType: TRANSACTIONS[action], - VPSProtocol: @options.fetch(:protocol_version, '3.00') + VPSProtocol: @protocol_version ) parameters.update(ReferrerID: application_id) if application_id && (application_id != Gateway.application_id) parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') + end + + def format_boolean(value) + return if value.nil? + + value ? '1' : '0' end # SagePay returns data in the following format # Key1=value1 # Key2=value2