activemerchant 1.3.2 → 1.4.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 (85) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +58 -0
  3. data/CONTRIBUTERS +25 -0
  4. data/MIT-LICENSE +3 -3
  5. data/README +16 -10
  6. data/Rakefile +4 -3
  7. data/lib/active_merchant.rb +7 -1
  8. data/lib/active_merchant/billing/check.rb +16 -9
  9. data/lib/active_merchant/billing/gateway.rb +1 -1
  10. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +702 -0
  11. data/lib/active_merchant/billing/gateways/beanstream.rb +102 -0
  12. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +233 -0
  13. data/lib/active_merchant/billing/gateways/beanstream_interac.rb +54 -0
  14. data/lib/active_merchant/billing/gateways/braintree.rb +10 -1
  15. data/lib/active_merchant/billing/gateways/cyber_source.rb +26 -2
  16. data/lib/active_merchant/billing/gateways/data_cash.rb +255 -59
  17. data/lib/active_merchant/billing/gateways/modern_payments.rb +36 -0
  18. data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +214 -0
  19. data/lib/active_merchant/billing/gateways/net_registry.rb +1 -0
  20. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +2 -2
  21. data/lib/active_merchant/billing/gateways/payflow_express.rb +3 -11
  22. data/lib/active_merchant/billing/gateways/payment_express.rb +2 -2
  23. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +39 -21
  24. data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
  25. data/lib/active_merchant/billing/gateways/paypal_express.rb +3 -12
  26. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +20 -0
  27. data/lib/active_merchant/billing/gateways/protx.rb +25 -25
  28. data/lib/active_merchant/billing/gateways/sage.rb +145 -0
  29. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +88 -0
  30. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +110 -0
  31. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +97 -0
  32. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +3 -1
  33. data/lib/active_merchant/billing/gateways/skip_jack.rb +2 -0
  34. data/lib/active_merchant/billing/gateways/trust_commerce.rb +1 -1
  35. data/lib/active_merchant/billing/gateways/wirecard.rb +304 -0
  36. data/lib/active_merchant/billing/integrations.rb +8 -2
  37. data/lib/active_merchant/billing/integrations/action_view_helper.rb +18 -4
  38. data/lib/active_merchant/billing/integrations/hi_trust/notification.rb +4 -2
  39. data/lib/active_merchant/billing/integrations/notification.rb +10 -1
  40. data/lib/active_merchant/lib/posts_data.rb +12 -3
  41. data/script/destroy +0 -0
  42. data/script/generate +0 -0
  43. data/test/extra/binding_of_caller.rb +0 -0
  44. data/test/extra/breakpoint.rb +0 -0
  45. data/test/fixtures.yml +24 -0
  46. data/test/remote/gateways/remote_authorize_net_cim_test.rb +459 -0
  47. data/test/remote/gateways/remote_beanstream_interac_test.rb +53 -0
  48. data/test/remote/gateways/remote_beanstream_test.rb +150 -0
  49. data/test/remote/gateways/remote_braintree_test.rb +22 -0
  50. data/test/remote/gateways/remote_cyber_source_test.rb +28 -3
  51. data/test/remote/gateways/remote_data_cash_test.rb +250 -48
  52. data/test/remote/gateways/remote_modern_payments_cim_test.rb +58 -0
  53. data/test/remote/gateways/remote_modern_payments_test.rb +43 -0
  54. data/test/remote/gateways/remote_sage_bankcard_test.rb +109 -0
  55. data/test/remote/gateways/remote_sage_test.rb +87 -0
  56. data/test/remote/gateways/remote_sage_virtual_check_test.rb +62 -0
  57. data/test/remote/gateways/remote_wirecard_test.rb +76 -0
  58. data/test/remote/integrations/remote_paypal_integration_test.rb +15 -3
  59. data/test/test_helper.rb +31 -13
  60. data/test/unit/check_test.rb +14 -2
  61. data/test/unit/credit_card_methods_test.rb +18 -0
  62. data/test/unit/gateways/authorize_net_cim_test.rb +641 -0
  63. data/test/unit/gateways/beanstream_interac_test.rb +51 -0
  64. data/test/unit/gateways/beanstream_test.rb +108 -0
  65. data/test/unit/gateways/braintree_test.rb +2 -5
  66. data/test/unit/gateways/cyber_source_test.rb +18 -0
  67. data/test/unit/gateways/data_cash_test.rb +32 -4
  68. data/test/unit/gateways/gateway_test.rb +8 -1
  69. data/test/unit/gateways/modern_payments_cim_test.rb +171 -0
  70. data/test/unit/gateways/net_registry_test.rb +6 -0
  71. data/test/unit/gateways/payflow_express_test.rb +18 -2
  72. data/test/unit/gateways/paypal_express_test.rb +154 -0
  73. data/test/unit/gateways/paypal_test.rb +140 -0
  74. data/test/unit/gateways/sage_bankcard_test.rb +162 -0
  75. data/test/unit/gateways/sage_virtual_check_test.rb +71 -0
  76. data/test/unit/gateways/secure_pay_au_test.rb +58 -1
  77. data/test/unit/gateways/skip_jack_test.rb +8 -0
  78. data/test/unit/gateways/verifi_test.rb +0 -1
  79. data/test/unit/gateways/wirecard_test.rb +232 -0
  80. data/test/unit/integrations/action_view_helper_test.rb +3 -0
  81. data/test/unit/integrations/notifications/hi_trust_notification_test.rb +23 -2
  82. data/test/unit/integrations/notifications/notification_test.rb +13 -0
  83. data/test/unit/posts_data_test.rb +20 -6
  84. metadata +40 -5
  85. metadata.gz.sig +0 -0
