activemerchant 1.21.0 → 1.22.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 (55) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +63 -0
  3. data/CONTRIBUTORS +29 -0
  4. data/README.md +195 -0
  5. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +2 -0
  6. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +2 -2
  7. data/lib/active_merchant/billing/gateways/blue_pay.rb +492 -11
  8. data/lib/active_merchant/billing/gateways/braintree_blue.rb +46 -19
  9. data/lib/active_merchant/billing/gateways/certo_direct.rb +1 -1
  10. data/lib/active_merchant/billing/gateways/cyber_source.rb +342 -106
  11. data/lib/active_merchant/billing/gateways/elavon.rb +2 -0
  12. data/lib/active_merchant/billing/gateways/epay.rb +3 -1
  13. data/lib/active_merchant/billing/gateways/itransact.rb +450 -0
  14. data/lib/active_merchant/billing/gateways/migs.rb +259 -0
  15. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
  16. data/lib/active_merchant/billing/gateways/moneris_us.rb +211 -0
  17. data/lib/active_merchant/billing/gateways/ogone.rb +104 -12
  18. data/lib/active_merchant/billing/gateways/orbital.rb +15 -6
  19. data/lib/active_merchant/billing/gateways/paybox_direct.rb +1 -4
  20. data/lib/active_merchant/billing/gateways/payflow.rb +8 -3
  21. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +4 -1
  22. data/lib/active_merchant/billing/gateways/payflow_express.rb +4 -2
  23. data/lib/active_merchant/billing/gateways/payment_express.rb +1 -1
  24. data/lib/active_merchant/billing/gateways/paypal.rb +3 -18
  25. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +287 -1
  26. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +245 -0
  27. data/lib/active_merchant/billing/gateways/paypal_express.rb +14 -66
  28. data/lib/active_merchant/billing/gateways/realex.rb +5 -7
  29. data/lib/active_merchant/billing/gateways/stripe.rb +1 -9
  30. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +2 -2
  31. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -5
  32. data/lib/active_merchant/billing/gateways/viaklix.rb +7 -2
  33. data/lib/active_merchant/billing/gateways/vindicia.rb +359 -0
  34. data/lib/active_merchant/billing/integrations/dotpay.rb +22 -0
  35. data/lib/active_merchant/billing/integrations/dotpay/helper.rb +77 -0
  36. data/lib/active_merchant/billing/integrations/dotpay/notification.rb +86 -0
  37. data/lib/active_merchant/billing/integrations/dotpay/return.rb +11 -0
  38. data/lib/active_merchant/billing/integrations/epay.rb +21 -0
  39. data/lib/active_merchant/billing/integrations/epay/helper.rb +55 -0
  40. data/lib/active_merchant/billing/integrations/epay/notification.rb +110 -0
  41. data/lib/active_merchant/billing/integrations/paypal/notification.rb +2 -1
  42. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +2 -3
  43. data/lib/active_merchant/billing/integrations/robokassa.rb +49 -0
  44. data/lib/active_merchant/billing/integrations/robokassa/common.rb +19 -0
  45. data/lib/active_merchant/billing/integrations/robokassa/helper.rb +50 -0
  46. data/lib/active_merchant/billing/integrations/robokassa/notification.rb +55 -0
  47. data/lib/active_merchant/billing/integrations/robokassa/return.rb +17 -0
  48. data/lib/active_merchant/billing/integrations/two_checkout.rb +25 -3
  49. data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +15 -0
  50. data/lib/active_merchant/billing/integrations/verkkomaksut.rb +20 -0
  51. data/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb +87 -0
  52. data/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb +59 -0
  53. data/lib/active_merchant/version.rb +1 -1
  54. metadata +59 -26
  55. metadata.gz.sig +0 -0
