activemerchant 1.66.0 → 1.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,33 +2,6 @@ require 'active_merchant/billing/gateways/viaklix'
2
2
 
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
- # = Elavon Virtual Merchant Gateway
6
- #
7
- # == Example use:
8
- #
9
- # gateway = ActiveMerchant::Billing::ElavonGateway.new(
10
- # :login => "my_virtual_merchant_id",
11
- # :password => "my_virtual_merchant_pin",
12
- # :user => "my_virtual_merchant_user_id" # optional
13
- # )
14
- #
15
- # # set up credit card obj as in main ActiveMerchant example
16
- # creditcard = ActiveMerchant::Billing::CreditCard.new(
17
- # :type => 'visa',
18
- # :number => '41111111111111111',
19
- # :month => 10,
20
- # :year => 2011,
21
- # :first_name => 'Bob',
22
- # :last_name => 'Bobsen'
23
- # )
24
- #
25
- # # run request
26
- # response = gateway.purchase(1000, creditcard) # authorize and capture 10 USD
27
- #
28
- # puts response.success? # Check whether the transaction was successful
29
- # puts response.message # Retrieve the message returned by Elavon
30
- # puts response.authorization # Retrieve the unique transaction ID returned by Elavon
31
- #
32
5
  class ElavonGateway < Gateway
33
6
  include Empty
34
7
 
@@ -55,23 +28,11 @@ module ActiveMerchant #:nodoc:
55
28
  :update => 'CCUPDATETOKEN',
56
29
  }
57
30
 
58
- # Initialize the Gateway
59
- #
60
- # The gateway requires that a valid login and password be passed
61
- # in the +options+ hash.
62
- #
63
- # ==== Options
64
- #
65
- # * <tt>:login</tt> -- Merchant ID
66
- # * <tt>:password</tt> -- PIN
67
- # * <tt>:user</tt> -- Specify a subuser of the account (optional)
68
- # * <tt>:test => +true+ or +false+</tt> -- Force test transactions
69
31
  def initialize(options = {})
70
32
  requires!(options, :login, :password)
71
33
  super
72
34
  end
73
35
 
74
- # Make a purchase
75
36
  def purchase(money, payment_method, options = {})
76
37
  form = {}
77
38
  add_salestax(form, options)
@@ -85,16 +46,9 @@ module ActiveMerchant #:nodoc:
85
46
  add_customer_data(form, options)
86
47
  add_test_mode(form, options)
87
48
  add_ip(form, options)
88
- commit(:purchase, money, form)
49
+ commit(:purchase, money, form, options)
89
50
  end
90
51
 
91
- # Authorize a credit card for a given amount.
92
- #
93
- # ==== Parameters
94
- # * <tt>money</tt> - The amount to be authorized as an Integer value in cents.
95
- # * <tt>credit_card</tt> - The CreditCard details for the transaction.
96
- # * <tt>options</tt>
97
- # * <tt>:billing_address</tt> - The billing address for the cardholder.
98
52
  def authorize(money, creditcard, options = {})
99
53
  form = {}
100
54
  add_salestax(form, options)
@@ -104,16 +58,9 @@ module ActiveMerchant #:nodoc:
104
58
  add_customer_data(form, options)
105
59
  add_test_mode(form, options)
106
60
  add_ip(form, options)
107
- commit(:authorize, money, form)
61
+ commit(:authorize, money, form, options)
108
62
  end
109
63
 
110
- # Capture authorized funds from a credit card.
111
- #
112
- # ==== Parameters
113
- # * <tt>money</tt> - The amount to be captured as an Integer value in cents.
114
- # * <tt>authorization</tt> - The approval code returned from the initial authorization.
115
- # * <tt>options</tt>
116
- # * <tt>:credit_card</tt> - The CreditCard details from the initial transaction (required).
117
64
  def capture(money, authorization, options = {})
118
65
  form = {}
119
66
  if options[:credit_card]
@@ -130,45 +77,23 @@ module ActiveMerchant #:nodoc:
130
77
  add_partial_shipment_flag(form, options)
131
78
  add_test_mode(form, options)
132
79
  end
