activemerchant 1.25.0 → 1.26.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,15 +2,16 @@ module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class NetbillingGateway < Gateway
4
4
  URL = 'https://secure.netbilling.com:1402/gw/sas/direct3.1'
5
-
5
+
6
6
  TRANSACTIONS = {
7
- :authorization => 'A',
8
- :purchase => 'S',
9
- :referenced_credit => 'R',
10
- :unreferenced_credit => 'C',
11
- :capture => 'D'
7
+ :authorization => 'A',
8
+ :purchase => 'S',
9
+ :refund => 'R',
10
+ :credit => 'C',
11
+ :capture => 'D',
12
+ :void => 'U'
12
13
  }
13
-
14
+
14
15
  SUCCESS_CODES = [ '1', 'T' ]
15
16
  SUCCESS_MESSAGE = 'The transaction was approved'
16
17
  FAILURE_MESSAGE = 'The transaction failed'
@@ -20,54 +21,78 @@ module ActiveMerchant #:nodoc:
20
21
  self.homepage_url = 'http://www.netbilling.com'
21
22
  self.supported_countries = ['US']
22
23
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
23
-
24
+
24
25
  def initialize(options = {})
25
26
  requires!(options, :login)
26
27
  @options = options
27
28
  super
28
- end
29
-
29
+ end
30
+
30
31
  def authorize(money, credit_card, options = {})
31
32
  post = {}
32
33
  add_amount(post, money)
33
34
  add_invoice(post, options)
34
- add_credit_card(post, credit_card)
35
- add_address(post, credit_card, options)
35
+ add_credit_card(post, credit_card)
36
+ add_address(post, credit_card, options)
36
37
  add_customer_data(post, options)
37
-
38
- commit(:authorization, money, post)
38
+
39
+ commit(:authorization, post)
39
40
  end
40
-
41
+
41
42
  def purchase(money, credit_card, options = {})
42
43
  post = {}
43
44
  add_amount(post, money)
44
45
  add_invoice(post, options)
45
- add_credit_card(post, credit_card)
46
- add_address(post, credit_card, options)
46
+ add_credit_card(post, credit_card)
47
+ add_address(post, credit_card, options)
47
48
  add_customer_data(post, options)
48
-
49
- commit(:purchase, money, post)
50
- end
51
-
49
+
50
+ commit(:purchase, post)
51
+ end
52
+
52
53
  def capture(money, authorization, options = {})
53
54
  post = {}
54
55
  add_reference(post, authorization)
55
- commit(:capture, money, post)
56
+ commit(:capture, post)
57
+ end
58
+
59
+ def refund(money, source, options = {})
60
+ post = {}
61
+ add_amount(post, money)
62
+ add_reference(post, source)
63
+ commit(:refund, post)
56
64
  end
57
-
65
+
66
+ def credit(money, credit_card, options = {})
67
+ post = {}
68
+ add_amount(post, money)
69
+ add_invoice(post, options)
70
+ add_credit_card(post, credit_card)
71
+ add_address(post, credit_card, options)
72
+ add_customer_data(post, options)
73
+
74
+ commit(:credit, post)
75
+ end
76
+
77
+ def void(source, options = {})
78
+ post = {}
79
+ add_reference(post, source)
80
+ commit(:void, post)
81
+ end
82
+
58
83
  def test?
59
84
  @options[:login] == TEST_LOGIN || super
60
85
  end
61
-
62
- private
86
+
87
+ private
63
88
  def add_amount(post, money)
64
89
  post[:amount] = amount(money)
65
90
  end
66
-
91
+
67
92
  def add_reference(post, reference)
68
93
  post[:orig_id] = reference
69
94
  end
70
-
95
+
71
96
  def add_customer_data(post, options)
72
97
  post[:cust_email] = options[:email]
73
98
  post[:cust_ip] = options[:ip]
@@ -82,10 +107,10 @@ module ActiveMerchant #:nodoc:
82
107
  post[:bill_country] = billing_address[:country]
