start_activemerchant 1.50.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +1769 -0
  3. data/CONTRIBUTORS +540 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +226 -0
  6. data/lib/active_merchant.rb +67 -0
  7. data/lib/active_merchant/billing.rb +15 -0
  8. data/lib/active_merchant/billing/apple_pay_payment_token.rb +22 -0
  9. data/lib/active_merchant/billing/avs_result.rb +98 -0
  10. data/lib/active_merchant/billing/base.rb +72 -0
  11. data/lib/active_merchant/billing/check.rb +76 -0
  12. data/lib/active_merchant/billing/compatibility.rb +120 -0
  13. data/lib/active_merchant/billing/credit_card.rb +404 -0
  14. data/lib/active_merchant/billing/credit_card_formatting.rb +24 -0
  15. data/lib/active_merchant/billing/credit_card_methods.rb +195 -0
  16. data/lib/active_merchant/billing/cvv_result.rb +38 -0
  17. data/lib/active_merchant/billing/gateway.rb +291 -0
  18. data/lib/active_merchant/billing/gateways.rb +14 -0
  19. data/lib/active_merchant/billing/gateways/allied_wallet.rb +203 -0
  20. data/lib/active_merchant/billing/gateways/app55.rb +176 -0
  21. data/lib/active_merchant/billing/gateways/authorize_net.rb +510 -0
  22. data/lib/active_merchant/billing/gateways/authorize_net_arb.rb +417 -0
  23. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +976 -0
  24. data/lib/active_merchant/billing/gateways/axcessms.rb +181 -0
  25. data/lib/active_merchant/billing/gateways/balanced.rb +256 -0
  26. data/lib/active_merchant/billing/gateways/bank_frick.rb +225 -0
  27. data/lib/active_merchant/billing/gateways/banwire.rb +105 -0
  28. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +314 -0
  29. data/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb +15 -0
  30. data/lib/active_merchant/billing/gateways/be2bill.rb +131 -0
  31. data/lib/active_merchant/billing/gateways/beanstream.rb +192 -0
  32. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +389 -0
  33. data/lib/active_merchant/billing/gateways/beanstream_interac.rb +58 -0
  34. data/lib/active_merchant/billing/gateways/blue_pay.rb +506 -0
  35. data/lib/active_merchant/billing/gateways/bogus.rb +140 -0
  36. data/lib/active_merchant/billing/gateways/borgun.rb +211 -0
  37. data/lib/active_merchant/billing/gateways/bpoint.rb +277 -0
  38. data/lib/active_merchant/billing/gateways/braintree.rb +19 -0
  39. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +9 -0
  40. data/lib/active_merchant/billing/gateways/braintree_blue.rb +574 -0
  41. data/lib/active_merchant/billing/gateways/braintree_orange.rb +20 -0
  42. data/lib/active_merchant/billing/gateways/bridge_pay.rb +189 -0
  43. data/lib/active_merchant/billing/gateways/card_save.rb +23 -0
  44. data/lib/active_merchant/billing/gateways/card_stream.rb +238 -0
  45. data/lib/active_merchant/billing/gateways/cashnet.rb +202 -0
  46. data/lib/active_merchant/billing/gateways/cc5.rb +201 -0
  47. data/lib/active_merchant/billing/gateways/cecabank.rb +229 -0
  48. data/lib/active_merchant/billing/gateways/cenpos.rb +262 -0
  49. data/lib/active_merchant/billing/gateways/certo_direct.rb +278 -0
  50. data/lib/active_merchant/billing/gateways/checkout.rb +216 -0
  51. data/lib/active_merchant/billing/gateways/checkout_v2.rb +200 -0
  52. data/lib/active_merchant/billing/gateways/commercegate.rb +143 -0
  53. data/lib/active_merchant/billing/gateways/conekta.rb +210 -0
  54. data/lib/active_merchant/billing/gateways/cyber_source.rb +720 -0
  55. data/lib/active_merchant/billing/gateways/data_cash.rb +600 -0
  56. data/lib/active_merchant/billing/gateways/dibs.rb +206 -0
  57. data/lib/active_merchant/billing/gateways/efsnet.rb +219 -0
  58. data/lib/active_merchant/billing/gateways/elavon.rb +348 -0
  59. data/lib/active_merchant/billing/gateways/epay.rb +274 -0
  60. data/lib/active_merchant/billing/gateways/evo_ca.rb +308 -0
  61. data/lib/active_merchant/billing/gateways/eway.rb +214 -0
  62. data/lib/active_merchant/billing/gateways/eway_managed.rb +291 -0
  63. data/lib/active_merchant/billing/gateways/eway_rapid.rb +522 -0
  64. data/lib/active_merchant/billing/gateways/exact.rb +227 -0
  65. data/lib/active_merchant/billing/gateways/ezic.rb +206 -0
  66. data/lib/active_merchant/billing/gateways/fat_zebra.rb +213 -0
  67. data/lib/active_merchant/billing/gateways/federated_canada.rb +160 -0
  68. data/lib/active_merchant/billing/gateways/finansbank.rb +23 -0
  69. data/lib/active_merchant/billing/gateways/first_giving.rb +143 -0
  70. data/lib/active_merchant/billing/gateways/first_pay.rb +160 -0
  71. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +413 -0
  72. data/lib/active_merchant/billing/gateways/flo2cash.rb +215 -0
  73. data/lib/active_merchant/billing/gateways/flo2cash_simple.rb +20 -0
  74. data/lib/active_merchant/billing/gateways/garanti.rb +261 -0
  75. data/lib/active_merchant/billing/gateways/global_transport.rb +179 -0
  76. data/lib/active_merchant/billing/gateways/hdfc.rb +207 -0
  77. data/lib/active_merchant/billing/gateways/hps.rb +287 -0
  78. data/lib/active_merchant/billing/gateways/iats_payments.rb +277 -0
  79. data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +246 -0
  80. data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +13 -0
  81. data/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +29 -0
  82. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +66 -0
  83. data/lib/active_merchant/billing/gateways/inspire.rb +219 -0
  84. data/lib/active_merchant/billing/gateways/instapay.rb +163 -0
  85. data/lib/active_merchant/billing/gateways/ipp.rb +175 -0
  86. data/lib/active_merchant/billing/gateways/iridium.rb +457 -0
  87. data/lib/active_merchant/billing/gateways/itransact.rb +448 -0
  88. data/lib/active_merchant/billing/gateways/jetpay.rb +275 -0
  89. data/lib/active_merchant/billing/gateways/linkpoint.rb +438 -0
  90. data/lib/active_merchant/billing/gateways/litle.rb +345 -0
  91. data/lib/active_merchant/billing/gateways/maxipago.rb +197 -0
  92. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +170 -0
  93. data/lib/active_merchant/billing/gateways/merchant_one.rb +114 -0
  94. data/lib/active_merchant/billing/gateways/merchant_partners.rb +245 -0
  95. data/lib/active_merchant/billing/gateways/merchant_ware.rb +319 -0
  96. data/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb +268 -0
  97. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +195 -0
  98. data/lib/active_merchant/billing/gateways/mercury.rb +326 -0
  99. data/lib/active_merchant/billing/gateways/metrics_global.rb +303 -0
  100. data/lib/active_merchant/billing/gateways/migs.rb +280 -0
  101. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
  102. data/lib/active_merchant/billing/gateways/modern_payments.rb +37 -0
  103. data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +219 -0
  104. data/lib/active_merchant/billing/gateways/monei.rb +307 -0
  105. data/lib/active_merchant/billing/gateways/moneris.rb +309 -0
  106. data/lib/active_merchant/billing/gateways/moneris_us.rb +298 -0
  107. data/lib/active_merchant/billing/gateways/money_movers.rb +152 -0
  108. data/lib/active_merchant/billing/gateways/nab_transact.rb +290 -0
  109. data/lib/active_merchant/billing/gateways/net_registry.rb +198 -0
  110. data/lib/active_merchant/billing/gateways/netaxept.rb +181 -0
  111. data/lib/active_merchant/billing/gateways/netbilling.rb +224 -0
  112. data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
  113. data/lib/active_merchant/billing/gateways/network_merchants.rb +242 -0
  114. data/lib/active_merchant/billing/gateways/nmi.rb +256 -0
  115. data/lib/active_merchant/billing/gateways/ogone.rb +435 -0
  116. data/lib/active_merchant/billing/gateways/omise.rb +319 -0
  117. data/lib/active_merchant/billing/gateways/openpay.rb +194 -0
  118. data/lib/active_merchant/billing/gateways/optimal_payment.rb +314 -0
  119. data/lib/active_merchant/billing/gateways/orbital.rb +834 -0
  120. data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +47 -0
  121. data/lib/active_merchant/billing/gateways/pac_net_raven.rb +207 -0
  122. data/lib/active_merchant/billing/gateways/pago_facil.rb +122 -0
  123. data/lib/active_merchant/billing/gateways/pay_conex.rb +246 -0
  124. data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +277 -0
  125. data/lib/active_merchant/billing/gateways/pay_hub.rb +213 -0
  126. data/lib/active_merchant/billing/gateways/pay_junction.rb +390 -0
  127. data/lib/active_merchant/billing/gateways/pay_secure.rb +112 -0
  128. data/lib/active_merchant/billing/gateways/paybox_direct.rb +188 -0
  129. data/lib/active_merchant/billing/gateways/payex.rb +412 -0
  130. data/lib/active_merchant/billing/gateways/payflow.rb +308 -0
  131. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +220 -0
  132. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +39 -0
  133. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +13 -0
  134. data/lib/active_merchant/billing/gateways/payflow_express.rb +224 -0
  135. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +15 -0
  136. data/lib/active_merchant/billing/gateways/payflow_uk.rb +21 -0
  137. data/lib/active_merchant/billing/gateways/payment_express.rb +353 -0
  138. data/lib/active_merchant/billing/gateways/paymill.rb +282 -0
  139. data/lib/active_merchant/billing/gateways/paypal.rb +129 -0
  140. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +679 -0
  141. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +65 -0
  142. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +262 -0
  143. data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
  144. data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +44 -0
  145. data/lib/active_merchant/billing/gateways/paypal_express.rb +264 -0
  146. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +30 -0
  147. data/lib/active_merchant/billing/gateways/payscout.rb +162 -0
  148. data/lib/active_merchant/billing/gateways/paystation.rb +199 -0
  149. data/lib/active_merchant/billing/gateways/payu_in.rb +247 -0
  150. data/lib/active_merchant/billing/gateways/payway.rb +207 -0
  151. data/lib/active_merchant/billing/gateways/pin.rb +207 -0
  152. data/lib/active_merchant/billing/gateways/plugnpay.rb +283 -0
  153. data/lib/active_merchant/billing/gateways/psigate.rb +216 -0
  154. data/lib/active_merchant/billing/gateways/psl_card.rb +303 -0
  155. data/lib/active_merchant/billing/gateways/qbms.rb +292 -0
  156. data/lib/active_merchant/billing/gateways/quantum.rb +276 -0
  157. data/lib/active_merchant/billing/gateways/quickbooks.rb +280 -0
  158. data/lib/active_merchant/billing/gateways/quickpay.rb +26 -0
  159. data/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb +188 -0
  160. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +240 -0
  161. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb +227 -0
  162. data/lib/active_merchant/billing/gateways/qvalent.rb +179 -0
  163. data/lib/active_merchant/billing/gateways/realex.rb +298 -0
  164. data/lib/active_merchant/billing/gateways/redsys.rb +406 -0
  165. data/lib/active_merchant/billing/gateways/s5.rb +226 -0
  166. data/lib/active_merchant/billing/gateways/sage.rb +173 -0
  167. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +89 -0
  168. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +115 -0
  169. data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +149 -0
  170. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +97 -0
  171. data/lib/active_merchant/billing/gateways/sage_pay.rb +399 -0
  172. data/lib/active_merchant/billing/gateways/sallie_mae.rb +143 -0
  173. data/lib/active_merchant/billing/gateways/secure_net.rb +263 -0
  174. data/lib/active_merchant/billing/gateways/secure_pay.rb +201 -0
  175. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +281 -0
  176. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +105 -0
  177. data/lib/active_merchant/billing/gateways/skip_jack.rb +451 -0
  178. data/lib/active_merchant/billing/gateways/smart_ps.rb +283 -0
  179. data/lib/active_merchant/billing/gateways/so_easy_pay.rb +194 -0
  180. data/lib/active_merchant/billing/gateways/spreedly_core.rb +247 -0
  181. data/lib/active_merchant/billing/gateways/stripe.rb +489 -0
  182. data/lib/active_merchant/billing/gateways/swipe_checkout.rb +157 -0
  183. data/lib/active_merchant/billing/gateways/tns.rb +227 -0
  184. data/lib/active_merchant/billing/gateways/trans_first.rb +126 -0
  185. data/lib/active_merchant/billing/gateways/transax.rb +23 -0
  186. data/lib/active_merchant/billing/gateways/transnational.rb +10 -0
  187. data/lib/active_merchant/billing/gateways/trust_commerce.rb +416 -0
  188. data/lib/active_merchant/billing/gateways/usa_epay.rb +25 -0
  189. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +1516 -0
  190. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +259 -0
  191. data/lib/active_merchant/billing/gateways/vanco.rb +280 -0
  192. data/lib/active_merchant/billing/gateways/verifi.rb +225 -0
  193. data/lib/active_merchant/billing/gateways/viaklix.rb +183 -0
  194. data/lib/active_merchant/billing/gateways/webpay.rb +97 -0
  195. data/lib/active_merchant/billing/gateways/wepay.rb +205 -0
  196. data/lib/active_merchant/billing/gateways/wirecard.rb +420 -0
  197. data/lib/active_merchant/billing/gateways/worldpay.rb +331 -0
  198. data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +204 -0
  199. data/lib/active_merchant/billing/gateways/worldpay_us.rb +181 -0
  200. data/lib/active_merchant/billing/model.rb +30 -0
  201. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +24 -0
  202. data/lib/active_merchant/billing/payment_token.rb +21 -0
  203. data/lib/active_merchant/billing/rails.rb +3 -0
  204. data/lib/active_merchant/billing/response.rb +92 -0
  205. data/lib/active_merchant/connection.rb +172 -0
  206. data/lib/active_merchant/country.rb +332 -0
  207. data/lib/active_merchant/empty.rb +20 -0
  208. data/lib/active_merchant/errors.rb +35 -0
  209. data/lib/active_merchant/network_connection_retries.rb +79 -0
  210. data/lib/active_merchant/post_data.rb +24 -0
  211. data/lib/active_merchant/posts_data.rb +84 -0
  212. data/lib/active_merchant/version.rb +3 -0
  213. data/lib/activemerchant.rb +1 -0
  214. data/lib/certs/cacert.pem +3866 -0
  215. data/lib/support/gateway_support.rb +71 -0
  216. data/lib/support/outbound_hosts.rb +28 -0
  217. data/lib/support/ssl_verify.rb +93 -0
  218. metadata +387 -0
