activemerchant 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (178) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +226 -0
  3. data/CONTRIBUTERS +52 -0
  4. data/README +34 -24
  5. data/Rakefile +152 -0
  6. data/gem-public_cert.pem +20 -0
  7. data/init.rb +3 -0
  8. data/lib/active_merchant.rb +3 -1
  9. data/lib/active_merchant/billing/credit_card.rb +21 -17
  10. data/lib/active_merchant/billing/credit_card_methods.rb +17 -19
  11. data/lib/active_merchant/billing/gateway.rb +160 -44
  12. data/lib/active_merchant/billing/gateways.rb +2 -15
  13. data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -21
  14. data/lib/active_merchant/billing/gateways/bogus.rb +6 -6
  15. data/lib/active_merchant/billing/gateways/brain_tree.rb +191 -0
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +207 -0
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +402 -0
  18. data/lib/active_merchant/billing/gateways/data_cash.rb +41 -97
  19. data/lib/active_merchant/billing/gateways/efsnet.rb +256 -0
  20. data/lib/active_merchant/billing/gateways/eway.rb +77 -29
  21. data/lib/active_merchant/billing/gateways/exact.rb +230 -0
  22. data/lib/active_merchant/billing/gateways/linkpoint.rb +6 -33
  23. data/lib/active_merchant/billing/gateways/moneris.rb +155 -125
  24. data/lib/active_merchant/billing/gateways/net_registry.rb +257 -0
  25. data/lib/active_merchant/billing/gateways/pay_junction.rb +407 -0
  26. data/lib/active_merchant/billing/gateways/payflow.rb +163 -25
  27. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +56 -38
  28. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +10 -1
  29. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +9 -0
  30. data/lib/active_merchant/billing/gateways/payflow_express.rb +36 -11
  31. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +6 -0
  32. data/lib/active_merchant/billing/gateways/payflow_uk.rb +7 -3
  33. data/lib/active_merchant/billing/gateways/payment_express.rb +261 -0
  34. data/lib/active_merchant/billing/gateways/paypal.rb +18 -4
  35. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +31 -15
  36. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +8 -0
  37. data/lib/active_merchant/billing/gateways/paypal_express.rb +33 -8
  38. data/lib/active_merchant/billing/gateways/plugnpay.rb +300 -0
  39. data/lib/active_merchant/billing/gateways/protx.rb +285 -0
  40. data/lib/active_merchant/billing/gateways/psigate.rb +13 -12
  41. data/lib/active_merchant/billing/gateways/psl_card.rb +297 -0
  42. data/lib/active_merchant/billing/gateways/quickpay.rb +197 -0
  43. data/lib/active_merchant/billing/gateways/realex.rb +212 -0
  44. data/lib/active_merchant/billing/gateways/secure_pay.rb +31 -0
  45. data/lib/active_merchant/billing/gateways/trans_first.rb +136 -0
  46. data/lib/active_merchant/billing/gateways/trust_commerce.rb +43 -20
  47. data/lib/active_merchant/billing/gateways/usa_epay.rb +6 -5
  48. data/lib/active_merchant/billing/gateways/verifi.rb +235 -0
  49. data/lib/active_merchant/billing/gateways/viaklix.rb +171 -0
  50. data/lib/active_merchant/billing/integrations/gestpay/helper.rb +0 -2
  51. data/lib/active_merchant/billing/integrations/helper.rb +8 -1
  52. data/lib/active_merchant/billing/integrations/nochex.rb +62 -1
  53. data/lib/active_merchant/billing/integrations/nochex/notification.rb +9 -16
  54. data/lib/active_merchant/billing/integrations/notification.rb +1 -1
  55. data/lib/active_merchant/billing/integrations/paypal/helper.rb +59 -46
  56. data/lib/active_merchant/billing/integrations/paypal/notification.rb +14 -47
  57. data/lib/active_merchant/lib/error.rb +4 -0
  58. data/lib/active_merchant/lib/post_data.rb +22 -0
  59. data/lib/active_merchant/lib/posts_data.rb +23 -5
  60. data/lib/active_merchant/lib/requires_parameters.rb +2 -2
  61. data/lib/active_merchant/lib/validateable.rb +1 -1
  62. data/lib/support/gateway_support.rb +45 -0
  63. data/lib/tasks/cia.rb +1 -1
  64. data/script/generate +14 -0
  65. data/script/generator/base.rb +45 -0
  66. data/script/generator/generator.rb +24 -0
  67. data/script/generator/generators/gateway/gateway_generator.rb +14 -0
  68. data/script/generator/generators/gateway/templates/gateway.rb +73 -0
  69. data/script/generator/generators/gateway/templates/gateway_test.rb +41 -0
  70. data/script/generator/generators/gateway/templates/remote_gateway_test.rb +56 -0
  71. data/script/generator/generators/integration/integration_generator.rb +25 -0
  72. data/script/generator/generators/integration/templates/helper.rb +34 -0
  73. data/script/generator/generators/integration/templates/helper_test.rb +54 -0
  74. data/script/generator/generators/integration/templates/integration.rb +18 -0
  75. data/script/generator/generators/integration/templates/module_test.rb +9 -0
  76. data/script/generator/generators/integration/templates/notification.rb +100 -0
  77. data/script/generator/generators/integration/templates/notification_test.rb +41 -0
  78. data/script/generator/manifest.rb +20 -0
  79. data/test/extra/binding_of_caller.rb +80 -0
  80. data/test/extra/breakpoint.rb +547 -0
  81. data/test/fixtures.yml +251 -0
  82. data/test/remote_tests/remote_authorize_net_test.rb +113 -0
  83. data/test/remote_tests/remote_brain_tree_test.rb +78 -0
  84. data/test/remote_tests/remote_card_stream_test.rb +160 -0
  85. data/test/remote_tests/remote_cyber_source_test.rb +130 -0
  86. data/test/remote_tests/remote_data_cash_test.rb +155 -0
  87. data/test/remote_tests/remote_efsnet_test.rb +93 -0
  88. data/test/remote_tests/remote_eway_test.rb +71 -0
  89. data/test/remote_tests/remote_exact_test.rb +59 -0
  90. data/test/remote_tests/remote_gestpay_integration_test.rb +37 -0
  91. data/test/remote_tests/remote_linkpoint_test.rb +144 -0
  92. data/test/remote_tests/remote_moneris_test.rb +110 -0
  93. data/test/remote_tests/remote_net_registry_test.rb +120 -0
  94. data/test/remote_tests/remote_pay_junction_test.rb +162 -0
  95. data/test/remote_tests/remote_payflow_express_test.rb +50 -0
  96. data/test/remote_tests/remote_payflow_test.rb +241 -0
  97. data/test/remote_tests/remote_payflow_uk_test.rb +172 -0
  98. data/test/remote_tests/remote_payment_express_test.rb +136 -0
  99. data/test/remote_tests/remote_paypal_express_test.rb +49 -0
  100. data/test/remote_tests/remote_paypal_integration_test.rb +14 -0
  101. data/test/remote_tests/remote_paypal_test.rb +163 -0
  102. data/test/remote_tests/remote_plugnpay_test.rb +70 -0
  103. data/test/remote_tests/remote_protx_test.rb +184 -0
  104. data/test/remote_tests/remote_psigate_test.rb +87 -0
  105. data/test/remote_tests/remote_psl_card_test.rb +105 -0
  106. data/test/remote_tests/remote_quickpay_test.rb +182 -0
  107. data/test/remote_tests/remote_realex_test.rb +227 -0
  108. data/test/remote_tests/remote_secure_pay_test.rb +36 -0
  109. data/test/remote_tests/remote_trans_first_test.rb +37 -0
  110. data/test/remote_tests/remote_trust_commerce_test.rb +136 -0
  111. data/test/remote_tests/remote_usa_epay_test.rb +57 -0
  112. data/test/remote_tests/remote_verifi_test.rb +107 -0
  113. data/test/remote_tests/remote_viaklix_test.rb +53 -0
  114. data/test/test_helper.rb +132 -0
  115. data/test/unit/base_test.rb +61 -0
  116. data/test/unit/country_code_test.rb +33 -0
  117. data/test/unit/country_test.rb +64 -0
  118. data/test/unit/credit_card_formatting_test.rb +24 -0
  119. data/test/unit/credit_card_methods_test.rb +37 -0
  120. data/test/unit/credit_card_test.rb +365 -0
  121. data/test/unit/gateways/authorize_net_test.rb +140 -0
  122. data/test/unit/gateways/bogus_test.rb +43 -0
  123. data/test/unit/gateways/brain_tree_test.rb +77 -0
  124. data/test/unit/gateways/card_stream_test.rb +37 -0
  125. data/test/unit/gateways/cyber_source_test.rb +151 -0
  126. data/test/unit/gateways/data_cash_test.rb +23 -0
  127. data/test/unit/gateways/efsnet_test.rb +70 -0
  128. data/test/unit/gateways/eway_test.rb +105 -0
  129. data/test/unit/gateways/exact_test.rb +118 -0
  130. data/test/unit/gateways/gateway_test.rb +24 -0
  131. data/test/unit/gateways/linkpoint_test.rb +165 -0
  132. data/test/unit/gateways/moneris_test.rb +167 -0
  133. data/test/unit/gateways/net_registry_test.rb +478 -0
  134. data/test/unit/gateways/pay_junction_test.rb +61 -0
  135. data/test/unit/gateways/payflow_express_test.rb +165 -0
  136. data/test/unit/gateways/payflow_express_uk_test.rb +14 -0
  137. data/test/unit/gateways/payflow_test.rb +230 -0
  138. data/test/unit/gateways/payflow_uk_test.rb +68 -0
  139. data/test/unit/gateways/payment_express_test.rb +215 -0
  140. data/test/unit/gateways/paypal_express_test.rb +222 -0
  141. data/test/unit/gateways/paypal_test.rb +241 -0
  142. data/test/unit/gateways/plugnpay_test.rb +79 -0
  143. data/test/unit/gateways/protx_test.rb +110 -0
  144. data/test/unit/gateways/psigate_test.rb +110 -0
  145. data/test/unit/gateways/psl_card_test.rb +51 -0
  146. data/test/unit/gateways/quickpay_test.rb +125 -0
  147. data/test/unit/gateways/realex_test.rb +150 -0
  148. data/test/unit/gateways/secure_pay_test.rb +78 -0
  149. data/test/unit/gateways/trans_first_test.rb +125 -0
  150. data/test/unit/gateways/trust_commerce_test.rb +57 -0
  151. data/test/unit/gateways/usa_epay_test.rb +117 -0
  152. data/test/unit/gateways/verifi_test.rb +91 -0
  153. data/test/unit/gateways/viaklix_test.rb +72 -0
  154. data/test/unit/integrations/action_view_helper_test.rb +54 -0
  155. data/test/unit/integrations/bogus_module_test.rb +16 -0
  156. data/test/unit/integrations/chronopay_module_test.rb +9 -0
  157. data/test/unit/integrations/gestpay_module_test.rb +10 -0
  158. data/test/unit/integrations/helpers/bogus_helper_test.rb +28 -0
  159. data/test/unit/integrations/helpers/chronopay_helper_test.rb +67 -0
  160. data/test/unit/integrations/helpers/gestpay_helper_test.rb +100 -0
  161. data/test/unit/integrations/helpers/nochex_helper_test.rb +53 -0
  162. data/test/unit/integrations/helpers/paypal_helper_test.rb +162 -0
  163. data/test/unit/integrations/helpers/two_checkout_helper_test.rb +92 -0
  164. data/test/unit/integrations/nochex_module_test.rb +9 -0
  165. data/test/unit/integrations/notifications/chronopay_notification_test.rb +66 -0
  166. data/test/unit/integrations/notifications/gestpay_notification_test.rb +60 -0
  167. data/test/unit/integrations/notifications/nochex_notification_test.rb +51 -0
  168. data/test/unit/integrations/notifications/notification_test.rb +41 -0
  169. data/test/unit/integrations/notifications/paypal_notification_test.rb +85 -0
  170. data/test/unit/integrations/notifications/two_checkout_notification_test.rb +55 -0
  171. data/test/unit/integrations/paypal_module_test.rb +24 -0
  172. data/test/unit/integrations/two_checkout_module_test.rb +9 -0
  173. data/test/unit/post_data_test.rb +55 -0
  174. data/test/unit/response_test.rb +14 -0
  175. data/test/unit/validateable_test.rb +56 -0
  176. metadata +160 -7
  177. metadata.gz.sig +0 -0
  178. data/lib/active_merchant/billing/gateways/payflow/f73e89fd.0 +0 -17
