activemerchant 1.1.0 → 1.2.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 (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
@@ -1,13 +1,16 @@
1
1
  require File.dirname(__FILE__) + '/paypal/paypal_common_api'
2
+ require File.dirname(__FILE__) + '/paypal_express'
2
3
 
3
4
  module ActiveMerchant #:nodoc:
4
5
  module Billing #:nodoc:
5
6
  class PaypalGateway < Gateway
6
7
  include PaypalCommonAPI
7
8
 
8
- def self.supported_cardtypes
9
- [:visa, :master, :american_express, :discover]
10
- end
9
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
10
+ self.supported_countries = ['US']
11
+
12
+ self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside'
13
+ self.display_name = 'PayPal Website Payments Pro (US)'
11
14
 
12
15
  def authorize(money, credit_card, options = {})
13
16
  requires!(options, :ip)
@@ -36,6 +39,7 @@ module ActiveMerchant #:nodoc:
36
39
  private
37
40
  def build_sale_or_authorization_request(action, money, credit_card, options)
38
41
  shipping_address = options[:shipping_address] || options[:address]
42
+ currency_code = options[:currency] || currency(money)
39
43
 
40
44
  xml = Builder::XmlMarkup.new :indent => 2
41
45
  xml.tag! 'DoDirectPaymentReq', 'xmlns' => PAYPAL_NAMESPACE do
@@ -44,10 +48,20 @@ module ActiveMerchant #:nodoc:
44
48
  xml.tag! 'n2:DoDirectPaymentRequestDetails' do
45
49
  xml.tag! 'n2:PaymentAction', action
46
50
  xml.tag! 'n2:PaymentDetails' do
47
- xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency(money)
51
+ xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
52
+
53
+ # All of the values must be included together and add up to the order total
54
+ if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
55
+ xml.tag! 'n2:ItemTotal', amount(options[:subtotal]), 'currencyID' => currency_code
56
+ xml.tag! 'n2:ShippingTotal', amount(options[:shipping]),'currencyID' => currency_code
57
+ xml.tag! 'n2:HandlingTotal', amount(options[:handling]),'currencyID' => currency_code
58
+ xml.tag! 'n2:TaxTotal', amount(options[:tax]), 'currencyID' => currency_code
59
+ end
60
+
48
61
  xml.tag! 'n2:NotifyURL', options[:notify_url]
49
62
  xml.tag! 'n2:OrderDescription', options[:description]
50
63
  xml.tag! 'n2:InvoiceID', options[:order_id]
64
+ xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
51
65
 
52
66
  add_address(xml, 'n2:ShipToAddress', shipping_address)
53
67
  end
@@ -1,10 +1,12 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
- # This class is an abstract base class for both PaypalGateway and
4
- # PaypalExpressGateway
3
+ # This class is included in both PaypalGateway and PaypalExpressGateway
5
4
  module PaypalCommonAPI
6
5
  def self.included(base)
6
+
7
+ base.default_currency = 'USD'
7
8
  base.cattr_accessor :pem_file
9
+ base.cattr_accessor :signature
8
10
  end
9
11
 
10
12
  API_VERSION = '2.0'
@@ -33,18 +35,29 @@ module ActiveMerchant #:nodoc:
33
35
  'VIC' => 'Victoria',
34
36
  'WA' => 'Western Australia'
35
37
  }
36
-
38
+
39
+ # The gateway must be configured with either your PayPal PEM file
40
+ # or your PayPal API Signature. Only one is required.
41
+ #
37
42
  # <tt>:pem</tt> The text of your PayPal PEM file. Note
38
43
  # this is not the path to file, but its
39
44
  # contents. If you are only using one PEM
40
45
  # file on your site you can declare it
41
46
  # globally and then you won't need to
42
47
  # include this option
48
+ #
49
+ # <tt>:signature</tt> The text of your PayPal signature.
50
+ # If you are only using one API Signature
51
+ # on your site you can declare it
52
+ # globally and then you won't need to
53
+ # include this option
54
+
43
55
  def initialize(options = {})
44
56
  requires!(options, :login, :password)
45
57
 
46
58
  @options = {
47
- :pem => self.class.pem_file
59
+ :pem => pem_file,
60
+ :signature => signature
48
61
  }.update(options)
