activemerchant 1.32.1 → 1.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +41 -0
  3. data/CONTRIBUTORS +8 -0
  4. data/README.md +6 -4
  5. data/lib/active_merchant/billing/check.rb +4 -3
  6. data/lib/active_merchant/billing/credit_card.rb +7 -3
  7. data/lib/active_merchant/billing/gateways/authorize_net.rb +27 -7
  8. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +8 -1
  9. data/lib/active_merchant/billing/gateways/blue_pay.rb +201 -185
  10. data/lib/active_merchant/billing/gateways/bogus.rb +1 -1
  11. data/lib/active_merchant/billing/gateways/card_stream_modern.rb +155 -0
  12. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +94 -12
  13. data/lib/active_merchant/billing/gateways/litle.rb +41 -11
  14. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +27 -6
  15. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +2 -2
  16. data/lib/active_merchant/billing/gateways/net_registry.rb +8 -3
  17. data/lib/active_merchant/billing/gateways/netaxept.rb +65 -117
  18. data/lib/active_merchant/billing/gateways/orbital.rb +181 -48
  19. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +12 -10
  20. data/lib/active_merchant/billing/gateways/paymill.rb +5 -5
  21. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +11 -6
  22. data/lib/active_merchant/billing/gateways/paypal_express.rb +25 -7
  23. data/lib/active_merchant/billing/gateways/pin.rb +5 -5
  24. data/lib/active_merchant/billing/gateways/sage.rb +10 -5
  25. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +16 -11
  26. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +1 -1
  27. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +21 -16
  28. data/lib/active_merchant/billing/gateways/sage_pay.rb +1 -0
  29. data/lib/active_merchant/billing/gateways/transnational.rb +239 -0
  30. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +8 -3
  31. data/lib/active_merchant/billing/integrations/direc_pay.rb +1 -1
  32. data/lib/active_merchant/billing/integrations/direc_pay/status.rb +1 -1
  33. data/lib/active_merchant/billing/integrations/dwolla.rb +5 -12
  34. data/lib/active_merchant/billing/integrations/dwolla/common.rb +21 -0
  35. data/lib/active_merchant/billing/integrations/dwolla/helper.rb +15 -6
  36. data/lib/active_merchant/billing/integrations/dwolla/notification.rb +11 -6
  37. data/lib/active_merchant/billing/integrations/dwolla/return.rb +12 -4
  38. data/lib/active_merchant/billing/integrations/notification.rb +13 -8
  39. data/lib/active_merchant/billing/integrations/payflow_link/helper.rb +13 -1
  40. data/lib/active_merchant/billing/integrations/payu_in.rb +43 -0
  41. data/lib/active_merchant/billing/integrations/payu_in/helper.rb +74 -0
  42. data/lib/active_merchant/billing/integrations/payu_in/notification.rb +167 -0
  43. data/lib/active_merchant/billing/integrations/payu_in/return.rb +53 -0
  44. data/lib/active_merchant/billing/integrations/quickpay/notification.rb +68 -5
  45. data/lib/active_merchant/billing/integrations/rbkmoney.rb +17 -0
  46. data/lib/active_merchant/billing/integrations/rbkmoney/helper.rb +23 -0
  47. data/lib/active_merchant/billing/integrations/rbkmoney/notification.rb +91 -0
  48. data/lib/active_merchant/version.rb +1 -1
  49. metadata +14 -4
  50. metadata.gz.sig +0 -0