83
108
  post[:bill_state] = billing_address[:state]
84
109
  end
85
-
110
+
86
111
  if shipping_address = options[:shipping_address]
87
112
  first_name, last_name = parse_first_and_last_name(shipping_address[:name])
88
-
113
+
89
114
  post[:ship_name1] = first_name
90
115
  post[:ship_name2] = last_name
91
116
  post[:ship_street] = shipping_address[:address1]
@@ -95,11 +120,11 @@ module ActiveMerchant #:nodoc:
95
120
  post[:ship_state] = shipping_address[:state]
96
121
  end
97
122
  end
98
-
123
+
99
124
  def add_invoice(post, options)
100
125
  post[:description] = options[:description]
101
126
  end
102
-
127
+
103
128
  def add_credit_card(post, credit_card)
104
129
  post[:bill_name1] = credit_card.first_name
105
130
  post[:bill_name2] = credit_card.last_name
@@ -107,31 +132,34 @@ module ActiveMerchant #:nodoc:
107
132
  post[:card_expire] = expdate(credit_card)
108
133
  post[:card_cvv2] = credit_card.verification_value
109
134
  end
110
-
135
+
111
136
  def parse(body)
112
137
  results = {}
113
138
  body.split(/&/).each do |pair|
114
- key,val = pair.split(/=/)
139
+ key,val = pair.split(/\=/)
115
140
  results[key.to_sym] = CGI.unescape(val)
116
141
  end
117
142
  results
118
- end
119
-
120
- def commit(action, money, parameters)
143
+ end
144
+
145
+ def commit(action, parameters)
121
146
  response = parse(ssl_post(URL, post_data(action, parameters)))