49
62
 
50
63
  super
@@ -92,7 +105,7 @@ module ActiveMerchant #:nodoc:
92
105
  xml.tag! 'DoReauthorizationRequest', 'xmlns:n2' => EBAY_NAMESPACE do
93
106
  xml.tag! 'n2:Version', API_VERSION
94
107
  xml.tag! 'AuthorizationID', authorization
95
- xml.tag! 'Amount', amount(money), 'currencyID' => currency(money)
108
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
96
109
  end
97
110
  end
98
111
 
@@ -106,7 +119,7 @@ module ActiveMerchant #:nodoc:
106
119
  xml.tag! 'DoCaptureRequest', 'xmlns:n2' => EBAY_NAMESPACE do
107
120
  xml.tag! 'n2:Version', API_VERSION
108
121
  xml.tag! 'AuthorizationID', authorization
109
- xml.tag! 'Amount', amount(money), 'currencyID' => currency(money)
122
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
110
123
  xml.tag! 'CompleteType', 'Complete'
111
124
  xml.tag! 'Note', options[:description]
112
125
  end
@@ -122,7 +135,7 @@ module ActiveMerchant #:nodoc:
122
135
  xml.tag! 'RefundTransactionRequest', 'xmlns:n2' => EBAY_NAMESPACE do
123
136
  xml.tag! 'n2:Version', API_VERSION
124
137
  xml.tag! 'TransactionID', identification
125
- xml.tag! 'Amount', amount(money), 'currencyID' => currency(money)
138
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
126
139
  xml.tag! 'RefundType', 'Partial'
127
140
  xml.tag! 'Memo', options[:note] unless options[:note].blank?
128
141
  end
@@ -159,7 +172,7 @@ module ActiveMerchant #:nodoc:
159
172
  options ||= default_options
160
173
  xml.tag! 'MassPayItem' do
161
174
  xml.tag! 'ReceiverEmail', recipient
162
- xml.tag! 'Amount', amount(money), 'currencyID' => currency(money)
175
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
163
176
  xml.tag! 'Note', options[:note] if options[:note]
164
177
  xml.tag! 'UniqueId', options[:unique_id] if options[:unique_id]
165
178
  end
@@ -234,6 +247,7 @@ module ActiveMerchant #:nodoc:
234
247
  xml.tag! 'Username', @options[:login]
235
248
  xml.tag! 'Password', @options[:password]
236
249
  xml.tag! 'Subject', @options[:subject]
250
+ xml.tag! 'Signature', @options[:signature] unless @options[:signature].blank?
237
251
  end
238
252
  end
239
253
  end
@@ -253,21 +267,23 @@ module ActiveMerchant #:nodoc:
253
267
  end
254
268
 
255
269
  def lookup_state(address)
256
- state = if address[:country].to_s.upcase == 'AU' || address[:country] == 'Australia'
270
+ country = Country.find(address[:country]) rescue nil
271
+ return '' if country.nil?
272
+
273
+ case country.code(:alpha2).to_s
274
+ when 'AU'
257
275
  AUSTRALIAN_STATES[address[:state]] || address[:state]
276
+ when 'GB'
277
+ address[:state].blank? ? 'N/A' : address[:state]
258
278
  else
259
279
  address[:state]
260
280
  end
261
281
  end
262
282
 
263
- def currency(money)
264
- money.respond_to?(:currency) ? money.currency : 'USD'
265
- end
266
-
267
283
  def commit(action, request)
268
284
  url = test? ? TEST_URL : LIVE_URL
285
+
269
286
  data = ssl_post(url, build_request(request))
270
-
271
287
  @response = parse(action, data)
272
288
 
273
289
  success = @response[:ack] == "Success"
@@ -280,4 +296,4 @@ module ActiveMerchant #:nodoc:
280
296
  end
281
297
  end
282
298
  end
283
- end
299
+ end
@@ -5,6 +5,10 @@ module ActiveMerchant #:nodoc:
5
5
  @params['payer']
6
6
  end
7
7
 
8
+ def name
9
+ [@params['first_name'], @params['middle_name'], @params['last_name']].compact.join(' ')
10
+ end
11
+
8
12
  def token
9
13
  @params['token']
10
14
  end
