activemerchant 1.25.0 → 1.26.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.
@@ -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
+