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 = %i[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