activemerchant 1.31.1 → 1.32.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.
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