Completed
Pull Request — master (#114)
by Naman
01:45
created

XmlTransaction.setOAuthOptions()   A

Complexity

Conditions 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
c 1
b 0
f 1
dl 0
loc 17
rs 9.4285
1
module AuthorizeNet
2
3
  # The ARB transaction class.
4
  class XmlTransaction < AuthorizeNet::Transaction
5
    
6
    # The XML namespace used by the ARB API.
7
    XML_NAMESPACE = 'AnetApi/xml/v1/schema/AnetApiSchema.xsd'
8
    
9
    # Constants for both the various Authorize.Net subscription gateways are defined here.
10
    module Gateway
11
      LIVE = 'https://api2.authorize.net/xml/v1/request.api'
12
      TEST = 'https://apitest.authorize.net/xml/v1/request.api'
13
    end
14
    
15
    # Constants for both the various Authorize.Net transaction types are defined here.
16
    module Type
17
      ARB_CREATE = "ARBCreateSubscriptionRequest"
18
      ARB_UPDATE = "ARBUpdateSubscriptionRequest"
19
      ARB_GET_STATUS = "ARBGetSubscriptionStatusRequest"
20
      ARB_CANCEL = "ARBCancelSubscriptionRequest"
21
      ARB_GET_SUBSCRIPTION_LIST = "ARBGetSubscriptionListRequest"
22
      CIM_CREATE_PROFILE = "createCustomerProfileRequest"
23
      CIM_CREATE_PAYMENT = "createCustomerPaymentProfileRequest"
24
      CIM_CREATE_ADDRESS = "createCustomerShippingAddressRequest"
25
      CIM_CREATE_TRANSACTION = "createCustomerProfileTransactionRequest"
26
      CIM_DELETE_PROFILE = "deleteCustomerProfileRequest"
27
      CIM_DELETE_PAYMENT = "deleteCustomerPaymentProfileRequest"
28
      CIM_DELETE_ADDRESS = "deleteCustomerShippingAddressRequest"
29
      CIM_GET_PROFILE_IDS = "getCustomerProfileIdsRequest"
30
      CIM_GET_PROFILE = "getCustomerProfileRequest"
31
      CIM_GET_PAYMENT = "getCustomerPaymentProfileRequest"
32
      CIM_GET_ADDRESS = "getCustomerShippingAddressRequest"
33
      CIM_GET_HOSTED_PROFILE = "getHostedProfilePageRequest"
34
      CIM_UPDATE_PROFILE = "updateCustomerProfileRequest"
35
      CIM_UPDATE_PAYMENT = "updateCustomerPaymentProfileRequest"
36
      CIM_UPDATE_ADDRESS = "updateCustomerShippingAddressRequest"
37
      CIM_UPDATE_SPLIT = "updateSplitTenderGroupRequest"
38
      CIM_VALIDATE_PAYMENT = "validateCustomerPaymentProfileRequest"
39
      REPORT_GET_BATCH_LIST = "getSettledBatchListRequest"
40
      REPORT_GET_TRANSACTION_LIST = "getTransactionListRequest"
41
      REPORT_GET_UNSETTLED_TRANSACTION_LIST = "getUnsettledTransactionListRequest"
42
      REPORT_GET_TRANSACTION_DETAILS = "getTransactionDetailsRequest"
43
    end
44
    
45
    # Fields to convert to/from booleans.
46
    @@boolean_fields = []
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using a class variable like @@boolean_fields is generally not recommended; did you consider
using an class instance variable instead?
Loading history...
47
    
48
    # Fields to convert to/from BigDecimal.
49
    @@decimal_fields = []
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using a class variable like @@decimal_fields is generally not recommended; did you consider
using an class instance variable instead?
Loading history...
50
    
51
    # Fields to convert to/from Date.
52
    @@date_fields = []
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using a class variable like @@date_fields is generally not recommended; did you consider
using an class instance variable instead?
Loading history...
53
    
54
    # Fields to convert to/from DateTime.
55
    @@datetime_fields = []
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using a class variable like @@datetime_fields is generally not recommended; did you consider
using an class instance variable instead?
Loading history...
56
    
57
    # The class to wrap our response in.
58
    @response_class = AuthorizeNet::XmlResponse
59
    
60
    # The default options for the constructor.
61
    @@option_defaults = {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using a class variable like @@option_defaults is generally not recommended; did you consider
using an class instance variable instead?
Loading history...
62
      :gateway => :production,
63
      :verify_ssl => true,
64
      :reference_id => nil
65
    }
66
    
67
    # DO NOT USE. Instantiate AuthorizeNet::ARB::Transaction or AuthorizeNet::CIM::Transaction instead.
68
    def initialize(api_login_id, api_transaction_key, options = {})
69
      super()
70
      @api_login_id = api_login_id
71
      @api_transaction_key = api_transaction_key
72
      
73
      @response ||= nil
74
      @type ||= nil
75
      
76
      options = @@option_defaults.merge(options)
77
      @verify_ssl = options[:verify_ssl]
78
      @reference_id = options[:reference_id]
79
      @gateway = case options[:gateway].to_s
80
      when 'sandbox', 'test'
81
        Gateway::TEST
82
      when 'production', 'live'
83
        Gateway::LIVE
84
      else
85
        @gateway = options[:gateway]
86
        options[:gateway]
87
      end
88
    end
89
    
90
	def setOAuthOptions()
91
		if !@options_OAuth.blank?
92
			@options_OAuth = @@option_defaults.merge(@options_OAuth)
93
			@verify_ssl = options_OAuth[:verify_ssl]
94
			@reference_id = options_OAuth[:reference_id]
95
			
96
			@gateway = case options_OAuth[:gateway].to_s
97
			when 'sandbox', 'test'
98
				Gateway::TEST
99
			when 'production', 'live'
100
				Gateway::LIVE
101
			else
102
				@gateway = options_OAuth[:gateway]
103
				options_OAuth[:gateway]
104
			end
105
		end
106
	end
107
	
108
    # Checks if the transaction has been configured for the sandbox or not. Return FALSE if the
109
    # transaction is running against the production, TRUE otherwise.
110
    def test?
111
      @gateway != Gateway::LIVE
112
    end
113
        
114
    # Checks to see if the transaction has a response (meaning it has been submitted to the gateway).
115
    # Returns TRUE if a response is present, FALSE otherwise.
116
    def has_response?
117
      [email protected]?
118
    end
119
    
120
    # Retrieve the response object (or Nil if transaction hasn't been sent to the gateway).
121
    def response
122
      @response
123
    end
124
    
125
    # Submits the transaction to the gateway for processing. Returns a response object. If the transaction
126
    # has already been run, it will return nil.
127
    def run
128
      make_request
129
    end
130
    
131
    # Returns a deep-copy of the XML object sent to the payment gateway. Or nil if there was no XML payload.
132
    def xml
133
      @xml
134
    end
135
    
136
    #:enddoc:
137
    protected
138
    
139
    # Takes a list of nodes (a Hash is a node, and Array is a list) and returns True if any nodes
140
    # would be built by build_nodes. False if no new nodes would be generated.
141
    def has_content(nodeList, data)
142
      nodeList.each do |node|
143
        nodeName = (node.keys.reject {|k| nodeName.to_s[0..0] == '_' }).first
144
        multivalue = node[:_multivalue]
145
        conditional = node[:_conditional]
146
        value = node[nodeName]
147
        unless conditional.nil?
148
          value = self.send(conditional, nodeName)
149
        end
150
        case value
151
        when Array
152
          if multivalue.nil?
153
            if has_content(value, data)
154
              return true
155
            end
156
          else
157
            data[multivalue].each do |v|
158
              if has_content(value, v)
159
                return true
160
              end
161
            end
162
          end
163
        when Symbol
164
          converted = convert_field(value, data[value])
165
          return true unless converted.nil?
166
        else
167
          return true
168
        end
169
      end
170
      false
171
    end
172
    
173
    # Takes a list of nodes (a Hash is a node, and Array is a list) and recursively builds the XML by pulling
174
    # values as needed from data.
175
    def build_nodes(builder, nodeList, data)
176
      nodeList.each do |node|
177
        # TODO - ADD COMMENTS HERE
178
        nodeName = (node.keys.reject {|k| k.to_s[0..0] == '_' }).first
179
        multivalue = node[:_multivalue]
180
        conditional = node[:_conditional]
181
        value = node[nodeName]
182
183
        unless conditional.nil?
184
          value = self.send(conditional, nodeName)
185
        end
186
        case value
187
        when Array # node containing other nodes
188
          if multivalue.nil?
189
            proc = Proc.new { build_nodes(builder, value, data) }
190
            builder.send(nodeName, &proc) if has_content(value, data)
191
          else
192
            data[multivalue].to_a.each do |v|
193
              proc = Proc.new { build_nodes(builder, value, v) }
194
              builder.send(nodeName, &proc) if has_content(value, v)
195
            end
196
          end
197
        when Symbol # node containing actual data
198
          if data[value].kind_of?(Array)
199
            data[value].each do |v|
200
              converted = convert_field(value, v)
201
              builder.send(nodeName, converted) unless converted.nil?
202
            end
203
          else
204
            converted = convert_field(value, data[value])
205
            builder.send(nodeName, converted) unless converted.nil?
206
          end
207
        else
208
          builder.send(nodeName, value)
209
        end
210
      end
211
    end
212
    
213
    def convert_field(field, value)
214
      if @@boolean_fields.include?(field) and !value.nil?
215
        return boolean_to_value(value)
216
      elsif @@decimal_fields.include?(field) and !value.nil?
217
        return decimal_to_value(value)
218
      elsif @@date_fields.include?(field) and !value.nil?
219
        return date_to_value(value)
220
      elsif @@datetime_fields.include?(field) and !value.nil?
221
        return datetime_to_value(value)
222
      elsif field == :extra_options
223
        # handle converting extra options
224
        options = []
225
        unless value.nil?
226
          value.each_pair{|k,v| options <<= self.to_param(k, v)}
227
        end
228
        unless @custom_fields.nil?
229
          # special sort to maintain compatibility with AIM custom field ordering
230
          # FIXME - This should be DRY'd up.
231
          custom_field_keys = @custom_fields.keys.collect(&:to_s).sort.collect(&:to_sym)
232
          for key in custom_field_keys
233
            options <<= self.to_param(key, @custom_fields[key.to_sym], '')
234
          end
235
        end
236
        
237
        if options.length > 0
238
          return options.join('&')
239
        else
240
          return nil
241
        end
242
      elsif field == :exp_date
243
        # convert MMYY expiration dates into the XML equivalent
244
        unless value.nil?
245
          begin
246
            return value.to_s.downcase == 'xxxx' ? 'XXXX' : Date.strptime(value.to_s, '%m%y').strftime('%Y-%m')
247
          rescue
248
            # If we didn't get the exp_date in MMYY format, try our best to convert it
249
            return Date.parse(value.to_s).strftime('%Y-%m')
250
          end
251
        end
252
      end
253
      
254
      value
255
    end
256
    
257
    # An internal method that builds the POST body, submits it to the gateway, and constructs a Response object with the response.
258
    def make_request
259
      if has_response?
260
        return nil
261
      end
262
      
263
      fields = @fields
264
  
265
      builder = Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |x|
266
        x.send(@type.to_sym, :xmlns => XML_NAMESPACE) {
267
          x.merchantAuthentication {
268
            x.name @api_login_id
269
            x.transactionKey @api_transaction_key
270
          }
271
          build_nodes(x, self.class.const_get(:FIELDS)[@type], fields)
272
        }
273
      end
274
      @xml = builder.to_xml 
275
276
      url = URI.parse(@gateway)
277
      
278
      request = Net::HTTP::Post.new(url.path)
279
      request.content_type = 'text/xml'
280
      request.body = @xml
281
      connection = Net::HTTP.new(url.host, url.port)
282
      connection.use_ssl = true
283
      if @verify_ssl
284
        connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
285
      else
286
        connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
287
      end
288
      
289
      # Use our Class's @response_class variable to find the Response class we are supposed to use.
290
      begin
291
        @response = self.class.instance_variable_get(:@response_class).new((connection.start {|http| http.request(request)}), self)
292
      rescue
293
        @response = self.class.instance_variable_get(:@response_class).new($!, self)
294
      end
295
    end
296
    
297
  end
298
end
299