@@ -0,0 +1,13 @@
1
+ require File.dirname(__FILE__) + '/paypal'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ # The PayPal gateway for PayPal Website Payments Pro Canada only supports Visa and MasterCard
6
+ class PaypalCaGateway < PaypalGateway
7
+ self.supported_cardtypes = [:visa, :master]
8
+ self.supported_countries = ['CA']
9
+ self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside'
10
+ self.display_name = 'PayPal Website Payments Pro (CA)'
11
+ end
12
+ end
13
+ end
@@ -1,26 +1,17 @@
1
1
  require File.dirname(__FILE__) + '/paypal/paypal_common_api'
2
2
  require File.dirname(__FILE__) + '/paypal/paypal_express_response'
3
+ require File.dirname(__FILE__) + '/paypal_express_common'
3
4
 
4
5
  module ActiveMerchant #:nodoc:
5
6
  module Billing #:nodoc:
6
7
  class PaypalExpressGateway < Gateway
7
8
  include PaypalCommonAPI
9
+ include PaypalExpressCommon
8
10
 
11
+ self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='
9
12
  self.supported_countries = ['US']
10
-
11
- LIVE_REDIRECT_URL = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout&token='
12
- TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='
13
-
14
13
  self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
15
14
  self.display_name = 'PayPal Express Checkout'
16
-
17
- def redirect_url
18
- test? ? TEST_REDIRECT_URL : LIVE_REDIRECT_URL
19
- end
20
-
21
- def redirect_url_for(token)
22
- "#{redirect_url}#{token}"
23
- end
24
15
 
25
16
  def setup_authorization(money, options = {})
26
17
  requires!(options, :return_url, :cancel_return_url)
@@ -0,0 +1,20 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+ module PaypalExpressCommon
4
+ def self.included(base)
5
+ base.cattr_accessor :test_redirect_url
6
+ base.cattr_accessor :live_redirect_url
7
+ base.live_redirect_url = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout&token='
8
+ end
9
+
10
+ def redirect_url
11
+ test? ? test_redirect_url : live_redirect_url
12
+ end
13
+
14
+ def redirect_url_for(token, options = {})
15
+ options = {:review => true}.update(options)
16
+ options[:review] ? "#{redirect_url}#{token}" : "#{redirect_url}#{token}&useraction=commit"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -23,6 +23,7 @@ module ActiveMerchant #:nodoc:
23
23
  :master => "MC",
24
24
  :delta => "DELTA",