133
- commit(action, money, form)
134
- end
135
-
136
- # Refund a transaction.
137
- #
138
- # This transaction indicates to the gateway that
139
- # money should flow from the merchant to the customer.
140
- #
141
- # ==== Parameters
142
- #
143
- # * <tt>money</tt> -- The amount to be credited to the customer as an Integer value in cents.
144
- # * <tt>identification</tt> -- The ID of the original transaction against which the refund is being issued.
145
- # * <tt>options</tt> -- A hash of parameters.
80
+ commit(action, money, form, options)
81
+ end
82
+
146
83
  def refund(money, identification, options = {})
147
84
  form = {}
148
85
  add_txn_id(form, identification)
149
86
  add_test_mode(form, options)
150
- commit(:refund, money, form)
87
+ commit(:refund, money, form, options)
151
88
  end
152
89
 
153
- # Void a previous transaction
154
- #
155
- # ==== Parameters
156
- #
157
- # * <tt>authorization</tt> - The authorization returned from the previous request.
158
90
  def void(identification, options = {})
159
91
  form = {}
160
92
  add_txn_id(form, identification)
161
93
  add_test_mode(form, options)
162
- commit(:void, nil, form)
94
+ commit(:void, nil, form, options)
163
95
  end
164
96
 
165
- # Make a credit to a card. Use the refund method if you'd like to credit using
166
- # previous transaction
167
- #
168
- # ==== Parameters
169
- # * <tt>money</tt> - The amount to be credited as an Integer value in cents.
170
- # * <tt>creditcard</tt> - The credit card to be credited.
171
- # * <tt>options</tt>
172
97
  def credit(money, creditcard, options = {})
173
98
  if creditcard.is_a?(String)
174
99
  raise ArgumentError, "Reference credits are not supported. Please supply the original credit card or use the #refund method."
@@ -180,7 +105,7 @@ module ActiveMerchant #:nodoc:
180
105
  add_address(form, options)
181
106
  add_customer_data(form, options)
182
107
  add_test_mode(form, options)
183
- commit(:credit, money, form)
108
+ commit(:credit, money, form, options)
184
109
  end
185
110
 
186
111
  def verify(credit_card, options = {})
@@ -198,7 +123,7 @@ module ActiveMerchant #:nodoc:
198
123
  add_test_mode(form, options)
199
124
  add_verification(form, options)
200
125
  form[:add_token] = 'Y'
201
- commit(:store, nil, form)
126
+ commit(:store, nil, form, options)
202
127
  end
203
128
 
204
129
  def update(token, creditcard, options = {})
@@ -208,7 +133,7 @@ module ActiveMerchant #:nodoc:
208
133
  add_address(form, options)
209
134
  add_customer_data(form, options)
210
135
  add_test_mode(form, options)
211
- commit(:update, nil, form)
136
+ commit(:update, nil, form, options)
212
137
  end
213
138
 
214
139
  private
@@ -255,6 +180,11 @@ module ActiveMerchant #:nodoc:
255
180
  form[:email] = truncate(options[:email], 100) unless empty?(options[:email])
256
181
  form[:customer_code] = truncate(options[:customer], 10) unless empty?(options[:customer])
257
182
  form[:customer_number] = options[:customer_number] unless empty?(options[:customer_number])
183
+ if options[:custom_fields]
184
+ options[:custom_fields].each do |key, value|
185
+ form[key.to_s] = value
186
+ end
187
+ end
258
188
  end
259
189
 
260
190
  def add_salestax(form, options)
@@ -313,11 +243,11 @@ module ActiveMerchant #:nodoc:
313
243
  !response.has_key?('errorMessage')
314
244
  end
315
245
 
316
- def commit(action, money, parameters)
246
+ def commit(action, money, parameters, options)
317
247
  parameters[:amount] = amount(money)
318
248
  parameters[:transaction_type] = self.actions[action]
319
249
 
320
- response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters)) )
250
+ response = parse( ssl_post(test? ? self.test_url : self.live_url, post_data(parameters, options)) )
321
251
 
322
252
  Response.new(response['result'] == '0', message_from(response), response,
323
253
  :test => @options[:test] || test?,
@@ -327,21 +257,22 @@ module ActiveMerchant #:nodoc:
327
257
  )
