aktivemerchant 2.0.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 (193) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +1596 -0
  3. data/CONTRIBUTORS +511 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +18 -0
  6. data/lib/active_merchant.rb +108 -0
  7. data/lib/active_merchant/billing.rb +13 -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 +352 -0
  14. data/lib/active_merchant/billing/credit_card_formatting.rb +24 -0
  15. data/lib/active_merchant/billing/credit_card_methods.rb +160 -0
  16. data/lib/active_merchant/billing/cvv_result.rb +38 -0
  17. data/lib/active_merchant/billing/gateway.rb +268 -0
  18. data/lib/active_merchant/billing/gateways.rb +17 -0
  19. data/lib/active_merchant/billing/gateways/adyen.rb +209 -0
  20. data/lib/active_merchant/billing/gateways/alfabank.rb +117 -0
  21. data/lib/active_merchant/billing/gateways/app55.rb +176 -0
  22. data/lib/active_merchant/billing/gateways/authorize_net.rb +419 -0
  23. data/lib/active_merchant/billing/gateways/authorize_net_arb.rb +417 -0
  24. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +976 -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 +188 -0
  32. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +393 -0
  33. data/lib/active_merchant/billing/gateways/beanstream_interac.rb +54 -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 +210 -0
  37. data/lib/active_merchant/billing/gateways/braintree.rb +19 -0
  38. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +9 -0
  39. data/lib/active_merchant/billing/gateways/braintree_blue.rb +515 -0
  40. data/lib/active_merchant/billing/gateways/braintree_orange.rb +20 -0
  41. data/lib/active_merchant/billing/gateways/bridge_pay.rb +189 -0
  42. data/lib/active_merchant/billing/gateways/card_save.rb +23 -0
  43. data/lib/active_merchant/billing/gateways/card_stream.rb +220 -0
  44. data/lib/active_merchant/billing/gateways/cashnet.rb +191 -0
  45. data/lib/active_merchant/billing/gateways/cc5.rb +201 -0
  46. data/lib/active_merchant/billing/gateways/cecabank.rb +229 -0
  47. data/lib/active_merchant/billing/gateways/certo_direct.rb +278 -0
  48. data/lib/active_merchant/billing/gateways/checkout.rb +213 -0
  49. data/lib/active_merchant/billing/gateways/commercegate.rb +143 -0
  50. data/lib/active_merchant/billing/gateways/conekta.rb +209 -0
  51. data/lib/active_merchant/billing/gateways/cyber_source.rb +709 -0
  52. data/lib/active_merchant/billing/gateways/data_cash.rb +600 -0
  53. data/lib/active_merchant/billing/gateways/efsnet.rb +219 -0
  54. data/lib/active_merchant/billing/gateways/elavon.rb +348 -0
  55. data/lib/active_merchant/billing/gateways/epay.rb +275 -0
  56. data/lib/active_merchant/billing/gateways/evo_ca.rb +308 -0
  57. data/lib/active_merchant/billing/gateways/eway.rb +214 -0
  58. data/lib/active_merchant/billing/gateways/eway_managed.rb +291 -0
  59. data/lib/active_merchant/billing/gateways/eway_rapid.rb +524 -0
  60. data/lib/active_merchant/billing/gateways/exact.rb +218 -0
  61. data/lib/active_merchant/billing/gateways/fat_zebra.rb +173 -0
  62. data/lib/active_merchant/billing/gateways/federated_canada.rb +160 -0
  63. data/lib/active_merchant/billing/gateways/finansbank.rb +23 -0
  64. data/lib/active_merchant/billing/gateways/first_giving.rb +143 -0
  65. data/lib/active_merchant/billing/gateways/first_pay.rb +160 -0
  66. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +355 -0
  67. data/lib/active_merchant/billing/gateways/garanti.rb +257 -0
  68. data/lib/active_merchant/billing/gateways/global_transport.rb +183 -0
  69. data/lib/active_merchant/billing/gateways/hdfc.rb +207 -0
  70. data/lib/active_merchant/billing/gateways/hps.rb +288 -0
  71. data/lib/active_merchant/billing/gateways/iats_payments.rb +251 -0
  72. data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +246 -0
  73. data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +13 -0
  74. data/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +29 -0
  75. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +66 -0
  76. data/lib/active_merchant/billing/gateways/inspire.rb +213 -0
  77. data/lib/active_merchant/billing/gateways/instapay.rb +163 -0
  78. data/lib/active_merchant/billing/gateways/iridium.rb +457 -0
  79. data/lib/active_merchant/billing/gateways/itransact.rb +448 -0
  80. data/lib/active_merchant/billing/gateways/jetpay.rb +275 -0
  81. data/lib/active_merchant/billing/gateways/linkpoint.rb +438 -0
  82. data/lib/active_merchant/billing/gateways/litle.rb +346 -0
  83. data/lib/active_merchant/billing/gateways/maxipago.rb +197 -0
  84. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +170 -0
  85. data/lib/active_merchant/billing/gateways/merchant_one.rb +114 -0
  86. data/lib/active_merchant/billing/gateways/merchant_ware.rb +319 -0
  87. data/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb +268 -0
  88. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +195 -0
  89. data/lib/active_merchant/billing/gateways/mercury.rb +333 -0
  90. data/lib/active_merchant/billing/gateways/metrics_global.rb +303 -0
  91. data/lib/active_merchant/billing/gateways/migs.rb +265 -0
  92. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
  93. data/lib/active_merchant/billing/gateways/modern_payments.rb +37 -0
  94. data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +219 -0
  95. data/lib/active_merchant/billing/gateways/moneris.rb +309 -0
  96. data/lib/active_merchant/billing/gateways/moneris_us.rb +291 -0
  97. data/lib/active_merchant/billing/gateways/money_movers.rb +152 -0
  98. data/lib/active_merchant/billing/gateways/nab_transact.rb +280 -0
  99. data/lib/active_merchant/billing/gateways/net_registry.rb +198 -0
  100. data/lib/active_merchant/billing/gateways/netaxept.rb +181 -0
  101. data/lib/active_merchant/billing/gateways/netbilling.rb +190 -0
  102. data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
  103. data/lib/active_merchant/billing/gateways/network_merchants.rb +242 -0
  104. data/lib/active_merchant/billing/gateways/nmi.rb +256 -0
  105. data/lib/active_merchant/billing/gateways/ogone.rb +435 -0
  106. data/lib/active_merchant/billing/gateways/openpay.rb +194 -0
  107. data/lib/active_merchant/billing/gateways/optimal_payment.rb +313 -0
  108. data/lib/active_merchant/billing/gateways/orbital.rb +803 -0
  109. data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +47 -0
  110. data/lib/active_merchant/billing/gateways/pac_net_raven.rb +207 -0
  111. data/lib/active_merchant/billing/gateways/pago_facil.rb +122 -0
  112. data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +261 -0
  113. data/lib/active_merchant/billing/gateways/pay_junction.rb +390 -0
  114. data/lib/active_merchant/billing/gateways/pay_secure.rb +112 -0
  115. data/lib/active_merchant/billing/gateways/pay_u_latam.rb +462 -0
  116. data/lib/active_merchant/billing/gateways/paybox_direct.rb +188 -0
  117. data/lib/active_merchant/billing/gateways/payex.rb +412 -0
  118. data/lib/active_merchant/billing/gateways/payflow.rb +304 -0
  119. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +209 -0
  120. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +39 -0
  121. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +13 -0
  122. data/lib/active_merchant/billing/gateways/payflow_express.rb +224 -0
  123. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +15 -0
  124. data/lib/active_merchant/billing/gateways/payflow_uk.rb +21 -0
  125. data/lib/active_merchant/billing/gateways/payment_express.rb +353 -0
  126. data/lib/active_merchant/billing/gateways/paymill.rb +281 -0
  127. data/lib/active_merchant/billing/gateways/paypal.rb +117 -0
  128. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +670 -0
  129. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +65 -0
  130. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +262 -0
  131. data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
  132. data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +44 -0
  133. data/lib/active_merchant/billing/gateways/paypal_express.rb +264 -0
  134. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +30 -0
  135. data/lib/active_merchant/billing/gateways/payscout.rb +162 -0
  136. data/lib/active_merchant/billing/gateways/paystation.rb +199 -0
  137. data/lib/active_merchant/billing/gateways/payway.rb +207 -0
  138. data/lib/active_merchant/billing/gateways/pin.rb +197 -0
  139. data/lib/active_merchant/billing/gateways/plugnpay.rb +283 -0
  140. data/lib/active_merchant/billing/gateways/psigate.rb +216 -0
  141. data/lib/active_merchant/billing/gateways/psl_card.rb +303 -0
  142. data/lib/active_merchant/billing/gateways/qbms.rb +292 -0
  143. data/lib/active_merchant/billing/gateways/quantum.rb +276 -0
  144. data/lib/active_merchant/billing/gateways/quickpay.rb +367 -0
  145. data/lib/active_merchant/billing/gateways/realex.rb +298 -0
  146. data/lib/active_merchant/billing/gateways/redsys.rb +391 -0
  147. data/lib/active_merchant/billing/gateways/sage.rb +175 -0
  148. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +87 -0
  149. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +114 -0
  150. data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +149 -0
  151. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +102 -0
  152. data/lib/active_merchant/billing/gateways/sage_pay.rb +398 -0
  153. data/lib/active_merchant/billing/gateways/sallie_mae.rb +143 -0
  154. data/lib/active_merchant/billing/gateways/secure_net.rb +252 -0
  155. data/lib/active_merchant/billing/gateways/secure_pay.rb +201 -0
  156. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +281 -0
  157. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +105 -0
  158. data/lib/active_merchant/billing/gateways/skip_jack.rb +452 -0
  159. data/lib/active_merchant/billing/gateways/smart_ps.rb +283 -0
  160. data/lib/active_merchant/billing/gateways/so_easy_pay.rb +194 -0
  161. data/lib/active_merchant/billing/gateways/spreedly_core.rb +247 -0
  162. data/lib/active_merchant/billing/gateways/stripe.rb +411 -0
  163. data/lib/active_merchant/billing/gateways/swipe_checkout.rb +157 -0
  164. data/lib/active_merchant/billing/gateways/tns.rb +227 -0
  165. data/lib/active_merchant/billing/gateways/trans_first.rb +126 -0
  166. data/lib/active_merchant/billing/gateways/transax.rb +23 -0
  167. data/lib/active_merchant/billing/gateways/transnational.rb +10 -0
  168. data/lib/active_merchant/billing/gateways/trust_commerce.rb +416 -0
  169. data/lib/active_merchant/billing/gateways/usa_epay.rb +25 -0
  170. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +1516 -0
  171. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +254 -0
  172. data/lib/active_merchant/billing/gateways/verifi.rb +225 -0
  173. data/lib/active_merchant/billing/gateways/viaklix.rb +183 -0
  174. data/lib/active_merchant/billing/gateways/vindicia.rb +385 -0
  175. data/lib/active_merchant/billing/gateways/webpay.rb +97 -0
  176. data/lib/active_merchant/billing/gateways/wepay.rb +189 -0
  177. data/lib/active_merchant/billing/gateways/wirecard.rb +421 -0
  178. data/lib/active_merchant/billing/gateways/worldpay.rb +331 -0
  179. data/lib/active_merchant/billing/gateways/worldpay_us.rb +181 -0
  180. data/lib/active_merchant/billing/model.rb +30 -0
  181. data/lib/active_merchant/billing/payment_token.rb +21 -0
  182. data/lib/active_merchant/billing/rails.rb +3 -0
  183. data/lib/active_merchant/billing/response.rb +91 -0
  184. data/lib/active_merchant/country.rb +332 -0
  185. data/lib/active_merchant/empty.rb +20 -0
  186. data/lib/active_merchant/errors.rb +29 -0
  187. data/lib/active_merchant/offsite_payments_shim.rb +19 -0
  188. data/lib/active_merchant/version.rb +3 -0
  189. data/lib/activemerchant.rb +1 -0
  190. data/lib/support/gateway_support.rb +71 -0
  191. data/lib/support/outbound_hosts.rb +25 -0
  192. data/lib/support/ssl_verify.rb +93 -0
  193. metadata +400 -0
