activemerchant 1.62.0 → 1.79.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +420 -2
  3. data/README.md +1 -2
  4. data/lib/active_merchant/billing/credit_card.rb +13 -14
  5. data/lib/active_merchant/billing/credit_card_methods.rb +3 -1
  6. data/lib/active_merchant/billing/gateway.rb +25 -9
  7. data/lib/active_merchant/billing/gateways/adyen.rb +299 -0
  8. data/lib/active_merchant/billing/gateways/authorize_net.rb +168 -56
  9. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +4 -2
  10. data/lib/active_merchant/billing/gateways/barclaycard_smartpay.rb +65 -22
  11. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +87 -7
  12. data/lib/active_merchant/billing/gateways/beanstream.rb +2 -0
  13. data/lib/active_merchant/billing/gateways/blue_snap.rb +3 -8
  14. data/lib/active_merchant/billing/gateways/borgun.rb +10 -10
  15. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +1 -0
  16. data/lib/active_merchant/billing/gateways/braintree_blue.rb +49 -15
  17. data/lib/active_merchant/billing/gateways/card_connect.rb +286 -0
  18. data/lib/active_merchant/billing/gateways/card_stream.rb +97 -2
  19. data/lib/active_merchant/billing/gateways/cardprocess.rb +254 -0
  20. data/lib/active_merchant/billing/gateways/cashnet.rb +14 -2
  21. data/lib/active_merchant/billing/gateways/cenpos.rb +1 -1
  22. data/lib/active_merchant/billing/gateways/checkout.rb +1 -1
  23. data/lib/active_merchant/billing/gateways/checkout_v2.rb +44 -14
  24. data/lib/active_merchant/billing/gateways/citrus_pay.rb +0 -1
  25. data/lib/active_merchant/billing/gateways/clearhaus.rb +0 -2
  26. data/lib/active_merchant/billing/gateways/conekta.rb +4 -4
  27. data/lib/active_merchant/billing/gateways/creditcall.rb +71 -9
  28. data/lib/active_merchant/billing/gateways/credorax.rb +117 -5
  29. data/lib/active_merchant/billing/gateways/culqi.rb +279 -0
  30. data/lib/active_merchant/billing/gateways/cyber_source.rb +54 -15
  31. data/lib/active_merchant/billing/gateways/data_cash.rb +12 -0
  32. data/lib/active_merchant/billing/gateways/dibs.rb +0 -1
  33. data/lib/active_merchant/billing/gateways/digitzs.rb +292 -0
  34. data/lib/active_merchant/billing/gateways/ebanx.rb +296 -0
  35. data/lib/active_merchant/billing/gateways/elavon.rb +37 -95
  36. data/lib/active_merchant/billing/gateways/element.rb +11 -1
  37. data/lib/active_merchant/billing/gateways/fat_zebra.rb +3 -29
  38. data/lib/active_merchant/billing/gateways/first_pay.rb +12 -10
  39. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +37 -20
  40. data/lib/active_merchant/billing/gateways/forte.rb +0 -1
  41. data/lib/active_merchant/billing/gateways/global_collect.rb +55 -16
  42. data/lib/active_merchant/billing/gateways/global_transport.rb +16 -2
  43. data/lib/active_merchant/billing/gateways/hps.rb +12 -1
  44. data/lib/active_merchant/billing/gateways/iats_payments.rb +2 -2
  45. data/lib/active_merchant/billing/gateways/iveri.rb +251 -0
  46. data/lib/active_merchant/billing/gateways/jetpay.rb +12 -9
  47. data/lib/active_merchant/billing/gateways/jetpay_v2.rb +437 -0
  48. data/lib/active_merchant/billing/gateways/kushki.rb +227 -0
  49. data/lib/active_merchant/billing/gateways/linkpoint.rb +2 -2
  50. data/lib/active_merchant/billing/gateways/litle.rb +107 -30
  51. data/lib/active_merchant/billing/gateways/mercado_pago.rb +262 -0
  52. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +11 -0
  53. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +16 -4
  54. data/lib/active_merchant/billing/gateways/mercury.rb +14 -1
  55. data/lib/active_merchant/billing/gateways/migs.rb +28 -6
  56. data/lib/active_merchant/billing/gateways/moneris.rb +20 -12
  57. data/lib/active_merchant/billing/gateways/moneris_us.rb +11 -0
  58. data/lib/active_merchant/billing/gateways/mundipagg.rb +292 -0
  59. data/lib/active_merchant/billing/gateways/nab_transact.rb +4 -4
  60. data/lib/active_merchant/billing/gateways/netbanx.rb +60 -16
  61. data/lib/active_merchant/billing/gateways/netbilling.rb +0 -1
  62. data/lib/active_merchant/billing/gateways/nmi.rb +12 -1
  63. data/lib/active_merchant/billing/gateways/ogone.rb +1 -1
  64. data/lib/active_merchant/billing/gateways/omise.rb +9 -5
  65. data/lib/active_merchant/billing/gateways/openpay.rb +13 -0
  66. data/lib/active_merchant/billing/gateways/opp.rb +124 -114
  67. data/lib/active_merchant/billing/gateways/optimal_payment.rb +14 -1
  68. data/lib/active_merchant/billing/gateways/orbital.rb +83 -14
  69. data/lib/active_merchant/billing/gateways/pay_hub.rb +2 -2
  70. data/lib/active_merchant/billing/gateways/payeezy.rb +152 -46
  71. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +12 -2
  72. data/lib/active_merchant/billing/gateways/payflow.rb +24 -2
  73. data/lib/active_merchant/billing/gateways/payment_express.rb +3 -2
  74. data/lib/active_merchant/billing/gateways/paymentez.rb +276 -0
  75. data/lib/active_merchant/billing/gateways/paymill.rb +18 -10
  76. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +14 -0
  77. data/lib/active_merchant/billing/gateways/paypal.rb +0 -12
  78. data/lib/active_merchant/billing/gateways/paystation.rb +14 -1
  79. data/lib/active_merchant/billing/gateways/payu_latam.rb +102 -62
  80. data/lib/active_merchant/billing/gateways/pin.rb +5 -0
  81. data/lib/active_merchant/billing/gateways/pro_pay.rb +326 -0
  82. data/lib/active_merchant/billing/gateways/psigate.rb +12 -1
  83. data/lib/active_merchant/billing/gateways/qbms.rb +11 -0
  84. data/lib/active_merchant/billing/gateways/quickbooks.rb +10 -0
  85. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +21 -17
  86. data/lib/active_merchant/billing/gateways/quickpay.rb +3 -3
  87. data/lib/active_merchant/billing/gateways/qvalent.rb +60 -3
  88. data/lib/active_merchant/billing/gateways/realex.rb +16 -6
  89. data/lib/active_merchant/billing/gateways/redsys.rb +8 -2
  90. data/lib/active_merchant/billing/gateways/safe_charge.rb +262 -0
  91. data/lib/active_merchant/billing/gateways/sage.rb +8 -3
  92. data/lib/active_merchant/billing/gateways/sage_pay.rb +29 -13
  93. data/lib/active_merchant/billing/gateways/secure_net.rb +11 -1
  94. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +12 -0
  95. data/lib/active_merchant/billing/gateways/smart_ps.rb +1 -1
  96. data/lib/active_merchant/billing/gateways/spreedly_core.rb +53 -7
  97. data/lib/active_merchant/billing/gateways/stripe.rb +84 -26
  98. data/lib/active_merchant/billing/gateways/tns.rb +0 -1
  99. data/lib/active_merchant/billing/gateways/trans_first.rb +3 -2
  100. data/lib/active_merchant/billing/gateways/trans_first_transaction_express.rb +61 -26
  101. data/lib/active_merchant/billing/gateways/trexle.rb +217 -0
  102. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +114 -9
  103. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +45 -22
  104. data/lib/active_merchant/billing/gateways/vanco.rb +1 -1
  105. data/lib/active_merchant/billing/gateways/wepay.rb +79 -46
  106. data/lib/active_merchant/billing/gateways/wirecard.rb +5 -4
  107. data/lib/active_merchant/billing/gateways/worldpay.rb +85 -20
  108. data/lib/active_merchant/billing/gateways/worldpay_us.rb +27 -8
  109. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +1 -1
  110. data/lib/active_merchant/connection.rb +48 -12
  111. data/lib/active_merchant/net_http_ssl_connection.rb +9 -0
  112. data/lib/active_merchant/network_connection_retries.rb +6 -4
  113. data/lib/active_merchant/posts_data.rb +11 -1
  114. data/lib/active_merchant/version.rb +1 -1
  115. data/lib/active_merchant.rb +2 -5
  116. data/lib/certs/cacert.pem +85 -0
  117. data/lib/support/ssl_version.rb +87 -0
  118. metadata +25 -9
  119. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +0 -314
