start_activemerchant 1.50.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 (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,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,489 @@
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(AT AU BE CA CH DE DK ES FI FR GB IE IT LU NL NO SE US)
28
+ self.default_currency = 'USD'
29
+ self.money_format = :cents
30
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
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
+ 'call_issuer' => STANDARD_ERROR_CODE[:call_issuer],
46
+ 'processing_error' => STANDARD_ERROR_CODE[:processing_error]
47
+ }
48
+
49
+ def initialize(options = {})
50
+ requires!(options, :login)
51
+ @api_key = options[:login]
52
+ @fee_refund_api_key = options[:fee_refund_login]
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
64
+ post = create_post_for_auth_or_purchase(money, payment, options)
65
+ post[:capture] = "false" unless emv_payment?(payment)
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
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
+
94
+ add_application_fee(post, options)
95
+
96
+ if emv_tc_response = options.delete(:icc_data)
97
+ post[:card] = { emv_approval_data: emv_tc_response }
98
+ commit(:post, "charges/#{CGI.escape(authorization)}", post, options)
99
+ else
100
+ add_amount(post, money, options)
101
+ commit(:post, "charges/#{CGI.escape(authorization)}/capture", post, options)
102
+ end
103
+ end
104
+
105
+ def void(identification, options = {})
106
+ commit(:post, "charges/#{CGI.escape(identification)}/refund", {}, options)
107
+ end
108
+
109
+ def refund(money, identification, options = {})
110
+ post = {}
111
+ add_amount(post, money, options)
112
+ post[:refund_application_fee] = true if options[:refund_application_fee]
113
+ post[:reverse_transfer] = options[:reverse_transfer] if options[:reverse_transfer]
114
+
115
+ MultiResponse.run(:first) do |r|
116
+ r.process { commit(:post, "charges/#{CGI.escape(identification)}/refund", post, options) }
117
+
118
+ return r unless options[:refund_fee_amount]
119
+
120
+ r.process { fetch_application_fees(identification, options) }
121
+ r.process { refund_application_fee(options[:refund_fee_amount], application_fee_from_response(r.responses.last), options) }
122
+ end
123
+ end
124
+
125
+ def verify(payment, options = {})
126
+ MultiResponse.run(:use_first_response) do |r|
127
+ r.process { authorize(50, payment, options) }
128
+ r.process(:ignore_result) { void(r.authorization, options) }
129
+ end
130
+ end
131
+
132
+ def application_fee_from_response(response)
133
+ return unless response.success?
134
+
135
+ application_fees = response.params["data"].select { |fee| fee["object"] == "application_fee" }
136
+ application_fees.first["id"] unless application_fees.empty?
137
+ end
138
+
139
+ def refund_application_fee(money, identification, options = {})
140
+ return Response.new(false, "Application fee id could not be found") unless identification
141
+
142
+ post = {}
143
+ add_amount(post, money, options)
144
+ options.merge!(:key => @fee_refund_api_key)
145
+
146
+ commit(:post, "application_fees/#{CGI.escape(identification)}/refund", post, options)
147
+ end
148
+
149
+ # Note: creating a new credit card will not change the customer's existing default credit card (use :set_default => true)
150
+ def store(payment, options = {})
151
+ card_params = {}
152
+ post = {}
153
+
154
+ if payment.is_a?(ApplePayPaymentToken)
155
+ token_exchange_response = tokenize_apple_pay_token(payment)
156
+ card_params = { card: token_exchange_response.params["token"]["id"] } if token_exchange_response.success?
157
+ else
158
+ add_creditcard(card_params, payment, options)
159
+ end
160
+
161
+ post[:description] = options[:description] if options[:description]
162
+ post[:email] = options[:email] if options[:email]
163
+
164
+ if options[:customer]
165
+ MultiResponse.run(:first) do |r|
166
+ # The /cards endpoint does not update other customer parameters.
167
+ r.process { commit(:post, "customers/#{CGI.escape(options[:customer])}/cards", card_params, options) }
168
+
169
+ if options[:set_default] and r.success? and !r.params['id'].blank?
170
+ post[:default_card] = r.params['id']
171
+ end
172
+
173
+ if post.count > 0
174
+ r.process { update_customer(options[:customer], post) }
175
+ end
176
+ end
177
+ else
178
+ commit(:post, 'customers', post.merge(card_params), options)
179
+ end
180
+ end
181
+
182
+ def update(customer_id, card_id, options = {})
183
+ commit(:post, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(card_id)}", options, options)
184
+ end
185
+
186
+ def update_customer(customer_id, options = {})
187
+ commit(:post, "customers/#{CGI.escape(customer_id)}", options, options)
188
+ end
189
+
190
+ def unstore(customer_id, options = {}, deprecated_options = {})
191
+ if options.kind_of?(String)
192
+ ActiveMerchant.deprecated "Passing the card_id as the 2nd parameter is deprecated. Put it in the options hash instead."
193
+ options = deprecated_options.merge(card_id: options)
194
+ end
195
+
196
+ if options[:card_id]
197
+ commit(:delete, "customers/#{CGI.escape(customer_id)}/cards/#{CGI.escape(options[:card_id])}", nil, options)
198
+ else
199
+ commit(:delete, "customers/#{CGI.escape(customer_id)}", nil, options)
200
+ end
201
+ end
202
+
203
+ def tokenize_apple_pay_token(apple_pay_payment_token, options = {})
204
+ token_response = api_request(:post, "tokens?pk_token=#{CGI.escape(apple_pay_payment_token.payment_data.to_json)}")
205
+ success = !token_response.key?("error")
206
+
207
+ if success && token_response.key?("id")
208
+ Response.new(success, nil, token: token_response)
209
+ else
210
+ Response.new(success, token_response["error"]["message"])
211
+ end
212
+ end
213
+
214
+ def supports_scrubbing?
215
+ true
216
+ end
217
+
218
+ def scrub(transcript)
219
+ transcript.
220
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
221
+ gsub(%r((card\[number\]=)\d+), '\1[FILTERED]').
222
+ gsub(%r((card\[cvc\]=)\d+), '\1[FILTERED]').
223
+ gsub(%r((&?three_d_secure\[cryptogram\]=)[\w=]*(&?)), '\1[FILTERED]\2')
224
+ end
225
+
226
+ private
227
+
228
+ class StripePaymentToken < PaymentToken
229
+ def type
230
+ 'stripe'
231
+ end
232
+ end
233
+
234
+ def create_post_for_auth_or_purchase(money, payment, options)
235
+ post = {}
236
+
237
+ if payment.is_a?(StripePaymentToken)
238
+ add_payment_token(post, payment, options)
239
+ else
240
+ add_creditcard(post, payment, options)
241
+ end
242
+ unless emv_payment?(payment)
243
+ add_amount(post, money, options, true)
244
+ add_customer_data(post, options)
245
+ add_metadata(post, options)
246
+ post[:description] = options[:description]
247
+ post[:statement_descriptor] = options[:statement_description]
248
+ add_customer(post, payment, options)
249
+ add_flags(post, options)
250
+ end
251
+
252
+ add_application_fee(post, options)
253
+ add_destination(post, options)
254
+ post
255
+ end
256
+
257
+ def add_amount(post, money, options, include_currency = false)
258
+ currency = options[:currency] || currency(money)
259
+ post[:amount] = localized_amount(money, currency)
260
+ post[:currency] = currency.downcase if include_currency
261
+ end
262
+
263
+ def add_application_fee(post, options)
264
+ post[:application_fee] = options[:application_fee] if options[:application_fee]
265
+ end
266
+
267
+ def add_destination(post, options)
268
+ post[:destination] = options[:destination] if options[:destination]
269
+ end
270
+
271
+ def add_expand_parameters(post, options)
272
+ post[:expand] = Array.wrap(options[:expand])
273
+ end
274
+
275
+ def add_customer_data(post, options)
276
+ metadata_options = [:description, :ip, :user_agent, :referrer]
277
+ post.update(options.slice(*metadata_options))
278
+
279
+ post[:external_id] = options[:order_id]
280
+ post[:payment_user_agent] = "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}"
281
+ end
282
+
283
+ def add_address(post, options)
284
+ return unless post[:card] && post[:card].kind_of?(Hash)
285
+ if address = options[:billing_address] || options[:address]
286
+ post[:card][:address_line1] = address[:address1] if address[:address1]
287
+ post[:card][:address_line2] = address[:address2] if address[:address2]
288
+ post[:card][:address_country] = address[:country] if address[:country]
289
+ post[:card][:address_zip] = address[:zip] if address[:zip]
290
+ post[:card][:address_state] = address[:state] if address[:state]
291
+ post[:card][:address_city] = address[:city] if address[:city]
292
+ end
293
+ end
294
+
295
+ def add_creditcard(post, creditcard, options)
296
+ card = {}
297
+ if emv_payment?(creditcard)
298
+ add_emv_creditcard(post, creditcard.icc_data)
299
+ post[:card][:read_method] = "contactless" if creditcard.contactless
300
+ if creditcard.encrypted_pin_cryptogram.present? && creditcard.encrypted_pin_ksn.present?
301
+ post[:card][:encrypted_pin] = creditcard.encrypted_pin_cryptogram
302
+ post[:card][:encrypted_pin_key_id] = creditcard.encrypted_pin_ksn
303
+ end
304
+ elsif creditcard.respond_to?(:number)
305
+ if creditcard.respond_to?(:track_data) && creditcard.track_data.present?
306
+ card[:swipe_data] = creditcard.track_data
307
+ card[:fallback_reason] = creditcard.fallback_reason if creditcard.fallback_reason
308
+ else
309
+ card[:number] = creditcard.number
310
+ card[:exp_month] = creditcard.month
311
+ card[:exp_year] = creditcard.year
312
+ card[:cvc] = creditcard.verification_value if creditcard.verification_value?
313
+ card[:name] = creditcard.name if creditcard.name
314
+ end
315
+ post[:card] = card
316
+
317
+ if creditcard.is_a?(NetworkTokenizationCreditCard)
318
+ post[:three_d_secure] = {
319
+ apple_pay: true,
320
+ cryptogram: creditcard.payment_cryptogram
321
+ }
322
+ end
323
+
324
+ add_address(post, options)
325
+ elsif creditcard.kind_of?(String)
326
+ if options[:track_data]
327
+ card[:swipe_data] = options[:track_data]
328
+ else
329
+ card = creditcard
330
+ end
331
+ post[:card] = card
332
+ end
333
+ end
334
+
335
+ def add_emv_creditcard(post, icc_data, options = {})
336
+ post[:card] = { emv_auth_data: icc_data }
337
+ end
338
+
339
+ def add_payment_token(post, token, options = {})
340
+ post[:card] = token.payment_data["id"]
341
+ end
342
+
343
+ def add_customer(post, payment, options)
344
+ post[:customer] = options[:customer] if options[:customer] && !payment.respond_to?(:number)
345
+ end
346
+
347
+ def add_flags(post, options)
348
+ post[:uncaptured] = true if options[:uncaptured]
349
+ post[:recurring] = true if (options[:eci] == 'recurring' || options[:recurring])
350
+ end
351
+
352
+ def add_metadata(post, options = {})
353
+ post[:metadata] = options[:metadata] || {}
354
+ post[:metadata][:email] = options[:email] if options[:email]
355
+ post[:metadata][:order_id] = options[:order_id] if options[:order_id]
356
+ post.delete(:metadata) if post[:metadata].empty?
357
+ end
358
+
359
+ def fetch_application_fees(identification, options = {})
360
+ options.merge!(:key => @fee_refund_api_key)
361
+
362
+ commit(:get, "application_fees?charge=#{identification}", nil, options)
363
+ end
364
+
365
+ def parse(body)
366
+ JSON.parse(body)
367
+ end
368
+
369
+ def post_data(params)
370
+ return nil unless params
371
+
372
+ params.map do |key, value|
373
+ next if value.blank?
374
+ if value.is_a?(Hash)
375
+ h = {}
376
+ value.each do |k, v|
377
+ h["#{key}[#{k}]"] = v unless v.blank?
378
+ end
379
+ post_data(h)
380
+ elsif value.is_a?(Array)
381
+ value.map { |v| "#{key}[]=#{CGI.escape(v.to_s)}" }.join("&")
382
+ else
383
+ "#{key}=#{CGI.escape(value.to_s)}"
384
+ end
385
+ end.compact.join("&")
386
+ end
387
+
388
+ def headers(options = {})
389
+ key = options[:key] || @api_key
390
+ idempotency_key = options[:idempotency_key]
391
+
392
+ headers = {
393
+ "Authorization" => "Basic " + Base64.encode64(key.to_s + ":").strip,
394
+ "User-Agent" => "Stripe/v1 ActiveMerchantBindings/#{ActiveMerchant::VERSION}",
395
+ "Stripe-Version" => api_version(options),
396
+ "X-Stripe-Client-User-Agent" => user_agent,
397
+ "X-Stripe-Client-User-Metadata" => {:ip => options[:ip]}.to_json
398
+ }
399
+ headers.merge!("Idempotency-Key" => idempotency_key) if idempotency_key
400
+ headers
401
+ end
402
+
403
+ def api_version(options)
404
+ options[:version] || @options[:version] || "2015-04-07"
405
+ end
406
+
407
+ def api_request(method, endpoint, parameters = nil, options = {})
408
+ raw_response = response = nil
409
+ begin
410
+ raw_response = ssl_request(method, self.live_url + endpoint, post_data(parameters), headers(options))
411
+ response = parse(raw_response)
412
+ rescue ResponseError => e
413
+ raw_response = e.response.body
414
+ response = response_error(raw_response)
415
+ rescue JSON::ParserError
416
+ response = json_error(raw_response)
417
+ end
418
+ response
419
+ end
420
+
421
+ def commit(method, url, parameters = nil, options = {})
422
+ add_expand_parameters(parameters, options) if parameters
423
+ response = api_request(method, url, parameters, options)
424
+
425
+ success = !response.key?("error")
426
+
427
+ card = card_from_response(response)
428
+ avs_code = AVS_CODE_TRANSLATOR["line1: #{card["address_line1_check"]}, zip: #{card["address_zip_check"]}"]
429
+ cvc_code = CVC_CODE_TRANSLATOR[card["cvc_check"]]
430
+
431
+ Response.new(success,
432
+ success ? "Transaction approved" : response["error"]["message"],
433
+ response,
434
+ :test => response.has_key?("livemode") ? !response["livemode"] : false,
435
+ :authorization => success ? response["id"] : response["error"]["charge"],
436
+ :avs_result => { :code => avs_code },
437
+ :cvv_result => cvc_code,
438
+ :emv_authorization => emv_authorization_from_response(response),
439
+ :error_code => success ? nil : error_code_from(response)
440
+ )
441
+ end
442
+
443
+ def response_error(raw_response)
444
+ begin
445
+ parse(raw_response)
446
+ rescue JSON::ParserError
447
+ json_error(raw_response)
448
+ end
449
+ end
450
+
451
+ def json_error(raw_response)
452
+ msg = 'Invalid response received from the Stripe API. Please contact support@stripe.com if you continue to receive this message.'
453
+ msg += " (The raw response returned by the API was #{raw_response.inspect})"
454
+ {
455
+ "error" => {
456
+ "message" => msg
457
+ }
458
+ }
459
+ end
460
+
461
+ def non_fractional_currency?(currency)
462
+ CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s)
463
+ end
464
+
465
+ def emv_payment?(payment)
466
+ payment.respond_to?(:emv?) && payment.emv?
467
+ end
468
+
469
+ def card_from_response(response)
470
+ response["card"] || response["active_card"] || response["source"] || {}
471
+ end
472
+
473
+ def emv_authorization_from_response(response)
474
+ return response["error"]["emv_auth_data"] if response["error"]
475
+
476
+ card_from_response(response)["emv_auth_data"]
477
+ end
478
+
479
+ def error_code_from(response)
480
+ code = response['error']['code']
481
+ decline_code = response['error']['decline_code'] if code == 'card_declined'
482
+
483
+ error_code = STANDARD_ERROR_CODE_MAPPING[decline_code]
484
+ error_code ||= STANDARD_ERROR_CODE_MAPPING[code]
485
+ error_code
486
+ end
487
+ end
488
+ end
489
+ end