activemerchant 1.32.1 → 1.33.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 (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