@@ -0,0 +1,247 @@
1
+ require 'nokogiri'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ # Public: This gateway allows you to interact with any gateway you've
6
+ # created in Spreedly (https://spreedly.com). It's an adapter which can be
7
+ # particularly useful if you already have code interacting with
8
+ # ActiveMerchant and want to easily take advantage of Spreedly's vault.
9
+ class SpreedlyCoreGateway < Gateway
10
+ self.live_url = 'https://core.spreedly.com/v1'
11
+
12
+ self.supported_countries = %w(AD AE AT AU BD BE BG BN CA CH CY CZ DE DK EE EG ES FI FR GB
13
+ GI GR HK HU ID IE IL IM IN IS IT JO KW LB LI LK LT LU LV MC
14
+ MT MU MV MX MY NL NO NZ OM PH PL PT QA RO SA SE SG SI SK SM
15
+ TR TT UM US VA VN ZA)
16
+
17
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
18
+ self.homepage_url = 'https://spreedly.com'
19
+ self.display_name = 'Spreedly'
20
+ self.money_format = :cents
21
+ self.default_currency = 'USD'
22
+
23
+ # Public: Create a new Spreedly gateway.
24
+ #
25
+ # options - A hash of options:
26
+ # :login - The environment key.
27
+ # :password - The access secret.
28
+ # :gateway_token - The token of the gateway you've created in
29
+ # Spreedly.
30
+ def initialize(options = {})
31
+ requires!(options, :login, :password, :gateway_token)
32
+ super
33
+ end
34
+
35
+ # Public: Run a purchase transaction.
36
+ #
37
+ # money - The monetary amount of the transaction in cents.
38
+ # payment_method - The CreditCard or the Spreedly payment method token.
39
+ # options - A hash of options:
40
+ # :store - Retain the payment method if the purchase
41
+ # succeeds. Defaults to false. (optional)
42
+ def purchase(money, payment_method, options = {})
43
+ if payment_method.is_a?(String)
44
+ purchase_with_token(money, payment_method, options)
45
+ else
46
+ MultiResponse.run do |r|
47
+ r.process { save_card(false, payment_method, options) }
48
+ r.process { purchase_with_token(money, r.authorization, options) }
49
+ end
50
+ end
51
+ end
52
+
53
+ # Public: Run an authorize transaction.
54
+ #
55
+ # money - The monetary amount of the transaction in cents.
56
+ # payment_method - The CreditCard or the Spreedly payment method token.
57
+ # options - A hash of options:
58
+ # :store - Retain the payment method if the authorize
59
+ # succeeds. Defaults to false. (optional)
60
+ def authorize(money, payment_method, options = {})
61
+ if payment_method.is_a?(String)
62
+ authorize_with_token(money, payment_method, options)
63
+ else
64
+ MultiResponse.run do |r|
65
+ r.process { save_card(false, payment_method, options) }
66
+ r.process { authorize_with_token(money, r.authorization, options) }
67
+ end
68
+ end
69
+ end
70
+
71
+ def capture(money, authorization, options={})
72
+ request = build_xml_request('transaction') do |doc|
73
+ add_invoice(doc, money, options)
74
+ end
75
+
76
+ commit("transactions/#{authorization}/capture.xml", request)
77
+ end
78
+
79
+ def refund(money, authorization, options={})
80
+ request = build_xml_request('transaction') do |doc|
81
+ add_invoice(doc, money, options)
82
+ end
83
+
84
+ commit("transactions/#{authorization}/credit.xml", request)
85
+ end
86
+
87
+ def void(authorization, options={})
88
+ commit("transactions/#{authorization}/void.xml", '')
89
+ end
90
+
91
+ # Public: Store a credit card in the Spreedly vault and retain it.
92
+ #
93
+ # credit_card - The CreditCard to store
94
+ # options - A standard ActiveMerchant options hash
95
+ def store(credit_card, options={})
96
+ retain = (options.has_key?(:retain) ? options[:retain] : true)
97
+ save_card(retain, credit_card, options)
98
+ end
99
+
100
+ # Public: Redact the CreditCard in Spreedly. This wipes the sensitive
101
+ # payment information from the card.
102
+ #
103
+ # credit_card - The CreditCard to store
104
+ # options - A standard ActiveMerchant options hash
105
+ def unstore(authorization, options={})
106
+ commit("payment_methods/#{authorization}/redact.xml", '', :put)
107
+ end
108
+
109
+ private
110
+
111
+ def save_card(retain, credit_card, options)
112
+ request = build_xml_request('payment_method') do |doc|
113
+ add_credit_card(doc, credit_card, options)
114
+ add_extra_options(:data, doc, options)
115
+ doc.retained(true) if retain
116
+ end
117
+
118
+ commit("payment_methods.xml", request, :post, :payment_method_token)
119
+ end
120
+
121
+ def purchase_with_token(money, payment_method_token, options)
122
+ request = auth_purchase_request(money, payment_method_token, options)
123
+ commit("gateways/#{options[:gateway_token] || @options[:gateway_token]}/purchase.xml", request)
124
+ end
125
+
126
+ def authorize_with_token(money, payment_method_token, options)
127
+ request = auth_purchase_request(money, payment_method_token, options)
128
+ commit("gateways/#{@options[:gateway_token]}/authorize.xml", request)
129
+ end
130
+
131
+ def auth_purchase_request(money, payment_method_token, options)
132
+ build_xml_request('transaction') do |doc|
133
+ add_invoice(doc, money, options)
134
+ doc.ip(options[:ip])
135
+ add_extra_options(:gateway_specific_fields, doc, options)
136
+ doc.payment_method_token(payment_method_token)
137
+ doc.retain_on_success(true) if options[:store]
138
+ end
139
+ end
140
+
141
+ def add_invoice(doc, money, options)
142
+ doc.amount amount(money)
143
+ doc.currency_code(options[:currency] || currency(money) || default_currency)
144
+ doc.order_id(options[:order_id])
145
+ doc.ip(options[:ip])
146
+ doc.description(options[:description])
147
+ end
148
+
149
+ def add_credit_card(doc, credit_card, options)
150
+ doc.credit_card do
151
+ doc.number(credit_card.number)
152
+ doc.verification_value(credit_card.verification_value)
153
+ doc.first_name(credit_card.first_name)
154
+ doc.last_name(credit_card.last_name)
155
+ doc.month(credit_card.month)
156
+ doc.year(credit_card.year)
157
+ doc.email(options[:email])
158
+ doc.address1(options[:billing_address].try(:[], :address1))
159
+ doc.address2(options[:billing_address].try(:[], :address2))
160
+ doc.city(options[:billing_address].try(:[], :city))
161
+ doc.state(options[:billing_address].try(:[], :state))
162
+ doc.zip(options[:billing_address].try(:[], :zip))
163
+ doc.country(options[:billing_address].try(:[], :country))
164
+ end
165
+ end
166
+
167
+ def add_extra_options(type, doc, options)
168
+ doc.send(type) do
169
+ extra_options_to_doc(doc, options[type])
170
+ end
171
+ end
172
+
173
+ def extra_options_to_doc(doc, value)
174
+ return doc.text value unless value.kind_of? Hash
175
+ value.each do |k, v|
176
+ doc.send(k) do
177
+ extra_options_to_doc(doc, v)
178
+ end
179
+ end
180
+ end
181
+
182
+ def parse(xml)
183
+ response = {}
184
+
185
+ doc = Nokogiri::XML(xml)
186
+ doc.root.xpath('*').each do |node|
187
+ if (node.elements.empty?)
188
+ response[node.name.downcase.to_sym] = node.text
189
+ else
190
+ node.elements.each do |childnode|
191
+ childnode_to_response(response, node, childnode)
192
+ end
193
+ end
194
+ end
195
+
196
+ response
197
+ end
198
+
199
+ def childnode_to_response(response, node, childnode)
200
+ name = "#{node.name.downcase}_#{childnode.name.downcase}"
201
+ if name == 'payment_method_data' && !childnode.elements.empty?
202
+ response[name.to_sym] = Hash.from_xml(childnode.to_s).values.first
203
+ else
204
+ response[name.to_sym] = childnode.text
205
+ end
206
+ end
207
+
208
+ def build_xml_request(root)
209
+ builder = Nokogiri::XML::Builder.new
210
+ builder.__send__(root) do |doc|
211
+ yield(doc)
212
+ end
213
+ builder.to_xml
214
+ end
215
+
216
+ def commit(relative_url, request, method = :post, authorization_field = :token)
217
+ begin
218
+ raw_response = ssl_request(method, "#{live_url}/#{relative_url}", request, headers)
219
+ rescue ResponseError => e
220
+ raw_response = e.response.body
221
+ end
222
+
223
+ response_from(raw_response, authorization_field)
224
+ end
225
+
226
+ def response_from(raw_response, authorization_field)
227
+ parsed = parse(raw_response)
228
+ options = {
229
+ :authorization => parsed[authorization_field],
230
+ :test => (parsed[:on_test_gateway] == 'true'),
231
+ :avs_result => { :code => parsed[:response_avs_code] },
232
+ :cvv_result => parsed[:response_cvv_code]
233
+ }
234
+
235
+ Response.new(parsed[:succeeded] == 'true', parsed[:message] || parsed[:error], parsed, options)
236
+ end
237
+
238
+ def headers
239
+ {
240
+ 'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:login]}:#{@options[:password]}").chomp),
241
+ 'Content-Type' => 'text/xml'
242
+ }
243
+ end
244
+ end
245
+ end
246
+ end
247
+
@@ -0,0 +1,411 @@
1
+ require 'active_support/core_ext/hash/slice'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class StripeGateway < Gateway
6
+ self.live_url = 'https://api.stripe.com/v1/'
7
+
8
+ AVS_CODE_TRANSLATOR = {
9
+ 'line1: pass, zip: pass' => 'Y',
10
+ 'line1: pass, zip: fail' => 'A',
11
+ 'line1: pass, zip: unchecked' => 'B',
12
+ 'line1: fail, zip: pass' => 'Z',
13
+ 'line1: fail, zip: fail' => 'N',
14
+ 'line1: unchecked, zip: pass' => 'P',
15
+ 'line1: unchecked, zip: unchecked' => 'I'
16
+ }
17
+
18
+ CVC_CODE_TRANSLATOR = {
19
+ 'pass' => 'M',
20
+ 'fail' => 'N',
21
+ 'unchecked' => 'P'
22
+ }
23
+
24
+ # Source: https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
25
+ CURRENCIES_WITHOUT_FRACTIONS = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VUV', 'XAF', 'XOF', 'XPF']
26
+
27
+ self.supported_countries = %w(AU BE CA CH DE ES FI FR GB IE IT LU NL US)
28
+ self.default_currency = 'USD'
29
+ self.money_format = :cents
30
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club]
31
+
32
+ self.homepage_url = 'https://stripe.com/'
33
+ self.display_name = 'Stripe'
34
+
35
+ STANDARD_ERROR_CODE_MAPPING = {
36
+ 'incorrect_number' => STANDARD_ERROR_CODE[:incorrect_number],
37
+ 'invalid_number' => STANDARD_ERROR_CODE[:invalid_number],
38
+ 'invalid_expiry_month' => STANDARD_ERROR_CODE[:invalid_expiry_date],
39
+ 'invalid_expiry_year' => STANDARD_ERROR_CODE[:invalid_expiry_date],
40
+ 'invalid_cvc' => STANDARD_ERROR_CODE[:invalid_cvc],
41
+ 'expired_card' => STANDARD_ERROR_CODE[:expired_card],
42
+ 'incorrect_cvc' => STANDARD_ERROR_CODE[:incorrect_cvc],
43
+ 'incorrect_zip' => STANDARD_ERROR_CODE[:incorrect_zip],
44
+ 'card_declined' => STANDARD_ERROR_CODE[:card_declined],
45
+ 'processing_error' => STANDARD_ERROR_CODE[:processing_error]
46
+ }
47
+
48
+ def initialize(options = {})
49
+ requires!(options, :login)
50
+ @api_key = options[:login]
51
+ @fee_refund_api_key = options[:fee_refund_login]
52
+ @version = options[:version]
53
+
54
+ super
55
+ end
56
+
57
+ def authorize(money, payment, options = {})
58
+ MultiResponse.run do |r|
59
+ if payment.is_a?(ApplePayPaymentToken)
60
+ r.process { tokenize_apple_pay_token(payment) }
61
+ payment = StripePaymentToken.new(r.params["token"]) if r.success?
62
+ end
63
+ r.process do |r|
64
+ post = create_post_for_auth_or_purchase(money, payment, options)
65
+ post[:capture] = "false"
66
+ commit(:post, 'charges', post, options)
67
+ end
68
+ end.responses.last
69
+ end
70
+
71
+ # To create a charge on a card or a token, call
72
+ #
73
+ # purchase(money, card_hash_or_token, { ... })
74
+ #
75
+ # To create a charge on a customer, call
76
+ #
77
+ # purchase(money, nil, { :customer => id, ... })
78
+ def purchase(money, payment, options = {})
79
+ MultiResponse.run do |r|
80
+ if payment.is_a?(ApplePayPaymentToken)
81
+ r.process { tokenize_apple_pay_token(payment) }
82
+ payment = StripePaymentToken.new(r.params["token"]) if r.success?
83
+ end
84
+ r.process do |r|
85
+ post = create_post_for_auth_or_purchase(money, payment, options)
86
+ commit(:post, 'charges', post, options)
87
+ end
88
+ end.responses.last
89
+ end
90
+
91
+ def capture(money, authorization, options = {})
92
+ post = {}
93
+ add_amount(post, money, options)
94
+ add_application_fee(post, options)
95
+
96
+ commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options)
97
+ end
98
+
99
+ def void(identification, options = {})
100
+ commit(:post, "charges/#{CGI.escape(identification)}/refund", {}, options)
101
+ end
102
+
103
+ def refund(money, identification, options = {})
104
+ post = {}
105
+ add_amount(post, money, options)
106
+ post[:refund_application_fee] = true if options[:refund_application_fee]
107
+
108
+ MultiResponse.run(:first) do |r|
109
+ r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, options) }
110
+
111
+ return r unless options[:refund_fee_amount]
112
+
113
+ r.process { fetch_application_fees(identification, options) }
114
+ r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r.responses.last), options) }
115
+ end
116
+ end
117
+
118
+ def verify(payment, options = {})
119
+ MultiResponse.run(:use_first_response) do |r|
120
+ r.process { authorize(50, payment, options) }
121
+ r.process(:ignore_result) { void(r.authorization, options) }
122
+ end
123
+ end
124
+
125
+ def application_fee_from_response(response)
126
+ return unless response.success?
127
+
128
+ application_fees = response.params["data"].select { |fee| fee["object"] == "application_fee" }
129
+ application_fees.first["id"] unless application_fees.empty?
130
+ end
131
+
132
+ def refund_application_fee(money, identification, options = {})
133
+ return Response.new(false, "Application fee id could not be found") unless identification
134
+
135
+ post = {}
136
+ add_amount(post, money, options)
137
+ options.merge!(:key => @fee_refund_api_key)
138
+
139
+ commit(:post, "application_fees/#{CGI.escape(identification)}/refund", post, options)
140
+ end
141
+
142
+ # Note: creating a new credit card will not change the customer's existing default credit card (use :set_default => true)
143
+ def store(payment, options = {})
144
+ card_params = {}
145
+ post = {}
146
+
147
+ if payment.is_a?(ApplePayPaymentToken)
148
+ token_exchange_response = tokenize_apple_pay_token(payment)
149
+ card_params = { card: token_exchange_response.params["token"]["id"] } if token_exchange_response.success?
150
+ else
151
+ add_creditcard(card_params, payment, options)
152
+ end
153
+
154
+ post[:description] = options[:description] if options[:description]
155
+ post[:email] = options[:email] if options[:email]
156
+
157
+ if options[:customer]
158
+ MultiResponse.run(:first) do |r|
159
+ # The /cards endpoint does not update other customer parameters.
160
+ r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", card_params, options) }
161
+
162
+ if options[:set_default] and r.success? and !r.params['id'].blank?
163
+ post[:default_card] = r.params['id']
164
+ end
165
+
166
+ if post.count > 0
167
+ r.process { update_customer(options[:customer], post) }
168
+ end
169
+ end
170
+ else
171
+ commit(:post, 'customers', post.merge(card_params), options)
172
+ end
173
+ end
174
+
175
+ def update(customer_id, card_id, options = {})
176
+ commit(:post, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", options, options)
177
+ end
178
+
179
+ def update_customer(customer_id, options = {})
180
+ commit(:post, "customers/#{CGI.escape(customer_id)}", options, options)
181
+ end
182
+
183
+ def unstore(customer_id, options = {}, deprecated_options = {})
184
+ if options.kind_of?(String)
185
+ ActiveMerchant.deprecated "Passing the card_id as the 2nd parameter is deprecated. Put it in the options hash instead."
186
+ options = deprecated_options.merge(card_id: options)
187
+ end
188
+
189
+ if options[:card_id]
190
+ commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(options[:card_id])}", nil, options)
191
+ else
192
+ commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, options)
193
+ end
194
+ end
195
+
196
+ def tokenize_apple_pay_token(apple_pay_payment_token, options = {})
197
+ token_response = api_request(:post, "tokens?pk_token=#{CGI.escape(apple_pay_payment_token.payment_data.to_json)}")
198
+ success = !token_response.key?("error")
199
+
200
+ if success && token_response.key?("id")
201
+ Response.new(success, nil, token: token_response)
202
+ else
203
+ Response.new(success, token_response["error"]["message"])
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ class StripePaymentToken < PaymentToken
210
+ def type
211
+ 'stripe'
212
+ end
213
+ end
214
+
215
+ def create_post_for_auth_or_purchase(money, payment, options)
216
+ post = {}
217
+ add_amount(post, money, options, true)
218
+ if payment.is_a?(StripePaymentToken)
219
+ add_payment_token(post, payment, options)
220
+ else
221
+ add_creditcard(post, payment, options)
222
+ end
223
+ add_customer(post, payment, options)
224
+ add_customer_data(post, options)
225
+ post[:description] = options[:description]
226
+ post[:statement_description] = options[:statement_description]
227
+
228
+ post[:metadata] = {}
229
+ post[:metadata][:email] = options[:email] if options[:email]
230
+ post[:metadata][:order_id] = options[:order_id] if options[:order_id]
231
+ post.delete(:metadata) if post[:metadata].empty?
232
+
233
+ add_flags(post, options)
234
+ add_application_fee(post, options)
235
+ post
236
+ end
237
+
238
+ def add_amount(post, money, options, include_currency = false)
239
+ currency = options[:currency] || currency(money)
240
+ post[:amount] = localized_amount(money, currency)
241
+ post[:currency] = currency.downcase if include_currency
242
+ end
243
+
244
+ def add_application_fee(post, options)
245
+ post[:application_fee] = options[:application_fee] if options[:application_fee]
246
+ end
247
+
248
+ def add_expand_parameters(post, options)
249
+ post[:expand] = Array.wrap(options[:expand])
250
+ end
251
+
252
+ def add_customer_data(post, options)
253
+ metadata_options = [:description, :ip, :user_agent, :referrer]
254
+ post.update(options.slice(*metadata_options))
255
+
256
+ post[:external_id] = options[:order_id]
257
+ post[:payment_user_agent] = "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
258
+ end
259
+
260
+ def add_address(post, options)
261
+ return unless post[:card] && post[:card].kind_of?(Hash)
262
+ if address = options[:billing_address] || options[:address]
263
+ post[:card][:address_line1] = address[:address1] if address[:address1]
264
+ post[:card][:address_line2] = address[:address2] if address[:address2]
265
+ post[:card][:address_country] = address[:country] if address[:country]
266
+ post[:card][:address_zip] = address[:zip] if address[:zip]
267
+ post[:card][:address_state] = address[:state] if address[:state]
268
+ post[:card][:address_city] = address[:city] if address[:city]
269
+ end
270
+ end
271
+
272
+ def add_creditcard(post, creditcard, options)
273
+ card = {}
274
+ if creditcard.respond_to?(:number)
275
+ if creditcard.respond_to?(:track_data) && creditcard.track_data.present?
276
+ card[:swipe_data] = creditcard.track_data
277
+ else
278
+ card[:number] = creditcard.number
279
+ card[:exp_month] = creditcard.month
280
+ card[:exp_year] = creditcard.year
281
+ card[:cvc] = creditcard.verification_value if creditcard.verification_value?
282
+ card[:name] = creditcard.name if creditcard.name
283
+ end
284
+
285
+ post[:card] = card
286
+ add_address(post, options)
287
+ elsif creditcard.kind_of?(String)
288
+ if options[:track_data]
289
+ card[:swipe_data] = options[:track_data]
290
+ else
291
+ card = creditcard
292
+ end
293
+ post[:card] = card
294
+ end
295
+ end
296
+
297
+ def add_payment_token(post, token, options = {})
298
+ post[:card] = token.payment_data["id"]
299
+ end
300
+
301
+ def add_customer(post, payment, options)
302
+ post[:customer] = options[:customer] if options[:customer] && !payment.respond_to?(:number)
303
+ end
304
+
305
+ def add_flags(post, options)
306
+ post[:uncaptured] = true if options[:uncaptured]
307
+ post[:recurring] = true if (options[:eci] == 'recurring' || options[:recurring])
308
+ end
309
+
310
+ def fetch_application_fees(identification, options = {})
311
+ options.merge!(:key => @fee_refund_api_key)
312
+
313
+ commit(:get, "application_fees?charge=#{identification}", nil, options)
314
+ end
315
+
316
+ def parse(body)
317
+ JSON.parse(body)
318
+ end
319
+
320
+ def post_data(params)
321
+ return nil unless params
322
+
323
+ params.map do |key, value|
324
+ next if value.blank?
325
+ if value.is_a?(Hash)
326
+ h = {}
327
+ value.each do |k, v|
328
+ h["#{key}[#{k}]"] = v unless v.blank?
329
+ end
330
+ post_data(h)
331
+ elsif value.is_a?(Array)
332
+ value.map { |v| "#{key}[]=#{CGI.escape(v.to_s)}" }.join("&")
333
+ else
334
+ "#{key}=#{CGI.escape(value.to_s)}"
335
+ end
336
+ end.compact.join("&")
337
+ end
338
+
339
+ def headers(options = {})
340
+ key = options[:key] || @api_key
341
+ version = options[:version] || @version
342
+
343
+ headers = {
344
+ "Authorization" => "Basic " + Base64.encode64(key.to_s + ":").strip,
345
+ "User-Agent" => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
346
+ "X-Stripe-Client-User-Agent" => user_agent,
347
+ "X-Stripe-Client-User-Metadata" => {:ip => options[:ip]}.to_json
348
+ }
349
+ headers.merge!("Stripe-Version" => version) if version
350
+ headers
351
+ end
352
+
353
+ def api_request(method, endpoint, parameters = nil, options = {})
354
+ raw_response = response = nil
355
+ begin
356
+ raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options))
357
+ response = parse(raw_response)
358
+ rescue ResponseError => e
359
+ raw_response = e.response.body
360
+ response = response_error(raw_response)
361
+ rescue JSON::ParserError
362
+ response = json_error(raw_response)
363
+ end
364
+ response
365
+ end
366
+
367
+ def commit(method, url, parameters = nil, options = {})
368
+ add_expand_parameters(parameters, options) if parameters
369
+
370
+ response = api_request(method, url, parameters, options)
371
+ success = !response.key?("error")
372
+
373
+ card = response["card"] || response["active_card"] || {}
374
+ avs_code = AVS_CODE_TRANSLATOR["line1: #{card["address_line1_check"]}, zip: #{card["address_zip_check"]}"]
375
+ cvc_code = CVC_CODE_TRANSLATOR[card["cvc_check"]]
376
+
377
+ Response.new(success,
378
+ success ? "Transaction approved" : response["error"]["message"],
379
+ response,
380
+ :test => response.has_key?("livemode") ? !response["livemode"] : false,
381
+ :authorization => success ? response["id"] : response["error"]["charge"],
382
+ :avs_result => { :code => avs_code },
383
+ :cvv_result => cvc_code,
384
+ :error_code => success ? nil : STANDARD_ERROR_CODE_MAPPING[response["error"]["code"]]
385
+ )
386
+ end
387
+
388
+ def response_error(raw_response)
389
+ begin
390
+ parse(raw_response)
391
+ rescue JSON::ParserError
392
+ json_error(raw_response)
393
+ end
394
+ end
395
+
396
+ def json_error(raw_response)
397
+ msg = 'Invalid response received from the Stripe API. Please contact support@stripe.com if you continue to receive this message.'
398
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
399
+ {
400
+ "error" => {
401
+ "message" => msg
402
+ }
403
+ }
404
+ end
405
+
406
+ def non_fractional_currency?(currency)
407
+ CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
408
+ end
409
+ end
410
+ end
411
+ end