@@ -13,6 +17,10 @@ module ActiveMerchant #:nodoc:
13
17
  @params['payer_id']
14
18
  end
15
19
 
20
+ def payer_country
21
+ @params['payer_country']
22
+ end
23
+
16
24
  def address
17
25
  { 'name' => @params['name'],
18
26
  'company' => @params['payer_business'],
@@ -6,8 +6,13 @@ module ActiveMerchant #:nodoc:
6
6
  class PaypalExpressGateway < Gateway
7
7
  include PaypalCommonAPI
8
8
 
9
+ self.supported_countries = ['US']
10
+
9
11
  LIVE_REDIRECT_URL = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout&token='
10
12
  TEST_REDIRECT_URL = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='
13
+
14
+ self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
15
+ self.display_name = 'PayPal Express Checkout'
11
16
 
12
17
  def redirect_url
13
18
  test? ? TEST_REDIRECT_URL : LIVE_REDIRECT_URL
@@ -39,7 +44,7 @@ module ActiveMerchant #:nodoc:
39
44
  commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Authorization', money, options)
40
45
  end
41
46
 
42
- def purchase(money, token, payer_id, options = {})
47
+ def purchase(money, options = {})
43
48
  requires!(options, :token, :payer_id)
44
49
 
45
50
  commit 'DoExpressCheckoutPayment', build_sale_or_authorization_request('Sale', money, options)
@@ -59,6 +64,8 @@ module ActiveMerchant #:nodoc:
59
64
  end
60
65
 
61
66
  def build_sale_or_authorization_request(action, money, options)
67
+ currency_code = options[:currency] || currency(money)
68
+
62
69
  xml = Builder::XmlMarkup.new :indent => 2
63
70
  xml.tag! 'DoExpressCheckoutPaymentReq', 'xmlns' => PAYPAL_NAMESPACE do
64
71
  xml.tag! 'DoExpressCheckoutPaymentRequest', 'xmlns:n2' => EBAY_NAMESPACE do
@@ -68,8 +75,18 @@ module ActiveMerchant #:nodoc:
68
75
  xml.tag! 'n2:Token', options[:token]
69
76
  xml.tag! 'n2:PayerID', options[:payer_id]
70
77
  xml.tag! 'n2:PaymentDetails' do
71
- xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency(money)
78
+ xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
79
+
80
+ # All of the values must be included together and add up to the order total
81
+ if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
82
+ xml.tag! 'n2:ItemTotal', amount(options[:subtotal]), 'currencyID' => currency_code
83
+ xml.tag! 'n2:ShippingTotal', amount(options[:shipping]),'currencyID' => currency_code
84
+ xml.tag! 'n2:HandlingTotal', amount(options[:handling]),'currencyID' => currency_code
85
+ xml.tag! 'n2:TaxTotal', amount(options[:tax]), 'currencyID' => currency_code
86
+ end
87
+
72
88
  xml.tag! 'n2:NotifyURL', options[:notify_url]
89
+ xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
73
90
  end
74
91
  end
75
92
  end
@@ -85,12 +102,13 @@ module ActiveMerchant #:nodoc:
85
102
  xml.tag! 'n2:Version', API_VERSION
86
103
  xml.tag! 'n2:SetExpressCheckoutRequestDetails' do
87
104
  xml.tag! 'n2:PaymentAction', action
88
- xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency(money)
89
- xml.tag! 'n2:MaxAmount', amount(options[:max_amount]), 'currencyID' => currency(options[:max_amount]) if options[:max_amount]
90
- add_address(xml, 'n2:Address', options[:billing_address] || options[:address])
91
- #xml.tag! 'n2:AddressOverride', 1
92
- #xml.tag! 'n2:NoShipping', 1
93
- xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank?
105
+ xml.tag! 'n2:OrderTotal', amount(money).to_f.zero? ? amount(100) : amount(money), 'currencyID' => options[:currency] || currency(money)
106
+ if options[:max_amount]
107
+ xml.tag! 'n2:MaxAmount', amount(options[:max_amount]), 'currencyID' => options[:currency] || currency(options[:max_amount])
108
+ end
109
+ add_address(xml, 'n2:Address', options[:shipping_address] || options[:address])
110
+ xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0'
111
+ xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0'
94
112
  xml.tag! 'n2:ReturnURL', options[:return_url]
95
113
  xml.tag! 'n2:CancelURL', options[:cancel_return_url]
96
114
  xml.tag! 'n2:IPAddress', options[:ip]
@@ -98,6 +116,13 @@ module ActiveMerchant #:nodoc:
98
116
  xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank?
99
117
  xml.tag! 'n2:InvoiceID', options[:order_id]
100
118
 
119
+ # Customization of the payment page
120
+ xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank?
121
+ xml.tag! 'n2:cpp-image-header', options[:header_image] unless options[:header_image].blank?
122
+ xml.tag! 'n2:cpp-header-back-color', options[:header_background_color] unless options[:header_background_color].blank?
123
+ xml.tag! 'n2:cpp-header-border-color', options[:header_border_color] unless options[:header_border_color].blank?
124
+ xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank?
125
+
101
126
  # This should be set to the same locale as the shop
102
127
  # localeCode - String
103
128
  end
@@ -0,0 +1,300 @@
1
+ module ActiveMerchant
2
+ module Billing
3
+
4
+ class PlugnpayGateway < Gateway
5
+ class PlugnpayPostData < PostData
6
+ # Fields that will be sent even if they are blank
7
+ self.required_fields = [ :publisher_name, :publisher_password,
8
+ :card_amount, :card_name, :card_number, :card_exp, :orderID ]
9
+ end
10
+
11
+ URL = 'https://pay1.plugnpay.com/payment/pnpremote.cgi'
12
+
13
+ CARD_CODE_MESSAGES = {
14
+ "M" => "Card verification number matched",
15
+ "N" => "Card verification number didn't match",
16
+ "P" => "Card verification number was not processed",
17
+ "S" => "Card verification number should be on card but was not indicated",
18
+ "U" => "Issuer was not certified for card verification"
19
+ }
20
+
21
+ CARD_CODE_ERRORS = %w( N S )
22
+
23
+ AVS_MESSAGES = {
24
+ "A" => "Street address matches billing information, zip/postal code does not",
25
+ "B" => "Address information not provided for address verification check",
26
+ "E" => "Address verification service error",
27
+ "G" => "Non-U.S. card-issuing bank",
28
+ "N" => "Neither street address nor zip/postal match billing information",
29
+ "P" => "Address verification not applicable for this transaction",
30
+ "R" => "Payment gateway was unavailable or timed out",
31
+ "S" => "Address verification service not supported by issuer",
32
+ "U" => "Address information is unavailable",
33
+ "W" => "9-digit zip/postal code matches billing information, street address does not",
34
+ "X" => "Street address and 9-digit zip/postal code matches billing information",
35
+ "Y" => "Street address and 5-digit zip/postal code matches billing information",
36
+ "Z" => "5-digit zip/postal code matches billing information, street address does not",
37
+ }
38
+
39
+ AVS_ERRORS = %w( A E N R W Z )
40
+
41
+ PAYMENT_GATEWAY_RESPONSES = {
42
+ "P01" => "AVS Mismatch Failure",
43
+ "P02" => "CVV2 Mismatch Failure",
44
+ "P21" => "Transaction may not be marked",
45
+ "P30" => "Test Tran. Bad Card",
46
+ "P35" => "Test Tran. Problem",
47
+ "P40" => "Username already exists",
48
+ "P41" => "Username is blank",
49
+ "P50" => "Fraud Screen Failure",
50
+ "P51" => "Missing PIN Code",
51
+ "P52" => "Invalid Bank Acct. No.",
52
+ "P53" => "Invalid Bank Routing No.",
53
+ "P54" => "Invalid/Missing Check No.",
54
+ "P55" => "Invalid Credit Card No.",
55
+ "P56" => "Invalid CVV2/CVC2 No.",
56
+ "P57" => "Expired. CC Exp. Date",
57
+ "P58" => "Missing Data",
58
+ "P59" => "Missing Email Address",
59
+ "P60" => "Zip Code does not match Billing State.",
60
+ "P61" => "Invalid Billing Zip Code",
61
+ "P62" => "Zip Code does not match Shipping State.",
62
+ "P63" => "Invalid Shipping Zip Code",
63
+ "P64" => "Invalid Credit Card CVV2/CVC2 Format.",
64
+ "P65" => "Maximum number of attempts has been exceeded.",
65
+ "P66" => "Credit Card number has been flagged and can not be used to access this service.",
66
+ "P67" => "IP Address is on Blocked List.",
67
+ "P68" => "Billing country does not match ipaddress country.",
68
+ "P69" => "US based ipaddresses are currently blocked.",
69
+ "P70" => "Credit Cards issued from this bank are currently not being accepted.",
70
+ "P71" => "Credit Cards issued from this bank are currently not being accepted.",
71
+ "P72" => "Daily volume exceeded.",
72
+ "P73" => "Too many transactions within allotted time.",
73
+ "P91" => "Missing/incorrect password",
74
+ "P92" => "Account not configured for mobil administration",
75
+ "P93" => "IP Not registered to username.",
76
+ "P94" => "Mode not permitted for this account.",
77
+ "P95" => "Currently Blank",
78
+ "P96" => "Currently Blank",
79
+ "P97" => "Processor not responding",
80
+ "P98" => "Missing merchant/publisher name",
81
+ "P99" => "Currently Blank"
82
+ }
83
+
84
+ TRANSACTIONS = {
85
+ :authorization => 'auth',
86
+ :purchase => 'auth',
87
+ :capture => 'mark',
88
+ :void => 'void',
89
+ :refund => 'return',
90
+ :credit => 'newreturn'
91
+ }
92
+
93
+ SUCCESS_CODES = [ 'pending', 'success' ]
94
+ FAILURE_CODES = [ 'badcard', 'fraud' ]
95
+
96
+ # URL
97
+ attr_reader :response
98
+ attr_reader :options
99
+
100
+ self.default_currency = 'USD'
101
+ self.supported_countries = ['US']
102
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
103
+ self.homepage_url = 'http://www.plugnpay.com/'
104
+ self.display_name = "Plug'n Pay"
105
+
106
+ def initialize(options = {})
107
+ requires!(options, :login, :password)
108
+ @options = options
109
+ super
110
+ end
111
+
112
+ def purchase(money, creditcard, options = {})
113
+ post = PlugnpayPostData.new
114
+
115
+ add_amount(post, money, options)
116
+ add_creditcard(post, creditcard)
117
+ add_addresses(post, options)
118
+ add_invoice_data(post, options)
119
+ add_customer_data(post, options)
120
+
121
+ post[:authtype] = 'authpostauth'
122
+ commit(:authorization, post)
123
+ end
124
+
125
+ def authorize(money, creditcard, options = {})
126
+ post = PlugnpayPostData.new
127
+
128
+ add_amount(post, money, options)
129
+ add_creditcard(post, creditcard)
130
+ add_addresses(post, options)
131
+ add_invoice_data(post, options)
132
+ add_customer_data(post, options)
133
+
134
+ post[:authtype] = 'authonly'
135
+ commit(:authorization, post)
136
+ end
137
+
138
+ def capture(money, authorization, options = {})
139
+ post = PlugnpayPostData.new
140
+
141
+ post[:orderID] = authorization
142
+
143
+ add_amount(post, money, options)
144
+ add_customer_data(post, options)
145
+
146
+ commit(:capture, post)
147
+ end
148
+
149
+ def void(authorization, options = {})
150
+ post = PlugnpayPostData.new
151
+
152
+ post[:orderID] = authorization
153
+ post[:txn_type] = 'auth'
154
+
155
+ commit(:void, post)
156
+ end
157
+
158
+ def credit(money, identification_or_creditcard, options = {})
159
+ post = PlugnpayPostData.new
160
+ add_amount(post, money, options)
161
+
162
+ if identification_or_creditcard.is_a?(String)
163
+ post[:orderID] = identification_or_creditcard
164
+
165
+ commit(:refund, post)
166
+ else
167
+ add_creditcard(post, identification_or_creditcard)
168
+ add_addresses(post, options)
169
+ add_customer_data(post, options)
170
+
171
+ commit(:credit, post)
172
+ end
173
+ end
174
+
175
+ private
176
+ def commit(action, post)
177
+ if result = test_result_from_cc_number(post[:card_number])
178
+ return result
179
+ end
180
+
181
+ data = ssl_post(URL, post_data(action, post))
182
+
183
+ @response = parse(data)
184
+
185
+ success = SUCCESS_CODES.include?(@response[:finalstatus])
186
+ message = success ? 'Success' : message_from(@response)
187
+
188
+ Response.new(success, message, @response,
189
+ :test => test?,
190
+ :authorization => @response[:orderid]
191
+ )
192
+ end
193
+
194
+ def parse(body)
195
+ body = CGI.unescape(body)
196
+ results = {}
197
+ body.split('&').collect { |e| e.split('=') }.each do |key,value|
198
+ results[key.downcase.to_sym] = normalize(value.to_s.strip)
199
+ end
200
+
201
+ results.delete(:publisher_password)
202
+ results[:avs_message] = AVS_MESSAGES[results[:avs_code]] if results[:avs_code]
203
+ results[:card_code_message] = CARD_CODE_MESSAGES[results[:cvvresp]] if results[:cvvresp]
204
+
205
+ results
206
+ end
207
+
208
+ def post_data(action, post)
209
+ post[:mode] = TRANSACTIONS[action]
210
+ post[:convert] = 'underscores'
211
+ post[:app_level] = 0
212
+ post[:publisher_name] = @options[:login]
213
+ post[:publisher_password] = @options[:password]
214
+
215
+ post.to_s
216
+ end
217
+
218
+ def add_creditcard(post, creditcard)
219
+ post[:card_number] = creditcard.number
220
+ post[:card_cvv] = creditcard.verification_value
221
+ post[:card_exp] = expdate(creditcard)
222
+ post[:card_name] = creditcard.name.slice(0..38)
223
+ end
224
+
225
+ def add_customer_data(post, options)
226
+ post[:email] = options[:email]
227
+ post[:dontsndmail] = 'yes' unless options[:send_email_confirmation]
228
+ post[:ipaddress] = options[:ip]
229
+ end
230
+
231
+ def add_invoice_data(post, options)
232
+ post[:shipping] = amount(options[:shipping]) unless options[:shipping].blank?
233
+ post[:tax] = amount(options[:tax]) unless options[:tax].blank?
234
+ end
235
+
236
+ def add_addresses(post, options)
237
+ if address = options[:billing_address] || options[:address]
238
+ post[:card_address1] = address[:address1]
239
+ post[:card_zip] = address[:zip]
240
+ post[:card_city] = address[:city]
241
+ post[:card_country] = address[:country]
242
+ post[:phone] = address[:phone]
243
+
244
+ case address[:country]
245
+ when 'US', 'CA'
246
+ post[:card_state] = address[:state]
247
+ else
248
+ post[:card_state] = 'ZZ'
249
+ post[:card_prov] = address[:state]
250
+ end
251
+ end
252
+
253
+ if shipping_address = options[:shipping_address] || address
254
+ post[:shipname] = shipping_address[:name]
255
+ post[:address1] = shipping_address[:address1]
256
+ post[:address2] = shipping_address[:address2]
257
+ post[:city] = shipping_address[:city]
258
+
259
+ case shipping_address[:country]
260
+ when 'US', 'CA'
261
+ post[:state] = shipping_address[:state]
262
+ else
263
+ post[:state] = 'ZZ'
264
+ post[:province] = shipping_address[:state]
265
+ end
266
+
267
+ post[:country] = shipping_address[:country]
268
+ post[:zip] = shipping_address[:zip]
269
+ end
270
+ end
271
+
272
+ def add_amount(post, money, options)
273
+ post[:card_amount] = amount(money)
274
+ post[:currency] = options[:currency] || currency(money)
275
+ end
276
+
277
+ # Make a ruby type out of the response string
278
+ def normalize(field)
279
+ case field
280
+ when "true" then true
281
+ when "false" then false
282
+ when "" then nil
283
+ when "null" then nil
284
+ else field
285
+ end
286
+ end
287
+
288
+ def message_from(results)
289
+ PAYMENT_GATEWAY_RESPONSES[results[:resp_code]]
290
+ end
291
+
292
+ def expdate(creditcard)
293
+ year = sprintf("%.4i", creditcard.year)
294
+ month = sprintf("%.2i", creditcard.month)
295
+
296
+ "#{month}/#{year[-2..-1]}"
297
+ end
298
+ end
299
+ end
300
+ end