25
25
  :solo => "SOLO",
26
+ :switch => "MAESTRO",
26
27
  :maestro => "MAESTRO",
27
28
  :american_express => "AMEX",
28
29
  :electron => "UKE",
@@ -39,7 +40,7 @@ module ActiveMerchant #:nodoc:
39
40
  "NOTMATCHED" => 'N'
40
41
  }
41
42
 
42
- self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :solo, :maestro, :diners_club]
43
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :switch, :solo, :maestro, :diners_club]
43
44
  self.supported_countries = ['GB']
44
45
  self.default_currency = 'GBP'
45
46
 
@@ -53,7 +54,7 @@ module ActiveMerchant #:nodoc:
53
54
  end
54
55
 
55
56
  def test?
56
- @options[:test] || Base.gateway_mode == :test
57
+ @options[:test] || super
57
58
  end
58
59
 
59
60
  def purchase(money, credit_card, options = {})
@@ -84,11 +85,13 @@ module ActiveMerchant #:nodoc:
84
85
  commit(:authorization, post)
85
86
  end
86
87
 
87
- # Only supports capturing the original amount of the transaction
88
+ # You can only capture a transaction once, even if you didn't capture the full amount the first time.
88
89
  def capture(money, identification, options = {})
89
90
  post = {}
90
91
 
91
92
  add_reference(post, identification)
93
+ add_release_amount(post, money, options)
94
+
92
95
  commit(:capture, post)
93
96
  end
94
97
 
@@ -136,10 +139,15 @@ module ActiveMerchant #:nodoc:
136
139
  add_pair(post, :Currency, options[:currency] || currency(money), :required => true)
137
140
  end
138
141
 
142
+ # doesn't actually use the currency -- dodgy!
143
+ def add_release_amount(post, money, options)
144
+ add_pair(post, :ReleaseAmount, amount(money), :required => true)
145
+ end
146
+
139
147
  def add_customer_data(post, options)
140
- add_pair(post, :BillingEmail, options[:email])
141
- add_pair(post, :ContactNumber, options[:phone])
142
- add_pair(post, :ContactFax, options[:fax])
148
+ add_pair(post, :BillingEmail, options[:email][0,255]) unless options[:email].blank?
149
+ add_pair(post, :ContactNumber, options[:phone].gsub(/[^0-9+]/, '')[0,20]) unless options[:phone].blank?
150
+ add_pair(post, :ContactFax, options[:fax].gsub(/[^0-9+]/, '')[0,20]) unless options[:fax].blank?
143
151
  add_pair(post, :ClientIPAddress, options[:ip])
144
152
  end
145
153
 
@@ -171,12 +179,11 @@ module ActiveMerchant #:nodoc:
171
179
  add_pair(post, :CardHolder, credit_card.name, :required => true)
172
180
  add_pair(post, :CardNumber, credit_card.number, :required => true)
173
181
 
174
- add_pair(post, :ExpiryDate, format_expiry_date(credit_card), :required => true)
182
+ add_pair(post, :ExpiryDate, format_date(credit_card.month, credit_card.year), :required => true)
175
183
 
176
184
  if requires_start_date_or_issue_number?(credit_card)
177
- add_pair(post, :StartDate, format(credit_card.start_year, :four_digits))
178
-
179
- add_pair(post, :IssueNumber, format_issue_number(credit_card))
185
+ add_pair(post, :StartDate, format_date(credit_card.start_month, credit_card.start_year))
186
+ add_pair(post, :IssueNumber, credit_card.issue_number)
180
187
  end
181
188
  add_pair(post, :CardType, map_card_type(credit_card))
182
189
 
@@ -201,17 +208,15 @@ module ActiveMerchant #:nodoc:
201
208
  end
202
209
 
203
210
  # MMYY format
204
- def format_expiry_date(credit_card)
205
- year = sprintf("%.4i", credit_card.year)
206
- month = sprintf("%.2i", credit_card.month)
211
+ def format_date(month, year)
212
+ return nil if year.blank? || month.blank?
213
+
214
+ year = sprintf("%.4i", year)
215
+ month = sprintf("%.2i", month)
207
216
 