@@ -3,6 +3,11 @@ module ActiveMerchant #:nodoc:
3
3
  # Bogus Gateway
4
4
  class BogusGateway < Gateway
5
5
 
6
+ self.supported_countries = ['US']
7
+ self.supported_cardtypes = [:bogus]
8
+ self.homepage_url = 'http://example.com'
9
+ self.display_name = 'Bogus'
10
+
6
11
  def authorize(money, creditcard, options = {})
7
12
  case creditcard.number
8
13
  when '1'
@@ -68,12 +73,7 @@ module ActiveMerchant #:nodoc:
68
73
  raise Error, 'Bogus Gateway: Use trans_id 1 for success, 2 for exception and anything else for error'
69
74
  end
70
75
  end
71
-
72
- # We support visa and master card
73
- def self.supported_cardtypes
74
- [:bogus]
75
- end
76
-
76
+
77
77
  private
78
78
 
79
79
  def deal_with_cc(creditcard)
@@ -0,0 +1,191 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class BrainTreeGateway < Gateway
4
+ URL = 'https://secure.braintreepaymentgateway.com/api/transact.php'
5
+ attr_reader :url
6
+ attr_reader :response
7
+ attr_reader :options
8
+
9
+ self.supported_countries = ['US']
10
+ self.supported_cardtypes = [:visa, :master, :american_express]
11
+ self.homepage_url = 'http://www.braintreepaymentsolutions.com'
12
+ self.display_name = 'Braintree'
13
+
14
+ AVS_MESSAGES = {
15
+ "X" => "Exact match, 9-character numeric ZIP",
16
+ "Y" => "Exact match, 5-character numeric ZIP",
17
+ "D" => "Exact match, 5-character numeric ZIP",
18
+ "M" => "Exact match, 5-character numeric ZIP",
19
+ "A" => "Address match only",
20
+ "B" => "Address match only",
21
+ "W" => "9-character numeric ZIP match only",
22
+ "Z" => "5-character Zip match only",
23
+ "P" => "5-character Zip match only",
24
+ "L" => "5-character Zip match only",
25
+ "N" => "No address or ZIP match",
26
+ "C" => "No address or ZIP match",
27
+ "U" => "Address unavailable",
28
+ "G" => "Non-U.S. Issuer does not participate",
29
+ "I" => "Non-U.S. Issuer does not participate",
30
+ "R" => "Issuer system unavailable",
31
+ "E" => "Not a mail/phone order",
32
+ "S" => "Service not supported",
33
+ "0" => "AVS Not Available",
34
+ "O" => "AVS Not Available",
35
+ "B" => "AVS Not Available"
36
+ }
37
+
38
+ CARD_CODE_MESSAGES = {
39
+ "M" => "CVV2/CVC2 Match",
40
+ "N" => "CVV2/CVC2 No Match",
41
+ "P" => "Not Processed",
42
+ "S" => "Merchant has indicated that CVV2/CVC2 is not present on card",
43
+ "U" => "Issuer is not certified and/or has not provided Visa encryption keys"
44
+ }
45
+
46
+ def initialize(options = {})
47
+ requires!(options, :login, :password)
48
+ @options = options
49
+ super
50
+ end
51
+
52
+ def authorize(money, creditcard, options = {})
53
+ post = {}
54
+ add_invoice(post, options)
55
+ add_payment_source(post, creditcard,options)
56
+ add_address(post, creditcard, options)
57
+ add_customer_data(post, options)
58
+
59
+ commit('auth', money, post)
60
+ end
61
+
62
+ def purchase(money, creditcard, options = {})
63
+ post = {}
64
+ add_invoice(post, options)
65
+ add_payment_source(post, creditcard,options)
66
+ add_address(post, creditcard, options)
67
+ add_customer_data(post, options)
68
+
69
+ commit('sale', money, post)
70
+ end
71
+
72
+ def capture(money, authorization, options = {})
73
+ post ={}
74
+ post[:transactionid] = authorization
75
+ commit('capture', money, post)
76
+ end
77
+
78
+ private
79
+ def add_customer_data(post, options)
80
+ if options.has_key? :email
81
+ post[:email] = options[:email]
82
+ end
83
+
84
+ if options.has_key? :ip
85
+ post[:ipaddress] = options[:ip]
86
+ end
87
+ end
88
+
89
+ def add_address(post, creditcard, options)
90
+ if address = options[:billing_address] || options[:address]
91
+ post[:address1] = address[:address1].to_s
92
+ post[:address2] = address[:address2].to_s unless address[:address2].blank?
93
+ post[:company] = address[:company].to_s
94
+ post[:phone] = address[:phone].to_s
95
+ post[:zip] = address[:zip].to_s
96
+ post[:city] = address[:city].to_s
97
+ post[:country] = address[:country].to_s
98
+ post[:state] = address[:state].blank? ? 'n/a' : address[:state]
99
+ end
100
+ end
101
+
102
+ def add_invoice(post, options)
103
+ post[:orderid] = options[:order_id].to_s.gsub(/[^\w.]/, '')
104
+ end
105
+
106
+ def add_payment_source(params, source,options)
107
+ if source.is_a?(String)
108
+ add_customer_vault_id(params, source)
109
+ else
110
+ add_creditcard(params, source,options)
111
+ end
112
+ end
113
+
114
+ def add_customer_vault_id(params,vault_id)
115
+ params[:customer_vault_id] = vault_id
116
+ end
117
+
118
+ def add_creditcard(post, creditcard,options)
119
+ post[:customer_vault] = "add_customer" if options[:store]
120
+
121
+ post[:ccnumber] = creditcard.number
122
+ post[:cvv] = creditcard.verification_value if creditcard.verification_value?
123
+ post[:ccexp] = expdate(creditcard)
124
+ post[:firstname] = creditcard.first_name
125
+ post[:lastname] = creditcard.last_name
126
+ end
127
+
128
+ def parse(body)
129
+ results = {}
130
+ body.split(/&/).each do |pair|
131
+ key,val = pair.split(/=/)
132
+ results[key] = val
133
+ end
134
+ results[:card_code_message] = CARD_CODE_MESSAGES[results[:cvvresponse]] if results[:cvvresponse]
135
+ results[:avs_message] = AVS_MESSAGES[results["avsresponse"]] if results["avsresponse"]
136
+ results
137
+
138
+ end
139
+
140
+ def commit(action, money, parameters)
141
+ parameters[:amount] = amount(money) if money
142
+
143
+ if result = test_result_from_cc_number(parameters[:ccnumber])
144
+ return result
145
+ end
146
+
147
+ data = ssl_post URL, post_data(action,parameters)
148
+
149
+ @response = parse(data)
150
+
151
+ Response.new(@response["response"]=="1", message_from(@response), @response,
152
+ :authorization => @response["transactionid"],
153
+ :test => test?
154
+ )
155
+
156
+ end
157
+
158
+ def expdate(creditcard)
159
+ year = sprintf("%.4i", creditcard.year)
160
+ month = sprintf("%.2i", creditcard.month)
161
+
162
+ "#{month}#{year[-2..-1]}"
163
+ end
164
+
165
+
166
+ def message_from(response)
167
+ r=response["responsetext"]
168
+ case r
169
+ when "SUCCESS","Approved"
170
+ "This transaction has been approved"
171
+ when "DECLINE"
172
+ "This transaction has been declined"
173
+ else
174
+ r
175
+ end
176
+ end
177
+
178
+ def post_data(action, parameters = {})
179
+ post = {}
180
+ post[:username] = @options[:login]
181
+ post[:password] = @options[:password]
182
+ post[:type] = action
183
+
184
+ request = post.merge(parameters).map {|key,value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&")
185
+ request
186
+ end
187
+
188
+ end
189
+ end
190
+ end
191
+
@@ -0,0 +1,207 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ #
4
+ # CardStream supports the following credit cards, which are auto-detected by
5
+ # the gateway based on the card number used:
6
+ # * AM American Express
7
+ # * Diners Club
8
+ # * Electron
9
+ # * JCB
10
+ # * UK Maestro
11
+ # * Maestro International
12
+ # * Mastercard
13
+ # * Solo
14
+ # * Style
15
+ # * Switch
16
+ # * Visa Credit
17
+ # * Visa Debit
18
+ # * Visa Purchasing
19
+ #
20
+ class CardStreamGateway < Gateway
21
+ TEST_URL = 'https://www.cardstream.com/merchantsecure/Cardstream/VPDirect.cfm'
22
+ LIVE_URL = 'https://www.cardstream.com/merchantsecure/Cardstream/VPDirect.cfm'
23
+
24
+ self.money_format = :cents
25
+ self.default_currency = 'GBP'
26
+ self.supported_countries = ['GB']
27
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :maestro, :solo, :switch]
28
+ self.homepage_url = 'http://www.cardstream.com/'
29
+ self.display_name = 'CardStream'
30
+
31
+ APPROVED = '00'
32
+
33
+ CURRENCY_CODES = {
34
+ "AUD"=> '036',
35
+ "CAD"=> '124',
36
+ "CZK"=> '203',
37
+ "DKK"=> '208',
38
+ "HKD"=> '344',
39
+ "ICK"=> '352',
40
+ "JPY"=> '392',
41
+ "NOK"=> '578',
42
+ "SGD"=> '702',
43
+ "SEK"=> '752',
44
+ "CHF"=> '756',
45
+ "GBP"=> '826',
46
+ "USD"=> '840',
47
+ "EUR"=> '978'
48
+ }
49
+
50
+ TRANSACTIONS = {
51
+ :purchase => 'ESALE_KEYED',
52
+ :refund => 'EREFUND_KEYED',
53
+ :authorization => 'ESALE_KEYED'
54
+ }
55
+
56
+ POST_HEADERS = { 'Content-Type' => 'application/x-www-form-urlencoded' }
57
+
58
+ attr_reader :url
59
+ attr_reader :response
60
+ attr_reader :options
61
+
62
+ def initialize(options = {})
63
+ requires!(options, :login, :password)
64
+ @options = options
65
+ super
66
+ end
67
+
68
+ def purchase(money, credit_card, options = {})
69
+ requires!(options, :order_id)
70
+
71
+ post = {}
72
+
73
+ add_amount(post, money, options)
74
+ add_invoice(post, money, credit_card, options)
75
+ add_credit_card(post, credit_card)
76
+ add_address(post, options)
77
+ add_customer_data(post, options)
78
+
79
+ commit(:purchase, post)
80
+ end
81
+
82
+ private
83
+
84
+ def add_amount(post, money, options)
85
+ add_pair(post, :Amount, amount(money), :required => true)
86
+ add_pair(post, :CurrencyCode, currency_code(options[:currency] || currency(money)), :required => true)
87
+ end
88
+
89
+ def add_customer_data(post, options)
90
+ add_pair(post, :BillingEmail, options[:email])
91
+ add_pair(post, :BillingPhoneNumber, options[:phone])
92
+ end
93
+
94
+ def add_address(post, options)
95
+ address = options[:billing_address] || options[:address]
96
+
97
+ return if address.nil?
98
+
99
+ add_pair(post, :BillingStreet, address[:address1])
100
+ add_pair(post, :BillingHouseNumber, address[:address2])
101
+ add_pair(post, :BillingCity, address[:city])
102
+ add_pair(post, :BillingState, address[:state])
103
+ add_pair(post, :BillingPostCode, address[:zip])
104
+ end
105
+
106
+ def add_invoice(post, money, credit_card, options)
107
+ add_pair(post, :TransactionUnique, options[:order_id], :required => true)
108
+ add_pair(post, :OrderDesc, options[:description] || options[:order_id], :required => true)
109
+
110
+ if [ 'american_express', 'diners_club' ].include?(credit_card.type.to_s)
111
+ add_pair(post, :AEIT1Quantity, 1)
112
+ add_pair(post, :AEIT1Description, options[:description] || options[:order_id])
113
+ add_pair(post, :AEIT1GrossValue, amount(money))
114
+ end
115
+ end
116
+
117
+ def add_credit_card(post, credit_card)
118
+ add_pair(post, :CardName, credit_card.name, :required => true)
119
+ add_pair(post, :CardNumber, credit_card.number, :required => true)
120
+
121
+ add_pair(post, :ExpiryDateMM, format(credit_card.month, :two_digits), :required => true)
122
+ add_pair(post, :ExpiryDateYY, format(credit_card.year, :two_digits), :required => true)
123
+
124
+ if requires_start_date_or_issue_number?(credit_card)
125
+ add_pair(post, :StartDateMM, format(credit_card.start_month, :two_digits))
126
+ add_pair(post, :StartDateYY, format(credit_card.start_year, :two_digits))
127
+
128
+ add_pair(post, :IssueNumber, format_issue_number(credit_card))
129
+ end
130
+
131
+ add_pair(post, :CV2, credit_card.verification_value)
132
+ end
133
+
134
+ def format_issue_number(credit_card)
135
+ credit_card.type.to_s == 'solo' ? format(credit_card.issue_number, :two_digits) : credit_card.issue_number
136
+ end
137
+
138
+ def commit(action, parameters)
139
+ data = ssl_post(test? ? TEST_URL : LIVE_URL, post_data(action, parameters), POST_HEADERS)
140
+ @response = parse(data)
141
+
142
+ success = @response[:response_code] == APPROVED
143
+ message = message_from(@response)
144
+
145
+ Response.new(success, message, @response,
146
+ :test => test?,
147
+ :authorization => @response[:cross_reference]
148
+ )
149
+ end
150
+
151
+ def message_from(results)
152
+ results[:response_code] == APPROVED ? "APPROVED" : results[:message]
153
+ end
154
+
155
+ def post_data(action, parameters = {})
156
+ parameters.update(
157
+ :MerchantPassword => @options[:password],
158
+ :MerchantID => @options[:login],
159
+ :MessageType => TRANSACTIONS[action],
160
+ :CallBack => "disable",
161
+ :DuplicateDelay => "0",
162
+ :EchoCardType => "YES",
163
+ :EchoAmount => "YES",
164
+ :EchoAVSCV2ResponseCode => "YES",
165
+ :ReturnAVSCV2Message => "YES",
166
+ :CountryCode => '826' # 826 for UK based merchant
167
+ )
168
+
169
+ add_pair(parameters, :Dispatch, action == :authorization ? "LATER" : "NOW")
170
+
171
+ parameters.collect { |key, value| "VP#{key}=#{CGI.escape(value.to_s)}" }.join("&")
172
+ end
173
+
174
+ # VPCrossReference
175
+ # The value in VPCrossReference on a success transaction will contain
176
+ # a unique reference that you may use to run future transactions.
177
+ # Please note that cross reference transactions must come a static IP
178
+ # addressed that has been pre-registered with Cardstream. To
179
+ # register an IP address please send it to support@cardstream.com
180
+ # with your Cardstream issued merchant ID and it will be added to
181
+ # your account.
182
+ def parse(body)
183
+ result = {}
184
+ pairs = body.split("&")
185
+ pairs.each do |pair|
186
+ a = pair.split("=")
187
+ result[a[0].gsub(/^VP/,'').underscore.to_sym] = a[1]
188
+ end
189
+
190
+ result
191
+ end
192
+
193
+ def test?
194
+ @options[:test] || Base.gateway_mode == :test
195
+ end
196
+
197
+ def currency_code(currency)
198
+ CURRENCY_CODES[currency]
199
+ end
200
+
201
+ def add_pair(post, key, value, options = {})
202
+ post[key] = value if !value.blank? || options[:required]
203
+ end
204
+ end
205
+ end
206
+ end
207
+
@@ -0,0 +1,402 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ # See the remote and mocked unit test files for example usage. Pay special attention to the contents of the options hash.
4
+ #
5
+ # Initial setup instructions can be found in http://cybersource.com/support_center/implementation/downloads/soap_api/SOAP_toolkits.pdf
6
+ #
7
+ # Debugging
8
+ # If you experience an issue with this gateway be sure to examine the transaction information from a general transaction search inside the CyberSource Business
9
+ # Center for the full error messages including field names.
10
+ #
11
+ # Important Notes
12
+ # * AVS and CVV only work against the production server. You will always get back X for AVS and no response for CVV against the test server.
13
+ # * Nexus is the list of states or provinces where you have a physical presence. Nexus is used to calculate tax. Leave blank to tax everyone.
14
+ # * If you want to calculate VAT for overseas customers you must supply a registration number in the options hash as vat_reg_number.
15
+ # * productCode is a value in the line_items hash that is used to tell CyberSource what kind of item you are selling. It is used when calculating tax/VAT.
16
+ # * All transactions use dollar values.
17
+ class CyberSourceGateway < Gateway
18
+
19
+ attr_reader :url
20
+ attr_reader :response
21
+ attr_accessor :options
22
+
23
+ TEST_URL = 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor'
24
+ LIVE_URL = 'https://ics2ws.ic3.com/commerce/1.x/transactionProcessor'
25
+
26
+ # visa, master, american_express, discover
27
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
28
+ self.supported_countries = ['US']
29
+ self.default_currency = 'USD'
30
+ self.homepage_url = 'http://www.cybersource.com'
31
+ self.display_name = 'CyberSource'
32
+
33
+ # map credit card to the CyberSource expected representation
34
+ @@credit_card_codes = {
35
+ :visa => '001',
36
+ :master => '002',
37
+ :american_express => '003',
38
+ :discover => '004'
39
+ }
40
+
41
+ # map response codes to something humans can read
42
+ @@response_codes = {
43
+ :r100 => "Successful transaction",
44
+ :r101 => "Request is missing one or more required fields" ,
45
+ :r102 => "One or more fields contains invalid data",
46
+ :r150 => "General failure",
47
+ :r151 => "The request was received but a server time-out occurred",
48
+ :r152 => "The request was received, but a service timed out",
49
+ :r200 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the AVS check",
50
+ :r201 => "The issuing bank has questions about the request",
51
+ :r202 => "Expired card",
52
+ :r203 => "General decline of the card",
53
+ :r204 => "Insufficient funds in the account",
54
+ :r205 => "Stolen or lost card",
55
+ :r207 => "Issuing bank unavailable",
56
+ :r208 => "Inactive card or card not authorized for card-not-present transactions",
57
+ :r209 => "American Express Card Identifiction Digits (CID) did not match",
58
+ :r210 => "The card has reached the credit limit",
59
+ :r211 => "Invalid card verification number",
60
+ :r221 => "The customer matched an entry on the processor's negative file",
61
+ :r230 => "The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification check",
62
+ :r231 => "Invalid account number",
63
+ :r232 => "The card type is not accepted by the payment processor",
64
+ :r233 => "General decline by the processor",
65
+ :r234 => "A problem exists with your CyberSource merchant configuration",
66
+ :r235 => "The requested amount exceeds the originally authorized amount",
67
+ :r236 => "Processor failure",
68
+ :r237 => "The authorization has already been reversed",
69
+ :r238 => "The authorization has already been captured",
70
+ :r239 => "The requested transaction amount must match the previous transaction amount",
71
+ :r240 => "The card type sent is invalid or does not correlate with the credit card number",
72
+ :r241 => "The request ID is invalid",
73
+ :r242 => "You requested a capture, but there is no corresponding, unused authorization record.",
74
+ :r243 => "The transaction has already been settled or reversed",
75
+ :r244 => "The bank account number failed the validation check",
76
+ :r246 => "The capture or credit is not voidable because the capture or credit information has already been submitted to your processor",
77
+ :r247 => "You requested a credit for a capture that was previously voided",
78
+ :r250 => "The request was received, but a time-out occurred with the payment processor",
79
+ :r254 => "Your CyberSource account is prohibited from processing stand-alone refunds",
80
+ :r255 => "Your CyberSource account is not configured to process the service in the country you specified"
81
+ }
82
+
83
+ # These are the options that can be used when creating a new CyberSource Gateway object.
84
+ #
85
+ # :login => your username
86
+ #
87
+ # :password => the transaction key you generated in the Business Center
88
+ #
89
+ # :test => true sets the gateway to test mode
90
+ #
91
+ # :vat_reg_number => your VAT registration number
92
+ #
93
+ # :nexus => "WI CA QC" sets the states/provinces where you have a physical presense for tax purposes
94
+ #
95
+ # :ignore_avs => true don't want to use AVS so continue processing even if AVS would have failed
96
+ #
97
+ # :ignore_cvv => true don't want to use CVV so continue processing even if CVV would have failed
98
+ def initialize(options = {})
99
+ requires!(options, :login, :password)
100
+ @options = options
101
+ super
102
+ end
103
+
104
+ # Should run against the test servers or not?
105
+ def test?
106
+ @options[:test] || Base.gateway_mode == :test
107
+ end
108
+
109
+ # Request an authorization for an amount from CyberSource
110
+ #
111
+ # You must supply an :order_id in the options hash
112
+ def authorize(money, creditcard, options = {})
113
+ requires!(options, :order_id, :email)
114
+ setup_address_hash(options)
115
+ commit(build_auth_request(money, creditcard, options), options )
116
+ end
117
+
118
+ # Capture an authorization that has previously been requested
119
+ def capture(money, authorization, options = {})
120
+ setup_address_hash(options)
121
+ commit(build_capture_request(money, authorization, options), options)
122
+ end
123
+
124
+ # Purchase is an auth followed by a capture
125
+ # You must supply an order_id in the options hash
126
+ def purchase(money, creditcard, options = {})
127
+ requires!(options, :order_id, :email)
128
+ setup_address_hash(options)
129
+ commit(build_purchase_request(money, creditcard, options), options)
130
+ end
131
+
132
+ def void(identification, options = {})
133
+ commit(build_void_request(identification, options), options)
134
+ end
135
+
136
+ # CyberSource requires that you provide line item information for tax calculations
137
+ # If you do not have prices for each item or want to simplify the situation then pass in one fake line item that costs the subtotal of the order
138
+ #
139
+ # The line_item hash goes in the options hash and should look like
140
+ #
141
+ # :line_items => [
142
+ # {
143
+ # :declared_value => '1',
144
+ # :quantity => '2',
145
+ # :code => 'default',
146
+ # :description => 'Giant Walrus',
147
+ # :sku => 'WA323232323232323'
148
+ # },
149
+ # {
150
+ # :declared_value => '6',
151
+ # :quantity => '1',
152
+ # :code => 'default',
153
+ # :description => 'Marble Snowcone',
154
+ # :sku => 'FAKE1232132113123'
155
+ # }
156
+ # ]
157
+ #
158
+ # This functionality is only supported by this particular gateway may
159
+ # be changed at any time
160
+ def calculate_tax(creditcard, options)
161
+ requires!(options, :line_items)
162
+ setup_address_hash(options)
163
+ commit(build_tax_calculation_request(creditcard, options), options)
164
+ end
165
+
166
+ private
167
+ # Create all address hash key value pairs so that we still function if we were only provided with one or two of them
168
+ def setup_address_hash(options)
169
+ options[:billing_address] = options[:billing_address] || options[:address] || {}
170
+ options[:shipping_address] = options[:shipping_address] || options[:billing_address]
171
+ end
172
+
173
+ def build_auth_request(money, creditcard, options)
174
+ xml = Builder::XmlMarkup.new :indent => 2
175
+ add_address(xml, creditcard, options[:billing_address], options)
176
+ add_purchase_data(xml, money, true, options)
177
+ add_creditcard(xml, creditcard)
178
+ add_auth_service(xml)
179
+ add_business_rules_data(xml)
180
+ xml.target!
181
+ end
182
+
183
+ def build_tax_calculation_request(creditcard, options)
184
+ xml = Builder::XmlMarkup.new :indent => 2
185
+ add_address(xml, creditcard, options[:billing_address], options, false)
186
+ add_address(xml, creditcard, options[:shipping_address], options, true)
187
+ add_line_item_data(xml, options)
188
+ add_purchase_data(xml, 0, false, options)
189
+ add_tax_service(xml)
190
+ add_business_rules_data(xml)
191
+ xml.target!
192
+ end
193
+
194
+ def build_capture_request(money, authorization, options)
195
+ order_id, request_id, request_token = authorization.split(";")
196
+ options[:order_id] = order_id
197
+
198
+ xml = Builder::XmlMarkup.new :indent => 2
199
+ add_purchase_data(xml, money, true, options)
200
+ add_capture_service(xml, request_id, request_token)
201
+ add_business_rules_data(xml)
202
+ xml.target!
203
+ end
204
+
205
+ def build_purchase_request(money, creditcard, options)
206
+ xml = Builder::XmlMarkup.new :indent => 2
207
+ add_address(xml, creditcard, options[:billing_address], options)
208
+ add_purchase_data(xml, money, true, options)
209
+ add_creditcard(xml, creditcard)
210
+ add_purchase_service(xml, options)
211
+ add_business_rules_data(xml)
212
+ xml.target!
213
+ end
214
+
215
+ def build_void_request(identification, options)
216
+ order_id, request_id, request_token = identification.split(";")
217
+ options[:order_id] = order_id
218
+
219
+ xml = Builder::XmlMarkup.new :indent => 2
220
+ add_void_service(xml, request_id, request_token)
221
+ xml.target!
222
+ end
223
+
224
+ def add_business_rules_data(xml)
225
+ xml.tag! 'businessRules' do
226
+ xml.tag!('ignoreAVSResult', 'true') if @options[:ignore_avs]
227
+ xml.tag!('ignoreCVResult', 'true') if @options[:ignore_cvv]
228
+ end
229
+ end
230
+
231
+ def add_line_item_data(xml, options)
232
+ options[:line_items].each_with_index do |value, index|
233
+ xml.tag! 'item', {'id' => index} do
234
+ xml.tag! 'unitPrice', amount(value[:declared_value])
235
+ xml.tag! 'quantity', value[:quantity]
236
+ xml.tag! 'productCode', value[:code] || 'shipping_only'
237
+ xml.tag! 'productName', value[:description]
238
+ xml.tag! 'productSKU', value[:sku]
239
+ end
240
+ end
241
+ end
242
+
243
+ def add_merchant_data(xml, options)
244
+ xml.tag! 'merchantID', @options[:login]
245
+ xml.tag! 'merchantReferenceCode', options[:order_id]
246
+ xml.tag! 'clientLibrary' ,'Ruby Active Merchant'
247
+ xml.tag! 'clientLibraryVersion', '1.0'
248
+ xml.tag! 'clientEnvironment' , 'Linux'
249
+ end
250
+
251
+ def add_purchase_data(xml, money = 0, include_grand_total = false, options={})
252
+ xml.tag! 'purchaseTotals' do
253
+ xml.tag! 'currency', options[:currency] || currency(money)
254
+ xml.tag!('grandTotalAmount', amount(money)) if include_grand_total
255
+ end
256
+ end
257
+
258
+ def add_address(xml, creditcard, address, options, shipTo = false)
259
+ xml.tag! shipTo ? 'shipTo' : 'billTo' do
260
+ xml.tag! 'firstName', creditcard.first_name
261
+ xml.tag! 'lastName', creditcard.last_name
262
+ xml.tag! 'street1', address[:address1]
263
+ xml.tag! 'street2', address[:address2]
264
+ xml.tag! 'city', address[:city]
265
+ xml.tag! 'state', address[:state]
266
+ xml.tag! 'postalCode', address[:zip]
267
+ xml.tag! 'country', address[:country]
268
+ xml.tag! 'email', options[:email]
269
+ end
270
+ end
271
+
272
+ def add_creditcard(xml, creditcard)
273
+ xml.tag! 'card' do
274
+ xml.tag! 'accountNumber', creditcard.number
275
+ xml.tag! 'expirationMonth', format(creditcard.month, :two_digits)
276
+ xml.tag! 'expirationYear', format(creditcard.year, :four_digits)
277
+ xml.tag!('cvNumber', creditcard.verification_value) unless (@options[:ignore_cvv] || creditcard.verification_value.blank? )
278
+ xml.tag! 'cardType', @@credit_card_codes[creditcard.type.to_sym]
279
+ end
280
+ end
281
+
282
+ def add_tax_service(xml)
283
+ xml.tag! 'taxService', {'run' => 'true'} do
284
+ xml.tag!('nexus', @options[:nexus]) unless @options[:nexus].blank?
285
+ xml.tag!('sellerRegistration', @options[:vat_reg_number]) unless @options[:vat_reg_number].blank?
286
+ end
287
+ end
288
+
289
+ def add_auth_service(xml)
290
+ xml.tag! 'ccAuthService', {'run' => 'true'}
291
+ end
292
+
293
+ def add_capture_service(xml, request_id, request_token)
294
+ xml.tag! 'ccCaptureService', {'run' => 'true'} do
295
+ xml.tag! 'authRequestID', request_id
296
+ xml.tag! 'authRequestToken', request_token
297
+ end
298
+ end
299
+
300
+ def add_purchase_service(xml, options)
301
+ xml.tag! 'ccAuthService', {'run' => 'true'}
302
+ xml.tag! 'ccCaptureService', {'run' => 'true'}
303
+ end
304
+
305
+ def add_void_service(xml, request_id, request_token)
306
+ xml.tag! 'voidService', {'run' => 'true'} do
307
+ xml.tag! 'voidRequestID', request_id
308
+ xml.tag! 'voidRequestToken', request_token
309
+ end
310
+ end
311
+
312
+ # Where we actually build the full SOAP request using builder
313
+ def build_request(body, options)
314
+ xml = Builder::XmlMarkup.new :indent => 2
315
+ xml.instruct!
316
+ xml.tag! 's:Envelope', {'xmlns:s' => 'http://schemas.xmlsoap.org/soap/envelope/'} do
317
+ xml.tag! 's:Header' do
318
+ xml.tag! 'wsse:Security', {'s:mustUnderstand' => '1', 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'} do
319
+ xml.tag! 'wsse:UsernameToken' do
320
+ xml.tag! 'wsse:Username', @options[:login]
321
+ xml.tag! 'wsse:Password', @options[:password], 'Type' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText'
322
+ end
323
+ end
324
+ end
325
+ xml.tag! 's:Body', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do
326
+ xml.tag! 'requestMessage', {'xmlns' => 'urn:schemas-cybersource-com:transaction-data-1.26'} do
327
+ add_merchant_data(xml, options)
328
+ xml << body
329
+ end
330
+ end
331
+ end
332
+ xml.target!
333
+ end
334
+
335
+ # Contact CyberSource, make the SOAP request, and parse the reply into a Response object
336
+ def commit(request, options)
337
+ request_body = build_request(request, options)
338
+
339
+ if test?
340
+ card_number = parse_credit_card_number(request_body)
341
+ if result = test? && test_result_from_cc_number(card_number)
342
+ return result
343
+ end
344
+ end
345
+
346
+ url = test? ? TEST_URL : LIVE_URL
347
+ data = ssl_post(url, request_body)
348
+ reply = parse(data)
349
+
350
+ success = reply[:decision] == "ACCEPT"
351
+ message = @@response_codes[('r' + reply[:reasonCode]).to_sym] rescue reply[:message]
352
+ authorization = success ? [ options[:order_id], reply[:requestID], reply[:requestToken] ].compact.join(";") : nil
353
+
354
+ Response.new(success, message, reply,
355
+ :test => test?,
356
+ :authorization => authorization
357
+ )
358
+ end
359
+
360
+ def parse_credit_card_number(xml)
361
+ doc = REXML::Document.new(xml)
362
+ node = REXML::XPath.first(doc, '//card/accountNumber')
363
+ node && node.text
364
+ end
365
+
366
+ # Parse the SOAP response
367
+ # Technique inspired by the Paypal Gateway
368
+ def parse(xml)
369
+ reply = {}
370
+ xml = REXML::Document.new(xml)
371
+ if root = REXML::XPath.first(xml, "//c:replyMessage")
372
+ root.elements.to_a.each do |node|
373
+ case node.name
374
+ when 'c:reasonCode'
375
+ reply[:message] = reply(node.text)
376
+ else
377
+ parse_element(reply, node)
378
+ end
379
+ end
380
+ elsif root = REXML::XPath.first(xml, "//soap:Fault")
381
+ parse_element(reply, root)
382
+ reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}"
383
+ end
384
+ return reply
385
+ end
386
+
387
+ def parse_element(reply, node)
388
+ if node.has_elements?
389
+ node.elements.each{|e| parse_element(reply, e) }
390
+ else
391
+ if node.parent.name =~ /item/
392
+ parent = node.parent.name + (node.parent.attributes["id"] ? "_" + node.parent.attributes["id"] : '')
393
+ reply[(parent + '_' + node.name).to_sym] = node.text
394
+ else
395
+ reply[node.name.to_sym] = node.text
396
+ end
397
+ end
398
+ return reply
399
+ end
400
+ end
401
+ end
402
+ end