328
258
  end
329
259
 
330
- def post_data(parameters)
260
+ def post_data(parameters, options)
331
261
  result = preamble
332
262
  result.merge!(parameters)
333
- result.collect { |key, value| post_data_string(key, value) }.join("&")
263
+ result.collect { |key, value| post_data_string(key, value, options) }.join("&")
334
264
  end
335
265
 
336
- def post_data_string(key, value)
337
- if custom_field?(key)
266
+ def post_data_string(key, value, options)
267
+ if custom_field?(key, options)
338
268
  "#{key}=#{CGI.escape(value.to_s)}"
339
269
  else
340
270
  "ssl_#{key}=#{CGI.escape(value.to_s)}"
341
271
  end
342
272
  end
343
273
 
344
- def custom_field?(field_name)
274
+ def custom_field?(field_name, options)
275
+ return true if options[:custom_fields] && options[:custom_fields].include?(field_name.to_sym)
345
276
  field_name == :customer_number
346
277
  end
347
278
 
@@ -14,23 +14,11 @@ module ActiveMerchant #:nodoc:
14
14
  self.homepage_url = 'https://www.fatzebra.com.au/'
15
15
  self.display_name = 'Fat Zebra'
16
16
 
17
- # Setup a new instance of the gateway.
18
- #
19
- # The options hash should include :username and :token
20
- # You can find your username and token at https://dashboard.fatzebra.com.au
21
- # Under the Your Account section
22
17
  def initialize(options = {})
23
18
  requires!(options, :username, :token)
24
19
  super
25
20
  end
26
21
 
27
- # To create a purchase on a credit card use:
28
- #
29
- # purchase(money, creditcard)
30
- #
31
- # To charge a tokenized card
32
- #
33
- # purchase(money, "abzy87u", :cvv => "123")
34
22
  def purchase(money, creditcard, options = {})
35
23
  post = {}
36
24
 
@@ -65,11 +53,6 @@ module ActiveMerchant #:nodoc:
65
53
  commit(:post, "purchases/#{CGI.escape(authorization)}/capture", post)
66
54
  end
67
55
 
68
- # Refund a transaction
69
- #
70
- # amount - Integer - the amount to refund
71
- # txn_id - String - the original transaction to be refunded
72
- # reference - String - your transaction reference
73
56
  def refund(money, txn_id, options={})
74
57
  post = {}
75
58
 
@@ -81,9 +64,6 @@ module ActiveMerchant #:nodoc:
81
64
  commit(:post, "refunds", post)
82
65
  end
83
66
 
84
- # Tokenize a credit card
85
- #
86
- # The token is returned in the Response#authorization
87
67
  def store(creditcard, options={})
88
68
  post = {}
89
69
  add_creditcard(post, creditcard)
@@ -104,14 +84,12 @@ module ActiveMerchant #:nodoc:
104
84
 
105
85
  private
106
86
 
107
- # Add the money details to the request
108
87
  def add_amount(post, money, options)
109
88
  post[:currency] = (options[:currency] || currency(money))
110
89
  post[:currency] = post[:currency].upcase if post[:currency]
111
90
  post[:amount] = money
112
91
  end
113
92
 
114
- # Add the credit card details to the request
115
93
  def add_creditcard(post, creditcard, options = {})
116
94
  if creditcard.respond_to?(:number)
117
95
  post[:card_number] = creditcard.number
@@ -134,7 +112,7 @@ module ActiveMerchant #:nodoc:
134
112
  extra = {}
135
113
  extra[:ecm] = "32" if options[:recurring]
136
114
  extra[:cavv] = options[:cavv] if options[:cavv]
137
- extra[:xid] = options[:cavv] if options[:xid]
115
+ extra[:xid] = options[:xid] if options[:xid]
138
116
  extra[:sli] = options[:sli] if options[:sli]
139
117
  extra[:name] = options[:merchant] if options[:merchant]
140
118
  extra[:location] = options[:merchant_location] if options[:merchant_location]