208
217
  "#{month}#{year[-2..-1]}"
209
218
  end
210
219
 
211
- def format_issue_number(credit_card)
212
- card_brand(credit_card).to_s == 'solo' ? format(credit_card.issue_number, :two_digits) : credit_card.issue_number
213
- end
214
-
215
220
  def commit(action, parameters)
216
221
  response = parse( ssl_post(url_for(action), post_data(action, parameters)) )
217
222
 
@@ -230,7 +235,7 @@ module ActiveMerchant #:nodoc:
230
235
  [ params[:VendorTxCode],
231
236
  response["VPSTxId"],
232
237
  response["TxAuthNo"],
233
- response["SecurityKey"] ].compact.join(";")
238
+ response["SecurityKey"] ].join(";")
234
239
  end
235
240
 
236
241
  def url_for(action)
@@ -248,12 +253,7 @@ module ActiveMerchant #:nodoc:
248
253
  end
249
254
 
250
255
  def message_from(response)
251
- if response["Status"] == APPROVED
252
- return 'Success'
253
- else
254
- return 'Unspecified error' if response["StatusDetail"].blank?
255
- return response["StatusDetail"]
256
- end
256
+ response['Status'] == APPROVED ? 'Success' : (response['StatusDetail'] || 'Unspecified error') # simonr 20080207 can't actually get non-nil blanks, so this is shorter
257
257
  end
258
258
 
259
259
  def post_data(action, parameters = {})
@@ -271,7 +271,7 @@ module ActiveMerchant #:nodoc:
271
271
  # Key2=value2
272
272
  def parse(body)
273
273
  result = {}
274
- body.to_a.collect {|v| c=v.split('='); result[c[0]] = c[1].chomp if !c[1].blank? }
274
+ body.to_a.each { |pair| result[$1] = $2 if pair.strip =~ /\A([^=]+)=(.+)\Z/im }
275
275
  result
276
276
  end
277
277
 
