activemerchant 1.31.1 → 1.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +25 -0
  3. data/CONTRIBUTORS +8 -0
  4. data/README.md +2 -0
  5. data/lib/active_merchant/billing/credit_card.rb +1 -1
  6. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +2 -1
  7. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +1 -0
  8. data/lib/active_merchant/billing/gateways/braintree_blue.rb +1 -0
  9. data/lib/active_merchant/billing/gateways/cc5.rb +160 -0
  10. data/lib/active_merchant/billing/gateways/data_cash.rb +3 -3
  11. data/lib/active_merchant/billing/gateways/finansbank.rb +22 -0
  12. data/lib/active_merchant/billing/gateways/iridium.rb +8 -2
  13. data/lib/active_merchant/billing/gateways/litle.rb +289 -101
  14. data/lib/active_merchant/billing/gateways/ogone.rb +1 -1
  15. data/lib/active_merchant/billing/gateways/optimal_payment.rb +26 -16
  16. data/lib/active_merchant/billing/gateways/orbital.rb +6 -6
  17. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +2 -1
  18. data/lib/active_merchant/billing/gateways/paymill.rb +13 -9
  19. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +14 -9
  20. data/lib/active_merchant/billing/gateways/pin.rb +13 -5
  21. data/lib/active_merchant/billing/gateways/spreedly_core.rb +15 -17
  22. data/lib/active_merchant/billing/gateways/stripe.rb +25 -12
  23. data/lib/active_merchant/billing/gateways/webpay.rb +8 -0
  24. data/lib/active_merchant/billing/gateways/worldpay.rb +44 -22
  25. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +3 -2
  26. data/lib/active_merchant/billing/integrations/pxpay/helper.rb +1 -0
  27. data/lib/active_merchant/billing/integrations/robokassa/common.rb +1 -1
  28. data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +5 -1
  29. data/lib/active_merchant/billing/integrations/world_pay.rb +15 -8
  30. data/lib/active_merchant/version.rb +1 -1
  31. metadata +62 -60
  32. metadata.gz.sig +0 -0
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,30 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.32.0 (April 1, 2013)
4
+
5
+ * Optimal: Submit shipping address with requests [jduff]
6
+ * Iridium: Enable reference transactions for authorize [ntalbott]
7
+ * Stripe: Add authorize and capture methods [melari]
8
+ * Pin: Add a default description if none is specified to fix failures [melari]
9
+ * Litle: Add support for passing optional fields in token based transactions [forest]
10
+ * Add Finansbank gateway [scamurcuoglu]
11
+ * Paymill: Use .com instead of .de for save card url [besi]
12
+ * Worldpay integration: Use more robust endpoint urls [nashbridges]
13
+ * Braintree Blue: Return CC token in transaction hash [cyu]
14
+ * Robokassa: Fix signature for empty amount [ukolovda]
15
+ * Worldpay gateway: Fix error messages for some failures [duff]
16
+ * Worldpay gateway: Allow settled payments to be refunded [dougal]
17
+ * Spreedly: Update urls and terminology [duff]
18
+ * Make card brand error more user friendly [oggy]
19
+ * DataCash: Update test Mastercard number [jamesshipton]
20
+ * DataCash: Update test response fixtures [jamesshipton]
21
+ * Pin: Add Pin.js card token support [nagash]
22
+ * PayPal Express gateway: Fix error when no address information is in response [pierre]
23
+ * Ogone: Use BYPSP for ALIASOPERATION [ntalbott]
24
+ * Paymill: Handle error storing card [duff]
25
+ * SagePay integration: Add referrer field [melari]
26
+ * Pin: Add extra headers [duff]
27
+
3
28
  == Version 1.31.1 (February 25, 2013)
4
29
 
5
30
  * Cybersource: Bug fixes [natejgreene, jduff]
data/CONTRIBUTORS CHANGED
@@ -346,6 +346,10 @@ Balanced gateway (July 2012)
346
346
 
347
347
  * Marshall Jones (mjallday)
348
348
 
349
+ PayFast integration (October 2012)
350
+
351
+ * Vasiliy Ermolovich (nashby)
352
+
349
353
  A1Agregator (November 2012)
350
354
 
351
355
  * Roman Ivanilov (england)
@@ -384,3 +388,7 @@ Paymill (February 2013)
384
388
  EVO Canada (February 2013)
385
389
 
386
390
  * Alex Dunae (alexdunae)