@@ -149,7 +127,6 @@ module ActiveMerchant #:nodoc:
149
127
  post[:customer_ip] = options[:ip] || "127.0.0.1"
150
128
  end
151
129
 
152
- # Post the data to the gateway
153
130
  def commit(method, uri, parameters=nil)
154
131
  response = begin
155
132
  parse(ssl_request(method, get_url(uri), parameters.to_json, headers))
@@ -194,7 +171,6 @@ module ActiveMerchant #:nodoc:
194
171
  end
195
172
  end
196
173
 
197
- # Parse the returned JSON, if parse errors are raised then return a detailed error.
198
174
  def parse(response)
199
175
  begin
200
176
  JSON.parse(response)
@@ -209,13 +185,11 @@ module ActiveMerchant #:nodoc:
209
185
  end
210
186
  end
211
187
 
212
- # Build the URL based on the AM mode and the URI
213
188
  def get_url(uri)
214
189
  base = test? ? self.test_url : self.live_url
215
190
  base + "/" + uri
216
191
  end
217
192
 
218
- # Builds the auth and U-A headers for the request
219
193
  def headers
220
194
  {
221
195
  "Authorization" => "Basic " + Base64.strict_encode64(@options[:username].to_s + ":" + @options[:token].to_s).strip,
@@ -34,6 +34,8 @@ module ActiveMerchant #:nodoc:
34
34
 
35
35
  E4_BRANDS = BRANDS.merge({:mastercard => "Mastercard"})
36
36
 
37
+ DEFAULT_ECI = "07"
38
+
37
39
  self.supported_cardtypes = BRANDS.keys
38
40
  self.supported_countries = ["CA", "US"]
39
41
  self.default_currency = "USD"
@@ -235,6 +237,9 @@ module ActiveMerchant #:nodoc:
235
237
  xml.tag! "CardHoldersName", credit_card.name
236
238
  xml.tag! "CardType", card_type(credit_card.brand)
237
239
 
240
+ eci = (credit_card.respond_to?(:eci) ? credit_card.eci : nil) || options[:eci] || DEFAULT_ECI
241
+ xml.tag! "Ecommerce_Flag", eci
242
+
238
243
  add_credit_card_verification_strings(xml, credit_card, options)
239
244
  end
240
245
  end
@@ -256,8 +261,6 @@ module ActiveMerchant #:nodoc:
256
261
  end
257
262
 
258
263
  def add_network_tokenization_credit_card(xml, credit_card)
259
- xml.tag!("Ecommerce_Flag", credit_card.eci)
260
-
261
264
  case card_brand(credit_card).to_sym
262
265
  when :visa
263
266
  xml.tag!("XID", credit_card.transaction_id) if credit_card.transaction_id
@@ -275,7 +278,6 @@ module ActiveMerchant #:nodoc:
275
278
  def add_card_authentication_data(xml, options)
276
279
  xml.tag! "CAVV", options[:cavv]
277
280
  xml.tag! "XID", options[:xid]
278
- xml.tag! "Ecommerce_Flag", options[:eci]
279
281
  end
280
282
 
281
283
  def add_credit_card_token(xml, store_authorization)
@@ -0,0 +1,410 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class JetpayV2Gateway < Gateway
4
+ self.test_url = 'https://test1.jetpay.com/jetpay'
5
+ self.live_url = 'https://gateway20.jetpay.com/jetpay'
6
+
7
+ self.money_format = :cents
8
+ self.default_currency = 'USD'
9
+ self.supported_countries = ['US', 'CA']
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
11
+
12
+ self.homepage_url = 'http://www.jetpay.com'
13
+ self.display_name = 'JetPay'
14
+
15
+ API_VERSION = '2.2'
16
+
17
+ ACTION_CODE_MESSAGES = {
18
+ "000" => "Approved.",
19
+ "001" => "Refer to card issuer.",
20
+ "002" => "Refer to card issuer, special condition.",
21
+ "003" => "Invalid merchant or service provider.",
22
+ "004" => "Pick up card.",
23
+ "005" => "Do not honor.",
24
+ "006" => "Error.",
25
+ "007" => "Pick up card, special condition.",
26
+ "008" => "Honor with ID (Show ID).",
27
+ "010" => "Partial approval.",
28
+ "011" => "VIP approval.",
29
+ "012" => "Invalid transaction.",
30
+ "013" => "Invalid amount or exceeds maximum for card program.",
31
+ "014" => "Invalid account number (no such number).",
32
+ "015" => "No such issuer.",
33
+ "019" => "Re-enter Transaction.",
34
+ "021" => "No action taken (unable to back out prior transaction).",
35
+ "025" => "Transaction Not Found.",
36
+ "027" => "File update field edit error.",
37
+ "028" => "File is temporarily unavailable.",
38
+ "030" => "Format error.",
39
+ "039" => "No credit account.",
40
+ "041" => "Pick up card (lost card).",
41
+ "043" => "Pick up card (stolen card).",
42
+ "051" => "Insufficient funds.",
43
+ "052" => "No checking account.",
44
+ "053" => "Mp savomgs accpimt.",
45
+ "054" => "Expired Card.",
46
+ "055" => "Incorrect PIN.",
47
+ "057" => "Transaction not permitted to cardholder.",
48
+ "058" => "Transaction not allowed at terminal.",
49
+ "061" => "Exceeds withdrawal limit.",
50
+ "062" => "Restricted card (eg, Country Exclusion).",
51
+ "063" => "Security violation.",
52
+ "065" => "Activity count limit exceeded.",
53
+ "068" => "Response late.",
54
+ "070" => "Contact card issuer.",
55
+ "071" => "PIN not changed.",
56
+ "075" => "Allowable number of PIN-entry tries exceeded.",
57
+ "076" => "Unable to locate previous message (no matching retrieval reference number).",
58
+ "077" => "Repeat or reversal data are inconsistent with original message.",
59
+ "078" => "Blocked (first use), or non-existent account.",
60
+ "079" => "Key exchange validation failed.",
61
+ "080" => "Credit issuer unavailable or invalid date.",
62
+ "081" => "PIN cryptographic error found.",
63
+ "082" => "Negative online CVV results.",
64
+ "084" => "Invalid auth life cycle.",
65
+ "085" => "No reason to decline - CVV or AVS approved.",
66
+ "086" => "Cannot verify PIN.",
67
+ "087" => "Cashback not allowed.",
68
+ "089" => "Issuer Down.",
69
+ "091" => "Issuer Down.",
70
+ "092" => "Unable to route transaction.",
71
+ "093" => "Transaction cannot be completed - violation of law.",
72
+ "094" => "Duplicate transmission.",
73
+ "096" => "System error.",
74
+ "100" => "Deny.",
75
+ "101" => "Expired Card.",
76
+ "103" => "Deny - Invalid manual Entry 4DBC.",
77
+ "104" => "Deny - New card issued.",
78
+ "105" => "Deny - Account Cancelled.",
79
+ "106" => "Exceeded PIN Attempts.",
80
+ "107" => "Please Call Issuer.",
81
+ "109" => "Invalid merchant.",
82
+ "110" => "Invalid amount.",
83
+ "111" => "Invalid account.",
84
+ "115" => "Service not permitted.",
85
+ "117" => "Invalid PIN.",
86
+ "119" => "Card member not enrolled.",
87
+ "122" => "Invalid card (CID) security code.",
88
+ "125" => "Invalid effective date.",
89
+ "181" => "Format error.",
90
+ "182" => "Please wait.",
91
+ "183" => "Invalid currency code.",
92
+ "187" => "Deny - new card issued.",
93
+ "188" => "Deny - Expiration date required.",
94
+ "189" => "Deny - Cancelled or Closed Merchant/SE.",
95
+ "200" => "Deny - Pick up card.",
96
+ "400" => "Reversal accepted.",
97
+ "601" => "Reject - EMV Chip Declined Transaction.",
98
+ "602" => "Reject - Suspected Fraud.",
99
+ "603" => "Reject - Communications Error.",
100
+ "604" => "Reject - Insufficient Approval.",
101
+ "750" => "Velocity Check Fail.",
102
+ "899" => "Misc Decline.",
103
+ "900" => "Invalid Message Type.",
104
+ "901" => "Invalid Merchant ID.",
105
+ "903" => "Debit not supported.",
106
+ "904" => "Private label not supported.",
107
+ "905" => "Invalid card type.",
108
+ "906" => "Unit not active.",
109
+ "908" => "Manual card entry invalid.",
110
+ "909" => "Invalid track information.",
111
+ "911" => "Master merchant not found.",
112
+ "912" => "Invalid card format.",
113
+ "913" => "Invalid card type.",
114
+ "914" => "Invalid card length.",
115
+ "917" => "Expired card.",
116
+ "919" => "Invalid entry type.",
117
+ "920" => "Invalid amount.",
118
+ "921" => "Invalid messge format.",
119
+ "923" => "Invalid ABA.",
120
+ "924" => "Invalid DDA.",
121
+ "925" => "Invalid TID.",
122
+ "926" => "Invalid Password.",
123
+ "930" => "Invalid zipcode.",
124
+ "931" => "Invalid Address.",
125
+ "932" => "Invalid ZIP and Address.",
126
+ "933" => "Invalid CVV2.",
127
+ "934" => "Program Not Allowed.",
128
+ "935" => "Invalid Device/App.",
129
+ "940" => "Record Not Found.",
130
+ "941" => "Merchant ID error.",
131
+ "942" => "Refund Not Allowed.",
132
+ "943" => "Refund denied.",
133
+ "955" => "Invalid PIN block.",
134
+ "956" => "Invalid KSN.",
135
+ "958" => "Bad Status.",
136
+ "959" => "Seek Record limit exceeded.",
137
+ "960" => "Internal Key Database Error.",
138
+ "961" => "TRANS not Supported. Cash Disbursement required a specific MCC.",
139
+ "962" => "Invalid PIN key (Unknown KSN).",
140
+ "981" => "Invalid AVS.",
141
+ "987" => "Issuer Unavailable.",
142
+ "988" => "System error SD.",
143
+ "989" => "Database Error.",
144
+ "992" => "Transaction Timeout.",
145
+ "996" => "Bad Terminal ID.",
146
+ "997" => "Message rejected by association.",
147
+ "999" => "Communication failure",
148
+ nil => "No response returned (missing credentials?)."
149
+ }
150
+
151
+ def initialize(options = {})
152
+ requires!(options, :login)
153
+ super
154
+ end
155
+
156
+ def purchase(money, credit_card, options = {})
157
+ commit(money, build_sale_request(money, credit_card, options))
158
+ end
159
+
160
+ def authorize(money, credit_card, options = {})
161
+ commit(money, build_authonly_request(money, credit_card, options))
162
+ end
163
+
164
+ def capture(money, reference, options = {})
165
+ split_authorization = reference.split(";")
166
+ transaction_id = split_authorization[0]
167
+ token = split_authorization[3]
168
+ commit(money, build_capture_request(transaction_id, money, options), token)
169
+ end
170
+
171
+ def void(reference, options = {})
172
+ transaction_id, approval, amount, token = reference.split(";")
173
+ commit(amount.to_i, build_void_request(amount.to_i, transaction_id, approval, token, options), token)
174
+ end
175
+
176
+ def credit(money, credit_card, options = {})
177
+ commit(money, build_credit_request(money, nil, credit_card, nil, options))
178
+ end
179
+
180
+ def refund(money, reference, options = {})
181
+ split_authorization = reference.split(";")
182
+ transaction_id = split_authorization[0]
183
+ token = split_authorization[3]
184
+ commit(money, build_credit_request(money, transaction_id, nil, token, options))
185
+ end
186
+
187
+ def verify(credit_card, options={})
188
+ authorize(0, credit_card, options)
189
+ end
190
+
191
+ def supports_scrubbing
192
+ true
193
+ end
194
+
195
+ def scrub(transcript)
196
+ transcript.
197
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
198
+ gsub(%r((>)\d+(</CardNum>)), '\1[FILTERED]\2').
199
+ gsub(%r((<CVV2>)\d+(</CVV2>)), '\1[FILTERED]\2')
200
+ end
201
+
202
+ private
203
+
204
+ def build_xml_request(transaction_type, options = {}, transaction_id = nil, &block)
205
+ xml = Builder::XmlMarkup.new
206
+ xml.tag! 'JetPay', 'Version' => API_VERSION do
207
+ # Basic values needed for any request
208
+ xml.tag! 'TerminalID', @options[:login]
209
+ xml.tag! 'TransactionType', transaction_type
210
+ xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id
211
+ xml.tag! 'Origin', options[:origin] || 'INTERNET'
212
+ xml.tag! 'IndustryInfo', 'Type' => options[:industry_info] || 'ECOMMERCE'
213
+ xml.tag! 'Application', (options[:application] || 'n/a'), {'Version' => options[:application_version] || '1.0'}
214
+ xml.tag! 'Device', (options[:device] || 'n/a'), {'Version' => options[:device_version] || '1.0'}
215
+ xml.tag! 'Library', 'VirtPOS SDK', 'Version' => '1.5'
216
+ xml.tag! 'Gateway', 'JetPay'
217
+ xml.tag! 'DeveloperID', options[:developer_id] || 'n/a'
218
+
219
+ if block_given?
220
+ yield xml
221
+ else
222
+ xml.target!
223
+ end
224
+ end
225
+ end
226
+
227
+ def build_sale_request(money, credit_card, options)
228
+ build_xml_request('SALE', options) do |xml|
229
+ add_credit_card(xml, credit_card)
230
+ add_addresses(xml, options)
231
+ add_customer_data(xml, options)
232
+ add_invoice_data(xml, options)
233
+ add_user_defined_fields(xml, options)
234
+ xml.tag! 'TotalAmount', amount(money)
235
+
236
+ xml.target!
237
+ end
238
+ end
239
+
240
+ def build_authonly_request(money, credit_card, options)
241
+ build_xml_request('AUTHONLY', options) do |xml|
242
+ add_credit_card(xml, credit_card)
243
+ add_addresses(xml, options)
244
+ add_customer_data(xml, options)
245
+ add_invoice_data(xml, options)
246
+ add_user_defined_fields(xml, options)
247
+ xml.tag! 'TotalAmount', amount(money)
248
+
249
+ xml.target!
250
+ end
251
+ end
252
+
253
+ def build_capture_request(transaction_id, money, options)
254
+ build_xml_request('CAPT', options, transaction_id) do |xml|
255
+ add_invoice_data(xml, options)
256
+ add_user_defined_fields(xml, options)
257
+ xml.tag! 'TotalAmount', amount(money)
258
+
259
+ xml.target!
260
+ end
261
+ end
262
+
263
+ def build_void_request(money, transaction_id, approval, token, options)
264
+ build_xml_request('VOID', options, transaction_id) do |xml|
265
+ xml.tag! 'Approval', approval
266
+ xml.tag! 'TotalAmount', amount(money)
267
+ xml.tag! 'Token', token if token
268
+
269
+ xml.target!
270
+ end
271
+ end
272
+
273
+ def build_credit_request(money, transaction_id, card, token, options)
274
+ build_xml_request('CREDIT', options, transaction_id) do |xml|
275
+ add_credit_card(xml, card) if card
276
+ add_invoice_data(xml, options)
277
+ add_addresses(xml, options)
278
+ add_customer_data(xml, options)
279
+ add_user_defined_fields(xml, options)
280
+ xml.tag! 'TotalAmount', amount(money)
281
+ xml.tag! 'Token', token if token
282
+
283
+ xml.target!
284
+ end
285
+ end
286
+
287
+ def commit(money, request, token = nil)
288
+ response = parse(ssl_post(url, request))
289
+
290
+ success = success?(response)
291
+ Response.new(success,
292
+ success ? 'APPROVED' : message_from(response),
293
+ response,
294
+ :test => test?,
295
+ :authorization => authorization_from(response, money, token),
296
+ :avs_result => AVSResult.new(:code => response[:avs]),
297
+ :cvv_result => CVVResult.new(response[:cvv2]),
298
+ :error_code => success ? nil : error_code_from(response)
299
+ )
300
+ end
301
+
302
+ def url
303
+ test? ? test_url : live_url
304
+ end
305
+
306
+ def parse(body)
307
+ return {} if body.blank?
308
+
309
+ xml = REXML::Document.new(body)
310
+
311
+ response = {}
312
+ xml.root.elements.to_a.each do |node|
313
+ parse_element(response, node)
314
+ end
315
+ response
316
+ end
317
+
318
+ def parse_element(response, node)
319
+ if node.has_elements?
320
+ node.elements.each{|element| parse_element(response, element) }
321
+ else
322
+ response[node.name.underscore.to_sym] = node.text
323
+ end
324
+ end
325
+
326
+ def format_exp(value)
327
+ format(value, :two_digits)
328
+ end
329
+
330
+ def success?(response)
331
+ response[:action_code] == "000"
332
+ end
333
+
334
+ def message_from(response)
335
+ ACTION_CODE_MESSAGES[response[:action_code]]
336
+ end
337
+
338
+ def authorization_from(response, money, previous_token)
339
+ original_amount = amount(money) if money
340
+ [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(";")
341
+ end
342
+
343
+ def error_code_from(response)
344
+ response[:action_code]
345
+ end
346
+
347
+ def add_credit_card(xml, credit_card)
348
+ xml.tag! 'CardNum', credit_card.number, "CardPresent" => false, "Tokenize" => true
349
+ xml.tag! 'CardExpMonth', format_exp(credit_card.month)
350
+ xml.tag! 'CardExpYear', format_exp(credit_card.year)
351
+
352
+ if credit_card.first_name || credit_card.last_name
353
+ xml.tag! 'CardName', [credit_card.first_name,credit_card.last_name].compact.join(' ')
354
+ end
355
+
356
+ unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0)
357
+ xml.tag! 'CVV2', credit_card.verification_value
358
+ end
359
+ end
360
+
361
+ def add_addresses(xml, options)
362
+ if billing_address = options[:billing_address] || options[:address]
363
+ xml.tag! 'Billing' do
364
+ xml.tag! 'Address', [billing_address[:address1], billing_address[:address2]].compact.join(" ")
365
+ xml.tag! 'City', billing_address[:city]
366
+ xml.tag! 'StateProv', billing_address[:state]
367
+ xml.tag! 'PostalCode', billing_address[:zip]
368
+ xml.tag! 'Country', lookup_country_code(billing_address[:country])
369
+ xml.tag! 'Phone', billing_address[:phone]
370
+ xml.tag! 'Email', options[:email] if options[:email]
371
+ end
372
+ end
373
+
374
+ if shipping_address = options[:shipping_address]
375
+ xml.tag! 'Shipping' do
376
+ xml.tag! 'Name', shipping_address[:name]
377
+ xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(" ")
378
+ xml.tag! 'City', shipping_address[:city]
379
+ xml.tag! 'StateProv', shipping_address[:state]
380
+ xml.tag! 'PostalCode', shipping_address[:zip]
381
+ xml.tag! 'Country', lookup_country_code(shipping_address[:country])
382
+ xml.tag! 'Phone', shipping_address[:phone]
383
+ end
384
+ end
385
+ end
386
+
387
+ def add_customer_data(xml, options)
388
+ xml.tag! 'UserIPAddress', options[:ip] if options[:ip]
389
+ end
390
+
391
+ def add_invoice_data(xml, options)
392
+ xml.tag! 'OrderNumber', options[:order_id] if options[:order_id]
393
+ if tax_amount = options[:tax_amount]
394
+ xml.tag! 'TaxAmount', tax_amount, {'ExemptInd' => options[:tax_exemption] || "false"}
395
+ end
396
+ end
397
+
398
+ def add_user_defined_fields(xml, options)
399
+ xml.tag! 'UDField1', options[:ud_field_1] if options[:ud_field_1]
400
+ xml.tag! 'UDField2', options[:ud_field_2] if options[:ud_field_2]
401
+ xml.tag! 'UDField3', options[:ud_field_3] if options[:ud_field_3]
402
+ end
403
+
404
+ def lookup_country_code(code)
405
+ country = Country.find(code) rescue nil
406
+ country && country.code(:alpha3)
407
+ end
408
+ end
409
+ end
410
+ end