@@ -0,0 +1,145 @@
1
+ require File.dirname(__FILE__) + '/sage/sage_bankcard'
2
+ require File.dirname(__FILE__) + '/sage/sage_virtual_check'
3
+
4
+ module ActiveMerchant #:nodoc:
5
+ module Billing #:nodoc:
6
+ class SageGateway < Gateway
7
+ self.supported_cardtypes = SageBankcardGateway.supported_cardtypes
8
+
9
+ # Creates a new SageGateway
10
+ #
11
+ # The gateway requires that a valid login and password be passed
12
+ # in the +options+ hash.
13
+ #
14
+ # ==== Options
15
+ #
16
+ # * <tt>:login</tt> - The Sage Payment Solutions Merchant ID Number.
17
+ # * <tt>:password</tt> - The Sage Payment Solutions Merchant Key Number.
18
+ def initialize(options = {})
19
+ requires!(options, :login, :password)
20
+ @options = options
21
+ super
22
+ end
23
+
24
+ # Performs an authorization transaction
25
+ #
26
+ # ==== Parameters
27
+ # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
28
+ # * <tt>credit_card</tt> - The CreditCard object to be used as the funding source for the transaction.
29
+ # * <tt>options</tt> - A hash of optional parameters.
30
+ # * <tt>:order_id</tt> - A unique reference for this order. (maximum of 20 characters).
31
+ # * <tt>:email</tt> - The customer's email address
32
+ # * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
33
+ # * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
34
+ # * <tt>:address1</tt> - The billing address street
35
+ # * <tt>:city</tt> - The billing address city
36
+ # * <tt>:state</tt> - The billing address state
37
+ # * <tt>:country</tt> - The 2 digit ISO billing address country code
38
+ # * <tt>:zip</tt> - The billing address zip code
39
+ # * <tt>:phone</tt> - The billing address phone number
40
+ # * <tt>:fax</tt> - The billing address fax number
41
+ # * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
42
+ # * <tt>:name</tt> - The name at the shipping address
43
+ # * <tt>:address1</tt> - The shipping address street
44
+ # * <tt>:city</tt> - The shipping address city
45
+ # * <tt>:state</tt> - The shipping address state code
46
+ # * <tt>:country</tt> - The 2 digit ISO shipping address country code
47
+ # * <tt>:zip</tt> - The shipping address zip code
48
+ # * <tt>:tax</tt> - The tax amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_tax</tt>.
49
+ # * <tt>:shipping</tt> - The shipping amount for the transaction as an Integer value in cents. Maps to Sage <tt>T_shipping</tt>.
50
+ def authorize(money, credit_card, options = {})
51
+ bankcard.authorize(money, credit_card, options)
52
+ end
53
+
54
+ # Performs a purchase, which is essentially an authorization and capture in a single operation.
55
+ #
56
+ # ==== Parameters
57
+ #
58
+ # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
59
+ # * <tt>source</tt> - The CreditCard or Check object to be used as the funding source for the transaction.
60
+ # * <tt>options</tt> - A hash of optional parameters.
61
+ # * <tt>:order_id</tt> - A unique reference for this order. (maximum of 20 characters).
62
+ # * <tt>:email</tt> - The customer's email address
63
+ # * <tt>:customer</tt> - The Customer Number for Purchase Card Level II Transactions
64
+ # * <tt>:billing_address</tt> - The customer's billing address as a hash of address information.
65
+ # * <tt>:address1</tt> - The billing address street
66
+ # * <tt>:city</tt> - The billing address city
67
+ # * <tt>:state</tt> - The billing address state
68
+ # * <tt>:country</tt> - The 2 digit ISO billing address country code
69
+ # * <tt>:zip</tt> - The billing address zip code
70
+ # * <tt>:phone</tt> - The billing address phone number
71
+ # * <tt>:fax</tt> - The billing address fax number
72
+ # * <tt>:shipping_address</tt> - The customer's shipping address as a hash of address information.
73
+ # * <tt>:name</tt> - The name at the shipping address
74
+ # * <tt>:address1</tt> - The shipping address street
75
+ # * <tt>:city</tt> - The shipping address city
76
+ # * <tt>:state</tt> - The shipping address state code
77
+ # * <tt>:country</tt> - The 2 digit ISO shipping address country code
78
+ # * <tt>:zip</tt> - The shipping address zip code
79
+ # * <tt>:tax</tt> - The tax amount for the transaction as an integer value in cents. Maps to Sage <tt>T_tax</tt>.
80
+ # * <tt>:shipping</tt> - The shipping amount for the transaction as an integer value in cents. Maps to Sage <tt>T_shipping</tt>.
81
+ #
82
+ # ==== Additional options in the +options+ hash for when using a Check as the funding source
83
+ # * <tt>:originator_id</tt> - 10 digit originator. If not provided, Sage will use the default Originator ID for the specific customer type.
84
+ # * <tt>:addenda</tt> - Transaction addenda.
85
+ # * <tt>:ssn</tt> - The customer's Social Security Number.
86
+ # * <tt>:drivers_license_state</tt> - The customer's drivers license state code.
87
+ # * <tt>:drivers_license_number</tt> - The customer's drivers license number.
88
+ # * <tt>:date_of_birth</tt> - The customer's date of birth as a Time or Date object or a string in the format <tt>mm/dd/yyyy</tt>.
89
+ def purchase(money, source, options = {})
90
+ if source.type == "check"
91
+ virtual_check.purchase(money, source, options)
92
+ else
93
+ bankcard.purchase(money, source, options)
94
+ end
95
+ end
96
+
97
+ # Captures authorized funds.
98
+ #
99
+ # ==== Parameters
100
+ #
101
+ # * <tt>money</tt> - The amount to be authorized as an integer value in cents. Sage doesn't support changing the capture amount, so the full amount of the initial transaction will be captured.
102
+ # * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
103
+ def capture(money, reference, options = {})
104
+ bankcard.capture(money, reference, options)
105
+ end
106
+
107
+ # Voids a prior transaction. Works for both CreditCard and Check transactions.
108
+ #
109
+ # ==== Parameters
110
+ #
111
+ # * <tt>reference</tt> - The authorization reference string returned by the original transaction's Response#authorization.
112
+ def void(reference, options = {})
113
+ if reference.split(";").last == "virtual_check"
114
+ virtual_check.void(reference, options)
115
+ else
116
+ bankcard.void(reference, options)
117
+ end
118
+ end
119
+
120
+ # Performs a credit transaction. (Sage +Credit+ transaction).
121
+ #
122
+ # ==== Parameters
123
+ #
124
+ # * <tt>money</tt> - The amount to be authorized as an integer value in cents.
125
+ # * <tt>source</tt> - The CreditCard or Check object to be used as the target for the credit.
126
+ def credit(money, source, options = {})
127
+ if source.type == "check"
128
+ virtual_check.credit(money, source, options)
129
+ else
130
+ bankcard.credit(money, source, options)
131
+ end
132
+ end
133
+
134
+ private
135
+ def bankcard
136
+ @bankcard ||= SageBankcardGateway.new(@options)
137
+ end
138
+
139
+ def virtual_check
140
+ @virtual_check ||= SageVirtualCheckGateway.new(@options)
141
+ end
142
+ end
143
+ end
144
+ end
145
+
@@ -0,0 +1,88 @@
1
+ require File.dirname(__FILE__) + '/sage_core'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class SageBankcardGateway < Gateway #:nodoc:
6
+ include SageCore
7
+ self.url = 'https://www.sagepayments.net/cgi-bin/eftBankcard.dll?transaction'
8
+ self.source = 'bankcard'
9
+
10
+ # Credit cards supported by Sage
11
+ # * VISA
12
+ # * MasterCard
13
+ # * AMEX
14
+ # * Diners
15
+ # * Carte Blanche
16
+ # * Discover
17
+ # * JCB
18
+ # * Sears
19
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
20
+
21
+ def authorize(money, credit_card, options = {})
22
+ post = {}
23
+ add_credit_card(post, credit_card)
24
+ add_transaction_data(post, money, options)
25
+ commit(:authorization, post)
26
+ end
27
+
28
+ def purchase(money, credit_card, options = {})
29
+ post = {}
30
+ add_credit_card(post, credit_card)
31
+ add_transaction_data(post, money, options)
32
+ commit(:purchase, post)
33
+ end
34
+
35
+ # The +money+ amount is not used. The entire amount of the
36
+ # initial authorization will be captured.
37
+ def capture(money, reference, options = {})
38
+ post = {}
39
+ add_reference(post, reference)
40
+ commit(:capture, post)
41
+ end
42
+
43
+ def void(reference, options = {})
44
+ post = {}
45
+ add_reference(post, reference)
46
+ commit(:void, post)
47
+ end
48
+
49
+ def credit(money, credit_card, options = {})
50
+ post = {}
51
+ add_credit_card(post, credit_card)
52
+ add_transaction_data(post, money, options)
53
+ commit(:credit, post)
54
+ end
55
+
56
+ private
57
+ def exp_date(credit_card)
58
+ year = sprintf("%.4i", credit_card.year)
59
+ month = sprintf("%.2i", credit_card.month)
60
+
61
+ "#{month}#{year[-2..-1]}"
62
+ end
63
+
64
+ def add_credit_card(post, credit_card)
65
+ post[:C_name] = credit_card.name
66
+ post[:C_cardnumber] = credit_card.number
67
+ post[:C_exp] = exp_date(credit_card)
68
+ post[:C_cvv] = credit_card.verification_value if credit_card.verification_value?
69
+ end
70
+
71
+ def parse(data)
72
+ response = {}
73
+ response[:success] = data[1,1]
74
+ response[:code] = data[2,6]
75
+ response[:message] = data[8,32].strip
76
+ response[:front_end] = data[40, 2]
77
+ response[:cvv_result] = data[42, 1]
78
+ response[:avs_result] = data[43, 1].strip
79
+ response[:risk] = data[44, 2]
80
+ response[:reference] = data[46, 10]
81
+
82
+ response[:order_number], response[:recurring] = data[57...-1].split("\034")
83
+ response
84
+ end
85
+ end
86
+ end
87
+ end
88
+
@@ -0,0 +1,110 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ module SageCore #:nodoc:
4
+ def self.included(base)
5
+ base.cattr_accessor :url
6
+ base.cattr_accessor :source
7
+ base.supported_countries = ['US', 'CA']
8
+ base.homepage_url = 'http://www.sagepayments.com'
9
+ base.display_name = 'Sage Payment Solutions'
10
+ end
11
+
12
+ # Transactions types:
13
+ # <tt>01</tt> - Sale
14
+ # <tt>02</tt> - AuthOnly
15
+ # <tt>03</tt> - Force/PriorAuthSale
16
+ # <tt>04</tt> - Void
17
+ # <tt>06</tt> - Credit
18
+ # <tt>11</tt> - PriorAuthSale by Reference*
19
+ TRANSACTIONS = {
20
+ :purchase => '01',
21
+ :authorization => '02',
22
+ :capture => '11',
23
+ :void => '04',
24
+ :credit => '06'
25
+ }
26
+
27
+ def initialize(options = {})
28
+ requires!(options, :login, :password)
29
+ @options = options
30
+ super
31
+ end
32
+
33
+ private
34
+ def add_invoice(post, options)
35
+ post[:T_ordernum] = options[:order_id].slice(0, 20)
36
+ post[:T_tax] = amount(options[:tax]) unless options[:tax].blank?
37
+ post[:T_shipping] = amount(options[:shipping]) unless options[:shipping].blank?
38
+ end
39
+
40
+ def add_reference(post, reference)
41
+ ref, source = reference.to_s.split(";")
42
+ post[:T_reference] = ref
43
+ end
44
+
45
+ def add_amount(post, money)
46
+ post[:T_amt] = amount(money)
47
+ end
48
+
49
+ def add_customer_data(post, options)
50
+ post[:T_customer_number] = options[:customer]
51
+ end
52
+
53
+ def add_addresses(post, options)
54
+ billing_address = options[:billing_address] || options[:address] || {}
55
+
56
+ post[:C_address] = billing_address[:address1]
57
+ post[:C_city] = billing_address[:city]
58
+ post[:C_state] = billing_address[:state]
59
+ post[:C_zip] = billing_address[:zip]
60
+ post[:C_country] = billing_address[:country]
61
+ post[:C_telephone] = billing_address[:phone]
62
+ post[:C_fax] = billing_address[:fax]
63
+ post[:C_email] = options[:email]
64
+
65
+ if shipping_address = options[:shipping_address]
66
+ post[:C_ship_name] = shipping_address[:name]
67
+ post[:C_ship_address] = shipping_address[:address1]
68
+ post[:C_ship_city] = shipping_address[:city]
69
+ post[:C_ship_state] = shipping_address[:state]
70
+ post[:C_ship_zip] = shipping_address[:zip]
71
+ post[:C_ship_country] = shipping_address[:country]
72
+ end
73
+ end
74
+
75
+ def add_transaction_data(post, money, options)
76
+ add_amount(post, money)
77
+ add_invoice(post, options)
78
+ add_addresses(post, options)
79
+ add_customer_data(post, options)
80
+ end
81
+
82
+ def commit(action, params)
83
+ response = parse(ssl_post(url, post_data(action, params)))
84
+
85
+ Response.new(success?(response), response[:message], response,
86
+ :test => test?,
87
+ :authorization => authorization_from(response),
88
+ :avs_result => { :code => response[:avs_result] },
89
+ :cvv_result => response[:cvv_result]
90
+ )
91
+ end
92
+
93
+ def authorization_from(response)
94
+ "#{response[:reference]};#{source}"
95
+ end
96
+
97
+ def success?(response)
98
+ response[:success] == 'A'
99
+ end
100
+
101
+ def post_data(action, params = {})
102
+ params[:M_id] = @options[:login]
103
+ params[:M_key] = @options[:password]
104
+ params[:T_code] = TRANSACTIONS[action]
105
+
106
+ params.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
107
+ end
108
+ end
109
+ end
110
+ end