@@ -0,0 +1,282 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class PaymillGateway < Gateway
4
+ self.supported_countries = %w(AD AT BE BG CH CY CZ DE DK EE ES FI FO FR GB
5
+ GI GR HR HU IE IL IM IS IT LI LT LU LV MC MT
6
+ NL NO PL PT RO SE SI SK TR VA)
7
+
8
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :union_pay, :jcb]
9
+ self.homepage_url = 'https://paymill.com'
10
+ self.display_name = 'PAYMILL'
11
+ self.money_format = :cents
12
+ self.default_currency = 'EUR'
13
+ self.live_url = "https://api.paymill.com/v2/"
14
+
15
+ def initialize(options = {})
16
+ requires!(options, :public_key, :private_key)
17
+ super
18
+ end
19
+
20
+ def purchase(money, payment_method, options = {})
21
+ action_with_token(:purchase, money, payment_method, options)
22
+ end
23
+
24
+ def authorize(money, payment_method, options = {})
25
+ action_with_token(:authorize, money, payment_method, options)
26
+ end
27
+
28
+ def capture(money, authorization, options = {})
29
+ post = {}
30
+
31
+ add_amount(post, money, options)
32
+ post[:preauthorization] = preauth(authorization)
33
+ post[:description] = options[:description]
34
+ post[:source] = 'active_merchant'
35
+ commit(:post, 'transactions', post)
36
+ end
37
+
38
+ def refund(money, authorization, options={})
39
+ post = {}
40
+
41
+ post[:amount] = amount(money)
42
+ post[:description] = options[:description]
43
+ commit(:post, "refunds/#{transaction_id(authorization)}", post)
44
+ end
45
+
46
+ def void(authorization, options={})
47
+ commit(:delete, "preauthorizations/#{preauth(authorization)}")
48
+ end
49
+
50
+ def store(credit_card, options={})
51
+ save_card(credit_card)
52
+ end
53
+
54
+ private
55
+
56
+ def add_credit_card(post, credit_card)
57
+ post['account.number'] = credit_card.number
58
+ post['account.expiry.month'] = sprintf("%.2i", credit_card.month)
59
+ post['account.expiry.year'] = sprintf("%.4i", credit_card.year)
60
+ post['account.verification'] = credit_card.verification_value
61
+ end
62
+
63
+ def headers
64
+ { 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:private_key]}:X").chomp) }
65
+ end
66
+
67
+ def commit(method, action, parameters=nil)
68
+ begin
69
+ raw_response = ssl_request(method, live_url + action, post_data(parameters), headers)
70
+ rescue ResponseError => e
71
+ begin
72
+ parsed = JSON.parse(e.response.body)
73
+ rescue JSON::ParserError
74
+ return Response.new(false, "Unable to parse error response: '#{e.response.body}'")
75
+ end
76
+ return Response.new(false, response_message(parsed), parsed, {})
77
+ end
78
+
79
+ response_from(raw_response)
80
+ end
81
+
82
+ def response_from(raw_response)
83
+ parsed = JSON.parse(raw_response)
84
+ options = {
85
+ :authorization => authorization_from(parsed),
86
+ :test => (parsed['mode'] == 'test'),
87
+ }
88
+
89
+ succeeded = (parsed['data'] == []) || (parsed['data']['response_code'].to_i == 20000)
90
+ Response.new(succeeded, response_message(parsed), parsed, options)
91
+ end
92
+
93
+ def authorization_from(parsed_response)
94
+ parsed_data = parsed_response['data']
95
+ return '' unless parsed_data.kind_of?(Hash)
96
+
97
+ [
98
+ parsed_data['id'],
99
+ parsed_data['preauthorization'].try(:[], 'id')
100
+ ].join(";")
101
+ end
102
+
103
+ def action_with_token(action, money, payment_method, options)
104
+ case payment_method
105
+ when String
106
+ self.send("#{action}_with_token", money, payment_method, options)
107
+ else
108
+ MultiResponse.run do |r|
109
+ r.process { save_card(payment_method) }
110
+ r.process { self.send("#{action}_with_token", money, r.authorization, options) }
111
+ end
112
+ end
113
+ end
114
+
115
+ def purchase_with_token(money, card_token, options)
116
+ post = {}
117
+
118
+ add_amount(post, money, options)
119
+ post[:token] = card_token
120
+ post[:description] = options[:description]
121
+ post[:source] = 'active_merchant'
122
+ commit(:post, 'transactions', post)
123
+ end
124
+
125
+ def authorize_with_token(money, card_token, options)
126
+ post = {}
127
+
128
+ add_amount(post, money, options)
129
+ post[:token] = card_token
130
+ post[:description] = options[:description]
131
+ post[:source] = 'active_merchant'
132
+ commit(:post, 'preauthorizations', post)
133
+ end
134
+
135
+ def save_card(credit_card)
136
+ post = {}
137
+
138
+ add_credit_card(post, credit_card)
139
+ post['channel.id'] = @options[:public_key]
140
+ post['jsonPFunction'] = 'jsonPFunction'
141
+ post['transaction.mode'] = (test? ? 'CONNECTOR_TEST' : 'LIVE')
142
+
143
+ begin
144
+ raw_response = ssl_request(:get, "#{save_card_url}?#{post_data(post)}", nil, {})
145
+ rescue ResponseError => e
146
+ return Response.new(false, e.response.body, e.response.body, {})
147
+ end
148
+
149
+ response_for_save_from(raw_response)
150
+ end
151
+
152
+ def response_for_save_from(raw_response)
153
+ options = { :test => test? }
154
+
155
+ parser = ResponseParser.new(raw_response, options)
156
+ parser.generate_response
157
+ end
158
+
159
+ def parse_reponse(response)
160
+ JSON.parse(response.sub(/jsonPFunction\(/, '').sub(/\)\z/, ''))
161
+ end
162
+
163
+ def save_card_url
164
+ (test? ? 'https://test-token.paymill.com' : 'https://token-v2.paymill.de')
165
+ end
166
+
167
+ def post_data(params)
168
+ return nil unless params
169
+
170
+ no_blanks = params.reject { |key, value| value.blank? }
171
+ no_blanks.map { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
172
+ end
173
+
174
+ def add_amount(post, money, options)
175
+ post[:amount] = amount(money)
176
+ post[:currency] = (options[:currency] || currency(money))
177
+ end
178
+
179
+ def preauth(authorization)
180
+ authorization.split(";").last
181
+ end
182
+
183
+ def transaction_id(authorization)
184
+ authorization.split(';').first
185
+ end
186
+
187
+ RESPONSE_CODES = {
188
+ 10001 => "General undefined response.",
189
+ 10002 => "Still waiting on something.",
190
+
191
+ 20000 => "General success response.",
192
+
193
+ 40000 => "General problem with data.",
194
+ 40001 => "General problem with payment data.",
195
+ 40100 => "Problem with credit card data.",
196
+ 40101 => "Problem with cvv.",
197
+ 40102 => "Card expired or not yet valid.",
198
+ 40103 => "Limit exceeded.",
199
+ 40104 => "Card invalid.",
200
+ 40105 => "Expiry date not valid.",
201
+ 40106 => "Credit card brand required.",
202
+ 40200 => "Problem with bank account data.",
203
+ 40201 => "Bank account data combination mismatch.",
204
+ 40202 => "User authentication failed.",
205
+ 40300 => "Problem with 3d secure data.",
206
+ 40301 => "Currency / amount mismatch",
207
+ 40400 => "Problem with input data.",
208
+ 40401 => "Amount too low or zero.",
209
+ 40402 => "Usage field too long.",
210
+ 40403 => "Currency not allowed.",
211
+
212
+ 50000 => "General problem with backend.",
213
+ 50001 => "Country blacklisted.",
214
+ 50100 => "Technical error with credit card.",
215
+ 50101 => "Error limit exceeded.",
216
+ 50102 => "Card declined by authorization system.",
217
+ 50103 => "Manipulation or stolen card.",
218
+ 50104 => "Card restricted.",
219
+ 50105 => "Invalid card configuration data.",
220
+ 50200 => "Technical error with bank account.",
221
+ 50201 => "Card blacklisted.",
222
+ 50300 => "Technical error with 3D secure.",
223
+ 50400 => "Decline because of risk issues.",
224
+ 50500 => "General timeout.",
225
+ 50501 => "Timeout on side of the acquirer.",
226
+ 50502 => "Risk management transaction timeout.",
227
+ 50600 => "Duplicate transaction."
228
+ }
229
+
230
+ def response_message(parsed_response)
231
+ return parsed_response["error"] if parsed_response["error"]
232
+ return "Transaction approved." if (parsed_response['data'] == [])
233
+
234
+ code = parsed_response["data"]["response_code"].to_i
235
+ RESPONSE_CODES[code] || code.to_s
236
+ end
237
+
238
+
239
+ class ResponseParser
240
+ attr_reader :raw_response, :parsed, :succeeded, :message, :options
241
+
242
+ def initialize(raw_response="", options={})
243
+ @raw_response = raw_response
244
+ @options = options
245
+ end
246
+
247
+ def generate_response
248
+ parse_response
249
+ if parsed['error']
250
+ handle_response_parse_error
251
+ else
252
+ handle_response_correct_parsing
253
+ end
254
+
255
+ Response.new(succeeded, message, parsed, options)
256
+ end
257
+
258
+ private
259
+
260
+ def parse_response
261
+ @parsed = JSON.parse(raw_response.sub(/jsonPFunction\(/, '').sub(/\)\z/, ''))
262
+ end
263
+
264
+ def handle_response_parse_error
265
+ @succeeded = false
266
+ @message = parsed['error']['message']
267
+ end
268
+
269
+ def handle_response_correct_parsing
270
+ @message = parsed['transaction']['processing']['return']['message']
271
+ if @succeeded = is_ack?
272
+ @options[:authorization] = parsed['transaction']['identification']['uniqueId']
273
+ end
274
+ end
275
+
276
+ def is_ack?
277
+ parsed['transaction']['processing']['result'] == 'ACK'
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,129 @@
1
+ require 'active_merchant/billing/gateways/paypal/paypal_common_api'
2
+ require 'active_merchant/billing/gateways/paypal/paypal_recurring_api'
3
+ require 'active_merchant/billing/gateways/paypal_express'
4
+
5
+ module ActiveMerchant #:nodoc:
6
+ module Billing #:nodoc:
7
+ class PaypalGateway < Gateway
8
+ include PaypalCommonAPI
9
+ include PaypalRecurringApi
10
+
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
12
+ self.supported_countries = ['US']
13
+ self.homepage_url = 'https://www.paypal.com/us/webapps/mpp/paypal-payments-pro'
14
+ self.display_name = 'PayPal Payments Pro (US)'
15
+
16
+ def authorize(money, credit_card_or_referenced_id, options = {})
17
+ requires!(options, :ip)
18
+ commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Authorization', money, credit_card_or_referenced_id, options)
19
+ end
20
+
21
+ def purchase(money, credit_card_or_referenced_id, options = {})
22
+ requires!(options, :ip)
23
+ commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Sale', money, credit_card_or_referenced_id, options)
24
+ end
25
+
26
+ def verify(credit_card, options = {})
27
+ if %w(visa master).include?(credit_card.brand)
28
+ authorize(0, credit_card, options)
29
+ else
30
+ MultiResponse.run(:use_first_response) do |r|
31
+ r.process { authorize(100, credit_card, options) }
32
+ r.process(:ignore_result) { void(r.authorization, options) }
33
+ end
34
+ end
35
+ end
36
+
37
+ def express
38
+ @express ||= PaypalExpressGateway.new(@options)
39
+ end
40
+
41
+ def supports_scrubbing?
42
+ true
43
+ end
44
+
45
+ def scrub(transcript)
46
+ transcript.
47
+ gsub(%r((<n1:Password>).+(</n1:Password>)), '\1[FILTERED]\2').
48
+ gsub(%r((<n1:Username>).+(</n1:Username>)), '\1[FILTERED]\2').
49
+ gsub(%r((<n2:CreditCardNumber>).+(</n2:CreditCardNumber)), '\1[FILTERED]\2').
50
+ gsub(%r((<n2:CVV2>).+(</n2:CVV2)), '\1[FILTERED]\2')
51
+ end
52
+
53
+ private
54
+
55
+ def define_transaction_type(transaction_arg)
56
+ if transaction_arg.is_a?(String)
57
+ return 'DoReferenceTransaction'
58
+ else
59
+ return 'DoDirectPayment'
60
+ end
61
+ end
62
+
63
+ def build_sale_or_authorization_request(action, money, credit_card_or_referenced_id, options)
64
+ transaction_type = define_transaction_type(credit_card_or_referenced_id)
65
+ reference_id = credit_card_or_referenced_id if transaction_type == "DoReferenceTransaction"
66
+
67
+ billing_address = options[:billing_address] || options[:address]
68
+ currency_code = options[:currency] || currency(money)
69
+
70
+ xml = Builder::XmlMarkup.new :indent => 2
71
+ xml.tag! transaction_type + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
72
+ xml.tag! transaction_type + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do
73
+ xml.tag! 'n2:Version', API_VERSION
74
+ xml.tag! 'n2:' + transaction_type + 'RequestDetails' do
75
+ xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction'
76
+ xml.tag! 'n2:PaymentAction', action
77
+ add_payment_details(xml, money, currency_code, options)
78
+ add_credit_card(xml, credit_card_or_referenced_id, billing_address, options) unless transaction_type == 'DoReferenceTransaction'
79
+ xml.tag! 'n2:IPAddress', options[:ip]
80
+ end
81
+ end
82
+ end
83
+
84
+ xml.target!
85
+ end
86
+
87
+ def add_credit_card(xml, credit_card, address, options)
88
+ xml.tag! 'n2:CreditCard' do
89
+ xml.tag! 'n2:CreditCardType', credit_card_type(card_brand(credit_card))
90
+ xml.tag! 'n2:CreditCardNumber', credit_card.number
91
+ xml.tag! 'n2:ExpMonth', format(credit_card.month, :two_digits)
92
+ xml.tag! 'n2:ExpYear', format(credit_card.year, :four_digits)
93
+ xml.tag! 'n2:CVV2', credit_card.verification_value
94
+
95
+ if [ 'switch', 'solo' ].include?(card_brand(credit_card).to_s)
96
+ xml.tag! 'n2:StartMonth', format(credit_card.start_month, :two_digits) unless credit_card.start_month.blank?
97
+ xml.tag! 'n2:StartYear', format(credit_card.start_year, :four_digits) unless credit_card.start_year.blank?
98
+ xml.tag! 'n2:IssueNumber', format(credit_card.issue_number, :two_digits) unless credit_card.issue_number.blank?
99
+ end
100
+
101
+ xml.tag! 'n2:CardOwner' do
102
+ xml.tag! 'n2:PayerName' do
103
+ xml.tag! 'n2:FirstName', credit_card.first_name
104
+ xml.tag! 'n2:LastName', credit_card.last_name
105
+ end
106
+
107
+ xml.tag! 'n2:Payer', options[:email]
108
+ add_address(xml, 'n2:Address', address)
109
+ end
110
+ end
111
+ end
112
+
113
+ def credit_card_type(type)
114
+ case type
115
+ when 'visa' then 'Visa'
116
+ when 'master' then 'MasterCard'
117
+ when 'discover' then 'Discover'
118
+ when 'american_express' then 'Amex'
119
+ when 'switch' then 'Switch'
120
+ when 'solo' then 'Solo'
121
+ end
122
+ end
123
+
124
+ def build_response(success, message, response, options = {})
125
+ Response.new(success, message, response, options)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,679 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ # This module is included in both PaypalGateway and PaypalExpressGateway
4
+ module PaypalCommonAPI
5
+ include Empty
6
+
7
+ API_VERSION = '72'
8
+
9
+ URLS = {
10
+ :test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',
11
+ :signature => 'https://api-3t.sandbox.paypal.com/2.0/' },
12
+ :live => { :certificate => 'https://api.paypal.com/2.0/',
13
+ :signature => 'https://api-3t.paypal.com/2.0/' }
14
+ }
15
+
16
+ PAYPAL_NAMESPACE = 'urn:ebay:api:PayPalAPI'
17
+ EBAY_NAMESPACE = 'urn:ebay:apis:eBLBaseComponents'
18
+
19
+ ENVELOPE_NAMESPACES = { 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
20
+ 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
21
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
22
+ }
23
+ CREDENTIALS_NAMESPACES = { 'xmlns' => PAYPAL_NAMESPACE,
24
+ 'xmlns:n1' => EBAY_NAMESPACE,
25
+ 'env:mustUnderstand' => '0'
26
+ }
27
+
28
+ AUSTRALIAN_STATES = {
29
+ 'ACT' => 'Australian Capital Territory',
30
+ 'NSW' => 'New South Wales',
31
+ 'NT' => 'Northern Territory',
32
+ 'QLD' => 'Queensland',
33
+ 'SA' => 'South Australia',
34
+ 'TAS' => 'Tasmania',
35
+ 'VIC' => 'Victoria',
36
+ 'WA' => 'Western Australia'
37
+ }
38
+
39
+ SUCCESS_CODES = [ 'Success', 'SuccessWithWarning' ]
40
+
41
+ FRAUD_REVIEW_CODE = "11610"
42
+
43
+ def self.included(base)
44
+ base.default_currency = 'USD'
45
+ base.cattr_accessor :pem_file
46
+ base.cattr_accessor :signature
47
+ base.live_url = URLS[:live][:signature]
48
+ base.test_url = URLS[:test][:signature]
49
+ end
50
+
51
+ # The gateway must be configured with either your PayPal PEM file
52
+ # or your PayPal API Signature. Only one is required.
53
+ #
54
+ # <tt>:pem</tt> The text of your PayPal PEM file. Note
55
+ # this is not the path to file, but its
56
+ # contents. If you are only using one PEM
57
+ # file on your site you can declare it
58
+ # globally and then you won't need to
59
+ # include this option
60
+ #
61
+ # <tt>:signature</tt> The text of your PayPal signature.
62
+ # If you are only using one API Signature
63
+ # on your site you can declare it
64
+ # globally and then you won't need to
65
+ # include this option
66
+ def initialize(options = {})
67
+ requires!(options, :login, :password)
68
+
69
+ headers = {'X-PP-AUTHORIZATION' => options.delete(:auth_signature), 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} if options[:auth_signature]
70
+ options = {
71
+ :pem => pem_file,
72
+ :signature => signature,
73
+ :headers => headers || {}
74
+ }.update(options)
75
+
76
+
77
+ if options[:pem].blank? && options[:signature].blank?
78
+ raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal"
79
+ end
80
+
81
+ super(options)
82
+ end
83
+
84
+ def reauthorize(money, authorization, options = {})
85
+ commit 'DoReauthorization', build_reauthorize_request(money, authorization, options)
86
+ end
87
+
88
+ def capture(money, authorization, options = {})
89
+ commit 'DoCapture', build_capture_request(money, authorization, options)
90
+ end
91
+
92
+ # Transfer money to one or more recipients.
93
+ #
94
+ # gateway.transfer 1000, 'bob@example.com',
95
+ # :subject => "The money I owe you", :note => "Sorry it's so late"
96
+ #
97
+ # gateway.transfer [1000, 'fred@example.com'],
98
+ # [2450, 'wilma@example.com', :note => 'You will receive another payment on 3/24'],
99
+ # [2000, 'barney@example.com'],
100
+ # :subject => "Your Earnings", :note => "Thanks for your business."
101
+ #
102
+ def transfer(*args)
103
+ commit 'MassPay', build_mass_pay_request(*args)
104
+ end
105
+
106
+ def void(authorization, options = {})
107
+ commit 'DoVoid', build_void_request(authorization, options)
108
+ end
109
+
110
+ # Refunds a transaction.
111
+ #
112
+ # For a full refund pass nil for the amount:
113
+ #
114
+ # gateway.refund nil, 'G39883289DH238'
115
+ #
116
+ # This will automatically make the :refund_type be "Full".
117
+ #
118
+ # For a partial refund just pass the amount as usual:
119
+ #
120
+ # gateway.refund 100, 'UBU83983N920'
121
+ #
122
+ def refund(money, identification, options = {})
123
+ commit 'RefundTransaction', build_refund_request(money, identification, options)
124
+ end
125
+
126
+ def credit(money, identification, options = {})
127
+ ActiveMerchant.deprecated Gateway::CREDIT_DEPRECATION_MESSAGE
128
+ refund(money, identification, options)
129
+ end
130
+
131
+ # ==== For full documentation see {Paypal API Reference:}[https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_soap_r_DoReferenceTransaction]
132
+ # ==== Parameter:
133
+ # * <tt>:money</tt> -- (Required) The amount of this new transaction,
134
+ # required fo the payment details portion of this request
135
+ #
136
+ # ==== Options:
137
+ # * <tt>:reference_id</tt> -- (Required) A transaction ID from a previous purchase, such as a credit card charge using the DoDirectPayment API, or a billing agreement ID.
138
+ # * <tt>:payment_action</tt> -- (Optional) How you want to obtain payment. It is one of the following values:
139
+ #
140
+ # Authorization – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture.
141
+ # Sale – This is a final sale for which you are requesting payment.
142
+ #
143
+ # * <tt>:ip_address</tt> -- (Optional) IP address of the buyer’s browser.
144
+ # Note: PayPal records this IP addresses as a means to detect possible fraud.
145
+ # * <tt>:req_confirm_shipping</tt> -- Whether you require that the buyer’s shipping address on file with PayPal be a confirmed address. You must have permission from PayPal to not require a confirmed address. It is one of the following values:
146
+ #
147
+ # 0 – You do not require that the buyer’s shipping address be a confirmed address.
148
+ # 1 – You require that the buyer’s shipping address be a confirmed address.
149
+ #
150
+ # * <tt>:merchant_session_id</tt> -- (Optional) Your buyer session identification token.
151
+ # * <tt>:return_fmf_details</tt> -- (Optional) Flag to indicate whether you want the results returned by Fraud Management Filters. By default, you do not receive this information. It is one of the following values:
152
+ #
153
+ # 0 – Do not receive FMF details (default)
154
+ # 1 – Receive FMF details
155
+ #
156
+ # * <tt>:soft_descriptor</tt> -- (Optional) Per transaction description of the payment that is passed to the consumer’s credit card statement. If the API request provides a value for the soft descriptor field, the full descriptor displayed on the buyer’s statement has the following format:
157
+ #
158
+ # <PP * | PAYPAL *><Merchant descriptor as set in the Payment Receiving Preferences><1 space><soft descriptor>
159
+ # The soft descriptor can contain only the following characters:
160
+ #
161
+ # Alphanumeric characters
162
+ # - (dash)
163
+ # * (asterisk)
164
+ # . (period)
165
+ # {space}
166
+ #
167
+ def reference_transaction(money, options = {})
168
+ requires!(options, :reference_id)
169
+ commit 'DoReferenceTransaction', build_reference_transaction_request(money, options)
170
+ end
171
+
172
+ def transaction_details(transaction_id)
173
+ commit 'GetTransactionDetails', build_get_transaction_details(transaction_id)
174
+ end
175
+
176
+ # ==== For full documentation see {PayPal API Reference}[https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_soap_r_TransactionSearch]
177
+ # ==== Options:
178
+ # * <tt>:payer </tt> -- (Optional) Search by the buyer’s email address.
179
+ # * <tt>:receipt_id </tt> -- (Optional) Search by the PayPal Account Optional receipt ID.
180
+ # * <tt>:receiver </tt> -- (Optional) Search by the receiver’s email address. If the merchant account has only one email address, this is the primary email. It can also be a non-primary email address.
181
+ # * <tt>:transaction_id</tt> -- (Optional) Search by the transaction ID. The returned results are from the merchant’s transaction records.
182
+ # * <tt>:invoice_id</tt> -- (Optional) Search by invoice identification key, as set by you for the original transaction. This field searches the records for items the merchant sells, not the items purchased.
183
+ # * <tt>:card_number </tt> -- (Optional) Search by credit card number, as set by you for the original transaction. This field searches the records for items the merchant sells, not the items purchased.
184
+ # * <tt>:auction_item_number </tt> -- (Optional) Search by auction item number of the purchased goods.
185
+ # * <tt>:transaction_class </tt> -- (Optional) Search by classification of transaction. Some kinds of possible classes of transactions are not searchable with this field. You cannot search for bank transfer withdrawals, for example. It is one of the following values:
186
+ # All – All transaction classifications
187
+ # Sent – Only payments sent
188
+ # Received – Only payments received
189
+ # MassPay – Only mass payments
190
+ # MoneyRequest – Only money requests
191
+ # FundsAdded – Only funds added to balance
192
+ # FundsWithdrawn – Only funds withdrawn from balance
193
+ # Referral – Only transactions involving referrals
194
+ # Fee – Only transactions involving fees
195
+ # Subscription – Only transactions involving subscriptions
196
+ # Dividend – Only transactions involving dividends
197
+ # Billpay – Only transactions involving BillPay Transactions
198
+ # Refund – Only transactions involving funds
199
+ # CurrencyConversions – Only transactions involving currency conversions
200
+ # BalanceTransfer – Only transactions involving balance transfers
201
+ # Reversal – Only transactions involving BillPay reversals
202
+ # Shipping – Only transactions involving UPS shipping fees
203
+ # BalanceAffecting – Only transactions that affect the account balance
204
+ # ECheck – Only transactions involving eCheck
205
+ #
206
+ # * <tt>:currency_code </tt> -- (Optional) Search by currency code.
207
+ # * <tt>:status</tt> -- (Optional) Search by transaction status. It is one of the following values:
208
+ # One of:
209
+ # Pending – The payment is pending. The specific reason the payment is pending is returned by the GetTransactionDetails API PendingReason field.
210
+ # Processing – The payment is being processed.
211
+ # Success – The payment has been completed and the funds have been added successfully to your account balance.
212
+ # Denied – You denied the payment. This happens only if the payment was previously pending.
213
+ # Reversed – A payment was reversed due to a chargeback or other type of reversal. The funds have been removed from your account balance and returned to the buyer.
214
+ #
215
+ def transaction_search(options)
216
+ requires!(options, :start_date)
217
+ commit 'TransactionSearch', build_transaction_search(options)
218
+ end
219
+
220
+ # ==== Parameters:
221
+ # * <tt>:return_all_currencies</tt> -- Either '1' or '0'
222
+ # 0 – Return only the balance for the primary currency holding.
223
+ # 1 – Return the balance for each currency holding.
224
+ #
225
+ def balance(return_all_currencies = false)
226
+ clean_currency_argument = case return_all_currencies
227
+ when 1, '1' , true; '1'
228
+ else
229
+ '0'
230
+ end
231
+ commit 'GetBalance', build_get_balance(clean_currency_argument)
232
+ end
233
+
234
+ # DoAuthorization takes the transaction_id returned when you call
235
+ # DoExpressCheckoutPayment with a PaymentAction of 'Order'.
236
+ # When you did that, you created an order authorization subject to settlement
237
+ # with PayPal DoAuthorization and DoCapture
238
+ #
239
+ # ==== Parameters:
240
+ # * <tt>:transaction_id</tt> -- The ID returned by DoExpressCheckoutPayment with a PaymentAction of 'Order'.
241
+ # * <tt>:money</tt> -- The amount of money to be authorized for this purchase.
242
+ #
243
+ def authorize_transaction(transaction_id, money, options = {})
244
+ commit 'DoAuthorization', build_do_authorize(transaction_id, money, options)
245
+ end
246
+
247
+ # The ManagePendingTransactionStatus API operation accepts or denies a
248
+ # pending transaction held by Fraud Management Filters.
249
+ #
250
+ # ==== Parameters:
251
+ # * <tt>:transaction_id</tt> -- The ID of the transaction held by Fraud Management Filters.
252
+ # * <tt>:action</tt> -- Either 'Accept' or 'Deny'
253
+ #
254
+ def manage_pending_transaction(transaction_id, action)
255
+ commit 'ManagePendingTransactionStatus', build_manage_pending_transaction_status(transaction_id, action)
256
+ end
257
+
258
+ private
259
+ def build_request_wrapper(action, options = {})
260
+ xml = Builder::XmlMarkup.new :indent => 2
261
+ xml.tag! action + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
262
+ xml.tag! action + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do
263
+ xml.tag! 'n2:Version', API_VERSION
264
+ if options[:request_details]
265
+ xml.tag! 'n2:' + action + 'RequestDetails' do
266
+ yield(xml)
267
+ end
268
+ else
269
+ yield(xml)
270
+ end
271
+ end
272
+ end
273
+ xml.target!
274
+ end
275
+
276
+ def build_do_authorize(transaction_id, money, options = {})
277
+ build_request_wrapper('DoAuthorization') do |xml|
278
+ xml.tag! 'TransactionID', transaction_id
279
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
280
+ end
281
+ end
282
+
283
+ def build_reauthorize_request(money, authorization, options)
284
+ xml = Builder::XmlMarkup.new
285
+
286
+ xml.tag! 'DoReauthorizationReq', 'xmlns' => PAYPAL_NAMESPACE do
287
+ xml.tag! 'DoReauthorizationRequest', 'xmlns:n2' => EBAY_NAMESPACE do
288
+ xml.tag! 'n2:Version', API_VERSION
289
+ xml.tag! 'AuthorizationID', authorization
290
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
291
+ end
292
+ end
293
+
294
+ xml.target!
295
+ end
296
+
297
+ def build_capture_request(money, authorization, options)
298
+ xml = Builder::XmlMarkup.new
299
+
300
+ xml.tag! 'DoCaptureReq', 'xmlns' => PAYPAL_NAMESPACE do
301
+ xml.tag! 'DoCaptureRequest', 'xmlns:n2' => EBAY_NAMESPACE do
302
+ xml.tag! 'n2:Version', API_VERSION
303
+ xml.tag! 'AuthorizationID', authorization
304
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
305
+ xml.tag! 'CompleteType', options[:complete_type] || 'Complete'
306
+ xml.tag! 'InvoiceID', options[:order_id] unless options[:order_id].blank?
307
+ xml.tag! 'Note', options[:description]
308
+ end
309
+ end
310
+
311
+ xml.target!
312
+ end
313
+
314
+ def build_refund_request(money, identification, options)
315
+ xml = Builder::XmlMarkup.new
316
+
317
+ xml.tag! 'RefundTransactionReq', 'xmlns' => PAYPAL_NAMESPACE do
318
+ xml.tag! 'RefundTransactionRequest', 'xmlns:n2' => EBAY_NAMESPACE do
319
+ xml.tag! 'n2:Version', API_VERSION
320
+ xml.tag! 'TransactionID', identification
321
+ xml.tag! 'Amount', amount(money), 'currencyID' => (options[:currency] || currency(money)) if money.present?
322
+ xml.tag! 'RefundType', (options[:refund_type] || (money.present? ? 'Partial' : 'Full'))
323
+ xml.tag! 'Memo', options[:note] unless options[:note].blank?
324
+ end
325
+ end
326
+
327
+ xml.target!
328
+ end
329
+
330
+ def build_void_request(authorization, options)
331
+ xml = Builder::XmlMarkup.new
332
+
333
+ xml.tag! 'DoVoidReq', 'xmlns' => PAYPAL_NAMESPACE do
334
+ xml.tag! 'DoVoidRequest', 'xmlns:n2' => EBAY_NAMESPACE do
335
+ xml.tag! 'n2:Version', API_VERSION
336
+ xml.tag! 'AuthorizationID', authorization
337
+ xml.tag! 'Note', options[:description]
338
+ end
339
+ end
340
+
341
+ xml.target!
342
+ end
343
+
344
+ def build_mass_pay_request(*args)
345
+ default_options = args.last.is_a?(Hash) ? args.pop : {}
346
+ recipients = args.first.is_a?(Array) ? args : [args]
347
+ receiver_type = default_options[:receiver_type]
348
+
349
+ xml = Builder::XmlMarkup.new
350
+
351
+ xml.tag! 'MassPayReq', 'xmlns' => PAYPAL_NAMESPACE do
352
+ xml.tag! 'MassPayRequest', 'xmlns:n2' => EBAY_NAMESPACE do
353
+ xml.tag! 'n2:Version', API_VERSION
354
+ xml.tag! 'EmailSubject', default_options[:subject] if default_options[:subject]
355
+ xml.tag! 'ReceiverType', receiver_type if receiver_type
356
+ recipients.each do |money, recipient, options|
357
+ options ||= default_options
358
+ xml.tag! 'MassPayItem' do
359
+ if(!receiver_type || receiver_type == 'EmailAddress')
360
+ xml.tag! 'ReceiverEmail', recipient
361
+ elsif receiver_type == 'UserID'
362
+ xml.tag! 'ReceiverID', recipient
363
+ else
364
+ raise ArgumentError.new("Unknown receiver_type: #{receiver_type}")
365
+ end
366
+ xml.tag! 'Amount', amount(money), 'currencyID' => (options[:currency] || currency(money))
367
+ xml.tag! 'Note', options[:note] if options[:note]
368
+ xml.tag! 'UniqueId', options[:unique_id] if options[:unique_id]
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ xml.target!
375
+ end
376
+
377
+ def build_manage_pending_transaction_status(transaction_id, action)
378
+ build_request_wrapper('ManagePendingTransactionStatus') do |xml|
379
+ xml.tag! 'TransactionID', transaction_id
380
+ xml.tag! 'Action', action
381
+ end
382
+ end
383
+
384
+ def build_reference_transaction_request(money, options)
385
+ opts = options.dup
386
+ opts[:ip_address] ||= opts[:ip]
387
+ currency_code = opts[:currency] || currency(money)
388
+ reference_transaction_optional_fields = %w{ n2:ReferenceID n2:PaymentAction
389
+ n2:PaymentType n2:IPAddress
390
+ n2:ReqConfirmShipping n2:MerchantSessionId
391
+ n2:ReturnFMFDetails n2:SoftDescriptor }
392
+ build_request_wrapper('DoReferenceTransaction', :request_details => true) do |xml|
393
+ add_optional_fields(xml, reference_transaction_optional_fields, opts)
394
+ add_payment_details(xml, money, currency_code, opts)
395
+ end
396
+ end
397
+
398
+ def build_get_transaction_details(transaction_id)
399
+ build_request_wrapper('GetTransactionDetails') do |xml|
400
+ xml.tag! 'TransactionID', transaction_id
401
+ end
402
+ end
403
+
404
+ def build_transaction_search(options)
405
+ currency_code = options[:currency_code]
406
+ currency_code ||= currency(options[:amount]) if options[:amount]
407
+ transaction_search_optional_fields = %w{ Payer ReceiptID Receiver
408
+ TransactionID InvoiceID CardNumber
409
+ AuctionItemNumber TransactionClass
410
+ CurrencyCode Status ProfileID }
411
+ build_request_wrapper('TransactionSearch') do |xml|
412
+ xml.tag! 'StartDate', date_to_iso(options[:start_date])
413
+ xml.tag! 'EndDate', date_to_iso(options[:end_date]) unless options[:end_date].blank?
414
+ add_optional_fields(xml, transaction_search_optional_fields, options)
415
+ xml.tag! 'Amount', localized_amount(options[:amount], currency_code), 'currencyID' => currency_code unless options[:amount].blank?
416
+ end
417
+ end
418
+
419
+
420
+ def build_get_balance(return_all_currencies)
421
+ build_request_wrapper('GetBalance') do |xml|
422
+ xml.tag! 'ReturnAllCurrencies', return_all_currencies unless return_all_currencies.nil?
423
+ end
424
+ end
425
+
426
+ def parse(action, xml)
427
+ legacy_hash = legacy_parse(action, xml)
428
+ xml = strip_attributes(xml)
429
+ hash = Hash.from_xml(xml)
430
+ hash = hash.fetch('Envelope').fetch('Body').fetch("#{action}Response")
431
+ hash = hash["#{action}ResponseDetails"] if hash["#{action}ResponseDetails"]
432
+
433
+ legacy_hash.merge(hash)
434
+ rescue IndexError
435
+ legacy_hash.merge(hash['Envelope']['Body'])
436
+ end
437
+
438
+ def strip_attributes(xml)
439
+ xml = REXML::Document.new(xml)
440
+ REXML::XPath.each(xml, '//SOAP-ENV:Envelope//*[@*]') do |el|
441
+ el.attributes.each_attribute { |a| a.remove }
442
+ end
443
+ xml.to_s
444
+ end
445
+
446
+ def legacy_parse(action, xml)
447
+ response = {}
448
+
449
+ error_messages = []
450
+ error_codes = []
451
+
452
+ xml = REXML::Document.new(xml)
453
+ if root = REXML::XPath.first(xml, "//#{action}Response")
454
+ root.elements.each do |node|
455
+ case node.name
456
+ when 'Errors'
457
+ short_message = nil
458
+ long_message = nil
459
+
460
+ node.elements.each do |child|
461
+ case child.name
462
+ when "LongMessage"
463
+ long_message = child.text unless child.text.blank?
464
+ when "ShortMessage"
465
+ short_message = child.text unless child.text.blank?
466
+ when "ErrorCode"
467
+ error_codes << child.text unless child.text.blank?
468
+ end
469
+ end
470
+
471
+ if message = long_message || short_message
472
+ error_messages << message
473
+ end
474
+ else
475
+ legacy_parse_element(response, node)
476
+ end
477
+ end
478
+ response[:message] = error_messages.uniq.join(". ") unless error_messages.empty?
479
+ response[:error_codes] = error_codes.uniq.join(",") unless error_codes.empty?
480
+ elsif root = REXML::XPath.first(xml, "//SOAP-ENV:Fault")
481
+ legacy_parse_element(response, root)
482
+ response[:message] = "#{response[:faultcode]}: #{response[:faultstring]} - #{response[:detail]}"
483
+ end
484
+
485
+ response
486
+ end
487
+
488
+ def legacy_parse_element(response, node)
489
+ if node.has_elements?
490
+ node.elements.each{|e| legacy_parse_element(response, e) }
491
+ else
492
+ response[node.name.underscore.to_sym] = node.text
493
+ node.attributes.each do |k, v|
494
+ response["#{node.name.underscore}_#{k.underscore}".to_sym] = v if k == 'currencyID'
495
+ end
496
+ end
497
+ end
498
+
499
+ def build_request(body)
500
+ xml = Builder::XmlMarkup.new
501
+
502
+ xml.instruct!
503
+ xml.tag! 'env:Envelope', ENVELOPE_NAMESPACES do
504
+ xml.tag! 'env:Header' do
505
+ add_credentials(xml) unless @options[:headers] && @options[:headers]['X-PP-AUTHORIZATION']
506
+ end
507
+
508
+ xml.tag! 'env:Body' do
509
+ xml << body
510
+ end
511
+ end
512
+ xml.target!
513
+ end
514
+
515
+ def add_credentials(xml)
516
+ xml.tag! 'RequesterCredentials', CREDENTIALS_NAMESPACES do
517
+ xml.tag! 'n1:Credentials' do
518
+ xml.tag! 'n1:Username', @options[:login]
519
+ xml.tag! 'n1:Password', @options[:password]
520
+ xml.tag! 'n1:Subject', @options[:subject]
521
+ xml.tag! 'n1:Signature', @options[:signature] unless @options[:signature].blank?
522
+ end
523
+ end
524
+ end
525
+
526
+ def add_address(xml, element, address)
527
+ return if address.nil?
528
+ xml.tag! element do
529
+ xml.tag! 'n2:Name', address[:name]
530
+ xml.tag! 'n2:Street1', address[:address1]
531
+ xml.tag! 'n2:Street2', address[:address2]
532
+ xml.tag! 'n2:CityName', address[:city]
533
+ xml.tag! 'n2:StateOrProvince', address[:state].blank? ? 'N/A' : address[:state]
534
+ xml.tag! 'n2:Country', address[:country]
535
+ xml.tag! 'n2:Phone', address[:phone] unless address[:phone].blank?
536
+ xml.tag! 'n2:PostalCode', address[:zip]
537
+ end
538
+ end
539
+
540
+ def add_payment_details_items_xml(xml, options, currency_code)
541
+ options[:items].each do |item|
542
+ xml.tag! 'n2:PaymentDetailsItem' do
543
+ xml.tag! 'n2:Name', item[:name]
544
+ xml.tag! 'n2:Number', item[:number]
545
+ xml.tag! 'n2:Quantity', item[:quantity]
546
+ if item[:amount]
547
+ xml.tag! 'n2:Amount', item_amount(item[:amount], currency_code), 'currencyID' => currency_code
548
+ end
549
+ xml.tag! 'n2:Description', item[:description]
550
+ xml.tag! 'n2:ItemURL', item[:url]
551
+ xml.tag! 'n2:ItemCategory', item[:category] if item[:category]
552
+ end
553
+ end
554
+ end
555
+
556
+ def add_payment_details(xml, money, currency_code, options = {})
557
+ xml.tag! 'n2:PaymentDetails' do
558
+ xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code
559
+
560
+ # All of the values must be included together and add up to the order total
561
+ if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
562
+ xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code
563
+ xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code),'currencyID' => currency_code
564
+ xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code),'currencyID' => currency_code
565
+ xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code
566
+ end
567
+
568
+ xml.tag! 'n2:InsuranceTotal', localized_amount(options[:insurance_total], currency_code),'currencyID' => currency_code unless options[:insurance_total].blank?
569
+ xml.tag! 'n2:ShippingDiscount', localized_amount(options[:shipping_discount], currency_code),'currencyID' => currency_code unless options[:shipping_discount].blank?
570
+ xml.tag! 'n2:InsuranceOptionOffered', options[:insurance_option_offered] if options.has_key?(:insurance_option_offered)
571
+
572
+ xml.tag! 'n2:OrderDescription', options[:description] unless options[:description].blank?
573
+
574
+ # Custom field Character length and limitations: 256 single-byte alphanumeric characters
575
+ xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank?
576
+
577
+ xml.tag! 'n2:InvoiceID', (options[:order_id] || options[:invoice_id]) unless (options[:order_id] || options[:invoice_id]).blank?
578
+ add_button_source(xml)
579
+
580
+ # The notify URL applies only to DoExpressCheckoutPayment.
581
+ # This value is ignored when set in SetExpressCheckout or GetExpressCheckoutDetails
582
+ xml.tag! 'n2:NotifyURL', options[:notify_url] unless options[:notify_url].blank?
583
+
584
+ add_address(xml, 'n2:ShipToAddress', options[:shipping_address]) unless options[:shipping_address].blank?
585
+
586
+ add_payment_details_items_xml(xml, options, currency_code) unless options[:items].blank?
587
+
588
+ add_express_only_payment_details(xml, options) if options[:express_request]
589
+
590
+ # Any value other than Y – This is not a recurring transaction
591
+ # To pass Y in this field, you must have established a billing agreement with
592
+ # the buyer specifying the amount, frequency, and duration of the recurring payment.
593
+ # requires version 80.0 of the API
594
+ xml.tag! 'n2:Recurring', options[:recurring] unless options[:recurring].blank?
595
+ end
596
+ end
597
+
598
+ def add_button_source(xml)
599
+ button_source = (@options[:button_source] || application_id)
600
+ if !empty?(button_source)
601
+ xml.tag! 'n2:ButtonSource', button_source.to_s.slice(0, 32)
602
+ end
603
+ end
604
+
605
+ def add_express_only_payment_details(xml, options = {})
606
+ add_optional_fields(xml,
607
+ %w{n2:NoteText n2:SoftDescriptor
608
+ n2:TransactionId n2:AllowedPaymentMethodType
609
+ n2:PaymentRequestID n2:PaymentAction},
610
+ options)
611
+ end
612
+
613
+ def add_optional_fields(xml, optional_fields, options = {})
614
+ optional_fields.each do |optional_text_field|
615
+ if optional_text_field =~ /(\w+:)(\w+)/
616
+ ns = $1
617
+ field = $2
618
+ field_as_symbol = field.underscore.to_sym
619
+ else
620
+ ns = ''
621
+ field = optional_text_field
622
+ field_as_symbol = optional_text_field.underscore.to_sym
623
+ end
624
+ xml.tag! ns + field, options[field_as_symbol] unless options[field_as_symbol].blank?
625
+ end
626
+ xml
627
+ end
628
+
629
+ def endpoint_url
630
+ URLS[test? ? :test : :live][@options[:signature].blank? ? :certificate : :signature]
631
+ end
632
+
633
+ def commit(action, request)
634
+ response = parse(action, ssl_post(endpoint_url, build_request(request), @options[:headers]))
635
+
636
+ build_response(successful?(response), message_from(response), response,
637
+ :test => test?,
638
+ :authorization => authorization_from(response),
639
+ :fraud_review => fraud_review?(response),
640
+ :avs_result => { :code => response[:avs_code] },
641
+ :cvv_result => response[:cvv2_code]
642
+ )
643
+ end
644
+
645
+ def fraud_review?(response)
646
+ response[:error_codes] == FRAUD_REVIEW_CODE
647
+ end
648
+
649
+ def authorization_from(response)
650
+ (
651
+ response[:transaction_id] ||
652
+ response[:authorization_id] ||
653
+ response[:refund_transaction_id] ||
654
+ response[:billing_agreement_id]
655
+ )
656
+ end
657
+
658
+ def successful?(response)
659
+ SUCCESS_CODES.include?(response[:ack])
660
+ end
661
+
662
+ def message_from(response)
663
+ response[:message] || response[:ack]
664
+ end
665
+
666
+ def date_to_iso(date)
667
+ (date.is_a?(Date) ? date.to_time : date).utc.iso8601
668
+ end
669
+
670
+ def item_amount(amount, currency_code)
671
+ if amount.to_i < 0 && Gateway.non_fractional_currency?(currency_code)
672
+ amount(amount).to_f.floor
673
+ else
674
+ localized_amount(amount, currency_code)
675
+ end
676
+ end
677
+ end
678
+ end
679
+ end