activemerchant 1.40.0 → 1.41.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.
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,12 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.41.0 (October 24th, 2013)
4
+
5
+ * Stripe: Payments won't fail when specifying a customer with a creditcard number [melari]
6
+ * Add Conekta gateway [leofischer]
7
+ * Wirecard: Add support for void and refund [duff]
8
+ * Orbital: Mandatory field fix [juicedM3, jduff]
9
+
3
10
  == Version 1.40.0 (October 18th, 2013)
4
11
 
5
12
  * Paymill: Revert Add support for specifying the :customer [melari]
data/CONTRIBUTORS CHANGED
@@ -420,3 +420,7 @@ MoneyMovers (September 2013)
420
420
  Be2Bill (September 2013)
421
421
 
422
422
  * Michaël Hoste (MichaelHoste)
423
+
424
+ Conekta (October 2013)
425
+
426
+ * Leo Fischer (leofischer)
data/README.md CHANGED
@@ -94,6 +94,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
94
94
  * [CardSave](http://www.cardsave.net/) - GB
95
95
  * [CardStream](http://www.cardstream.com/) - GB
96
96
  * [CertoDirect](http://www.certodirect.com/) - BE, BG, CZ, DK, DE, EE, IE, EL, ES, FR, IT, CY, LV, LT, LU, HU, MT, NL, AT, PL, PT, RO, SI, SK, FI, SE, GB
97
+ * [Conekta](https://conekta.io) - MX
97
98
  * [CyberSource](http://www.cybersource.com) - US, BR, CA, CN, DK, FI, FR, DE, JP, MX, NO, SE, GB, SG
98
99
  * [DataCash](http://www.datacash.com/) - GB
99
100
  * [Efsnet](http://www.concordefsnet.com/) - US
@@ -0,0 +1,233 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class ConektaGateway < Gateway
4
+ self.live_url = 'https://api.conekta.io/'
5
+
6
+ self.supported_countries = ['MX']
7
+ self.supported_cardtypes = [:visa, :master]
8
+ self.homepage_url = 'https://conekta.io/'
9
+ self.display_name = 'Conekta Gateway'
10
+ self.money_format = :cents
11
+ self.default_currency = 'MXN'
12
+
13
+ def initialize(options = {})
14
+ requires!(options, :key)
15
+ options[:version] ||= '0.2.0'
16
+ super
17
+ end
18
+
19
+ def purchase(money, payment_source, options = {})
20
+ post = {}
21
+
22
+ add_order(post, money, options)
23
+ add_payment_source(post, payment_source, options)
24
+ add_details_data(post, options)
25
+
26
+ commit(:post, 'charges', post)
27
+ end
28
+
29
+ def authorize(money, payment_source, options = {})
30
+ post = {}
31
+
32
+ add_order(post, money, options)
33
+ add_payment_source(post, payment_source, options)
34
+ add_details_data(post, options)
35
+
36
+ post[:capture] = false
37
+ commit(:post, "charges", post)
38
+ end
39
+
40
+ def capture(identifier, money, options = {})
41
+ post = {}
42
+
43
+ post[:order_id] = identifier
44
+ add_order(post, money, options)
45
+
46
+ commit(:post, "charges/#{identifier}/capture", post)
47
+ end
48
+
49
+ def refund(identifier, money, options)
50
+ post = {}
51
+
52
+ post[:order_id] = identifier
53
+ add_order(post, money, options)
54
+
55
+ commit(:post, "charges/#{identifier}/refund", post)
56
+ end
57
+
58
+ def store(creditcard, options = {})
59
+ post = {}
60
+ add_payment_source(post, creditcard, options)
61
+ post[:name] = options[:name]
62
+ post[:email] = options[:email]
63
+
64
+ path = if options[:customer]
65
+ "customers/#{CGI.escape(options[:customer])}"
66
+ else
67
+ 'customers'
68
+ end
69
+
70
+ commit(:post, path, post)
71
+ end
72
+
73
+ def unstore(customer_id, options = {})
74
+ commit(:delete, "customers/#{CGI.escape(customer_id)}", nil)
75
+ end
76
+
77
+ private
78
+
79
+ def add_order(post, money, options)
80
+ post[:description] = options[:description]
81
+ post[:reference_id] = options[:order_id]
82
+ post[:amount] = amount(money)
83
+ end
84
+
85
+ def add_details_data(post, options)
86
+ details = {}
87
+ details[:name] = options[:customer]
88
+ details[:email] = options[:email]
89
+ details[:phone] = options[:phone]
90
+ details[:device_fingerprint] = options[:device_fingerprint]
91
+ details[:ip] = options[:ip]
92
+ add_billing_address(details, options)
93
+ add_line_items(details, options)
94
+ add_shipment(details, options)
95
+
96
+ post[:details] = details
97
+ end
98
+
99
+ def add_shipment(post, options)
100
+ shipment = {}
101
+ shipment[:carrier] = options[:carrier]
102
+ shipment[:service] = options[:service]
103
+ shipment[:tracking_number] = options[:tracking_number]
104
+ shipment[:price] = options[:price]
105
+ add_shipment_address(shipment, options)
106
+ post[:shipment] = shipment
107
+ end
108
+
109
+ def add_shipment_address(post, options)
110
+ address = {}
111
+ address[:street1] = options[:address1]
112
+ address[:street2] = options[:address2]
113
+ address[:street3] = options[:address3]
114
+ address[:city] = options[:city]
115
+ address[:state] = options[:state]
116
+ address[:country] = options[:country]
117
+ address[:zip] = options[:zip]
118
+ post[:address] = address
119
+ end
120
+
121
+ def add_line_items(post, options)
122
+ post[:line_items] = (options[:line_items] || []).collect do |line_item|
123
+ line_item
124
+ end
125
+ end
126
+
127
+ def add_billing_address(post, options)
128
+ address = {}
129
+ address[:street1] = options[:address1]
130
+ address[:street2] = options[:address2]
131
+ address[:street3] = options[:address3]
132
+ address[:city] = options[:city]
133
+ address[:state] = options[:state]
134
+ address[:country] = options[:country]
135
+ address[:zip] = options[:zip]
136
+ address[:company_name] = options[:company_name]
137
+ address[:tax_id] = options[:tax_id]
138
+ address[:name] = options[:name]
139
+ address[:phone] = options[:phone]
140
+ address[:email] = options[:email]
141
+ post[:billing_address] = address
142
+ end
143
+
144
+ def add_address(post, options)
145
+ address = {}
146
+ address[:street1] = options[:address1]
147
+ address[:street2] = options[:address2]
148
+ address[:street3] = options[:address3]
149
+ address[:city] = options[:city]
150
+ address[:state] = options[:state]
151
+ address[:country] = options[:country]
152
+ address[:zip] = options[:zip]
153
+ post[:address] = address
154
+ end
155
+
156
+ def add_payment_source(post, payment_source, options)
157
+ if payment_source.kind_of?(String)
158
+ post[:card] = payment_source
159
+ elsif payment_source.respond_to?(:number)
160
+ card = {}
161
+ card[:name] = payment_source.name
162
+ card[:cvc] = payment_source.verification_value
163
+ card[:number] = payment_source.number
164
+ card[:exp_month] = "#{sprintf("%02d", payment_source.month)}"
165
+ card[:exp_year] = "#{"#{payment_source.year}"[-2, 2]}"
166
+ post[:card] = card
167
+ add_address(post[:card], options)
168
+ end
169
+ end
170
+
171
+ def parse(body)
172
+ return {} unless body
173
+ JSON.parse(body)
174
+ end
175
+
176
+ def headers(meta)
177
+ @@ua ||= JSON.dump({
178
+ :bindings_version => ActiveMerchant::VERSION,
179
+ :lang => 'ruby',
180
+ :lang_version => "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})",
181
+ :platform => RUBY_PLATFORM,
182
+ :publisher => 'active_merchant'
183
+ })
184
+
185
+ {
186
+ "Accept" => "application/vnd.conekta-v#{options[:version]}+json",
187
+ "Authorization" => "Basic " + Base64.encode64("#{options[:key]}:"),
188
+ "RaiseHtmlError" => "false",
189
+ "User-Agent" => "Conekta ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
190
+ "X-Conekta-Client-User-Agent" => @@ua,
191
+ "X-Conekta-Client-User-Metadata" => meta.to_json
192
+ }
193
+ end
194
+
195
+ def commit(method, url, parameters, options = {})
196
+ success = false
197
+ begin
198
+ raw_response = parse(ssl_request(method, live_url + url, (parameters ? parameters.to_query : nil), headers(options[:meta])))
199
+ success = (raw_response.key?("object") && (raw_response["object"] != "error"))
200
+ rescue ResponseError => e
201
+ raw_response = response_error(e.response.body)
202
+ rescue JSON::ParserError
203
+ raw_response = json_error(raw_response)
204
+ end
205
+
206
+ Response.new(
207
+ success,
208
+ raw_response["message"],
209
+ raw_response,
210
+ :test => test?,
211
+ :authorization => raw_response["id"]
212
+ )
213
+ end
214
+
215
+ def response_error(raw_response)
216
+ begin
217
+ parse(raw_response)
218
+ rescue JSON::ParserError
219
+ json_error(raw_response)
220
+ end
221
+ end
222
+
223
+ def json_error(raw_response)
224
+ msg = 'Invalid response received from the Conekta API.'
225
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
226
+ {
227
+ "message" => msg
228
+ }
229
+ end
230
+ end
231
+ end
232
+ end
233
+
@@ -146,7 +146,7 @@ module ActiveMerchant #:nodoc:
146
146
  # A – Authorization request
147
147
  def authorize(money, creditcard, options = {})
148
148
  order = build_new_order_xml(AUTH_ONLY, money, options) do |xml|
149
- add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn]
149
+ add_creditcard(xml, creditcard, options[:currency])
150
150
  add_address(xml, creditcard, options)
151
151
  if @options[:customer_profiles]
152
152
  add_customer_data(xml, options)
@@ -159,7 +159,7 @@ module ActiveMerchant #:nodoc:
159
159
  # AC – Authorization and Capture
160
160
  def purchase(money, creditcard, options = {})
161
161
  order = build_new_order_xml(AUTH_AND_CAPTURE, money, options) do |xml|
162
- add_creditcard(xml, creditcard, options[:currency]) unless creditcard.nil? && options[:profile_txn]
162
+ add_creditcard(xml, creditcard, options[:currency])
163
163
  add_address(xml, creditcard, options)
164
164
  if @options[:customer_profiles]
165
165
  add_customer_data(xml, options)
@@ -327,8 +327,10 @@ module ActiveMerchant #:nodoc:
327
327
  end
328
328
 
329
329
  def add_creditcard(xml, creditcard, currency=nil)
330
- xml.tag! :AccountNum, creditcard.number
331
- xml.tag! :Exp, expiry_date(creditcard)
330
+ unless creditcard.nil?
331
+ xml.tag! :AccountNum, creditcard.number
332
+ xml.tag! :Exp, expiry_date(creditcard)
333
+ end
332
334
 
333
335
  xml.tag! :CurrencyCode, currency_code(currency)
334
336
  xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
@@ -342,10 +344,12 @@ module ActiveMerchant #:nodoc:
342
344
  # Null-fill this attribute OR
343
345
  # Do not submit the attribute at all.
344
346
  # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
345
- if %w( visa discover ).include?(creditcard.brand)
346
- xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9')
347
+ unless creditcard.nil?
348
+ if %w( visa discover ).include?(creditcard.brand)
349
+ xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9')
350
+ end
351
+ xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value?
347
352
  end
348
- xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value?
349
353
  end
350
354
 
351
355
  def add_refund(xml, currency=nil)
@@ -127,7 +127,7 @@ module ActiveMerchant #:nodoc:
127
127
  post = {}
128
128
  add_amount(post, money, options)
129
129
  add_creditcard(post, creditcard, options)
130
- add_customer(post, options)
130
+ add_customer(post, creditcard, options)
131
131
  add_customer_data(post,options)
132
132
  post[:description] = options[:description] || options[:email]
133
133
  add_flags(post, options)
@@ -189,8 +189,8 @@ module ActiveMerchant #:nodoc:
189
189
  end
190
190
  end
191
191
 
192
- def add_customer(post, options)
193
- post[:customer] = options[:customer] if options[:customer]
192
+ def add_customer(post, creditcard, options)
193
+ post[:customer] = options[:customer] if options[:customer] && !creditcard.respond_to?(:number)
194
194
  end
195
195
 
196
196
  def add_flags(post, options)
@@ -48,8 +48,8 @@ module ActiveMerchant #:nodoc:
48
48
  post[:amount] = localized_amount(money, post[:currency].upcase)
49
49
  end
50
50
 
51
- def add_customer(post, options)
52
- post[:customer] = options[:customer] if options[:customer] && post[:card].blank?
51
+ def add_customer(post, creditcard, options)
52
+ post[:customer] = options[:customer] if options[:customer] && !creditcard.respond_to?(:number)
53
53
  end
54
54
 
55
55
  def json_error(raw_response)
@@ -3,10 +3,7 @@ require 'base64'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class WirecardGateway < Gateway
6
- # Test server location
7
6
  self.test_url = 'https://c3-test.wirecard.com/secure/ssl-gateway'
8
-
9
- # Live server location
10
7
  self.live_url = 'https://c3.wirecard.com/secure/ssl-gateway'
11
8
 
12
9
  # The Namespaces are not really needed, because it just tells the System, that there's actually no namespace used.
@@ -29,57 +26,51 @@ module ActiveMerchant #:nodoc:
29
26
  # number 5551234 within area code 202 (country code 1).
30
27
  VALID_PHONE_FORMAT = /\+\d{1,3}(\(?\d{3}\)?)?\d{3}-\d{4}-\d{3}/
31
28
 
32
- # The countries the gateway supports merchants from as 2 digit ISO country codes
29
+ self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb, :switch ]
33
30
  self.supported_countries = %w(AD CY GI IM MT RO CH AT DK GR IT MC SM TR BE EE HU LV NL SK GB BG FI IS LI NO SI VA FR IL LT PL ES CZ DE IE LU PT SE)
34
-
35
- # Wirecard supports all major credit and debit cards:
36
- # Visa, Mastercard, American Express, Diners Club,
37
- # JCB, Switch, VISA Carte Bancaire, Visa Electron and UATP cards.
38
- # They also support the latest anti-fraud systems such as Verified by Visa or Master Secure Code.
39
- self.supported_cardtypes = [
40
- :visa, :master, :american_express, :diners_club, :jcb, :switch
41
- ]
42
-
43
- # The homepage URL of the gateway
44
31
  self.homepage_url = 'http://www.wirecard.com'
45
-
46
- # The name of the gateway
47
32
  self.display_name = 'Wirecard'
48
-
49
- # The currency should normally be EUROs
50
33
  self.default_currency = 'EUR'
51
-
52
- # 100 is 1.00 Euro
53
34
  self.money_format = :cents
54
35
 
36
+ # Public: Create a new Wirecard gateway.
37
+ #
38
+ # options - A hash of options:
39
+ # :login - The username
40
+ # :password - The password
41
+ # :signature - The BusinessCaseSignature
55
42
  def initialize(options = {})
56
- # verify that username and password are supplied
57
- requires!(options, :login, :password)
58
- # unfortunately Wirecard also requires a BusinessCaseSignature in the XML request
59
- requires!(options, :signature)
43
+ requires!(options, :login, :password, :signature)
60
44
  super
61
45
  end
62
46
 
63
- # Authorization
64
47
  def authorize(money, creditcard, options = {})
65
48
  options[:credit_card] = creditcard
66
49
  commit(:preauthorization, money, options)
67
50
  end
68
51
 
69
- # Capture Authorization
70
52
  def capture(money, authorization, options = {})
71
53
  options[:preauthorization] = authorization
72
54
  commit(:capture, money, options)
73
55
  end
74
56
 
75
- # Purchase
76
57
  def purchase(money, creditcard, options = {})
77
58
  options[:credit_card] = creditcard
78
59
  commit(:purchase, money, options)
79
60
  end
80
61
 
81
- private
62
+ def void(identification, options = {})
63
+ options[:preauthorization] = identification
64
+ commit(:reversal, nil, options)
65
+ end
66
+
67
+ def refund(money, identification, options = {})
68
+ options[:preauthorization] = identification
69
+ commit(:bookback, money, options)
70
+ end
82
71
 
72
+
73
+ private
83
74
  def prepare_options_hash(options)
84
75
  result = @options.merge(options)
85
76
  setup_address_hash!(result)
@@ -156,9 +147,11 @@ module ActiveMerchant #:nodoc:
156
147
  add_invoice(xml, money, options)
157
148
  add_creditcard(xml, options[:credit_card])
158
149
  add_address(xml, options[:billing_address])
159
- when :capture
150
+ when :capture, :bookback
160
151
  xml.tag! 'GuWID', options[:preauthorization]
161
152
  add_amount(xml, money)
153
+ when :reversal
154
+ xml.tag! 'GuWID', options[:preauthorization]
162
155
  end
163
156
  end
164
157
  end
@@ -46,12 +46,16 @@ module ActiveMerchant #:nodoc:
46
46
  add_field mappings[:signature], signature
47
47
  end
48
48
 
49
+ def amount_in_dollars
50
+ sprintf("%.2f", @amount_in_cents.to_f/100)
51
+ end
52
+
49
53
  def amount=(money)
50
54
  @amount_in_cents = money.respond_to?(:cents) ? money.cents : money
51
55
  if money.is_a?(String) or @amount_in_cents.to_i < 0
52
56
  raise ArgumentError, "money amount must be either a Money object or a positive integer in cents."
53
57
  end
54
- add_field mappings[:amount], sprintf("%.2f", @amount_in_cents.to_f/100)
58
+ add_field mappings[:amount], amount_in_dollars
55
59
  end
56
60
 
57
61
  def currency(symbol)
@@ -103,7 +107,7 @@ module ActiveMerchant #:nodoc:
103
107
  components = [merchant_key]
104
108
  components << fields[mappings[:account]]
105
109
  components << fields[mappings[:order]]
106
- components << amount_in_cents.to_s.gsub(/[.,]/, '')
110
+ components << amount_in_dollars.to_s.gsub(/0+$/, '').gsub(/[.,]/, '')
107
111
  components << fields[mappings[:currency]]
108
112
  components.join
109
113
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveMerchant
2
- VERSION = "1.40.0"
2
+ VERSION = "1.41.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.40.0
4
+ version: 1.41.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -37,7 +37,7 @@ cert_chain:
37
37
  Z1BvU1BxN25rK3MyRlFVQko5VVpGSzFsZ016aG8vNGZaZ3pKd2J1K2NPOFNO
38
38
  dWFMUy9iagpoUGFTVHlWVTB5Q1Nudz09Ci0tLS0tRU5EIENFUlRJRklDQVRF
39
39
  LS0tLS0K
40
- date: 2013-10-18 00:00:00.000000000 Z
40
+ date: 2013-10-24 00:00:00.000000000 Z
41
41
  dependencies:
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: activesupport
@@ -278,6 +278,7 @@ files:
278
278
  - lib/active_merchant/billing/gateways/card_stream_modern.rb
279
279
  - lib/active_merchant/billing/gateways/cc5.rb
280
280
  - lib/active_merchant/billing/gateways/certo_direct.rb
281
+ - lib/active_merchant/billing/gateways/conekta.rb
281
282
  - lib/active_merchant/billing/gateways/cyber_source.rb
282
283
  - lib/active_merchant/billing/gateways/data_cash.rb
283
284
  - lib/active_merchant/billing/gateways/efsnet.rb
metadata.gz.sig CHANGED
Binary file