391
+
392
+ Finansbank WebPOS (March 2013)
393
+
394
+ * scamurcuoglu
data/README.md CHANGED
@@ -100,6 +100,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
100
100
  * [E-xact](http://www.e-xact.com) - CA, US
101
101
  * [Fat Zebra](https://www.fatzebra.com.au) - AU
102
102
  * [Federated Canada](http://www.federatedcanada.com/) - CA
103
+ * [Finansbank WebPOS](https://www.fbwebpos.com/) - US, TR
103
104
  * [FirstData Global Gateway e4](http://www.firstdata.com) - CA, US
104
105
  * [FirstPay](http://www.first-pay.com) - US
105
106
  * [Garanti Sanal POS](https://ccpos.garanti.com.tr/ccRaporlar/garanti/ccReports) - US, TR
@@ -130,6 +131,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
130
131
  * [Optimal Payments](http://www.optimalpayments.com/) - CA, US, UK
131
132
  * [Orbital Paymentech](http://chasepaymentech.com/) - CA, US, UK, GB
132
133
  * [PayBox Direct](http://www.paybox.com) - FR
134
+ * [PayFast](https://www.payfast.co.za/) - ZA
133
135
  * [PayGate PayXML](http://paygate.co.za/) - US, ZA
134
136
  * [PayJunction](http://www.payjunction.com/) - US
135
137
  * [PaymentExpress](http://www.paymentexpress.com/) - AU, MY, NZ, SG, ZA, UK, US
@@ -229,7 +229,7 @@ module ActiveMerchant #:nodoc:
229
229
  end
230
230
 
231
231
  unless errors.on(:number) || errors.on(:brand)
232
- errors.add :brand, "is not the correct card brand" unless CreditCard.matching_brand?(number, brand)
232
+ errors.add :brand, "does not match the card number" unless CreditCard.matching_brand?(number, brand)
233
233
  end
234
234
  end
235
235
 
@@ -338,7 +338,8 @@ module ActiveMerchant #:nodoc:
338
338
  # * <tt>:type</tt> -- The type of transaction. Can be either <tt>:auth_only</tt>, <tt>:capture_only</tt>, <tt>:auth_capture</tt>, <tt>:prior_auth_capture</tt>, <tt>:refund</tt> or <tt>:void</tt>. (REQUIRED)
339
339
  # * <tt>:amount</tt> -- The amount for the tranaction. Formatted with a decimal. For example "4.95" (CONDITIONAL)
340
340
  # - :type == :void (NOT USED)
341
- # - :type == (:refund, :auth_only, :capture_only, :auth_capture, :prior_auth_capture) (REQUIRED)
341
+ # - :type == :refund (OPTIONAL)
342
+ # - :type == (:auth_only, :capture_only, :auth_capture, :prior_auth_capture) (REQUIRED)
342
343
  #
343
344
  # * <tt>:customer_profile_id</tt> -- The Customer Profile ID of the customer to use in this transaction. (CONDITIONAL)
344
345
  # - :type == (:void, :prior_auth_capture) (OPTIONAL)
@@ -239,6 +239,7 @@ module ActiveMerchant #:nodoc:
239
239
 
240
240
  def add_recurring_invoice(post, options)
241
241
  post[:rbApplyTax1] = options[:apply_tax1]
242
+ post[:rbApplyTax2] = options[:apply_tax2]
242
243
  end
243
244
 
244
245
  def add_recurring_operation_type(post, operation)
@@ -340,6 +340,7 @@ module ActiveMerchant #:nodoc:
340
340
  "bin" => transaction.credit_card_details.bin,
341
341
  "last_4" => transaction.credit_card_details.last_4,
342
342
  "card_type" => transaction.credit_card_details.card_type,
343
+ "token" => transaction.credit_card_details.token
343
344
  }
344
345
 
345
346
  {
@@ -0,0 +1,160 @@
1
+ if RUBY_VERSION < '1.9' && $KCODE == "NONE"
2
+ $KCODE = 'u'
3
+ end
4
+
5
+ module ActiveMerchant #:nodoc:
6
+ module Billing #:nodoc:
7
+ # CC5 API is used by many banks in Turkey. Extend this base class to provide
8
+ # concrete implementations.
9
+ class CC5Gateway < Gateway
10
+ self.default_currency = 'TRY'
11
+
12
+ CURRENCY_CODES = {
13
+ 'TRY' => 949,
14
+ 'YTL' => 949,
15
+ 'TRL' => 949,
16
+ 'TL' => 949,
17
+ 'USD' => 840,
18
+ 'EUR' => 978,
19
+ 'GBP' => 826,
20
+ 'JPY' => 392
21
+ }
22
+
23
+ def initialize(options = {})
24
+ requires!(options, :login, :password, :client_id)
25
+ super
26
+ end
27
+
28
+ def purchase(money, creditcard, options = {})
29
+ commit(build_sale_request('Auth', money, creditcard, options))
30
+ end
31
+
32
+ def authorize(money, creditcard, options = {})
33
+ commit(build_sale_request('PreAuth', money, creditcard, options))
34
+ end
35
+
36
+ def capture(money, authorization, options = {})
37
+ commit(build_capture_request(money, authorization, options))
38
+ end
39
+
40
+ protected
41
+
42
+ def build_sale_request(type, money, creditcard, options = {})
43
+ requires!(options, :order_id)
44
+
45
+ xml = Builder::XmlMarkup.new :indent => 2
46
+
47
+ xml.tag! 'CC5Request' do
48
+ add_login_tags(xml)
49
+ xml.tag! 'OrderId', options[:order_id]
50
+ xml.tag! 'Type', type
51
+ xml.tag! 'Number', creditcard.number
52
+ xml.tag! 'Expires', [format(creditcard.month, :two_digits), format(creditcard.year, :two_digits)].join('/')
53
+ xml.tag! 'Cvv2Val', creditcard.verification_value
54
+ add_amount_tags(money, options, xml)
55
+ xml.tag! 'Email', options[:email] if options[:email]
56
+
57
+ if(address = (options[:billing_address] || options[:address]))
58
+ xml.tag! 'BillTo' do
59
+ add_address(xml, address)
60
+ end
61
+ xml.tag! 'ShipTo' do
62
+ add_address(xml, address)
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ xml.target!
69
+ end
70
+
71
+ def build_capture_request(money, authorization, options = {})
72
+ xml = Builder::XmlMarkup.new :indent => 2
73
+
74
+ xml.tag! 'CC5Request' do
75
+ add_login_tags(xml)
76
+ xml.tag! 'OrderId', authorization
77
+ xml.tag! 'Type', 'PostAuth'
78
+ add_amount_tags(money, options, xml)
79
+ end
80
+ end
81
+
82
+ def add_address(xml, address)
83
+ xml.tag! 'Name', normalize(address[:name])
84
+ xml.tag! 'Street1', normalize(address[:address1])
85
+ xml.tag! 'Street2', normalize(address[:address2]) if address[:address2]
86
+ xml.tag! 'City', normalize(address[:city])
87
+ xml.tag! 'PostalCode', address[:zip]
88
+ xml.tag! 'Country', normalize(address[:country])
89
+ xml.tag! 'Company', normalize(address[:company])
90
+ xml.tag! 'TelVoice', address[:phone].to_s.gsub(/[^0-9]/, '') if address[:phone]
91
+ end
92
+
93
+ def add_login_tags(xml)
94
+ xml.tag! 'Name', @options[:login]
95
+ xml.tag! 'Password', @options[:password]
96
+ xml.tag! 'ClientId', @options[:client_id]
97
+ xml.tag! 'Mode', (test? ? 'T' : 'P')
98
+ end
99
+
100
+ def add_amount_tags(money, options, xml)
101
+ xml.tag! 'Total', amount(money)
102
+ xml.tag! 'Currency', currency_code(options[:currency] || currency(money))
103
+ end
104
+
105
+ def currency_code(currency)
106
+ (CURRENCY_CODES[currency] || CURRENCY_CODES[default_currency])
107
+ end
108
+
109
+ def commit(request)
110
+ raw_response = ssl_post((test? ? self.test_url : self.live_url), "DATA=" + request)
111
+
112
+ response = parse(raw_response)
113
+
114
+ success = success?(response)
115
+
116
+ Response.new(
117
+ success,
118
+ (success ? 'Approved' : "Declined (Reason: #{response[:proc_return_code]} - #{response[:err_msg]})"),
119
+ response,
120
+ :test => test?,
121
+ :authorization => response[:order_id]
122
+ )
123
+ end
124
+
125
+ def parse(body)
126
+ xml = REXML::Document.new(body)
127
+
128
+ response = {}
129
+ xml.root.elements.to_a.each do |node|
130
+ parse_element(response, node)
131
+ end
132
+ response
133
+ end
134
+
135
+ def parse_element(response, node)
136
+ if node.has_elements?
137
+ node.elements.each{|element| parse_element(response, element) }
138
+ else
139
+ response[node.name.underscore.to_sym] = node.text
140
+ end
141
+ end
142
+
143
+ def success?(response)
144
+ (response[:response] == "Approved")
145
+ end
146
+
147
+ def normalize(text)
148
+ return unless text
149
+
150
+ if ActiveSupport::Inflector.method(:transliterate).arity == -2
151
+ ActiveSupport::Inflector.transliterate(text,'')
152
+ elsif RUBY_VERSION >= '1.9'
153
+ text.gsub(/[^\x00-\x7F]+/, '')
154
+ else
155
+ ActiveSupport::Inflector.transliterate(text).to_s
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -65,7 +65,7 @@ module ActiveMerchant
65
65
  # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions.
66
66
  # * <tt>:address</tt>:: billing address for card
67
67
  #
68
- # The continuous authority reference will be available in response#params['ca_referece'] if you have requested one
68
+ # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one
69
69
  def purchase(money, authorization_or_credit_card, options = {})
70
70
  requires!(options, :order_id)
71
71
 
@@ -93,7 +93,7 @@ module ActiveMerchant
93
93
  # See http://www.datacash.com/services/recurring/historic.php for more details of historic transactions.
94
94
  # * <tt>:address</tt>:: billing address for card
95
95
  #
96
- # The continuous authority reference will be available in response#params['ca_referece'] if you have requested one
96
+ # The continuous authority reference will be available in response#params['ca_reference'] if you have requested one
97
97
  def authorize(money, authorization_or_credit_card, options = {})
98
98
  requires!(options, :order_id)
99
99
 
@@ -448,7 +448,7 @@ module ActiveMerchant
448
448
  end
449
449
  end
450
450
 
451
- # Add credit_card detals to the passed XML Builder doc
451
+ # Add credit_card details to the passed XML Builder doc
452
452
  #
453
453
  # Parameters:
454
454
  # -xml: Builder document that is being built up
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + '/cc5'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class FinansbankGateway < CC5Gateway
6
+ self.live_url = self.test_url = 'https://www.fbwebpos.com/servlet/cc5ApiServer'
7
+
8
+ # The countries the gateway supports merchants from as 2 digit ISO country codes
9
+ self.supported_countries = ['US', 'TR']
10
+
11
+ # The card types supported by the payment gateway
12
+ self.supported_cardtypes = [:visa, :master]
13
+
14
+ # The homepage URL of the gateway
15
+ self.homepage_url = 'https://www.fbwebpos.com/'
16
+
17
+ # The name of the gateway
18
+ self.display_name = 'Finansbank WebPOS'
19
+ end
20
+ end
21
+ end
22
+
@@ -38,8 +38,14 @@ module ActiveMerchant #:nodoc:
38
38
  super
39
39
  end
40
40
 
41
- def authorize(money, creditcard, options = {})
42
- commit(build_purchase_request('PREAUTH', money, creditcard, options), options)
41
+ def authorize(money, payment_source, options = {})
42
+ setup_address_hash(options)
43
+
44
+ if payment_source.respond_to?(:number)
45
+ commit(build_purchase_request('PREAUTH', money, payment_source, options), options)
46
+ else
47
+ commit(build_reference_request('PREAUTH', money, payment_source, options), options)
48
+ end
43
49
  end
44
50
 
45
51
  def purchase(money, payment_source, options = {})
@@ -37,7 +37,7 @@ module ActiveMerchant #:nodoc:
37
37
  self.test_url = 'https://www.testlitle.com/sandbox/communicator/online'
38
38
  self.live_url = 'https://payments.litle.com/vap/communicator/online'
39
39
 
40
- LITLE_SCHEMA_VERSION = '8.10'
40
+ LITLE_SCHEMA_VERSION = '8.13'
41
41
 
42
42
  # The countries the gateway supports merchants from as 2 digit ISO country codes
43
43
  self.supported_countries = ['US']
@@ -46,10 +46,10 @@ module ActiveMerchant #:nodoc:
46
46
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
47
47
 
48
48
  # The homepage URL of the gateway
49
- self.homepage_url = 'http://www.litle.com/'
49
+ self.homepage_url = 'http://www.litle.com/'
50
50
 
51
51
  # The name of the gateway
52
- self.display_name = 'Litle & Co.'
52
+ self.display_name = 'Litle & Co.'
53
53
 
54
54
  self.default_currency = 'USD'
55
55
 
@@ -91,8 +91,8 @@ module ActiveMerchant #:nodoc:
91
91
  build_response(:void, @litle.void(to_pass))
92
92
  end
93
93
 
94
- def credit(money, identification, options = {})
95
- to_pass = create_credit_hash(money, identification, options)
94
+ def credit(money, identification_or_token, options = {})
95
+ to_pass = build_credit_request(money, identification_or_token, options)
96
96
  build_response(:credit, @litle.credit(to_pass))
97
97
  end
98
98
 
@@ -104,30 +104,30 @@ module ActiveMerchant #:nodoc:
104
104
  private
105
105
 
106
106
  CARD_TYPE = {
107
- 'visa' => 'VI',
108
- 'master' => 'MC',
109
- 'american_express' => 'AX',
110
- 'discover' => 'DI',
111
- 'jcb' => 'DI',
112
- 'diners_club' => 'DI'
107
+ 'visa' => 'VI',
108
+ 'master' => 'MC',
109
+ 'american_express' => 'AX',
110
+ 'discover' => 'DI',
111
+ 'jcb' => 'DI',
112
+ 'diners_club' => 'DI'
113
113
  }
114
114
 
115
115
  AVS_RESPONSE_CODE = {
116
- '00' => 'Y',
117
- '01' => 'X',
118
- '02' => 'D',
119
- '10' => 'Z',
120
- '11' => 'W',
121
- '12' => 'A',
122
- '13' => 'A',
123
- '14' => 'P',
124
- '20' => 'N',
125
- '30' => 'S',
126
- '31' => 'R',
127
- '32' => 'U',
128
- '33' => 'R',
129
- '34' => 'I',
130
- '40' => 'E'
116
+ '00' => 'Y',
117
+ '01' => 'X',
118
+ '02' => 'D',
119
+ '10' => 'Z',
120
+ '11' => 'W',
121
+ '12' => 'A',
122
+ '13' => 'A',
123
+ '14' => 'P',
124
+ '20' => 'N',
125
+ '30' => 'S',
126
+ '31' => 'R',
127
+ '32' => 'U',
128
+ '33' => 'R',
129
+ '34' => 'I',
130
+ '40' => 'E'
131
131
  }
132
132
 
133
133
  def url
@@ -140,22 +140,22 @@ module ActiveMerchant #:nodoc:
140
140
  response = Hash.from_xml(litle_response.raw_xml.to_s)['litleOnlineResponse']
141
141
 
142
142
  if response['response'] == "0"
143
- detail = response["#{kind}Response"]
144
- fraud = fraud_result(detail)
143
+ detail = response["#{kind}Response"]
144
+ fraud = fraud_result(detail)
145
145
  authorization = case kind
146
- when :registerToken
147
- response['registerTokenResponse']['litleToken']
148
- else
149
- detail['litleTxnId']
150
- end
146
+ when :registerToken
147
+ response['registerTokenResponse']['litleToken']
148
+ else
149
+ detail['litleTxnId']
150
+ end
151
151
  Response.new(
152
- valid_responses.include?(detail['response']),
153
- detail['message'],
154
- {:litleOnlineResponse => response},
155
- :authorization => authorization,
156
- :avs_result => {:code => fraud['avs']},
157
- :cvv_result => fraud['cvv'],
158
- :test => test?
152
+ valid_responses.include?(detail['response']),
153
+ detail['message'],
154
+ { :litleOnlineResponse => response },
155
+ :authorization => authorization,
156
+ :avs_result => { :code => fraud['avs'] },
157
+ :cvv_result => fraud['cvv'],
158
+ :test => test?
159
159
  )
160
160
  else
161
161
  Response.new(false, response['message'], :litleOnlineResponse => response, :test => test?)
@@ -163,39 +163,83 @@ module ActiveMerchant #:nodoc:
163
163
  end
164
164
 
165
165
  def build_authorize_request(money, creditcard_or_token, options)
166
+ payment_method = build_payment_method(creditcard_or_token, options)
167
+
166
168
  hash = create_hash(money, options)
167
169
 
168
- add_credit_card_or_token_hash(hash, creditcard_or_token)
170
+ add_creditcard_or_cardtoken_hash(hash, payment_method)
169
171
 
170
172
  hash
171
173
  end
172
174
 
173
175
  def build_purchase_request(money, creditcard_or_token, options)
176
+ payment_method = build_payment_method(creditcard_or_token, options)
177
+
178
+ hash = create_hash(money, options)
179
+
180
+ add_creditcard_or_cardtoken_hash(hash, payment_method)
181
+
182
+ hash
183
+ end
184
+
185
+ def build_credit_request(money, identification_or_token, options)
186
+ payment_method = build_payment_method(identification_or_token, options)
187
+
174
188
  hash = create_hash(money, options)
175
189
 
176
- add_credit_card_or_token_hash(hash, creditcard_or_token)
190
+ add_identification_or_cardtoken_hash(hash, payment_method)
191
+
192
+ unless payment_method.is_a?(LitleCardToken)
193
+ hash['orderSource'] = nil
194
+ hash['orderId'] = nil
195
+ end
177
196
 
178
197
  hash
179
198
  end
180
199
 
181
- def add_credit_card_or_token_hash(hash, creditcard_or_token)
182
- if creditcard_or_token.is_a?(String)
183
- add_token_hash(hash, creditcard_or_token)
200
+ def build_payment_method(payment_method, options)
201
+ result = payment_method
202
+
203
+ # Build instance of the LitleCardToken class for internal use if this is a token request.
204
+ if payment_method.is_a?(String) && options.has_key?(:token)
205
+ result = LitleCardToken.new(:token => payment_method)
206
+ result.month = options[:token][:month]
207
+ result.year = options[:token][:year]
208
+ result.verification_value = options[:token][:verification_value]
209
+ result.brand = options[:token][:brand]
210
+ end
211
+
212
+ result
213
+ end
214
+
215
+ def add_creditcard_or_cardtoken_hash(hash, creditcard_or_cardtoken)
216
+ if creditcard_or_cardtoken.is_a?(LitleCardToken)
217
+ add_cardtoken_hash(hash, creditcard_or_cardtoken)
184
218
  else
185
- add_credit_card_hash(hash, creditcard_or_token)
219
+ add_creditcard_hash(hash, creditcard_or_cardtoken)
186
220
  end
187
221
  end
188
222
 
189
- def add_token_hash(hash, creditcard_or_token)
190
- token_info = {
191
- 'litleToken' => creditcard_or_token
192
- }
223
+ def add_identification_or_cardtoken_hash(hash, identification_or_cardtoken)
224
+ if identification_or_cardtoken.is_a?(LitleCardToken)
225
+ add_cardtoken_hash(hash, identification_or_cardtoken)
226
+ else
227
+ hash['litleTxnId'] = identification_or_cardtoken
228
+ end
229
+ end
230
+
231
+ def add_cardtoken_hash(hash, cardtoken)
232
+ token_info = {}
233
+ token_info['litleToken'] = cardtoken.token
234
+ token_info['expDate'] = cardtoken.exp_date if cardtoken.exp_date?
235
+ token_info['cardValidationNum'] = cardtoken.verification_value unless cardtoken.verification_value.blank?
236
+ token_info['type'] = cardtoken.type unless cardtoken.type.blank?
193
237
 
194
238
  hash['token'] = token_info
195
239
  hash
196
240
  end
197
241
 
198
- def add_credit_card_hash(hash, creditcard)
242
+ def add_creditcard_hash(hash, creditcard)
199
243
  cc_type = CARD_TYPE[creditcard.brand]
200
244
  exp_date_yr = creditcard.year.to_s[2..3]
201
245
  exp_date_mo = '%02d' % creditcard.month.to_i
@@ -213,27 +257,19 @@ module ActiveMerchant #:nodoc:
213
257
  end
214
258
 
215
259
  def create_capture_hash(money, authorization, options)
216
- hash = create_hash(money, options)
260
+ hash = create_hash(money, options)
217
261
  hash['litleTxnId'] = authorization
218
262
  hash
219
263
  end
220
264
 
221
- def create_credit_hash(money, identification, options)
222
- hash = create_hash(money, options)
223
- hash['litleTxnId'] = identification
224
- hash['orderSource'] = nil
225
- hash['orderId'] = nil
226
- hash
227
- end
228
-
229
265
  def create_token_hash(creditcard, options)
230
- hash = create_hash(0, options)
266
+ hash = create_hash(0, options)
231
267
  hash['accountNumber'] = creditcard.number
232
268
  hash
233
269
  end
234
270
 
235
271
  def create_void_hash(identification, options)
236
- hash = create_hash(nil, options)
272
+ hash = create_hash(nil, options)
237
273
  hash['litleTxnId'] = identification
238
274
  hash
239
275
  end
@@ -255,54 +291,54 @@ module ActiveMerchant #:nodoc:
255
291
 
256
292
  if options[:billing_address]
257
293
  bill_to_address = {
258
- 'name' => options[:billing_address][:name],
259
- 'companyName' => options[:billing_address][:company],
260
- 'addressLine1' => options[:billing_address][:address1],
261
- 'addressLine2' => options[:billing_address][:address2],
262
- 'city' => options[:billing_address][:city],
263
- 'state' => options[:billing_address][:state],
264
- 'zip' => options[:billing_address][:zip],
265
- 'country' => options[:billing_address][:country],
266
- 'email' => options[:email],
267
- 'phone' => options[:billing_address][:phone]
294
+ 'name' => options[:billing_address][:name],
295
+ 'companyName' => options[:billing_address][:company],
296
+ 'addressLine1' => options[:billing_address][:address1],
297
+ 'addressLine2' => options[:billing_address][:address2],
298
+ 'city' => options[:billing_address][:city],
299
+ 'state' => options[:billing_address][:state],
300
+ 'zip' => options[:billing_address][:zip],
301
+ 'country' => options[:billing_address][:country],
302
+ 'email' => options[:email],
303
+ 'phone' => options[:billing_address][:phone]
268
304
  }
269
305
  end
270
306
  if options[:shipping_address]
271
307
  ship_to_address = {
272
- 'name' => options[:shipping_address][:name],
273
- 'companyName' => options[:shipping_address][:company],
274
- 'addressLine1' => options[:shipping_address][:address1],
275
- 'addressLine2' => options[:shipping_address][:address2],
276
- 'city' => options[:shipping_address][:city],
277
- 'state' => options[:shipping_address][:state],
278
- 'zip' => options[:shipping_address][:zip],
279
- 'country' => options[:shipping_address][:country],
280
- 'email' => options[:email],
281
- 'phone' => options[:shipping_address][:phone]
308
+ 'name' => options[:shipping_address][:name],
309
+ 'companyName' => options[:shipping_address][:company],
310
+ 'addressLine1' => options[:shipping_address][:address1],
311
+ 'addressLine2' => options[:shipping_address][:address2],
312
+ 'city' => options[:shipping_address][:city],
313
+ 'state' => options[:shipping_address][:state],
314
+ 'zip' => options[:shipping_address][:zip],
315
+ 'country' => options[:shipping_address][:country],
316
+ 'email' => options[:email],
317
+ 'phone' => options[:shipping_address][:phone]
282
318
  }
283
319
  end
284
320
 
285
321
  hash = {
286
- 'billToAddress' => bill_to_address,
287
- 'shipToAddress' => ship_to_address,
288
- 'orderId' => (options[:order_id] || @options[:order_id]),
289
- 'customerId' => options[:customer],
290
- 'reportGroup' => (options[:merchant] || @options[:merchant]),
291
- 'merchantId' => (options[:merchant_id] || @options[:merchant_id]),
292
- 'orderSource' => (options[:order_source] || 'ecommerce'),
293
- 'enhancedData' => enhanced_data,
294
- 'fraudCheckType' => fraud_check_type,
295
- 'user' => (options[:user] || @options[:user]),
296
- 'password' => (options[:password] || @options[:password]),
297
- 'version' => (options[:version] || @options[:version]),
298
- 'url' => (options[:url] || url),
299
- 'proxy_addr' => (options[:proxy_addr] || @options[:proxy_addr]),
300
- 'proxy_port' => (options[:proxy_port] || @options[:proxy_port]),
301
- 'id' => (options[:id] || options[:order_id] || @options[:order_id])
322
+ 'billToAddress' => bill_to_address,
323
+ 'shipToAddress' => ship_to_address,
324
+ 'orderId' => (options[:order_id] || @options[:order_id]),
325
+ 'customerId' => options[:customer],
326
+ 'reportGroup' => (options[:merchant] || @options[:merchant]),
327
+ 'merchantId' => (options[:merchant_id] || @options[:merchant_id]),
328
+ 'orderSource' => (options[:order_source] || 'ecommerce'),
329
+ 'enhancedData' => enhanced_data,
330
+ 'fraudCheckType' => fraud_check_type,
331
+ 'user' => (options[:user] || @options[:user]),
332
+ 'password' => (options[:password] || @options[:password]),
333
+ 'version' => (options[:version] || @options[:version]),
334
+ 'url' => (options[:url] || url),
335
+ 'proxy_addr' => (options[:proxy_addr] || @options[:proxy_addr]),
336
+ 'proxy_port' => (options[:proxy_port] || @options[:proxy_port]),
337
+ 'id' => (options[:id] || options[:order_id] || @options[:order_id])
302
338
  }
303
339
 
304
- if( !money.nil? && money.to_s.length > 0 )
305
- hash.merge!({'amount' => money})
340
+ if (!money.nil? && money.to_s.length > 0)
341
+ hash.merge!({ 'amount' => money })
306
342
  end
307
343
  hash
308
344
  end
@@ -315,7 +351,159 @@ module ActiveMerchant #:nodoc:
315
351
 
316
352
  avs_to_pass = AVS_RESPONSE_CODE[result['avsResult']] unless result['avsResult'].blank?
317
353
  end
318
- {'cvv'=>cvv_to_pass, 'avs'=>avs_to_pass}
354
+ { 'cvv' => cvv_to_pass, 'avs' => avs_to_pass }
355
+ end
356
+
357
+ # A +LitleCardToken+ object represents a tokenized credit card, and is capable of validating the various
358
+ # data associated with these.
359
+ #
360
+ # == Example Usage
361
+ # token = LitleCardToken.new(
362
+ # :token => '1234567890123456',
363
+ # :month => '9',
364
+ # :year => '2010',
365
+ # :brand => 'visa',
366
+ # :verification_value => '123'
367
+ # )
368
+ #
369
+ # token.valid? # => true
370
+ # cc.exp_date # => 0910
371
+ #
372
+ class LitleCardToken
373
+ include Validateable
374
+
375
+ # Returns or sets the token. (required)
376
+ #
377
+ # @return [String]
378
+ attr_accessor :token
379
+
380
+ # Returns or sets the expiry month for the card associated with token. (optional)
381
+ #
382
+ # @return [Integer]
383
+ attr_accessor :month
384
+
385
+ # Returns or sets the expiry year for the card associated with token. (optional)
386
+ #
387
+ # @return [Integer]
388
+ attr_accessor :year
389
+
390
+ # Returns or sets the card verification value. (optional)
391
+ #
392
+ # @return [String] the verification value
393
+ attr_accessor :verification_value
394
+
395
+ # Returns or sets the credit card brand. (optional)
396
+ #
397
+ # Valid card types are
398
+ #
399
+ # * +'visa'+
400
+ # * +'master'+
401
+ # * +'discover'+
402
+ # * +'american_express'+
403
+ # * +'diners_club'+
404
+ # * +'jcb'+
405
+ # * +'switch'+
406
+ # * +'solo'+
407
+ # * +'dankort'+
408
+ # * +'maestro'+
409
+ # * +'forbrugsforeningen'+
410
+ # * +'laser'+
411
+ #
412
+ # @return (String) the credit card brand
413
+ attr_accessor :brand
414
+
415
+ # Returns the Litle credit card type identifier.
416
+ #
417
+ # @return (String) the credit card type identifier
418
+ def type
419
+ CARD_TYPE[brand] unless brand.blank?
420
+ end
421
+
422
+ # Returns true if the expiration date is set.
423
+ #
424
+ # @return (Boolean)
425
+ def exp_date?
426
+ !month.to_i.zero? && !year.to_i.zero?
427
+ end
428
+
429
+ # Returns the card token expiration date in MMYY format.
430
+ #
431
+ # @return (String) the expiration date in MMYY format
432
+ def exp_date
433
+ result = ''
434
+ if exp_date?
435
+ exp_date_yr = year.to_s[2..3]
436
+ exp_date_mo = '%02d' % month.to_i
437
+
438
+ result = exp_date_mo + exp_date_yr
439
+ end
440
+ result
441
+ end
442
+
443
+ # Validates the card token details.
444
+ #
445
+ # Any validation errors are added to the {#errors} attribute.
446
+ def validate
447
+ validate_card_token
448
+ validate_expiration_date
449
+ validate_card_brand
450
+ end
451
+
452
+ def check?
453
+ false
454
+ end
455
+
456
+ private
457
+
458
+ CARD_TYPE = {
459
+ 'visa' => 'VI',
460
+ 'master' => 'MC',
461
+ 'american_express' => 'AX',
462
+ 'discover' => 'DI',
463
+ 'jcb' => 'DI',
464
+ 'diners_club' => 'DI'
465
+ }
466
+
467
+ def before_validate #:nodoc:
468
+ self.month = month.to_i
469
+ self.year = year.to_i
470
+ end
471
+
472
+ # Litle XML Reference Guide 1.8.2
473
+ #
474
+ # The length of the original card number is reflected in the token, so a
475
+ # submitted 16-digit number results in a 16-digit token. Also, all tokens
476
+ # use only numeric characters, so you do not have to change your
477
+ # systems to accept alpha-numeric characters.
478
+ #
479
+ # The credit card token numbers themselves have two parts.
480
+ # The last four digits match the last four digits of the card number.
481
+ # The remaining digits (length can vary based upon original card number
482
+ # length) are a randomly generated.
483
+ def validate_card_token #:nodoc:
484
+ if token.to_s.length < 12 || token.to_s.match(/\A\d+\Z/).nil?
485
+ errors.add :token, "is not a valid card token"
486
+ end
487
+ end
488
+
489
+ def validate_expiration_date #:nodoc:
490
+ if !month.to_i.zero? || !year.to_i.zero?
491
+ errors.add :month, "is not a valid month" unless valid_month?(month)
492
+ errors.add :year, "is not a valid year" unless valid_expiry_year?(year)
493
+ end
494
+ end
495
+
496
+ def validate_card_brand #:nodoc:
497
+ errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand)
498
+ end
499
+
500
+ def valid_month?(month)
501
+ (1..12).include?(month.to_i)
502
+ end
503
+
504
+ def valid_expiry_year?(year)
505
+ year.to_s =~ /\A\d{4}\Z/ && year.to_i > 1987
506
+ end
319
507
  end
320
508
  end
321
509
  end