data.tar.gz.sig CHANGED
Binary file
data/CHANGELOG CHANGED
@@ -1,5 +1,46 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.33.0 (May 30, 2013)
4
+
5
+ * Netaxept: Completely revamped to use the "M" service type [rbjordan3, ntalbott]
6
+ * Litle: Void authorizations via an auth reversal [jrust]
7
+ * Add RBK Money integration [england]
8
+ * Direcpay: Update test url [ashish-d]
9
+ * PayPal Express gateway: Add support for creating billing agreements [fabiokr]
10
+ * PayPal Express gateway: Add reference authorizations [fabiokr]
11
+ * Add Cardstream Modern gateway [ExxKA]
12
+ * Pin: Fix special headers [duff]
13
+ * PayPal Express gateway: Remember the billing agreement id as Response#authorization [duff]
14
+ * PayPal Express gateway: Allow an amount of 0 [duff]
15
+ * PayPal Express gateway: Reduce parameter requirements [duff]
16
+ * Quickpay integration: Update notification parser to handle API v6 [larspind]
17
+ * Sage gateway: Deprecate #credit call [duff]
18
+ * Update notification generator to better match current notification class [lulalala]
19
+ * Paymill gateway: Change .com -> .de [louiskearns]
20
+ * Quickpay integration: Fix v6 response parsing [larspind]
21
+ * First Data e4: Add TransArmor store/tokenization support [gabetax]
22
+ * MerchantWarrior: Format expiration month/year correctly [klebervirgilio]
23
+ * Add iconv for ActiveSupport 2.3 under Ruby 2.0 [sanemat]
24
+ * Add Transnational gateway [bvandenbos]
25
+ * Authorize.Net: Add Check as payment method [andrunix]
26
+ * Merchant e-Solutions: Add ref number and recurring support [carlaares]
27
+ * Bogus gateway: Add authorization to purchase response [hron]
28
+ * Bluepay gateway: Fix Check support; general cleanup [ntalbott]
29
+ * Dwolla: Fix security issues and enable guest checkout [capablemonkey, schonfeld]
30
+ * SagePay gateway: Per-transaction 3D-secure selection [ExxKA]
31
+ * Barclays ePDQ: Handle incorrectly encoded response [jordanwheeler, aprofeit]
32
+ * Orbital: Bug fixes; add CustomerEmail, Retry Logic, Managed Billing, and Destination Address [juicedM3
33
+ * Distinguish invalid vs empty issue_numbers on CreditCards [drasch]
34
+ * Float Gemfiles to latest Rails [sanemat]
35
+ * USA ePay Advanced: Fix Check support [RyanScottLewis]
36
+ * Authorize.Net: Match up Check fields better with eCheck.Net requirements [ntalbott]
37
+ * Bluepay: Updated to bp20post api [cagerton, melari]
38
+ * Net Registry: Deprecate credit method [jduff]
39
+ * Sage: Don't include T_customer_number unless it is numeric [melari]
40
+ * Auth.net: Don't include cust_id unless it is numeric [melari]
41
+ * Epay: Deprecate credit method [melari]
42
+ * New PayU.in Integration [PayU, melari]
43
+
3
44
  == Version 1.32.1 (April 4, 2013)
4
45
 
5
46
  * CC5 and Garanti: Remove $KCODE modifications [melari]
@@ -392,3 +392,11 @@ EVO Canada (February 2013)
392
392
  Finansbank WebPOS (March 2013)
393
393
 
394
394
  * scamurcuoglu
395
+
396
+ Cardstream Modern (March 2013)
397
+
398
+ * Vincens (ExxKA)
399
+
400
+ Transnational (May 2013)
401
+
402
+ * Ben VandenBos (bvandenbos)
data/README.md CHANGED
@@ -12,7 +12,7 @@ Ruby applications which deal with financial transactions. It is maintained by th
12
12
  [Shopify](http://www.shopify.com) and [Spreedly](https://spreedly.com) teams, with much help
13
13
  from an ever-growing set of contributors.
14
14
 
15
- See {file:GettingStarted.md} if you want to learn more about using Active Merchant in your
15
+ See [GettingStarted.md](GettingStarted.md) if you want to learn more about using Active Merchant in your
16
16
  applications.
17
17
 
18
18
  ## Installation
@@ -72,7 +72,7 @@ credit card details.
72
72
  end
73
73
  end
74
74
 
75
- For more in-depth documentation and tutorials, see {file:GettingStarted.md} and the
75
+ For more in-depth documentation and tutorials, see [GettingStarted.md](GettingStarted.md) and the
76
76
  [API documentation](http://rubydoc.info/github/Shopify/active_merchant/master/file/README.md).
77
77
 
78
78
  ## Supported Direct Payment Gateways
@@ -135,7 +135,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
135
135
  * [PayGate PayXML](http://paygate.co.za/) - US, ZA
136
136
  * [PayJunction](http://www.payjunction.com/) - US
137
137
  * [PaymentExpress](http://www.paymentexpress.com/) - AU, MY, NZ, SG, ZA, UK, US
138
- * [Paymill](https://www.paymill.com) - AT, BE, CH, CZ, DE, DK, EE, ES, FI, FR, GB, HU, IE, IS, IT, LI, LU, LV, NL, NO, PL, PT, SE, SI, TR
138
+ * [PAYMILL](https://www.paymill.com) - AD, AT, BE, CH, CY, CZ, DE, DK, EE, ES, FI, FO, FR, GB, GR, HU, IE, IL, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, SE, SI, SK, TR, VA
139
139
  * [PayPal Express Checkout](https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside) - US, CA, SG, AU
140
140
  * [PayPal Payflow Pro](https://www.paypal.com/cgi-bin/webscr?cmd=_payflow-pro-overview-outside) - US, CA, SG, AU
141
141
  * [PayPal Website Payments Pro (UK)](https://www.paypal.com/uk/cgi-bin/webscr?cmd=_wp-pro-overview-outside) - UK
@@ -165,6 +165,7 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
165
165
  * [Spreedly Core](https://spreedlycore.com/) - AD, AE, AT, AU, BD, BE, BG, BN, CA, CH, CY, CZ, DE, DK, EE, EG, ES, FI, FR, GB, GI, GR, HK, HU, ID, IE, IL, IM, IN, IS, IT, JO, KW, LB, LI, LK, LT, LU, LV, MC, MT, MU, MV, MX, MY, NL, NO, NZ, OM, PH, PL, PT, QA, RO, SA, SE, SG, SI, SK, SM, TR, TT, UM, US, VA, VN, ZA
166
166
  * [Stripe](https://stripe.com/) - US
167
167
  * [TransFirst](http://www.transfirst.com/) - US
168
+ * [Transnational](http://www.tnbci.com/) - US
168
169
  * [TrustCommerce](http://www.trustcommerce.com/) - US
169
170
  * [USA ePay](http://www.usaepay.com/) - US
170
171
  * [Verifi](http://www.verifi.com/) - US
@@ -193,7 +194,8 @@ The [ActiveMerchant Wiki](http://github.com/Shopify/active_merchant/wikis) conta
193
194
  * [Paxum](https://www.paxum.com/)
194
195
  * [PayPal Website Payments Standard](https://www.paypal.com/cgi-bin/webscr?cmd#_wp-standard-overview-outside)
195
196
  * [Paysbuy](https://www.paysbuy.com/) - TH
196
- * [Robokassa](http://robokassa.ru/)
197
+ * [RBK Money](https://rbkmoney.ru/) - RU
198
+ * [Robokassa](http://robokassa.ru/) - RU
197
199
  * [SagePay Form](http://www.sagepay.com/products_services/sage_pay_go/integration/form)
198
200
  * [Suomen Maksuturva](https://www.maksuturva.fi/services/vendor_services/integration_guidelines.html)
199
201
  * [Valitor](http://www.valitor.is/) - IS
@@ -4,12 +4,13 @@ module ActiveMerchant #:nodoc:
4
4
  # of necessary attributes such as checkholder's name, routing and account numbers, but it is
5
5
  # not backed by any database.
6
6
  #
7
- # You may use Check in place of CreditCard with any gateway that supports it. Currently, only
8
- # +BraintreeGateway+ supports the Check object.
7
+ # You may use Check in place of CreditCard with any gateway that supports it.
9
8
  class Check
10
9
  include Validateable
11
10
 
12
- attr_accessor :first_name, :last_name, :routing_number, :account_number, :account_holder_type, :account_type, :number
11
+ attr_accessor :first_name, :last_name,
12
+ :bank_name, :routing_number, :account_number,
13
+ :account_holder_type, :account_type, :number
13
14
 
14
15
  # Used for Canadian bank accounts
15
16
  attr_accessor :institution_number, :transit_number
@@ -257,9 +257,13 @@ module ActiveMerchant #:nodoc:
257
257
  def validate_switch_or_solo_attributes #:nodoc:
258
258
  if %w[switch solo].include?(brand)
259
259
  unless valid_month?(@start_month) && valid_start_year?(@start_year) || valid_issue_number?(@issue_number)
260
- errors.add :start_month, "is invalid" unless valid_month?(@start_month)
261
- errors.add :start_year, "is invalid" unless valid_start_year?(@start_year)
262
- errors.add :issue_number, "cannot be empty" unless valid_issue_number?(@issue_number)
260
+ if @issue_number.blank?
261
+ errors.add :start_month, "is invalid" unless valid_month?(@start_month)
262
+ errors.add :start_year, "is invalid" unless valid_start_year?(@start_year)
263
+ errors.add :issue_number, "cannot be empty"
264
+ else
265
+ errors.add :issue_number, "is invalid" unless valid_issue_number?(@issue_number)
266
+ end
263
267
  end
264
268
  end
265
269
  end
@@ -83,13 +83,13 @@ module ActiveMerchant #:nodoc:
83
83
  # ==== Parameters
84
84
  #
85
85
  # * <tt>money</tt> -- The amount to be authorized as an Integer value in cents.
86
- # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
86
+ # * <tt>paysource</tt> -- The CreditCard or Check details for the transaction.
87
87
  # * <tt>options</tt> -- A hash of optional parameters.
88
- def authorize(money, creditcard, options = {})
88
+ def authorize(money, paysource, options = {})
89
89
  post = {}
90
90
  add_currency_code(post, money, options)
91
91
  add_invoice(post, options)
92
- add_creditcard(post, creditcard)
92
+ add_payment_source(post, paysource, options)
93
93
  add_address(post, options)
94
94
  add_customer_data(post, options)
95
95
  add_duplicate_window(post)
@@ -102,13 +102,13 @@ module ActiveMerchant #:nodoc:
102
102
  # ==== Parameters
103
103
  #
104
104
  # * <tt>money</tt> -- The amount to be purchased as an Integer value in cents.
105
- # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
105
+ # * <tt>paysource</tt> -- The CreditCard or Check details for the transaction.
106
106
  # * <tt>options</tt> -- A hash of optional parameters.
107
- def purchase(money, creditcard, options = {})
107
+ def purchase(money, paysource, options = {})
108
108
  post = {}
109
109
  add_currency_code(post, money, options)
110
110
  add_invoice(post, options)
111
- add_creditcard(post, creditcard)
111
+ add_payment_source(post, paysource, options)
112
112
  add_address(post, options)
113
113
  add_customer_data(post, options)
114
114
  add_duplicate_window(post)
@@ -349,6 +349,26 @@ module ActiveMerchant #:nodoc:
349
349
  post[:last_name] = creditcard.last_name
350
350
  end
351
351
 
352
+ def add_payment_source(params, source, options={})
353
+ if card_brand(source) == "check"
354
+ add_check(params, source, options)
355
+ else
356
+ add_creditcard(params, source)
357
+ end
358
+ end
359
+
360
+ def add_check(post, check, options)
361
+ post[:method] = "ECHECK"
362
+ post[:bank_name] = check.bank_name
363
+ post[:bank_aba_code] = check.routing_number
364
+ post[:bank_acct_num] = check.account_number
365
+ post[:bank_acct_type] = check.account_type
366
+ post[:echeck_type] = "WEB"
367
+ post[:bank_acct_name] = check.name
368
+ post[:bank_check_number] = check.number if check.number.present?
369
+ post[:recurring_billing] = (options[:recurring] ? "TRUE" : "FALSE")
370
+ end
371
+
352
372
  def add_customer_data(post, options)
353
373
  if options.has_key? :email
354
374
  post[:email] = options[:email]
@@ -356,7 +376,7 @@ module ActiveMerchant #:nodoc:
356
376
  end
357
377
 
358
378
  if options.has_key? :customer
359
- post[:cust_id] = options[:customer]
379
+ post[:cust_id] = options[:customer] if Float(options[:customer]) rescue nil
360
380
  end
361
381
 
362
382
  if options.has_key? :ip
@@ -134,7 +134,14 @@ module ActiveMerchant #:nodoc:
134
134
  end
135
135
 
136
136
  def parse
137
- doc = REXML::Document.new(@response)
137
+ require 'iconv' unless String.method_defined?(:encode)
138
+ if String.method_defined?(:encode)
139
+ doc = REXML::Document.new(@response.encode("UTF-8", "ISO-8859-1"))
140
+ else
141
+ ic = Iconv.new('UTF-8', 'ISO-8859-1')
142
+ doc = REXML::Document.new(ic.iconv(@response))
143
+ end
144
+
138
145
  auth_type = find(doc, "//Transaction/Type").to_s
139
146
 
140
147
  message = find(doc, "//Message/Text")
@@ -3,21 +3,47 @@ require 'digest/md5'
3
3
  module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  class BluePayGateway < Gateway
6
- class_attribute :live_url, :rebilling_url, :ignore_http_status
6
+ class_attribute :rebilling_url, :ignore_http_status
7
7
 
8
8
  self.live_url = 'https://secure.bluepay.com/interfaces/bp20post'
9
9
  self.rebilling_url = 'https://secure.bluepay.com/interfaces/bp20rebadmin'
10
10
 
11
11
  self.ignore_http_status = true
12
12
 
13
- RESPONSE_CODE, RESPONSE_REASON_CODE, RESPONSE_REASON_TEXT = 0, 2, 3
14
- AVS_RESULT_CODE, TRANSACTION_ID, CARD_CODE_RESPONSE_CODE = 5, 6, 38
15
-
16
13
  CARD_CODE_ERRORS = %w( N S )
17
14
  AVS_ERRORS = %w( A E N R W Z )
18
15
  AVS_REASON_CODES = %w(27 45)
19
16
 
20
- class_attribute :duplicate_window
17
+ FRAUD_REVIEW_STATUSES = %w( E 0 )
18
+
19
+ FIELD_MAP = {
20
+ 'TRANS_ID' => :transaction_id,
21
+ 'STATUS' => :response_code,
22
+ 'AVS' => :avs_result_code,
23
+ 'CVV2'=> :card_code,
24
+ 'AUTH_CODE' => :authorization,
25
+ 'MESSAGE' => :message,
26
+ 'REBID' => :rebid,
27
+ 'TRANS_TYPE' => :trans_type,
28
+ 'PAYMENT_ACCOUNT_MASK' => :acct_mask,
29
+ 'CARD_TYPE' => :card_type,
30
+ }
31
+
32
+ REBILL_FIELD_MAP = {
33
+ 'REBILL_ID' => :rebill_id,
34
+ 'ACCOUNT_ID'=> :account_id,
35
+ 'USER_ID' => :user_id,
36
+ 'TEMPLATE_ID' => :template_id,
37
+ 'STATUS' => :status,
38
+ 'CREATION_DATE' => :creation_date,
39
+ 'NEXT_DATE' => :next_date,
40
+ 'LAST_DATE' => :last_date,
41
+ 'SCHED_EXPR' => :schedule,
42
+ 'CYCLES_REMAIN' => :cycles_remain,
43
+ 'REB_AMOUNT' => :rebill_amount,
44
+ 'NEXT_AMOUNT' => :next_amount,
45
+ 'USUAL_DATE' => :undoc_usual_date, # Not found in the bp20rebadmin API doc.
46
+ }
21
47
 
22
48
  self.supported_countries = ['US']
23
49
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
@@ -25,7 +51,6 @@ module ActiveMerchant #:nodoc:
25
51
  self.display_name = 'BluePay'
26
52
  self.money_format = :dollars
27
53
 
28
-
29
54
  # Creates a new BluepayGateway
30
55
  #
31
56
  # The gateway requires that a valid Account ID and Secret Key be passed
@@ -54,24 +79,12 @@ module ActiveMerchant #:nodoc:
54
79
  # * <tt>options</tt> -- A hash of optional parameters.
55
80
  def authorize(money, payment_object, options = {})
56
81
  post = {}
57
- post[:MASTER_ID] = ''
58
- if payment_object != nil && payment_object.class() != String
59
- payment_object.class() == ActiveMerchant::Billing::Check ?
60
- add_check(post, payment_object) :
61
- add_creditcard(post, payment_object)
62
- else
63
- post[:MASTER_ID] = payment_object
64
- end
82
+ add_payment_method(post, payment_object)
65
83
  add_invoice(post, options)
66
84
  add_address(post, options)
67
85
  add_customer_data(post, options)
68
- if options[:rebill] != nil
69
- post[:DO_REBILL] = '1'
70
- post[:REB_AMOUNT] = amount(options[:rebill_amount])
71
- post[:REB_FIRST_DATE] = options[:rebill_start_date]
72
- post[:REB_EXPR] = options[:rebill_expression]
73
- post[:REB_CYCLES] = options[:rebill_cycles]
74
- end
86
+ add_rebill(post, options) if options[:rebill]
87
+ add_duplicate_override(post, options)
75
88
  post[:TRANS_TYPE] = 'AUTH'
76
89
  commit('AUTH_ONLY', money, post)
77
90
  end
@@ -89,24 +102,12 @@ module ActiveMerchant #:nodoc:
89
102
  # * <tt>options</tt> -- A hash of optional parameters.,
90
103
  def purchase(money, payment_object, options = {})
91
104
  post = {}
92
- post[:MASTER_ID] = ''
93
- if payment_object != nil && payment_object.class() != String
94
- payment_object.class() == ActiveMerchant::Billing::Check ?
95
- add_check(post, payment_object) :
96
- add_creditcard(post, payment_object)
97
- else
98
- post[:MASTER_ID] = payment_object
99
- end
105
+ add_payment_method(post, payment_object)
100
106
  add_invoice(post, options)
101
107
  add_address(post, options)
102
108
  add_customer_data(post, options)
103
- if options[:rebill] != nil
104
- post[:DO_REBILL] = '1'
105
- post[:REB_AMOUNT] = amount(options[:rebill_amount])
106
- post[:REB_FIRST_DATE] = options[:rebill_start_date]
107
- post[:REB_EXPR] = options[:rebill_expression]
108
- post[:REB_CYCLES] = options[:rebill_cycles]
109
- end
109
+ add_rebill(post, options) if options[:rebill]
110
+ add_duplicate_override(post, options)
110
111
  post[:TRANS_TYPE] = 'SALE'
111
112
  commit('AUTH_CAPTURE', money, post)
112
113
  end
@@ -154,20 +155,17 @@ module ActiveMerchant #:nodoc:
154
155
  # If the payment_object is a token, then the transaction type will reverse a previous capture or purchase transaction, returning the funds to the customer. If the amount is nil, a full credit will be processed. This is referred to a REFUND transaction in BluePay.
155
156
  # If the payment_object is either a CreditCard or Check object, then the transaction type will be an unmatched credit placing funds in the specified account. This is referred to a CREDIT transaction in BluePay.
156
157
  # * <tt>options</tt> -- A hash of parameters.
157
- def refund(money, payment_object, options = {})
158
- post = {}
159
- post[:PAYMENT_ACCOUNT] = ''
160
- if payment_object != nil && payment_object.class() != String
161
- payment_object.class() == ActiveMerchant::Billing::Check ?
162
- add_check(post, payment_object) :
163
- add_creditcard(post, payment_object)
164
- post[:TRANS_TYPE] = 'CREDIT'
165
- else
166
- post[:MASTER_ID] = payment_object
167
- post[:TRANS_TYPE] = 'REFUND'
158
+ def refund(money, identification, options = {})
159
+ if(identification && !identification.kind_of?(String))
160
+ deprecated "refund should only be used to refund a referenced transaction"
161
+ return credit(money, identification, options)
168
162
  end
169
163
 
170
- options[:first_name] ? post[:NAME1] = options[:first_name] : post[:NAME1] = ''
164
+ post = {}
165
+ post[:PAYMENT_ACCOUNT] = ''
166
+ post[:MASTER_ID] = identification
167
+ post[:TRANS_TYPE] = 'REFUND'
168
+ post[:NAME1] = (options[:first_name] ? options[:first_name] : "")
171
169
  post[:NAME2] = options[:last_name] if options[:last_name]
172
170
  post[:ZIP] = options[:zip] if options[:zip]
173
171
  add_invoice(post, options)
@@ -176,9 +174,24 @@ module ActiveMerchant #:nodoc:
176
174
  commit('CREDIT', money, post)
177
175
  end
178
176
 
179
- def credit(money, identification, options = {})
180
- deprecated CREDIT_DEPRECATION_MESSAGE
181
- refund(money, identification, options)
177
+ def credit(money, payment_object, options = {})
178
+ if(payment_object && payment_object.kind_of?(String))
179
+ deprecated "credit should only be used to credit a payment method"
180
+ return refund(money, payment_object, options)
181
+ end
182
+
183
+ post = {}
184
+ post[:PAYMENT_ACCOUNT] = ''
185
+ add_payment_method(post, payment_object)
186
+ post[:TRANS_TYPE] = 'CREDIT'
187
+
188
+ post[:NAME1] = (options[:first_name] ? options[:first_name] : "")
189
+ post[:NAME2] = options[:last_name] if options[:last_name]
190
+ post[:ZIP] = options[:zip] if options[:zip]
191
+ add_invoice(post, options)
192
+ add_address(post, options)
193
+ add_customer_data(post, options)
194
+ commit('CREDIT', money, post)
182
195
  end
183
196
 
184
197
  # Create a new recurring payment.
@@ -214,9 +227,12 @@ module ActiveMerchant #:nodoc:
214
227
  # A money object of 1995 cents would be passed into the 'money' parameter.
215
228
  def recurring(money, payment_object, options = {})
216
229
  requires!(options, :rebill_start_date, :rebill_expression)
217
- options[:rebill] = '1'
218
- money == nil ? authorize(money, payment_object, options) :
219
- purchase(money, payment_object, options)
230
+ options[:rebill] = true
231
+ if money
232
+ purchase(money, payment_object, options)
233
+ else
234
+ authorize(money, payment_object, options)
235
+ end
220
236
  end
221
237
 
222
238
  # View a recurring payment
@@ -252,11 +268,11 @@ module ActiveMerchant #:nodoc:
252
268
  requires!(options, :rebill_id)
253
269
  post[:REBILL_ID] = options[:rebill_id]
254
270
  post[:TRANS_TYPE] = 'SET'
255
- post[:REB_AMOUNT] = amount(options[:rebill_amount]) if !options[:rebill_amount].nil?
256
- post[:NEXT_DATE] = options[:rebill_next_date] if !options[:rebill_next_date].nil?
257
- post[:REB_EXPR] = options[:rebill_expression] if !options[:rebill_expression].nil?
258
- post[:REB_CYCLES] = options[:rebill_cycles] if !options[:rebill_cycles].nil?
259
- post[:NEXT_AMOUNT] = options[:rebill_next_amount] if !options[:rebill_next_amount].nil?
271
+ post[:REB_AMOUNT] = amount(options[:rebill_amount]) if options[:rebill_amount]
272
+ post[:NEXT_DATE] = options[:rebill_next_date]
273
+ post[:REB_EXPR] = options[:rebill_expression]
274
+ post[:REB_CYCLES] = options[:rebill_cycles]
275
+ post[:NEXT_AMOUNT] = options[:rebill_next_amount]
260
276
  commit('rebill', 'nil', post)
261
277
  end
262
278
 
@@ -279,117 +295,133 @@ module ActiveMerchant #:nodoc:
279
295
  private
280
296
 
281
297
  def commit(action, money, fields)
282
- fields[:AMOUNT] = amount(money) unless (fields[:TRANS_TYPE] == 'VOID' or action == 'rebill')
283
- test? == true || @options[:test] == true ? fields[:MODE] = 'TEST' : fields[:MODE] = 'LIVE'
284
- action == 'rebill' ? begin url = rebilling_url; fields[:TAMPER_PROOF_SEAL] = calc_rebill_tps(fields) end : begin url = live_url; fields[:TAMPER_PROOF_SEAL] = calc_tps(amount(money), fields) end
298
+ fields[:AMOUNT] = amount(money) unless(fields[:TRANS_TYPE] == 'VOID' || action == 'rebill')
299
+ fields[:MODE] = (test? ? 'TEST' : 'LIVE')
285
300
  fields[:ACCOUNT_ID] = @options[:login]
286
- data = ssl_post url, post_data(action, fields)
287
- response = parse(data)
288
- message = message_from(response)
289
- test_mode = test? || fields[:MODE] == 'TEST'
290
- if (response.has_key?('TRANS_ID'))
291
- response_id = response['TRANS_ID'].to_s()
292
- elsif (response.has_key?('rebill_id'))
293
- response_id = response['rebill_id'][0]
301
+
302
+ if action == 'rebill'
303
+ url = rebilling_url
304
+ fields[:TAMPER_PROOF_SEAL] = calc_rebill_tps(fields)
294
305
  else
295
- response_id = response[TRANSACTION_ID]
306
+ url = live_url
307
+ fields[:TAMPER_PROOF_SEAL] = calc_tps(amount(money), fields)
296
308
  end
297
- avs = (response[AVS_RESULT_CODE] != '' ? response[AVS_RESULT_CODE] : '')
298
- cvv2 = (!response[CARD_CODE_RESPONSE_CODE].empty? ? response[CARD_CODE_RESPONSE_CODE] : '')
299
- Response.new(success?(response), message, response,
300
- :test => test_mode,
301
- :authorization => response_id,
302
- :fraud_review => fraud_review?(response),
303
- :avs_result => { :code => avs },
304
- :cvv_result => cvv2
305
- )
309
+ parse(ssl_post(url, post_data(action, fields)))
306
310
  end
307
311
 
308
- def success?(response)
309
- if (response['STATUS'] == '1' || message_from(response) =~ /approved/ || response.has_key?('rebill_id') || response[RESPONSE_REASON_TEXT] =~ /approved/)
310
- return true
311
- else
312
- return false
312
+ def parse_recurring(response_fields, opts={}) # expected status?
313
+ parsed = {}
314
+ response_fields.each do |k,v|
315
+ mapped_key = REBILL_FIELD_MAP.include?(k) ? REBILL_FIELD_MAP[k] : k
316
+ parsed[mapped_key] = v
313
317
  end
314
- end
315
318
 
316
- def fraud_review?(response)
317
- response['STATUS'] == 'E' || response['STATUS'] == '0' || response[RESPONSE_REASON_TEXT] =~ /being reviewed/
318
- end
319
+ success = parsed[:status] != 'error'
320
+ message = parsed[:status]
319
321
 
320
- def get_rebill_id(response)
321
- response['REBID'] if response_has.key?('REBID')
322
+ Response.new(success, message, parsed,
323
+ :test => test?,
324
+ :authorization => parsed[:rebill_id])
322
325
  end
323
326
 
324
327
  def parse(body)
325
- fields = CGI::parse(body)
326
- if fields.has_key?('MESSAGE') or fields.has_key?('rebill_id')
327
- if fields.has_key?('MESSAGE')
328
- fields['MESSAGE'][0] == "Missing ACCOUNT_ID" ? message = "The merchant login ID or password is invalid" : message = fields['MESSAGE']
329
- fields['MESSAGE'][0] =~ /Approved/ ? message = "This transaction has been approved" : message = fields['MESSAGE'] if message == fields['MESSAGE']
330
- fields['MESSAGE'][0] =~ /Expired/ ? message = "The credit card has expired" : message = fields['MESSAGE'] if message == fields['MESSAGE']
331
- fields.delete('MESSAGE')
332
- end
333
- fields.has_key?('STATUS') ? begin status = fields['STATUS']; fields.delete('STATUS') end : status = ''
334
- fields.has_key?('AVS') ? begin avs = fields['AVS']; fields.delete('AVS') end : avs = ''
335
- fields.has_key?('CVV2') ? begin cvv2 = fields['CVV2']; fields.delete('CVV2') end : cvv2 = ''
336
- fields.has_key?('MASTER_ID') ? begin trans_id = fields['MASTER_ID']; fields.delete('MASTER_ID') end : trans_id = ''
337
- fields[:avs_result_code] = avs
338
- fields[:card_code] = cvv2
339
- fields[:response_code] = status
340
- fields[:response_reason_code] = ''
341
- fields[:response_reason_text] = message
342
- fields[:transaction_id] = trans_id
343
- return fields
328
+ # The bp20api has max one value per form field.
329
+ response_fields = Hash[CGI::parse(body).map{|k,v| [k.upcase,v.first]}]
330
+
331
+ if response_fields.include? "REBILL_ID"
332
+ return parse_recurring(response_fields)
333
+ end
334
+
335
+ parsed = {}
336
+ response_fields.each do |k,v|
337
+ mapped_key = FIELD_MAP.include?(k) ? FIELD_MAP[k] : k
338
+ parsed[mapped_key] = v
344
339
  end
345
- # parse response if using other old API
346
- hash = Hash.new
347
- fields = fields.first[0].split(",")
348
- fields.each_index do |x|
349
- hash[x] = fields[x].tr('$','')
340
+
341
+ # normalize message
342
+ message = message_from(parsed)
343
+ success = parsed[:response_code] == '1'
344
+ Response.new(success, message, parsed,
345
+ :test => test?,
346
+ :authorization => (parsed[:rebid] && parsed[:rebid] != '' ? parsed[:rebid] : parsed[:transaction_id]),
347
+ :fraud_review => FRAUD_REVIEW_STATUSES.include?(parsed[:response_code]),
348
+ :avs_result => { :code => parsed[:avs_result_code] },
349
+ :cvv_result => parsed[:card_code]
350
+ )
351
+ end
352
+
353
+ def message_from(parsed)
354
+ message = parsed[:message]
355
+ if(parsed[:response_code].to_i == 2)
356
+ if CARD_CODE_ERRORS.include?(parsed[:card_code])
357
+ message = CVVResult.messages[parsed[:card_code]]
358
+ elsif AVS_ERRORS.include?(parsed[:avs_result_code])
359
+ message = AVSResult.messages[ parsed[:avs_result_code] ]
360
+ else
361
+ message = message.chomp('.')
362
+ end
363
+ elsif message == "Missing ACCOUNT_ID"
364
+ message = "The merchant login ID or password is invalid"
365
+ elsif message =~ /Approved/
366
+ message = "This transaction has been approved"
367
+ elsif message =~ /Expired/
368
+ message = "The credit card has expired"
350
369
  end
351
- hash
370
+ message
352
371
  end
353
372
 
354
373
  def add_invoice(post, options)
355
- post[:ORDER_ID] = options[:order_id] if options.has_key? :order_id
356
- post[:INVOICE_ID] = options[:invoice] if options.has_key? :invoice
357
- post[:invoice_num] = options[:order_id] if options.has_key? :order_id
358
- post[:MEMO] = options[:description] if options.has_key? :description
359
- post[:description] = options[:description] if options.has_key? :description
374
+ post[:ORDER_ID] = options[:order_id]
375
+ post[:INVOICE_ID] = options[:invoice]
376
+ post[:invoice_num] = options[:order_id]
377
+ post[:MEMO] = options[:description]
378
+ post[:description] = options[:description]
379
+ end
380
+
381
+ def add_payment_method(post, payment_object)
382
+ post[:MASTER_ID] = ''
383
+ case payment_object
384
+ when String
385
+ post[:MASTER_ID] = payment_object
386
+ when Check
387
+ add_check(post, payment_object)
388
+ else
389
+ add_creditcard(post, payment_object)
390
+ end
360
391
  end
361
392
 
362
393
  def add_creditcard(post, creditcard)
363
394
  post[:PAYMENT_TYPE] = 'CREDIT'
364
395
  post[:PAYMENT_ACCOUNT] = creditcard.number
365
- post[:CARD_CVV2] = creditcard.verification_value if
366
- creditcard.verification_value?
396
+ post[:CARD_CVV2] = creditcard.verification_value
367
397
  post[:CARD_EXPIRE] = expdate(creditcard)
368
398
  post[:NAME1] = creditcard.first_name
369
399
  post[:NAME2] = creditcard.last_name
370
400
  end
371
401
 
402
+ CHECK_ACCOUNT_TYPES = {
403
+ "checking" => "C",
404
+ "savings" => "S"
405
+ }
406
+
372
407
  def add_check(post, check)
373
408
  post[:PAYMENT_TYPE] = 'ACH'
374
- post[:PAYMENT_ACCOUNT] = check.account_type + ":" + check.routing_number + ":" + check.account_number
409
+ post[:PAYMENT_ACCOUNT] = [CHECK_ACCOUNT_TYPES[check.account_type], check.routing_number, check.account_number].join(":")
375
410
  post[:NAME1] = check.first_name
376
411
  post[:NAME2] = check.last_name
377
412
  end
378
413
 
379
414
  def add_customer_data(post, options)
380
- post[:EMAIL] = options[:email] if options.has_key? :email
381
- post[:CUSTOM_ID] = options[:customer] if options.has_key? :customer
415
+ post[:EMAIL] = options[:email]
416
+ post[:CUSTOM_ID] = options[:customer]
382
417
  end
383
418
 
384
- def add_duplicate_window(post)
385
- unless duplicate_window.nil?
386
- post[:duplicate_window] = duplicate_window
387
- post[:DUPLICATE_OVERRIDE] = duplicate_window
388
- end
419
+ def add_duplicate_override(post, options)
420
+ post[:DUPLICATE_OVERRIDE] = options[:duplicate_override]
389
421
  end
390
422
 
391
423
  def add_address(post, options)
392
- if address = options[:billing_address] || options[:address]
424
+ if address = (options[:shipping_address] || options[:billing_address] || options[:address])
393
425
  post[:NAME1] = address[:first_name]
394
426
  post[:NAME2] = address[:last_name]
395
427
  post[:ADDR1] = address[:address1]
@@ -397,27 +429,23 @@ module ActiveMerchant #:nodoc:
397
429
  post[:COMPANY_NAME] = address[:company]
398
430
  post[:PHONE] = address[:phone]
399
431
  post[:CITY] = address[:city]
400
- post[:STATE] = address[:state].blank? ? 'n/a' : address[:state]
432
+ post[:STATE] = (address[:state].blank? ? 'n/a' : address[:state])
401
433
  post[:ZIP] = address[:zip]
402
434
  post[:COUNTRY] = address[:country]
403
435
  end
404
- if address = options[:shipping_address]
405
- post[:NAME1] = address[:first_name]
406
- post[:NAME2] = address[:last_name]
407
- post[:ADDR1] = address[:address1]
408
- post[:ADDR1] = address[:address1]
409
- post[:COMPANY_NAME] = address[:company]
410
- post[:PHONE] = address[:phone]
411
- post[:ZIP] = address[:zip]
412
- post[:CITY] = address[:city]
413
- post[:COUNTRY] = address[:country]
414
- post[:STATE] = address[:state].blank? ? 'n/a' : address[:state]
415
- end
436
+ end
437
+
438
+ def add_rebill(post, options)
439
+ post[:DO_REBILL] = '1'
440
+ post[:REB_AMOUNT] = amount(options[:rebill_amount])
441
+ post[:REB_FIRST_DATE] = options[:rebill_start_date]
442
+ post[:REB_EXPR] = options[:rebill_expression]
443
+ post[:REB_CYCLES] = options[:rebill_cycles]
416
444
  end
417
445
 
418
446
  def post_data(action, parameters = {})
419
447
  post = {}
420
- post[:version] = '3.0'
448
+ post[:version] = '1'
421
449
  post[:login] = ''
422
450
  post[:tran_key] = ''
423
451
  post[:relay_response] = "FALSE"
@@ -427,60 +455,48 @@ module ActiveMerchant #:nodoc:
427
455
  post[:encap_char] = "$"
428
456
  post[:card_num] = '4111111111111111'
429
457
  post[:exp_date] = '1212'
430
- post[:solution_ID] = application_id if application_id.present? && application_id != "ActiveMerchant"
431
- request = post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
432
- request
433
- end
434
-
435
- def message_from(results)
436
- if results[:response_code] == 2
437
- return CVVResult.messages[ results[:card_code] ] if CARD_CODE_ERRORS.include?(results[:card_code])
438
- if AVS_REASON_CODES.include?(results[:response_reason_code]) && AVS_ERRORS.include?(results[:avs_result_code])
439
- return AVSResult.messages[ results[:avs_result_code] ]
440
- end
441
- return (results[:response_reason_text] ? results[:response_reason_text].chomp('.') : '')
442
- end
443
- if results.has_key?(:response_reason_text)
444
- return results[:response_reason_text].to_s()
445
- end
446
- if !results.has_key?('STATUS')
447
- return results[RESPONSE_REASON_TEXT] ? results[RESPONSE_REASON_TEXT].chomp('.') : ''
448
- end
458
+ post[:solution_ID] = application_id if(application_id && application_id != "ActiveMerchant")
459
+ post.merge(parameters).collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
449
460
  end
450
461
 
451
462
  def expdate(creditcard)
452
- year = sprintf("%.4i", creditcard.year)
453
- month = sprintf("%.2i", creditcard.month)
463
+ year = format(creditcard.year, :two_digits)
464
+ month = format(creditcard.month, :two_digits)
454
465
 
455
- "#{month}#{year[-2..-1]}"
466
+ "#{month}#{year}"
456
467
  end
457
468
 
458
469
  def calc_tps(amount, post)
459
- post[:NAME1] = '' if post[:NAME1].nil?
460
- digest = Digest::MD5.hexdigest(@options[:password] +
461
- @options[:login] + post[:TRANS_TYPE] +
462
- amount.to_s() + post[:MASTER_ID].to_s() +
463
- post[:NAME1].to_s() + post[:PAYMENT_ACCOUNT].to_s())
464
- return digest
470
+ post[:NAME1] ||= ''
471
+ Digest::MD5.hexdigest(
472
+ [
473
+ @options[:password],
474
+ @options[:login],
475
+ post[:TRANS_TYPE],
476
+ amount,
477
+ post[:MASTER_ID],
478
+ post[:NAME1],
479
+ post[:PAYMENT_ACCOUNT]
480
+ ].join("")
481
+ )
465
482
  end
466
483
 
467
484
  def calc_rebill_tps(post)
468
- digest = Digest::MD5.hexdigest(@options[:password] +
469
- @options[:login] + post[:TRANS_TYPE] + post[:REBILL_ID][0].to_s())
470
- return digest
485
+ Digest::MD5.hexdigest(
486
+ [
487
+ @options[:password],
488
+ @options[:login],
489
+ post[:TRANS_TYPE],
490
+ post[:REBILL_ID]
491
+ ].join("")
492
+ )
471
493
  end
472
494
 
473
495
  def handle_response(response)
474
- if ignore_http_status then
496
+ if ignore_http_status || (200...300).include?(response.code.to_i)
475
497
  return response.body
476
- else
477
- case response.code.to_i
478
- when 200...300
479
- response.body
480
- else
481
- raise ResponseError.new(response)
482
- end
483
498
  end
499
+ raise ResponseError.new(response)
484
500
  end
485
501
  end
486
502
  end