@@ -0,0 +1,259 @@
1
+ require File.dirname(__FILE__) + '/migs/migs_codes'
2
+
3
+ require 'digest/md5' # Used in add_secure_hash
4
+
5
+ module ActiveMerchant #:nodoc:
6
+ module Billing #:nodoc:
7
+ class MigsGateway < Gateway
8
+ include MigsCodes
9
+
10
+ API_VERSION = 1
11
+
12
+ SERVER_HOSTED_URL = 'https://migs.mastercard.com.au/vpcpay'
13
+ MERCHANT_HOSTED_URL = 'https://migs.mastercard.com.au/vpcdps'
14
+
15
+ # MiGS is supported throughout Asia Pacific, Middle East and Africa
16
+ # MiGS is used in Australia (AU) by ANZ (eGate), CBA (CommWeb) and more
17
+ # Source of Country List: http://www.scribd.com/doc/17811923
18
+ self.supported_countries = %w(AU AE BD BN EG HK ID IN JO KW LB LK MU MV MY NZ OM PH QA SA SG TT VN)
19
+
20
+ # The card types supported by the payment gateway
21
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb]
22
+
23
+ self.money_format = :cents
24
+
25
+ # The homepage URL of the gateway
26
+ self.homepage_url = 'http://mastercard.com/mastercardsps'
27
+
28
+ # The name of the gateway
29
+ self.display_name = 'MasterCard Internet Gateway Service (MiGS)'
30
+
31
+ # Creates a new MigsGateway
32
+ # The advanced_login/advanced_password fields are needed for
33
+ # advanced methods such as the capture, refund and status methods
34
+ #
35
+ # ==== Options
36
+ #
37
+ # * <tt>:login</tt> -- The MiGS Merchant ID (REQUIRED)
38
+ # * <tt>:password</tt> -- The MiGS Access Code (REQUIRED)
39
+ # * <tt>:secure_hash</tt> -- The MiGS Secure Hash
40
+ # (Required for Server Hosted payments)
41
+ # * <tt>:advanced_login</tt> -- The MiGS AMA User
42
+ # * <tt>:advanced_password</tt> -- The MiGS AMA User's password
43
+ def initialize(options = {})
44
+ requires!(options, :login, :password)
45
+ @test = options[:login].start_with?('TEST')
46
+ @options = options
47
+ super
48
+ end
49
+
50
+ # ==== Options
51
+ #
52
+ # * <tt>:order_id</tt> -- A reference for tracking the order (REQUIRED)
53
+ # * <tt>:unique_id</tt> -- A unique id for this request (Max 40 chars).
54
+ # If not supplied one will be generated.
55
+ def purchase(money, creditcard, options = {})
56
+ requires!(options, :order_id)
57
+
58
+ post = {}
59
+ post[:Amount] = amount(money)
60
+ add_invoice(post, options)
61
+ add_creditcard(post, creditcard)
62
+ add_standard_parameters('pay', post, options[:unique_id])
63
+
64
+ commit(post)
65
+ end
66
+
67
+ # MiGS works by merchants being either purchase only or authorize/capture
68
+ # So authorize is the same as purchase when in authorize mode
69
+ alias_method :authorize, :purchase
70
+
71
+ # ==== Options
72
+ #
73
+ # * <tt>:unique_id</tt> -- A unique id for this request (Max 40 chars).
74
+ # If not supplied one will be generated.
75
+ def capture(money, authorization, options = {})
76
+ requires!(@options, :advanced_login, :advanced_password)
77
+
78
+ post = options.merge(:TransNo => authorization)
79
+ post[:Amount] = amount(money)
80
+ add_advanced_user(post)
81
+ add_standard_parameters('capture', post, options[:unique_id])
82
+
83
+ commit(post)
84
+ end
85
+
86
+ # ==== Options
87
+ #
88
+ # * <tt>:unique_id</tt> -- A unique id for this request (Max 40 chars).
89
+ # If not supplied one will be generated.
90
+ def refund(money, authorization, options = {})
91
+ requires!(@options, :advanced_login, :advanced_password)
92
+
93
+ post = options.merge(:TransNo => authorization)
94
+ post[:Amount] = amount(money)
95
+ add_advanced_user(post)
96
+ add_standard_parameters('refund', post, options[:unique_id])
97
+
98
+ commit(post)
99
+ end
100
+
101
+ def credit(money, authorization, options = {})
102
+ deprecated CREDIT_DEPRECATION_MESSAGE
103
+ refund(money, authorization, options)
104
+ end
105
+
106
+ # Checks the status of a previous transaction
107
+ # This can be useful when a response is not received due to network issues
108
+ #
109
+ # ==== Parameters
110
+ #
111
+ # * <tt>unique_id</tt> -- Unique id of transaction to find.
112
+ # This is the value of the option supplied in other methods or
113
+ # if not supplied is returned with key :MerchTxnRef
114
+ def status(unique_id)
115
+ requires!(@options, :advanced_login, :advanced_password)
116
+
117
+ post = {}
118
+ add_advanced_user(post)
119
+ add_standard_parameters('queryDR', post, unique_id)
120
+
121
+ commit(post)
122
+ end
123
+
124
+ # Generates a URL to redirect user to MiGS to process payment
125
+ # Once user is finished MiGS will redirect back to specified URL
126
+ # With a response hash which can be turned into a Response object
127
+ # with purchase_offsite_response
128
+ #
129
+ # ==== Options
130
+ #
131
+ # * <tt>:order_id</tt> -- A reference for tracking the order (REQUIRED)
132
+ # * <tt>:locale</tt> -- Change the language of the redirected page
133
+ # Values are 2 digit locale, e.g. en, es
134
+ # * <tt>:return_url</tt> -- the URL to return to once the payment is complete
135
+ # * <tt>:card_type</tt> -- Providing this skips the card type step.
136
+ # Values are ActiveMerchant formats: e.g. master, visa, american_express, diners_club
137
+ # * <tt>:unique_id</tt> -- Unique id of transaction to find.
138
+ # If not supplied one will be generated.
139
+ def purchase_offsite_url(money, options = {})
140
+ requires!(options, :order_id, :return_url)
141
+ requires!(@options, :secure_hash)
142
+
143
+ post = {}
144
+ post[:Amount] = amount(money)
145
+ add_invoice(post, options)
146
+ add_creditcard_type(post, options[:card_type]) if options[:card_type]
147
+
148
+ post.merge!(
149
+ :Locale => options[:locale] || 'en',
150
+ :ReturnURL => options[:return_url]
151
+ )
152
+
153
+ add_standard_parameters('pay', post, options[:unique_id])
154
+
155
+ add_secure_hash(post)
156
+
157
+ SERVER_HOSTED_URL + '?' + post_data(post)
158
+ end
159
+
160
+ # Parses a response from purchase_offsite_url once user is redirected back
161
+ #
162
+ # ==== Parameters
163
+ #
164
+ # * <tt>data</tt> -- All params when offsite payment returns
165
+ # e.g. returns to http://company.com/return?a=1&b=2, then input "a=1&b=2"
166
+ def purchase_offsite_response(data)
167
+ requires!(@options, :secure_hash)
168
+
169
+ response_hash = parse(data)
170
+
171
+ expected_secure_hash = calculate_secure_hash(response_hash.reject{|k, v| k == :SecureHash}, @options[:secure_hash])
172
+ unless response_hash[:SecureHash] == expected_secure_hash
173
+ raise SecurityError, "Secure Hash mismatch, response may be tampered with"
174
+ end
175
+
176
+ response_object(response_hash)
177
+ end
178
+
179
+ private
180
+
181
+ def add_advanced_user(post)
182
+ post[:User] = @options[:advanced_login]
183
+ post[:Password] = @options[:advanced_password]
184
+ end
185
+
186
+ def add_invoice(post, options)
187
+ post[:OrderInfo] = options[:order_id]
188
+ end
189
+
190
+ def add_creditcard(post, creditcard)
191
+ post[:CardNum] = creditcard.number
192
+ post[:CardSecurityCode] = creditcard.verification_value if creditcard.verification_value?
193
+ post[:CardExp] = format(creditcard.year, :two_digits) + format(creditcard.month, :two_digits)
194
+ end
195
+
196
+ def add_creditcard_type(post, card_type)
197
+ post[:Gateway] = 'ssl'
198
+ post[:card] = CARD_TYPES.detect{|ct| ct.am_code == card_type}.migs_long_code
199
+ end
200
+
201
+ def parse(body)
202
+ params = CGI::parse(body)
203
+ hash = {}
204
+ params.each do |key, value|
205
+ hash[key.gsub('vpc_', '').to_sym] = value[0]
206
+ end
207
+ hash
208
+ end
209
+
210
+ def commit(post)
211
+ data = ssl_post MERCHANT_HOSTED_URL, post_data(post)
212
+ response_hash = parse(data)
213
+ response_object(response_hash)
214
+ end
215
+
216
+ def response_object(response)
217
+ Response.new(success?(response), response[:Message], response,
218
+ :test => @test,
219
+ :authorization => response[:TransactionNo],
220
+ :fraud_review => fraud_review?(response),
221
+ :avs_result => { :code => response[:AVSResultCode] },
222
+ :cvv_result => response[:CSCResultCode]
223
+ )
224
+ end
225
+
226
+ def success?(response)
227
+ response[:TxnResponseCode] == '0'
228
+ end
229
+
230
+ def fraud_review?(response)
231
+ ISSUER_RESPONSE_CODES[response[:AcqResponseCode]] == 'Suspected Fraud'
232
+ end
233
+
234
+ def add_standard_parameters(action, post, unique_id = nil)
235
+ post.merge!(
236
+ :Version => API_VERSION,
237
+ :Merchant => @options[:login],
238
+ :AccessCode => @options[:password],
239
+ :Command => action,
240
+ :MerchTxnRef => unique_id || generate_unique_id.slice(0, 40)
241
+ )
242
+ end
243
+
244
+ def post_data(post)
245
+ post.collect { |key, value| "vpc_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
246
+ end
247
+
248
+ def add_secure_hash(post)
249
+ post[:SecureHash] = calculate_secure_hash(post, @options[:secure_hash])
250
+ end
251
+
252
+ def calculate_secure_hash(post, secure_hash)
253
+ sorted_values = post.sort_by(&:to_s).map(&:last)
254
+ input = secure_hash + sorted_values.join
255
+ Digest::MD5.hexdigest(input).upcase
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,100 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+ module MigsCodes
4
+ TXN_RESPONSE_CODES = {
5
+ '?' => 'Response Unknown',
6
+ '0' => 'Transaction Successful',
7
+ '1' => 'Transaction Declined - Bank Error',
8
+ '2' => 'Bank Declined Transaction',
9
+ '3' => 'Transaction Declined - No Reply from Bank',
10
+ '4' => 'Transaction Declined - Expired Card',
11
+ '5' => 'Transaction Declined - Insufficient funds',
12
+ '6' => 'Transaction Declined - Error Communicating with Bank',
13
+ '7' => 'Payment Server Processing Error - Typically caused by invalid input data such as an invalid credit card number. Processing errors can also occur',
14
+ '8' => 'Transaction Declined - Transaction Type Not Supported',
15
+ '9' => 'Bank Declined Transaction (Do not contact Bank)',
16
+ 'A' => 'Transaction Aborted',
17
+ 'C' => 'Transaction Cancelled',
18
+ 'D' => 'Deferred Transaction',
19
+ 'E' => 'Issuer Returned a Referral Response',
20
+ 'F' => '3D Secure Authentication Failed',
21
+ 'I' => 'Card Security Code Failed',
22
+ 'L' => 'Shopping Transaction Locked (This indicates that there is another transaction taking place using the same shopping transaction number)',
23
+ 'N' => 'Cardholder is not enrolled in 3D Secure (Authentication Only)',
24
+ 'P' => 'Transaction is Pending',
25
+ 'R' => 'Retry Limits Exceeded, Transaction Not Processed',
26
+ 'S' => 'Duplicate OrderInfo used. (This is only relevant for Payment Servers that enforce the uniqueness of this field)',
27
+ 'U' => 'Card Security Code Failed'
28
+ }
29
+
30
+ ISSUER_RESPONSE_CODES = {
31
+ '00' => 'Approved',
32
+ '01' => 'Refer to Card Issuer',
33
+ '02' => 'Refer to Card Issuer',
34
+ '03' => 'Invalid Merchant',
35
+ '04' => 'Pick Up Card',
36
+ '05' => 'Do Not Honor',
37
+ '07' => 'Pick Up Card',
38
+ '12' => 'Invalid Transaction',
39
+ '14' => 'Invalid Card Number (No such Number)',
40
+ '15' => 'No Such Issuer',
41
+ '33' => 'Expired Card',
42
+ '34' => 'Suspected Fraud',
43
+ '36' => 'Restricted Card',
44
+ '39' => 'No Credit Account',
45
+ '41' => 'Card Reported Lost',
46
+ '43' => 'Stolen Card',
47
+ '51' => 'Insufficient Funds',
48
+ '54' => 'Expired Card',
49
+ '57' => 'Transaction Not Permitted',
50
+ '59' => 'Suspected Fraud',
51
+ '62' => 'Restricted Card',
52
+ '65' => 'Exceeds withdrawal frequency limit',
53
+ '91' => 'Cannot Contact Issuer'
54
+ }
55
+
56
+ VERIFIED_3D_CODES = {
57
+ 'Y' => 'The cardholder was successfully authenticated.',
58
+ 'E' => 'The cardholder is not enrolled.',
59
+ 'N' => 'The cardholder was not verified.',
60
+ 'U' => 'The cardholder\'s Issuer was unable to authenticate due to a system error at the Issuer.',
61
+ 'F' => 'An error exists in the format of the request from the merchant. For example, the request did not contain all required fields, or the format of some fields was invalid.',
62
+ 'A' => 'Authentication of your Merchant ID and Password to the Directory Server Failed (see "What does a Payment Authentication Status of "A" mean?" on page 85).',
63
+ 'D' => 'Error communicating with the Directory Server, for example, the Payment Server could not connect to the directory server or there was a versioning mismatch.',
64
+ 'C' => 'The card type is not supported for authentication.',
65
+ 'M' => 'This indicates that attempts processing was used. Verification is marked with status M - ACS attempts processing used. Payment is performed with authentication. Attempts is when a cardholder has successfully passed the directory server but decides not to continue with the authentication process and cancels.',
66
+ 'S' => 'The signature on the response received from the Issuer could not be validated. This should be considered a failure.',
67
+ 'T' => 'ACS timed out. The Issuer\'s ACS did not respond to the Authentication request within the time out period.',
68
+ 'P' => 'Error parsing input from Issuer.',
69
+ 'I' => 'Internal Payment Server system error. This could be caused by a temporary DB failure or an error in the security module or by some error in an internal system.'
70
+ }
71
+
72
+ class CreditCardType
73
+ attr_accessor :am_code, :migs_code, :migs_long_code, :name
74
+ def initialize(am_code, migs_code, migs_long_code, name)
75
+ @am_code = am_code
76
+ @migs_code = migs_code
77
+ @migs_long_code = migs_long_code
78
+ @name = name
79
+ end
80
+ end
81
+
82
+ CARD_TYPES = [
83
+ # The following are 4 different representations of credit card types
84
+ # am_code: The active merchant code
85
+ # migs_code: Used in response for purchase/authorize/status
86
+ # migs_long_code: Used to pre-select card for server_purchase_url
87
+ # name: The nice display name
88
+ %w(american_express AE Amex American\ Express),
89
+ %w(diners_club DC Dinersclub Diners\ Club),
90
+ %w(jcb JC JCB JCB\ Card),
91
+ %w(maestro MS Maestro Maestro\ Card),
92
+ %w(master MC Mastercard MasterCard),
93
+ %w(na PL PrivateLabelCard Private\ Label\ Card),
94
+ %w(visa VC Visa Visa\ Card')
95
+ ].map do |am_code, migs_code, migs_long_code, name|
96
+ CreditCardType.new(am_code, migs_code, migs_long_code, name)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,211 @@
1
+ require 'rexml/document'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+
6
+ # To learn more about the Moneris (US) gateway, please contact
7
+ # ussales@moneris.com for a copy of their integration guide. For
8
+ # information on remote testing, please see "Test Environment Penny Value
9
+ # Response Table", and "Test Environment eFraud (AVS and CVD) Penny
10
+ # Response Values", available at Moneris' {eSelect Plus Documentation
11
+ # Centre}[https://www3.moneris.com/connect/en/documents/index.html].
12
+ class MonerisUsGateway < Gateway
13
+ TEST_URL = 'https://esplusqa.moneris.com/gateway_us/servlet/MpgRequest'
14
+ LIVE_URL = 'https://esplus.moneris.com/gateway_us/servlet/MpgRequest'
15
+
16
+ self.supported_countries = ['US']
17
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover]
18
+ self.homepage_url = 'http://www.monerisusa.com/'
19
+ self.display_name = 'Moneris (US)'
20
+
21
+ # login is your Store ID
22
+ # password is your API Token
23
+ def initialize(options = {})
24
+ requires!(options, :login, :password)
25
+ @options = { :crypt_type => 7 }.update(options)
26
+ super
27
+ end
28
+
29
+ # Referred to as "PreAuth" in the Moneris integration guide, this action
30
+ # verifies and locks funds on a customer's card, which then must be
31
+ # captured at a later date.
32
+ #
33
+ # Pass in +order_id+ and optionally a +customer+ parameter.
34
+ def authorize(money, creditcard, options = {})
35
+ debit_commit 'us_preauth', money, creditcard, options
36
+ end
37
+
38
+ # This action verifies funding on a customer's card, and readies them for
39
+ # deposit in a merchant's account.
40
+ #
41
+ # Pass in <tt>order_id</tt> and optionally a <tt>customer</tt> parameter
42
+ def purchase(money, creditcard, options = {})
43
+ debit_commit 'us_purchase', money, creditcard, options
44
+ end
45
+
46
+ # This method retrieves locked funds from a customer's account (from a
47
+ # PreAuth) and prepares them for deposit in a merchant's account.
48
+ #
49
+ # Note: Moneris requires both the order_id and the transaction number of
50
+ # the original authorization. To maintain the same interface as the other
51
+ # gateways the two numbers are concatenated together with a ; separator as
52
+ # the authorization number returned by authorization
53
+ def capture(money, authorization, options = {})
54
+ commit 'us_completion', crediting_params(authorization, :comp_amount => amount(money))
55
+ end
56
+
57
+ # Voiding requires the original transaction ID and order ID of some open
58
+ # transaction. Closed transactions must be refunded. Note that the only
59
+ # methods which may be voided are +capture+ and +purchase+.
60
+ #
61
+ # Concatenate your transaction number and order_id by using a semicolon
62
+ # (';'). This is to keep the Moneris interface consistent with other
63
+ # gateways. (See +capture+ for details.)
64
+ def void(authorization, options = {})
65
+ commit 'us_purchasecorrection', crediting_params(authorization)
66
+ end
67
+
68
+ # Performs a refund. This method requires that the original transaction
69
+ # number and order number be included. Concatenate your transaction
70
+ # number and order_id by using a semicolon (';'). This is to keep the
71
+ # Moneris interface consistent with other gateways. (See +capture+ for
72
+ # details.)
73
+ def credit(money, authorization, options = {})
74
+ deprecated CREDIT_DEPRECATION_MESSAGE
75
+ refund(money, authorization, options)
76
+ end
77
+
78
+ def refund(money, authorization, options = {})
79
+ commit 'us_refund', crediting_params(authorization, :amount => amount(money))
80
+ end
81
+
82
+ def test?
83
+ @options[:test] || super
84
+ end
85
+ private # :nodoc: all
86
+
87
+ def expdate(creditcard)
88
+ sprintf("%.4i", creditcard.year)[-2..-1] + sprintf("%.2i", creditcard.month)
89
+ end
90
+
91
+ def debit_commit(commit_type, money, creditcard, options)
92
+ requires!(options, :order_id)
93
+ commit(commit_type, debit_params(money, creditcard, options))
94
+ end
95
+
96
+ # Common params used amongst the +purchase+ and +authorization+ methods
97
+ def debit_params(money, creditcard, options = {})
98
+ {
99
+ :order_id => options[:order_id],
100
+ :cust_id => options[:customer],
101
+ :amount => amount(money),
102
+ :pan => creditcard.number,
103
+ :expdate => expdate(creditcard),
104
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
105
+ }
106
+ end
107
+
108
+ # Common params used amongst the +credit+, +void+ and +capture+ methods
109
+ def crediting_params(authorization, options = {})
110
+ {
111
+ :txn_number => split_authorization(authorization).first,
112
+ :order_id => split_authorization(authorization).last,
113
+ :crypt_type => options[:crypt_type] || @options[:crypt_type]
114
+ }.merge(options)
115
+ end
116
+
117
+ # Splits an +authorization+ param and retrives the order id and
118
+ # transaction number in that order.
119
+ def split_authorization(authorization)
120
+ if authorization.nil? || authorization.empty? || authorization !~ /;/
121
+ raise ArgumentError, 'You must include a valid authorization code (e.g. "1234;567")'
122
+ else
123
+ authorization.split(';')
124
+ end
125
+ end
126
+
127
+ def commit(action, parameters = {})
128
+ response = parse(ssl_post(test? ? TEST_URL : LIVE_URL, post_data(action, parameters)))
129
+
130
+ Response.new(successful?(response), message_from(response[:message]), response,
131
+ :test => test?,
132
+ :authorization => authorization_from(response)
133
+ )
134
+ end
135
+
136
+ # Generates a Moneris authorization string of the form 'trans_id;receipt_id'.
137
+ def authorization_from(response = {})
138
+ if response[:trans_id] && response[:receipt_id]
139
+ "#{response[:trans_id]};#{response[:receipt_id]}"
140
+ end
141
+ end
142
+
143
+ # Tests for a successful response from Moneris' servers
144
+ def successful?(response)
145
+ response[:response_code] &&
146
+ response[:complete] &&
147
+ (0..49).include?(response[:response_code].to_i)
148
+ end
149
+
150
+ def parse(xml)
151
+ response = { :message => "Global Error Receipt", :complete => false }
152
+ hashify_xml!(xml, response)
153
+ response
154
+ end
155
+
156
+ def hashify_xml!(xml, response)
157
+ xml = REXML::Document.new(xml)
158
+ return if xml.root.nil?
159
+ xml.elements.each('//receipt/*') do |node|
160
+ response[node.name.underscore.to_sym] = normalize(node.text)
161
+ end
162
+ end
163
+
164
+ def post_data(action, parameters = {})
165
+ xml = REXML::Document.new
166
+ root = xml.add_element("request")
167
+ root.add_element("store_id").text = options[:login]
168
+ root.add_element("api_token").text = options[:password]
169
+ transaction = root.add_element(action)
170
+
171
+ # Must add the elements in the correct order
172
+ actions[action].each do |key|
173
+ transaction.add_element(key.to_s).text = parameters[key] unless parameters[key].blank?
174
+ end
175
+
176
+ xml.to_s
177
+ end
178
+
179
+ def message_from(message)
180
+ return 'Unspecified error' if message.blank?
181
+ message.gsub(/[^\w]/, ' ').split.join(" ").capitalize
182
+ end
183
+
184
+ # Make a Ruby type out of the response string
185
+ def normalize(field)
186
+ case field
187
+ when "true" then true
188
+ when "false" then false
189
+ when '', "null" then nil
190
+ else field
191
+ end
192
+ end
193
+
194
+ def actions
195
+ {
196
+ "us_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
197
+ "us_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
198
+ "us_refund" => [:order_id, :amount, :txn_number, :crypt_type],
199
+ "us_ind_refund" => [:order_id, :cust_id, :amount, :pan, :expdate, :crypt_type],
200
+ "us_completion" => [:order_id, :comp_amount, :txn_number, :crypt_type],
201
+ "us_purchasecorrection" => [:order_id, :txn_number, :crypt_type],
202
+ "us_cavv_purchase" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
203
+ "us_cavv_preauth" => [:order_id, :cust_id, :amount, :pan, :expdate, :cavv],
204
+ "us_batchcloseall" => [],
205
+ "us_opentotals" => [:ecr_number],
206
+ "us_batchclose" => [:ecr_number]
207
+ }
208
+ end
209
+ end
210
+ end
211
+ end