122
-
123
- Response.new(success?(response), message_from(response), response,
124
- :test => test_response?(response),
147
+
148
+ Response.new(success?(response), message_from(response), response,
149
+ :test => test_response?(response),
125
150
  :authorization => response[:trans_id],
126
151
  :avs_result => { :code => response[:avs_code]},
127
152
  :cvv_result => response[:cvv2_code]
128
153
  )
154
+ rescue ActiveMerchant::ResponseError => e
155
+ raise unless(e.response.code =~ /^[67]\d\d$/)
156
+ return Response.new(false, e.response.message, {:status_code => e.response.code}, :test => test?)
129
157
  end
130
-
158
+
131
159
  def test_response?(response)
132
160
  !!(test? || response[:auth_msg] =~ /TEST/)
133
161
  end
134
-
162
+
135
163
  def success?(response)
136
164
  SUCCESS_CODES.include?(response[:status_code])
137
165
  end
@@ -139,28 +167,28 @@ module ActiveMerchant #:nodoc:
139
167
  def message_from(response)
140
168
  success?(response) ? SUCCESS_MESSAGE : (response[:auth_msg] || FAILURE_MESSAGE)
141
169
  end
142
-
170
+
143
171
  def expdate(credit_card)
144
172
  year = sprintf("%.4i", credit_card.year)
145
173
  month = sprintf("%.2i", credit_card.month)
146
174
 
147
175
  "#{month}#{year[-2..-1]}"
148
176
  end
149
-
177
+
150
178
  def post_data(action, parameters = {})
151
179
  parameters[:account_id] = @options[:login]
152
180
  parameters[:pay_type] = 'C'
153
- parameters[:tran_type] = TRANSACTIONS[action]
154
-
181
+ parameters[:tran_type] = TRANSACTIONS[action]
182
+
155
183
  parameters.reject{|k,v| v.blank?}.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
156
184
  end
157
-
185
+
158
186
  def parse_first_and_last_name(value)
159
187
  name = value.to_s.split(' ')
160
-
188
+
161
189
  last_name = name.pop || ''
162
190
  first_name = name.join(' ')
163
- [ first_name, last_name ]
191
+ [ first_name, last_name ]
164
192
  end
165
193
  end
166
194
  end
@@ -148,10 +148,20 @@ module ActiveMerchant #:nodoc:
148
148
  xml.tag! :SDMerchantEmail, soft_desc.merchant_email
149
149
  end
150
150
 
151
- def add_address(xml, creditcard, options)
151
+ def add_address(xml, creditcard, options)
152
152
  if address = options[:billing_address] || options[:address]
153
- add_avs_details(xml, address)
153
+ avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
154
+
155
+ if avs_supported
156
+ xml.tag! :AVSzip, address[:zip]
157
+ xml.tag! :AVSaddress1, address[:address1]
158
+ xml.tag! :AVSaddress2, address[:address2]
159
+ xml.tag! :AVScity, address[:city]
160
+ xml.tag! :AVSstate, address[:state]
161
+ xml.tag! :AVSphoneNum, address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil
162
+ end
154
163
  xml.tag! :AVSname, creditcard.name
164
+ xml.tag! :AVScountryCode, avs_supported ? address[:country] : ''
155
165
  end
156
166
  end
157
167
 
@@ -173,21 +183,6 @@ module ActiveMerchant #:nodoc:
173
183
  xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
174
184
  end
175
185
 
176
- def add_avs_details(xml, address)
177
- if AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
178
- xml.tag! :AVSzip, address[:zip]
179
- xml.tag! :AVSaddress1, address[:address1]
180
- xml.tag! :AVSaddress2, address[:address2]
181
- xml.tag! :AVScity, address[:city]
182
- xml.tag! :AVSstate, address[:state]
183
- xml.tag! :AVSphoneNum, address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil
184
- country_code = address[:country]
185
- else
186
- country_code = ''
187
- end
188
- xml.tag! :AVScountryCode, country_code
189
- end
190
-
191
186
 
192
187
  def parse(body)
193
188
  response = {}
@@ -0,0 +1,266 @@
1
+ require 'digest/md5'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ # This gateway accepts the following arguments:
6
+ # :login => your PayJunction username
7
+ # :password => your PayJunction pass
8
+ # Example use:
9
+ #
10
+ # gateway = ActiveMerchant::Billing::Base.gateway(:pay_gate_xml).new(
11
+ # :login => "my_account",
12
+ # :password => "my_pass"
13
+ # )
14
+ #
15
+ # # set up credit card obj as in main ActiveMerchant example
16
+ # creditcard = ActiveMerchant::Billing::CreditCard.new(
17
+ # :type => 'visa',
18
+ # :number => '4242424242424242',
19
+ # :month => 8,
20
+ # :year => 2009,
21
+ # :first_name => 'Bob',
22
+ # :last_name => 'Bobsen'
23
+ # )
24
+ #
25
+ # # run request
26
+ # response = gateway.purchase(1000, creditcard) # charge 10 dollars
27
+ #
28
+ # 1) Check whether the transaction was successful
29
+ #
30
+ # response.success?
31
+ #
32
+ # 2) Retrieve the message returned by PayJunction
33
+ #
34
+ # response.message
35
+ #
36
+ # 3) Retrieve the unique transaction ID returned by PayGateXML
37
+ #
38
+ # response.authorization
39
+ #
40
+ # This gateway has many other features which are not implemented here yet
41
+ # The basic setup here only supports auth/capture transactions.
42
+ #
43
+ # Test Transactions
44
+ #
45
+ # PayGateXML has a global test user/pass, but you can also sign up for your own.
46
+ # The class and the test come equipped with the global test creds
47
+ #
48
+ # Usage Details
49
+ #
50
+ # Below is a map of only SOME of the values accepted by PayGateXML and how you should submit
51
+ # each to ActiveMerchant
52
+ #
53
+ # PayGateXML Field ActiveMerchant Use
54
+ #
55
+ # pgid use :login value to gateway instantation
56
+ # pwd use :password value to gateway instantiation
57
+ #
58
+ # cname credit_card.name
59
+ # cc credit_card.number
60
+ # exp credit_card values formatted to YYYYMMDD
61
+ # budp South Africa only - set to 0 if purchase is not on budget
62
+ # amt include as argument to method for your transaction type
63
+ # ver do nothing, always set to current API version
64
+ #
65
+ # cref provide as :invoice in options, varchar(80)
66
+ # cur 3 char field, currently only ZAR
67
+ # cvv credit_card.verification
68
+ # bno batch processing number, i.e. you supply this
69
+ #
70
+ # others -- not used in this implementation
71
+ # nurl, rurl - must remain blank or absent or they will use an alternative authentication mechanism
72
+ # email, ip - must remain blank or absent or they will use a PayGate extra service call PayProtector
73
+ # threed - must remain blank unless you are using your own 3D Secure server
74
+ #
75
+ class PayGateXmlGateway < Gateway
76
+ LIVE_URL = 'https://www.paygate.co.za/payxml/process.trans'
77
+
78
+ # The countries the gateway supports merchants from as 2 digit ISO country codes
79
+ self.supported_countries = ['US', 'ZA']
80
+
81
+ # The card types supported by the payment gateway
82
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club]
83
+
84
+ # The homepage URL of the gateway
85
+ self.homepage_url = 'http://paygate.co.za/'
86
+
87
+ # The name of the gateway
88
+ self.display_name = 'PayGate PayXML'
89
+
90
+ # PayGate only supports Rands
91
+ self.default_currency = 'ZAR'
92
+
93
+ # PayGate accepts only lowest denomination
94
+ self.money_format = :cents
95
+
96
+ # PayGateXML public test account - you can get a private one too
97
+ TEST_ID_3DSECURE = '10011013800'
98
+ TEST_ID = '10011021600'
99
+ TEST_PWD = 'test'
100
+
101
+ API_VERSION = '4.0'
102
+
103
+ DECLINE_CODES = {
104
+ # Credit Card Errors - These RESULT_CODEs are returned if the transaction cannot be authorized due to a problem with the card. The TRANSACTION_STATUS will be 2
105
+ 900001 => "Call for Approval",
106
+ 900002 => "Card Expired",
107
+ 900003 => "Insufficient Funds",
108
+ 900004 => "Invalid Card Number",
109
+ 900005 => "Bank Interface Timeout", # indicates a communications failure between the banks systems
110
+ 900006 => "Invalid Card",
111
+ 900007 => "Declined",
112
+ 900009 => "Lost Card",
113
+ 900010 => "Invalid Card Length",
114
+ 900011 => "Suspected Fraud",
115
+ 900012 => "Card Reported As Stolen",
116
+ 900013 => "Restricted Card",
117
+ 900014 => "Excessive Card Usage",
118
+ 900015 => "Card Blacklisted",
119
+
120
+ 900207 => "Declined; authentication failed", # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly
121
+
122
+ 990020 => "Auth Declined",
123
+
124
+ 991001 => "Invalid expiry date",
125
+ 991002 => "Invalid amount",
126
+
127
+ # Communication Errors - These RESULT_CODEs are returned if the transaction cannot be completed due to an unexpected error. TRANSACTION_STATUS will be 0.
128
+ 900205 => "Unexpected authentication result (phase 1)",
129
+ 900206 => "Unexpected authentication result (phase 1)",
130
+
131
+ 990001 => "Could not insert into Database",
132
+
133
+ 990022 => "Bank not available",
134
+
135
+ 990053 => "Error processing transaction",
136
+
137
+ # Miscellaneous - Unless otherwise noted, the TRANSACTION_STATUS will be 0.
138
+ 900209 => "Transaction verification failed (phase 2)", # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered
139
+ 900210 => "Authentication complete; transaction must be restarted", # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed. Most likely caused by the customer clicking the refresh button
140
+
141
+ 990024 => "Duplicate Transaction Detected. Please check before submitting",
142
+
143
+ 990028 => "Transaction cancelled" # Customer clicks the 'Cancel' button on the payment page
144
+ }
145
+
146
+ SUCCESS_CODES = %w( 990004 990005 990017 990012 990018 990031 )
147
+
148
+ TRANSACTION_CODES = {
149
+ 0 => 'Not Done',
150
+ 1 => 'Approved',
151
+ 2 => 'Declined',
152
+ 3 => 'Paid',
153
+ 4 => 'Refunded',
154
+ 5 => 'Received by PayGate',
155
+ 6 => 'Replied to Client'
156
+ }
157
+
158
+ def initialize(options = {})
159
+ requires!(options, :login, :password)
160
+ @options = options
161
+ super
162
+ end
163
+
164
+ def purchase(money, creditcard, options = {})
165
+ MultiResponse.new.tap do |r|
166
+ r.process{authorize(money, creditcard, options)}
167
+ r.process{capture(money, r.authorization, options)}
168
+ end
169
+ end
170
+
171
+ def authorize(money, creditcard, options = {})
172
+ action = 'authtx'
173
+
174
+ options.merge!(:money => money, :creditcard => creditcard)
175
+ commit(action, build_request(action, options))
176
+ end
177
+
178
+ def capture(money, authorization, options = {})
179
+ action = 'settletx'
180
+
181
+ options.merge!(:money => money, :authorization => authorization)
182
+ commit(action, build_request(action, options))
183
+ end
184
+
185
+ def test?
186
+ @options[:test] || (Base.gateway_mode == :test)
187
+ end
188
+
189
+ private
190
+
191
+ def successful?(response)
192
+ SUCCESS_CODES.include?(response[:res])
193
+ end
194
+
195
+ def build_request(action, options={})
196
+ xml = Builder::XmlMarkup.new
197
+ xml.instruct!
198
+
199
+ xml.tag! 'protocol', :ver => API_VERSION, :pgid => (test? ? TEST_ID : @options[:login]), :pwd => @options[:password] do |protocol|
200
+ case action
201
+ when 'authtx'
202
+ money = options.delete(:money)
203
+ creditcard = options.delete(:creditcard)
204
+ build_authorization(protocol, money, creditcard, options)
205
+ when 'settletx'
206
+ money = options.delete(:money)
207
+ authorization = options.delete(:authorization)
208
+ build_capture(protocol, money, authorization, options)
209
+ else
210
+ raise "no action specified for build_request"
211
+ end
212
+ end
213
+
214
+ xml.target!
215
+ end
216
+
217
+ def build_authorization(xml, money, creditcard, options={})
218
+ xml.tag! 'authtx', {
219
+ :cref => options[:order_id],
220
+ :cname => creditcard.name,
221
+ :cc => creditcard.number,
222
+ :exp => "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :four_digits)}",
223
+ :budp => 0,
224
+ :amt => amount(money),
225
+ :cur => (options[:currency] || currency(money)),
226
+ :cvv => creditcard.verification_value
227
+ }
228
+ end
229
+
230
+ def build_capture(xml, money, authorization, options={})
231
+ xml.tag! 'settletx', {
232
+ :tid => authorization
233
+ }
234
+ end
235
+
236
+ def parse(action, body)
237
+ hash = {}
238
+ xml = REXML::Document.new(body)
239
+
240
+ response_action = action.gsub(/tx/, 'rx')
241
+ root = REXML::XPath.first(xml.root, response_action)
242
+ # we might have gotten an error
243
+ if root.nil?
244
+ root = REXML::XPath.first(xml.root, 'errorrx')
245
+ end
246
+ root.attributes.each do |name, value|
247
+ hash[name.to_sym] = value
248
+ end
249
+ hash
250
+ end
251
+
252
+ def commit(action, request)
253
+ response = parse(action, ssl_post(LIVE_URL, request))
254
+ Response.new(successful?(response), message_from(response), response,
255
+ :test => test?,
256
+ :authorization => response[:tid]
257
+ )
258
+ end
259
+
260
+ def message_from(response)
261
+ (response[:rdesc] || response[:edesc])
262
+ end
263
+ end
264
+ end
265
+ end
266
+