@@ -0,0 +1,437 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class JetpayV2Gateway < Gateway
4
+ self.test_url = 'https://test1.jetpay.com/jetpay'
5
+ self.live_url = 'https://gateway20.jetpay.com/jetpay'
6
+
7
+ self.money_format = :cents
8
+ self.default_currency = 'USD'
9
+ self.supported_countries = ['US', 'CA']
10
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
11
+
12
+ self.homepage_url = 'http://www.jetpay.com'
13
+ self.display_name = 'JetPay'
14
+
15
+ API_VERSION = '2.2'
16
+
17
+ ACTION_CODE_MESSAGES = {
18
+ "000" => "Approved.",
19
+ "001" => "Refer to card issuer.",
20
+ "002" => "Refer to card issuer, special condition.",
21
+ "003" => "Invalid merchant or service provider.",
22
+ "004" => "Pick up card.",
23
+ "005" => "Do not honor.",
24
+ "006" => "Error.",
25
+ "007" => "Pick up card, special condition.",
26
+ "008" => "Honor with ID (Show ID).",
27
+ "010" => "Partial approval.",
28
+ "011" => "VIP approval.",
29
+ "012" => "Invalid transaction.",
30
+ "013" => "Invalid amount or exceeds maximum for card program.",
31
+ "014" => "Invalid account number (no such number).",
32
+ "015" => "No such issuer.",
33
+ "019" => "Re-enter Transaction.",
34
+ "021" => "No action taken (unable to back out prior transaction).",
35
+ "025" => "Transaction Not Found.",
36
+ "027" => "File update field edit error.",
37
+ "028" => "File is temporarily unavailable.",
38
+ "030" => "Format error.",
39
+ "039" => "No credit account.",
40
+ "041" => "Pick up card (lost card).",
41
+ "043" => "Pick up card (stolen card).",
42
+ "051" => "Insufficient funds.",
43
+ "052" => "No checking account.",
44
+ "053" => "Mp savomgs accpimt.",
45
+ "054" => "Expired Card.",
46
+ "055" => "Incorrect PIN.",
47
+ "057" => "Transaction not permitted to cardholder.",
48
+ "058" => "Transaction not allowed at terminal.",
49
+ "061" => "Exceeds withdrawal limit.",
50
+ "062" => "Restricted card (eg, Country Exclusion).",
51
+ "063" => "Security violation.",
52
+ "065" => "Activity count limit exceeded.",
53
+ "068" => "Response late.",
54
+ "070" => "Contact card issuer.",
55
+ "071" => "PIN not changed.",
56
+ "075" => "Allowable number of PIN-entry tries exceeded.",
57
+ "076" => "Unable to locate previous message (no matching retrieval reference number).",
58
+ "077" => "Repeat or reversal data are inconsistent with original message.",
59
+ "078" => "Blocked (first use), or non-existent account.",
60
+ "079" => "Key exchange validation failed.",
61
+ "080" => "Credit issuer unavailable or invalid date.",
62
+ "081" => "PIN cryptographic error found.",
63
+ "082" => "Negative online CVV results.",
64
+ "084" => "Invalid auth life cycle.",
65
+ "085" => "No reason to decline - CVV or AVS approved.",
66
+ "086" => "Cannot verify PIN.",
67
+ "087" => "Cashback not allowed.",
68
+ "089" => "Issuer Down.",
69
+ "091" => "Issuer Down.",
70
+ "092" => "Unable to route transaction.",
71
+ "093" => "Transaction cannot be completed - violation of law.",
72
+ "094" => "Duplicate transmission.",
73
+ "096" => "System error.",
74
+ "100" => "Deny.",
75
+ "101" => "Expired Card.",
76
+ "103" => "Deny - Invalid manual Entry 4DBC.",
77
+ "104" => "Deny - New card issued.",
78
+ "105" => "Deny - Account Cancelled.",
79
+ "106" => "Exceeded PIN Attempts.",
80
+ "107" => "Please Call Issuer.",
81
+ "109" => "Invalid merchant.",
82
+ "110" => "Invalid amount.",
83
+ "111" => "Invalid account.",
84
+ "115" => "Service not permitted.",
85
+ "117" => "Invalid PIN.",
86
+ "119" => "Card member not enrolled.",
87
+ "122" => "Invalid card (CID) security code.",
88
+ "125" => "Invalid effective date.",
89
+ "181" => "Format error.",
90
+ "182" => "Please wait.",
91
+ "183" => "Invalid currency code.",
92
+ "187" => "Deny - new card issued.",
93
+ "188" => "Deny - Expiration date required.",
94
+ "189" => "Deny - Cancelled or Closed Merchant/SE.",
95
+ "200" => "Deny - Pick up card.",
96
+ "400" => "Reversal accepted.",
97
+ "601" => "Reject - EMV Chip Declined Transaction.",
98
+ "602" => "Reject - Suspected Fraud.",
99
+ "603" => "Reject - Communications Error.",
100
+ "604" => "Reject - Insufficient Approval.",
101
+ "750" => "Velocity Check Fail.",
102
+ "899" => "Misc Decline.",
103
+ "900" => "Invalid Message Type.",
104
+ "901" => "Invalid Merchant ID.",
105
+ "903" => "Debit not supported.",
106
+ "904" => "Private label not supported.",
107
+ "905" => "Invalid card type.",
108
+ "906" => "Unit not active.",
109
+ "908" => "Manual card entry invalid.",
110
+ "909" => "Invalid track information.",
111
+ "911" => "Master merchant not found.",
112
+ "912" => "Invalid card format.",
113
+ "913" => "Invalid card type.",
114
+ "914" => "Invalid card length.",
115
+ "917" => "Expired card.",
116
+ "919" => "Invalid entry type.",
117
+ "920" => "Invalid amount.",
118
+ "921" => "Invalid messge format.",
119
+ "923" => "Invalid ABA.",
120
+ "924" => "Invalid DDA.",
121
+ "925" => "Invalid TID.",
122
+ "926" => "Invalid Password.",
123
+ "930" => "Invalid zipcode.",
124
+ "931" => "Invalid Address.",
125
+ "932" => "Invalid ZIP and Address.",
126
+ "933" => "Invalid CVV2.",
127
+ "934" => "Program Not Allowed.",
128
+ "935" => "Invalid Device/App.",
129
+ "940" => "Record Not Found.",
130
+ "941" => "Merchant ID error.",
131
+ "942" => "Refund Not Allowed.",
132
+ "943" => "Refund denied.",
133
+ "955" => "Invalid PIN block.",
134
+ "956" => "Invalid KSN.",
135
+ "958" => "Bad Status.",
136
+ "959" => "Seek Record limit exceeded.",
137
+ "960" => "Internal Key Database Error.",
138
+ "961" => "TRANS not Supported. Cash Disbursement required a specific MCC.",
139
+ "962" => "Invalid PIN key (Unknown KSN).",
140
+ "981" => "Invalid AVS.",
141
+ "987" => "Issuer Unavailable.",
142
+ "988" => "System error SD.",
143
+ "989" => "Database Error.",
144
+ "992" => "Transaction Timeout.",
145
+ "996" => "Bad Terminal ID.",
146
+ "997" => "Message rejected by association.",
147
+ "999" => "Communication failure",
148
+ nil => "No response returned (missing credentials?)."
149
+ }
150
+
151
+ def initialize(options = {})
152
+ requires!(options, :login)
153
+ super
154
+ end
155
+
156
+ def purchase(money, payment, options = {})
157
+ commit(money, build_sale_request(money, payment, options))
158
+ end
159
+
160
+ def authorize(money, payment, options = {})
161
+ commit(money, build_authonly_request(money, payment, options))
162
+ end
163
+
164
+ def capture(money, reference, options = {})
165
+ transaction_id, _, _, token = reference.split(";")
166
+ commit(money, build_capture_request(money, transaction_id, options), token)
167
+ end
168
+
169
+ def void(reference, options = {})
170
+ transaction_id, _, amount, token = reference.split(";")
171
+ commit(amount.to_i, build_void_request(amount.to_i, transaction_id, options), token)
172
+ end
173
+
174
+ def credit(money, payment, options = {})
175
+ commit(money, build_credit_request(money, nil, payment, options))
176
+ end
177
+
178
+ def refund(money, reference, options = {})
179
+ transaction_id, _, _, token = reference.split(";")
180
+ commit(money, build_credit_request(money, transaction_id, token, options), token)
181
+ end
182
+
183
+ def verify(credit_card, options = {})
184
+ authorize(0, credit_card, options)
185
+ end
186
+
187
+ def store(credit_card, options = {})
188
+ commit(nil, build_store_request(credit_card, options))
189
+ end
190
+
191
+ def supports_scrubbing
192
+ true
193
+ end
194
+
195
+ def scrub(transcript)
196
+ transcript.
197
+ gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
198
+ gsub(%r((>)\d+(</CardNum>)), '\1[FILTERED]\2').
199
+ gsub(%r((<CVV2>)\d+(</CVV2>)), '\1[FILTERED]\2')
200
+ end
201
+
202
+ private
203
+
204
+ def build_xml_request(transaction_type, options = {}, transaction_id = nil, &block)
205
+ xml = Builder::XmlMarkup.new
206
+ xml.tag! 'JetPay', 'Version' => API_VERSION do
207
+ # Basic values needed for any request
208
+ xml.tag! 'TerminalID', @options[:login]
209
+ xml.tag! 'TransactionType', transaction_type
210
+ xml.tag! 'TransactionID', transaction_id.nil? ? generate_unique_id.slice(0, 18) : transaction_id
211
+ xml.tag! 'Origin', options[:origin] || 'INTERNET'
212
+ xml.tag! 'IndustryInfo', 'Type' => options[:industry_info] || 'ECOMMERCE'
213
+ xml.tag! 'Application', (options[:application] || 'n/a'), {'Version' => options[:application_version] || '1.0'}
214
+ xml.tag! 'Device', (options[:device] || 'n/a'), {'Version' => options[:device_version] || '1.0'}
215
+ xml.tag! 'Library', 'VirtPOS SDK', 'Version' => '1.5'
216
+ xml.tag! 'Gateway', 'JetPay'
217
+ xml.tag! 'DeveloperID', options[:developer_id] || 'n/a'
218
+
219
+ if block_given?
220
+ yield xml
221
+ else
222
+ xml.target!
223
+ end
224
+ end
225
+ end
226
+
227
+ def build_sale_request(money, payment, options)
228
+ build_xml_request('SALE', options) do |xml|
229
+ add_payment(xml, payment)
230
+ add_addresses(xml, options)
231
+ add_customer_data(xml, options)
232
+ add_invoice_data(xml, options)
233
+ add_user_defined_fields(xml, options)
234
+ xml.tag! 'TotalAmount', amount(money)
235
+
236
+ xml.target!
237
+ end
238
+ end
239
+
240
+ def build_authonly_request(money, payment, options)
241
+ build_xml_request('AUTHONLY', options) do |xml|
242
+ add_payment(xml, payment)
243
+ add_addresses(xml, options)
244
+ add_customer_data(xml, options)
245
+ add_invoice_data(xml, options)
246
+ add_user_defined_fields(xml, options)
247
+ xml.tag! 'TotalAmount', amount(money)
248
+
249
+ xml.target!
250
+ end
251
+ end
252
+
253
+ def build_capture_request(money, transaction_id, options)
254
+ build_xml_request('CAPT', options, transaction_id) do |xml|
255
+ add_invoice_data(xml, options)
256
+ add_purchase_order(xml, options)
257
+ add_user_defined_fields(xml, options)
258
+ xml.tag! 'TotalAmount', amount(money)
259
+
260
+ xml.target!
261
+ end
262
+ end
263
+
264
+ def build_void_request(money, transaction_id, options)
265
+ build_xml_request('VOID', options, transaction_id) do |xml|
266
+ xml.tag! 'TotalAmount', amount(money)
267
+ xml.target!
268
+ end
269
+ end
270
+
271
+ def build_credit_request(money, transaction_id, payment, options)
272
+ build_xml_request('CREDIT', options, transaction_id) do |xml|
273
+ add_payment(xml, payment)
274
+ add_invoice_data(xml, options)
275
+ add_addresses(xml, options)
276
+ add_customer_data(xml, options)
277
+ add_user_defined_fields(xml, options)
278
+ xml.tag! 'TotalAmount', amount(money)
279
+
280
+ xml.target!
281
+ end
282
+ end
283
+
284
+ def build_store_request(credit_card, options)
285
+ build_xml_request('TOKENIZE', options) do |xml|
286
+ add_payment(xml, credit_card)
287
+ add_addresses(xml, options)
288
+ add_customer_data(xml, options)
289
+
290
+ xml.target!
291
+ end
292
+ end
293
+
294
+ def commit(money, request, token = nil)
295
+ response = parse(ssl_post(url, request))
296
+
297
+ success = success?(response)
298
+ Response.new(success,
299
+ success ? 'APPROVED' : message_from(response),
300
+ response,
301
+ :test => test?,
302
+ :authorization => authorization_from(response, money, token),
303
+ :avs_result => AVSResult.new(:code => response[:avs]),
304
+ :cvv_result => CVVResult.new(response[:cvv2]),
305
+ :error_code => success ? nil : error_code_from(response)
306
+ )
307
+ end
308
+
309
+ def url
310
+ test? ? test_url : live_url
311
+ end
312
+
313
+ def parse(body)
314
+ return {} if body.blank?
315
+
316
+ xml = REXML::Document.new(body)
317
+
318
+ response = {}
319
+ xml.root.elements.to_a.each do |node|
320
+ parse_element(response, node)
321
+ end
322
+ response
323
+ end
324
+
325
+ def parse_element(response, node)
326
+ if node.has_elements?
327
+ node.elements.each{|element| parse_element(response, element) }
328
+ else
329
+ response[node.name.underscore.to_sym] = node.text
330
+ end
331
+ end
332
+
333
+ def format_exp(value)
334
+ format(value, :two_digits)
335
+ end
336
+
337
+ def success?(response)
338
+ response[:action_code] == "000"
339
+ end
340
+
341
+ def message_from(response)
342
+ ACTION_CODE_MESSAGES[response[:action_code]]
343
+ end
344
+
345
+ def authorization_from(response, money, previous_token)
346
+ original_amount = amount(money) if money
347
+ [ response[:transaction_id], response[:approval], original_amount, (response[:token] || previous_token)].join(";")
348
+ end
349
+
350
+ def error_code_from(response)
351
+ response[:action_code]
352
+ end
353
+
354
+ def add_payment(xml, payment)
355
+ return unless payment
356
+
357
+ if payment.is_a? String
358
+ token = payment
359
+ _, _, _, token = payment.split(";") if payment.include? ";"
360
+ xml.tag! 'Token', token if token
361
+ else
362
+ add_credit_card(xml, payment)
363
+ end
364
+ end
365
+
366
+ def add_credit_card(xml, credit_card)
367
+ xml.tag! 'CardNum', credit_card.number, "CardPresent" => false, "Tokenize" => true
368
+ xml.tag! 'CardExpMonth', format_exp(credit_card.month)
369
+ xml.tag! 'CardExpYear', format_exp(credit_card.year)
370
+
371
+ if credit_card.first_name || credit_card.last_name
372
+ xml.tag! 'CardName', [credit_card.first_name,credit_card.last_name].compact.join(' ')
373
+ end
374
+
375
+ unless credit_card.verification_value.nil? || (credit_card.verification_value.length == 0)
376
+ xml.tag! 'CVV2', credit_card.verification_value
377
+ end
378
+ end
379
+
380
+ def add_addresses(xml, options)
381
+ if billing_address = options[:billing_address] || options[:address]
382
+ xml.tag! 'Billing' do
383
+ xml.tag! 'Address', [billing_address[:address1], billing_address[:address2]].compact.join(" ")
384
+ xml.tag! 'City', billing_address[:city]
385
+ xml.tag! 'StateProv', billing_address[:state]
386
+ xml.tag! 'PostalCode', billing_address[:zip]
387
+ xml.tag! 'Country', lookup_country_code(billing_address[:country])
388
+ xml.tag! 'Phone', billing_address[:phone]
389
+ xml.tag! 'Email', options[:email] if options[:email]
390
+ end
391
+ end
392
+
393
+ if shipping_address = options[:shipping_address]
394
+ xml.tag! 'Shipping' do
395
+ xml.tag! 'Name', shipping_address[:name]
396
+ xml.tag! 'Address', [shipping_address[:address1], shipping_address[:address2]].compact.join(" ")
397
+ xml.tag! 'City', shipping_address[:city]
398
+ xml.tag! 'StateProv', shipping_address[:state]
399
+ xml.tag! 'PostalCode', shipping_address[:zip]
400
+ xml.tag! 'Country', lookup_country_code(shipping_address[:country])
401
+ xml.tag! 'Phone', shipping_address[:phone]
402
+ end
403
+ end
404
+ end
405
+
406
+ def add_customer_data(xml, options)
407
+ xml.tag! 'UserIPAddress', options[:ip] if options[:ip]
408
+ end
409
+
410
+ def add_invoice_data(xml, options)
411
+ xml.tag! 'OrderNumber', options[:order_id] if options[:order_id]
412
+ if tax_amount = options[:tax_amount]
413
+ xml.tag! 'TaxAmount', tax_amount, {'ExemptInd' => options[:tax_exempt] || "false"}
414
+ end
415
+ end
416
+
417
+ def add_purchase_order(xml, options)
418
+ if purchase_order = options[:purchase_order]
419
+ xml.tag! 'Billing' do
420
+ xml.tag! 'CustomerPO', purchase_order
421
+ end
422
+ end
423
+ end
424
+
425
+ def add_user_defined_fields(xml, options)
426
+ xml.tag! 'UDField1', options[:ud_field_1] if options[:ud_field_1]
427
+ xml.tag! 'UDField2', options[:ud_field_2] if options[:ud_field_2]
428
+ xml.tag! 'UDField3', options[:ud_field_3] if options[:ud_field_3]
429
+ end
430
+
431
+ def lookup_country_code(code)
432
+ country = Country.find(code) rescue nil
433
+ country && country.code(:alpha3)
434
+ end
435
+ end
436
+ end
437
+ end
@@ -0,0 +1,227 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class KushkiGateway < Gateway
4
+ self.display_name = "Kushki"
5
+ self.homepage_url = "https://www.kushkipagos.com"
6
+
7
+ self.test_url = "https://api-uat.kushkipagos.com/v1/"
8
+ self.live_url = "https://api.kushkipagos.com/v1/"
9
+
10
+ self.supported_countries = ["CO", "EC"]
11
+ self.default_currency = "USD"
12
+ self.money_format = :dollars
13
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club]
14
+
15
+ def initialize(options={})
16
+ requires!(options, :public_merchant_id, :private_merchant_id)
17
+ super
18
+ end
19
+
20
+ def purchase(amount, payment_method, options={})
21
+ MultiResponse.run() do |r|
22
+ r.process { tokenize(amount, payment_method, options) }
23
+ r.process { charge(amount, r.authorization, options) }
24
+ end
25
+ end
26
+
27
+ def refund(amount, authorization, options={})
28
+ action = "refund"
29
+
30
+ post = {}
31
+ post[:ticketNumber] = authorization
32
+
33
+ commit(action, post)
34
+ end
35
+
36
+ def void(authorization, options={})
37
+ action = "void"
38
+
39
+ post = {}
40
+ post[:ticketNumber] = authorization
41
+
42
+ commit(action, post)
43
+ end
44
+
45
+ def supports_scrubbing?
46
+ true
47
+ end
48
+
49
+ def scrub(transcript)
50
+ transcript.
51
+ gsub(%r((Private-Merchant-Id: )\d+), '\1[FILTERED]').
52
+ gsub(%r((\"card\\\":{\\\"number\\\":\\\")\d+), '\1[FILTERED]').
53
+ gsub(%r((\"cvv\\\":\\\")\d+), '\1[FILTERED]')
54
+ end
55
+
56
+ private
57
+
58
+ def tokenize(amount, payment_method, options)
59
+ action = "tokenize"
60
+
61
+ post = {}
62
+ add_invoice(action, post, amount, options)
63
+ add_payment_method(post, payment_method, options)
64
+
65
+ commit(action, post)
66
+ end
67
+
68
+ def charge(amount, authorization, options)
69
+ action = "charge"
70
+
71
+ post = {}
72
+ add_reference(post, authorization, options)
73
+ add_invoice(action, post, amount, options)
74
+
75
+ commit(action, post)
76
+ end
77
+
78
+ def add_invoice(action, post, money, options)
79
+ if action == "tokenize"
80
+ post[:totalAmount] = amount(money).to_f
81
+ post[:currency] = options[:currency] || currency(money)
82
+ post[:isDeferred] = false
83
+ else
84
+ sum = {}
85
+ sum[:currency] = options[:currency] || currency(money)
86
+ add_amount_defaults(sum, money, options)
87
+ add_amount_by_country(sum, options)
88
+ post[:amount] = sum
89
+ end
90
+ end
91
+
92
+ def add_amount_defaults(sum, money, options)
93
+ sum[:subtotalIva] = amount(money).to_f
94
+ sum[:iva] = 0
95
+ sum[:subtotalIva0] = 0
96
+
97
+ if sum[:currency] == "COP"
98
+ extra_taxes = {}
99
+ extra_taxes[:propina] = 0
100
+ extra_taxes[:tasaAeroportuaria] = 0
101
+ extra_taxes[:agenciaDeViaje] = 0
102
+ extra_taxes[:iac] = 0
103
+ sum[:extraTaxes] = extra_taxes
104
+ else
105
+ sum[:ice] = 0
106
+ end
107
+ end
108
+
109
+ def add_amount_by_country(sum, options)
110
+ if amount = options[:amount]
111
+ sum[:subtotalIva] = amount[:subtotal_iva].to_f if amount[:subtotal_iva]
112
+ sum[:iva] = amount[:iva].to_f if amount[:iva]
113
+ sum[:subtotalIva0] = amount[:subtotal_iva_0].to_f if amount[:subtotal_iva_0]
114
+ sum[:ice] = amount[:ice].to_f if amount[:ice]
115
+ if extra_taxes = amount[:extra_taxes] && sum[:currency] == "COP"
116
+ sum[:extraTaxes][:propina] = extra_taxes[:propina].to_f if extra_taxes[:propina]
117
+ sum[:extraTaxes][:tasaAeroportuaria] = extra_taxes[:tasa_aeroportuaria].to_f if extra_taxes[:tasa_aeroportuaria]
118
+ sum[:extraTaxes][:agenciaDeViaje] = extra_taxes[:agencia_de_viaje].to_f if extra_taxes[:agencia_de_viaje]
119
+ sum[:extraTaxes][:iac] = extra_taxes[:iac].to_f if extra_taxes[:iac]
120
+ end
121
+ end
122
+ end
123
+
124
+ def add_payment_method(post, payment_method, options)
125
+ card = {}
126
+ card[:number] = payment_method.number
127
+ card[:cvv] = payment_method.verification_value
128
+ card[:expiryMonth] = format(payment_method.month, :two_digits)
129
+ card[:expiryYear] = format(payment_method.year, :two_digits)
130
+ card[:name] = payment_method.name
131
+ post[:card] = card
132
+ end
133
+
134
+ def add_reference(post, authorization, options)
135
+ post[:token] = authorization
136
+ end
137
+
138
+ ENDPOINT = {
139
+ "tokenize" => "tokens",
140
+ "charge" => "charges",
141
+ "void" => "charges",
142
+ "refund" => "refund"
143
+ }
144
+
145
+ def commit(action, params)
146
+ response = begin
147
+ parse(ssl_invoke(action, params))
148
+ rescue ResponseError => e
149
+ parse(e.response.body)
150
+ end
151
+
152
+ success = success_from(response)
153
+
154
+ Response.new(
155
+ success,
156
+ message_from(success, response),
157
+ response,
158
+ authorization: success ? authorization_from(response) : nil,
159
+ error_code: success ? nil : error_from(response),
160
+ test: test?
161
+ )
162
+ end
163
+
164
+ def ssl_invoke(action, params)
165
+ if ["void", "refund"].include?(action)
166
+ ssl_request(:delete, url(action, params), nil, headers(action))
167
+ else
168
+ ssl_post(url(action, params), post_data(params), headers(action))
169
+ end
170
+ end
171
+
172
+ def headers(action)
173
+ hfields = {}
174
+ hfields["Public-Merchant-Id"] = @options[:public_merchant_id] if action == "tokenize"
175
+ hfields["Private-Merchant-Id"] = @options[:private_merchant_id] unless action == "tokenize"
176
+ hfields["Content-Type"] = "application/json"
177
+ hfields
178
+ end
179
+
180
+ def post_data(params)
181
+ params.to_json
182
+ end
183
+
184
+ def url(action, params)
185
+ base_url = test? ? test_url : live_url
186
+
187
+ if ["void", "refund"].include?(action)
188
+ base_url + ENDPOINT[action] + "/" + params[:ticketNumber].to_s
189
+ else
190
+ base_url + ENDPOINT[action]
191
+ end
192
+ end
193
+
194
+ def parse(body)
195
+ begin
196
+ JSON.parse(body)
197
+ rescue JSON::ParserError
198
+ message = "Invalid JSON response received from KushkiGateway. Please contact KushkiGateway if you continue to receive this message."
199
+ message += " (The raw response returned by the API was #{body.inspect})"
200
+ {
201
+ "message" => message
202
+ }
203
+ end
204
+ end
205
+
206
+ def success_from(response)
207
+ return true if response["token"] || response["ticketNumber"] || response["code"] == "K000"
208
+ end
209
+
210
+ def message_from(succeeded, response)
211
+ if succeeded
212
+ "Succeeded"
213
+ else
214
+ response["message"]
215
+ end
216
+ end
217
+
218
+ def authorization_from(response)
219
+ response["token"] || response["ticketNumber"]
220
+ end
221
+
222
+ def error_from(response)
223
+ response["code"]
224
+ end
225
+ end
226
+ end
227
+ end
@@ -143,9 +143,9 @@ module ActiveMerchant #:nodoc:
143
143
  :pem => LinkpointGateway.pem_file
144
144
  }.update(options)
145
145
 
146
- @options[:pem].strip!
147
-
148
146
  raise ArgumentError, "You need to pass in your pem file using the :pem parameter or set it globally using ActiveMerchant::Billing::LinkpointGateway.pem_file = File.read( File.dirname(__FILE__) + '/../mycert.pem' ) or similar" if @options[:pem].blank?
147
+
148
+ @options[:pem].strip!
149
149
  end
150
150
 
151
151
  # Send a purchase request with periodic options