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 = %w[US FI NO SE GB] self.default_currency = 'USD' self.money_format = :cents self.supported_cardtypes = %i[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