start_activemerchant 1.50.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +1769 -0
  3. data/CONTRIBUTORS +540 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +226 -0
  6. data/lib/active_merchant.rb +67 -0
  7. data/lib/active_merchant/billing.rb +15 -0
  8. data/lib/active_merchant/billing/apple_pay_payment_token.rb +22 -0
  9. data/lib/active_merchant/billing/avs_result.rb +98 -0
  10. data/lib/active_merchant/billing/base.rb +72 -0
  11. data/lib/active_merchant/billing/check.rb +76 -0
  12. data/lib/active_merchant/billing/compatibility.rb +120 -0
  13. data/lib/active_merchant/billing/credit_card.rb +404 -0
  14. data/lib/active_merchant/billing/credit_card_formatting.rb +24 -0
  15. data/lib/active_merchant/billing/credit_card_methods.rb +195 -0
  16. data/lib/active_merchant/billing/cvv_result.rb +38 -0
  17. data/lib/active_merchant/billing/gateway.rb +291 -0
  18. data/lib/active_merchant/billing/gateways.rb +14 -0
  19. data/lib/active_merchant/billing/gateways/allied_wallet.rb +203 -0
  20. data/lib/active_merchant/billing/gateways/app55.rb +176 -0
  21. data/lib/active_merchant/billing/gateways/authorize_net.rb +510 -0
  22. data/lib/active_merchant/billing/gateways/authorize_net_arb.rb +417 -0
  23. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +976 -0
  24. data/lib/active_merchant/billing/gateways/axcessms.rb +181 -0
  25. data/lib/active_merchant/billing/gateways/balanced.rb +256 -0
  26. data/lib/active_merchant/billing/gateways/bank_frick.rb +225 -0
  27. data/lib/active_merchant/billing/gateways/banwire.rb +105 -0
  28. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +314 -0
  29. data/lib/active_merchant/billing/gateways/barclays_epdq_extra_plus.rb +15 -0
  30. data/lib/active_merchant/billing/gateways/be2bill.rb +131 -0
  31. data/lib/active_merchant/billing/gateways/beanstream.rb +192 -0
  32. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +389 -0
  33. data/lib/active_merchant/billing/gateways/beanstream_interac.rb +58 -0
  34. data/lib/active_merchant/billing/gateways/blue_pay.rb +506 -0
  35. data/lib/active_merchant/billing/gateways/bogus.rb +140 -0
  36. data/lib/active_merchant/billing/gateways/borgun.rb +211 -0
  37. data/lib/active_merchant/billing/gateways/bpoint.rb +277 -0
  38. data/lib/active_merchant/billing/gateways/braintree.rb +19 -0
  39. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +9 -0
  40. data/lib/active_merchant/billing/gateways/braintree_blue.rb +574 -0
  41. data/lib/active_merchant/billing/gateways/braintree_orange.rb +20 -0
  42. data/lib/active_merchant/billing/gateways/bridge_pay.rb +189 -0
  43. data/lib/active_merchant/billing/gateways/card_save.rb +23 -0
  44. data/lib/active_merchant/billing/gateways/card_stream.rb +238 -0
  45. data/lib/active_merchant/billing/gateways/cashnet.rb +202 -0
  46. data/lib/active_merchant/billing/gateways/cc5.rb +201 -0
  47. data/lib/active_merchant/billing/gateways/cecabank.rb +229 -0
  48. data/lib/active_merchant/billing/gateways/cenpos.rb +262 -0
  49. data/lib/active_merchant/billing/gateways/certo_direct.rb +278 -0
  50. data/lib/active_merchant/billing/gateways/checkout.rb +216 -0
  51. data/lib/active_merchant/billing/gateways/checkout_v2.rb +200 -0
  52. data/lib/active_merchant/billing/gateways/commercegate.rb +143 -0
  53. data/lib/active_merchant/billing/gateways/conekta.rb +210 -0
  54. data/lib/active_merchant/billing/gateways/cyber_source.rb +720 -0
  55. data/lib/active_merchant/billing/gateways/data_cash.rb +600 -0
  56. data/lib/active_merchant/billing/gateways/dibs.rb +206 -0
  57. data/lib/active_merchant/billing/gateways/efsnet.rb +219 -0
  58. data/lib/active_merchant/billing/gateways/elavon.rb +348 -0
  59. data/lib/active_merchant/billing/gateways/epay.rb +274 -0
  60. data/lib/active_merchant/billing/gateways/evo_ca.rb +308 -0
  61. data/lib/active_merchant/billing/gateways/eway.rb +214 -0
  62. data/lib/active_merchant/billing/gateways/eway_managed.rb +291 -0
  63. data/lib/active_merchant/billing/gateways/eway_rapid.rb +522 -0
  64. data/lib/active_merchant/billing/gateways/exact.rb +227 -0
  65. data/lib/active_merchant/billing/gateways/ezic.rb +206 -0
  66. data/lib/active_merchant/billing/gateways/fat_zebra.rb +213 -0
  67. data/lib/active_merchant/billing/gateways/federated_canada.rb +160 -0
  68. data/lib/active_merchant/billing/gateways/finansbank.rb +23 -0
  69. data/lib/active_merchant/billing/gateways/first_giving.rb +143 -0
  70. data/lib/active_merchant/billing/gateways/first_pay.rb +160 -0
  71. data/lib/active_merchant/billing/gateways/firstdata_e4.rb +413 -0
  72. data/lib/active_merchant/billing/gateways/flo2cash.rb +215 -0
  73. data/lib/active_merchant/billing/gateways/flo2cash_simple.rb +20 -0
  74. data/lib/active_merchant/billing/gateways/garanti.rb +261 -0
  75. data/lib/active_merchant/billing/gateways/global_transport.rb +179 -0
  76. data/lib/active_merchant/billing/gateways/hdfc.rb +207 -0
  77. data/lib/active_merchant/billing/gateways/hps.rb +287 -0
  78. data/lib/active_merchant/billing/gateways/iats_payments.rb +277 -0
  79. data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +246 -0
  80. data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +13 -0
  81. data/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +29 -0
  82. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +66 -0
  83. data/lib/active_merchant/billing/gateways/inspire.rb +219 -0
  84. data/lib/active_merchant/billing/gateways/instapay.rb +163 -0
  85. data/lib/active_merchant/billing/gateways/ipp.rb +175 -0
  86. data/lib/active_merchant/billing/gateways/iridium.rb +457 -0
  87. data/lib/active_merchant/billing/gateways/itransact.rb +448 -0
  88. data/lib/active_merchant/billing/gateways/jetpay.rb +275 -0
  89. data/lib/active_merchant/billing/gateways/linkpoint.rb +438 -0
  90. data/lib/active_merchant/billing/gateways/litle.rb +345 -0
  91. data/lib/active_merchant/billing/gateways/maxipago.rb +197 -0
  92. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +170 -0
  93. data/lib/active_merchant/billing/gateways/merchant_one.rb +114 -0
  94. data/lib/active_merchant/billing/gateways/merchant_partners.rb +245 -0
  95. data/lib/active_merchant/billing/gateways/merchant_ware.rb +319 -0
  96. data/lib/active_merchant/billing/gateways/merchant_ware_version_four.rb +268 -0
  97. data/lib/active_merchant/billing/gateways/merchant_warrior.rb +195 -0
  98. data/lib/active_merchant/billing/gateways/mercury.rb +326 -0
  99. data/lib/active_merchant/billing/gateways/metrics_global.rb +303 -0
  100. data/lib/active_merchant/billing/gateways/migs.rb +280 -0
  101. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
  102. data/lib/active_merchant/billing/gateways/modern_payments.rb +37 -0
  103. data/lib/active_merchant/billing/gateways/modern_payments_cim.rb +219 -0
  104. data/lib/active_merchant/billing/gateways/monei.rb +307 -0
  105. data/lib/active_merchant/billing/gateways/moneris.rb +309 -0
  106. data/lib/active_merchant/billing/gateways/moneris_us.rb +298 -0
  107. data/lib/active_merchant/billing/gateways/money_movers.rb +152 -0
  108. data/lib/active_merchant/billing/gateways/nab_transact.rb +290 -0
  109. data/lib/active_merchant/billing/gateways/net_registry.rb +198 -0
  110. data/lib/active_merchant/billing/gateways/netaxept.rb +181 -0
  111. data/lib/active_merchant/billing/gateways/netbilling.rb +224 -0
  112. data/lib/active_merchant/billing/gateways/netpay.rb +223 -0
  113. data/lib/active_merchant/billing/gateways/network_merchants.rb +242 -0
  114. data/lib/active_merchant/billing/gateways/nmi.rb +256 -0
  115. data/lib/active_merchant/billing/gateways/ogone.rb +435 -0
  116. data/lib/active_merchant/billing/gateways/omise.rb +319 -0
  117. data/lib/active_merchant/billing/gateways/openpay.rb +194 -0
  118. data/lib/active_merchant/billing/gateways/optimal_payment.rb +314 -0
  119. data/lib/active_merchant/billing/gateways/orbital.rb +834 -0
  120. data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +47 -0
  121. data/lib/active_merchant/billing/gateways/pac_net_raven.rb +207 -0
  122. data/lib/active_merchant/billing/gateways/pago_facil.rb +122 -0
  123. data/lib/active_merchant/billing/gateways/pay_conex.rb +246 -0
  124. data/lib/active_merchant/billing/gateways/pay_gate_xml.rb +277 -0
  125. data/lib/active_merchant/billing/gateways/pay_hub.rb +213 -0
  126. data/lib/active_merchant/billing/gateways/pay_junction.rb +390 -0
  127. data/lib/active_merchant/billing/gateways/pay_secure.rb +112 -0
  128. data/lib/active_merchant/billing/gateways/paybox_direct.rb +188 -0
  129. data/lib/active_merchant/billing/gateways/payex.rb +412 -0
  130. data/lib/active_merchant/billing/gateways/payflow.rb +308 -0
  131. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +220 -0
  132. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +39 -0
  133. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +13 -0
  134. data/lib/active_merchant/billing/gateways/payflow_express.rb +224 -0
  135. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +15 -0
  136. data/lib/active_merchant/billing/gateways/payflow_uk.rb +21 -0
  137. data/lib/active_merchant/billing/gateways/payment_express.rb +353 -0
  138. data/lib/active_merchant/billing/gateways/paymill.rb +282 -0
  139. data/lib/active_merchant/billing/gateways/paypal.rb +129 -0
  140. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +679 -0
  141. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +65 -0
  142. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +262 -0
  143. data/lib/active_merchant/billing/gateways/paypal_ca.rb +13 -0
  144. data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +44 -0
  145. data/lib/active_merchant/billing/gateways/paypal_express.rb +264 -0
  146. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +30 -0
  147. data/lib/active_merchant/billing/gateways/payscout.rb +162 -0
  148. data/lib/active_merchant/billing/gateways/paystation.rb +199 -0
  149. data/lib/active_merchant/billing/gateways/payu_in.rb +247 -0
  150. data/lib/active_merchant/billing/gateways/payway.rb +207 -0
  151. data/lib/active_merchant/billing/gateways/pin.rb +207 -0
  152. data/lib/active_merchant/billing/gateways/plugnpay.rb +283 -0
  153. data/lib/active_merchant/billing/gateways/psigate.rb +216 -0
  154. data/lib/active_merchant/billing/gateways/psl_card.rb +303 -0
  155. data/lib/active_merchant/billing/gateways/qbms.rb +292 -0
  156. data/lib/active_merchant/billing/gateways/quantum.rb +276 -0
  157. data/lib/active_merchant/billing/gateways/quickbooks.rb +280 -0
  158. data/lib/active_merchant/billing/gateways/quickpay.rb +26 -0
  159. data/lib/active_merchant/billing/gateways/quickpay/quickpay_common.rb +188 -0
  160. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v10.rb +240 -0
  161. data/lib/active_merchant/billing/gateways/quickpay/quickpay_v4to7.rb +227 -0
  162. data/lib/active_merchant/billing/gateways/qvalent.rb +179 -0
  163. data/lib/active_merchant/billing/gateways/realex.rb +298 -0
  164. data/lib/active_merchant/billing/gateways/redsys.rb +406 -0
  165. data/lib/active_merchant/billing/gateways/s5.rb +226 -0
  166. data/lib/active_merchant/billing/gateways/sage.rb +173 -0
  167. data/lib/active_merchant/billing/gateways/sage/sage_bankcard.rb +89 -0
  168. data/lib/active_merchant/billing/gateways/sage/sage_core.rb +115 -0
  169. data/lib/active_merchant/billing/gateways/sage/sage_vault.rb +149 -0
  170. data/lib/active_merchant/billing/gateways/sage/sage_virtual_check.rb +97 -0
  171. data/lib/active_merchant/billing/gateways/sage_pay.rb +399 -0
  172. data/lib/active_merchant/billing/gateways/sallie_mae.rb +143 -0
  173. data/lib/active_merchant/billing/gateways/secure_net.rb +263 -0
  174. data/lib/active_merchant/billing/gateways/secure_pay.rb +201 -0
  175. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +281 -0
  176. data/lib/active_merchant/billing/gateways/secure_pay_tech.rb +105 -0
  177. data/lib/active_merchant/billing/gateways/skip_jack.rb +451 -0
  178. data/lib/active_merchant/billing/gateways/smart_ps.rb +283 -0
  179. data/lib/active_merchant/billing/gateways/so_easy_pay.rb +194 -0
  180. data/lib/active_merchant/billing/gateways/spreedly_core.rb +247 -0
  181. data/lib/active_merchant/billing/gateways/stripe.rb +489 -0
  182. data/lib/active_merchant/billing/gateways/swipe_checkout.rb +157 -0
  183. data/lib/active_merchant/billing/gateways/tns.rb +227 -0
  184. data/lib/active_merchant/billing/gateways/trans_first.rb +126 -0
  185. data/lib/active_merchant/billing/gateways/transax.rb +23 -0
  186. data/lib/active_merchant/billing/gateways/transnational.rb +10 -0
  187. data/lib/active_merchant/billing/gateways/trust_commerce.rb +416 -0
  188. data/lib/active_merchant/billing/gateways/usa_epay.rb +25 -0
  189. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +1516 -0
  190. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +259 -0
  191. data/lib/active_merchant/billing/gateways/vanco.rb +280 -0
  192. data/lib/active_merchant/billing/gateways/verifi.rb +225 -0
  193. data/lib/active_merchant/billing/gateways/viaklix.rb +183 -0
  194. data/lib/active_merchant/billing/gateways/webpay.rb +97 -0
  195. data/lib/active_merchant/billing/gateways/wepay.rb +205 -0
  196. data/lib/active_merchant/billing/gateways/wirecard.rb +420 -0
  197. data/lib/active_merchant/billing/gateways/worldpay.rb +331 -0
  198. data/lib/active_merchant/billing/gateways/worldpay_online_payments.rb +204 -0
  199. data/lib/active_merchant/billing/gateways/worldpay_us.rb +181 -0
  200. data/lib/active_merchant/billing/model.rb +30 -0
  201. data/lib/active_merchant/billing/network_tokenization_credit_card.rb +24 -0
  202. data/lib/active_merchant/billing/payment_token.rb +21 -0
  203. data/lib/active_merchant/billing/rails.rb +3 -0
  204. data/lib/active_merchant/billing/response.rb +92 -0
  205. data/lib/active_merchant/connection.rb +172 -0
  206. data/lib/active_merchant/country.rb +332 -0
  207. data/lib/active_merchant/empty.rb +20 -0
  208. data/lib/active_merchant/errors.rb +35 -0
  209. data/lib/active_merchant/network_connection_retries.rb +79 -0
  210. data/lib/active_merchant/post_data.rb +24 -0
  211. data/lib/active_merchant/posts_data.rb +84 -0
  212. data/lib/active_merchant/version.rb +3 -0
  213. data/lib/activemerchant.rb +1 -0
  214. data/lib/certs/cacert.pem +3866 -0
  215. data/lib/support/gateway_support.rb +71 -0
  216. data/lib/support/outbound_hosts.rb +28 -0
  217. data/lib/support/ssl_verify.rb +93 -0
  218. metadata +387 -0
@@ -0,0 +1,314 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ class OptimalPaymentGateway < Gateway
4
+ self.test_url = 'https://webservices.test.optimalpayments.com/creditcardWS/CreditCardServlet/v1'
5
+ self.live_url = 'https://webservices.optimalpayments.com/creditcardWS/CreditCardServlet/v1'
6
+
7
+ # The countries the gateway supports merchants from as 2 digit ISO country codes
8
+ self.supported_countries = ['CA', 'US', 'GB']
9
+
10
+ # The card types supported by the payment gateway
11
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :solo] # :switch?
12
+
13
+ # The homepage URL of the gateway
14
+ self.homepage_url = 'http://www.optimalpayments.com/'
15
+
16
+ # The name of the gateway
17
+ self.display_name = 'Optimal Payments'
18
+
19
+ def initialize(options = {})
20
+
21
+ if(options[:login])
22
+ ActiveMerchant.deprecated("The 'login' option is deprecated in favor of 'store_id' and will be removed in a future version.")
23
+ options[:store_id] = options[:login]
24
+ end
25
+
26
+ if(options[:account])
27
+ ActiveMerchant.deprecated("The 'account' option is deprecated in favor of 'account_number' and will be removed in a future version.")
28
+ options[:account_number] = options[:account]
29
+ end
30
+
31
+ requires!(options, :account_number, :store_id, :password)
32
+ super
33
+ end
34
+
35
+ def authorize(money, card_or_auth, options = {})
36
+ parse_card_or_auth(card_or_auth, options)
37
+ commit("cc#{@stored_data}Authorize", money, options)
38
+ end
39
+ alias stored_authorize authorize # back-compat
40
+
41
+ def purchase(money, card_or_auth, options = {})
42
+ parse_card_or_auth(card_or_auth, options)
43
+ commit("cc#{@stored_data}Purchase", money, options)
44
+ end
45
+ alias stored_purchase purchase # back-compat
46
+
47
+ def refund(money, authorization, options = {})
48
+ options[:confirmationNumber] = authorization
49
+ commit('ccCredit', money, options)
50
+ end
51
+
52
+ def void(authorization, options = {})
53
+ options[:confirmationNumber] = authorization
54
+ commit('ccAuthorizeReversal', nil, options)
55
+ end
56
+
57
+ def capture(money, authorization, options = {})
58
+ options[:confirmationNumber] = authorization
59
+ commit('ccSettlement', money, options)
60
+ end
61
+
62
+ private
63
+
64
+ def parse_card_or_auth(card_or_auth, options)
65
+ if card_or_auth.respond_to?(:number)
66
+ @credit_card = card_or_auth
67
+ @stored_data = ""
68
+ else
69
+ options[:confirmationNumber] = card_or_auth
70
+ @stored_data = "StoredData"
71
+ end
72
+ end
73
+
74
+ def parse(body)
75
+ REXML::Document.new(body || '')
76
+ end
77
+
78
+ def commit(action, money, post)
79
+ post[:order_id] ||= 'order_id'
80
+
81
+ xml = case action
82
+ when 'ccAuthorize', 'ccPurchase', 'ccVerification'
83
+ cc_auth_request(money, post)
84
+ when 'ccCredit', 'ccSettlement'
85
+ cc_post_auth_request(money, post)
86
+ when 'ccStoredDataAuthorize', 'ccStoredDataPurchase'
87
+ cc_stored_data_request(money, post)
88
+ when 'ccAuthorizeReversal'
89
+ cc_auth_reversal_request(post)
90
+ #when 'ccCancelSettle', 'ccCancelCredit', 'ccCancelPayment'
91
+ # cc_cancel_request(money, post)
92
+ #when 'ccPayment'
93
+ # cc_payment_request(money, post)
94
+ #when 'ccAuthenticate'
95
+ # cc_authenticate_request(money, post)
96
+ else
97
+ raise 'Unknown Action'
98
+ end
99
+ txnRequest = escape_uri(xml)
100
+ response = parse(ssl_post(test? ? self.test_url : self.live_url, "txnMode=#{action}&txnRequest=#{txnRequest}"))
101
+
102
+ Response.new(successful?(response), message_from(response), hash_from_xml(response),
103
+ :test => test?,
104
+ :authorization => authorization_from(response),
105
+ :avs_result => { :code => avs_result_from(response) },
106
+ :cvv_result => cvv_result_from(response)
107
+ )
108
+ end
109
+
110
+ # The upstream is picky and so we can't use CGI.escape like we want to
111
+ def escape_uri(uri)
112
+ URI::DEFAULT_PARSER.escape(uri)
113
+ end
114
+
115
+ def successful?(response)
116
+ REXML::XPath.first(response, '//decision').text == 'ACCEPTED' rescue false
117
+ end
118
+
119
+ def message_from(response)
120
+ REXML::XPath.each(response, '//detail') do |detail|
121
+ if detail.is_a?(REXML::Element) && detail.elements['tag'].text == 'InternalResponseDescription'
122
+ return detail.elements['value'].text
123
+ end
124
+ end
125
+ nil
126
+ end
127
+
128
+ def authorization_from(response)
129
+ get_text_from_document(response, '//confirmationNumber')
130
+ end
131
+
132
+ def avs_result_from(response)
133
+ get_text_from_document(response, '//avsResponse')
134
+ end
135
+
136
+ def cvv_result_from(response)
137
+ get_text_from_document(response, '//cvdResponse')
138
+ end
139
+
140
+ def hash_from_xml(response)
141
+ hsh = {}
142
+ %w(confirmationNumber authCode
143
+ decision code description
144
+ actionCode avsResponse cvdResponse
145
+ txnTime duplicateFound
146
+ ).each do |tag|
147
+ node = REXML::XPath.first(response, "//#{tag}")
148
+ hsh[tag] = node.text if node
149
+ end
150
+ REXML::XPath.each(response, '//detail') do |detail|
151
+ next unless detail.is_a?(REXML::Element)
152
+ tag = detail.elements['tag'].text
153
+ value = detail.elements['value'].text
154
+ hsh[tag] = value
155
+ end
156
+ hsh
157
+ end
158
+
159
+ def xml_document(root_tag)
160
+ xml = Builder::XmlMarkup.new :indent => 2
161
+ xml.tag!(root_tag, schema) do
162
+ yield xml
163
+ end
164
+ xml.target!
165
+ end
166
+
167
+ def get_text_from_document(document, node)
168
+ node = REXML::XPath.first(document, node)
169
+ node && node.text
170
+ end
171
+
172
+ def cc_auth_request(money, opts)
173
+ xml_document('ccAuthRequestV1') do |xml|
174
+ build_merchant_account(xml)
175
+ xml.merchantRefNum opts[:order_id]
176
+ xml.amount(money/100.0)
177
+ build_card(xml, opts)
178
+ build_billing_details(xml, opts)
179
+ build_shipping_details(xml, opts)
180
+ xml.customerIP opts[:ip] if opts[:ip]
181
+ end
182
+ end
183
+
184
+ def cc_auth_reversal_request(opts)
185
+ xml_document('ccAuthReversalRequestV1') do |xml|
186
+ build_merchant_account(xml)
187
+ xml.confirmationNumber opts[:confirmationNumber]
188
+ xml.merchantRefNum opts[:order_id]
189
+ end
190
+ end
191
+
192
+ def cc_post_auth_request(money, opts)
193
+ xml_document('ccPostAuthRequestV1') do |xml|
194
+ build_merchant_account(xml)
195
+ xml.confirmationNumber opts[:confirmationNumber]
196
+ xml.merchantRefNum opts[:order_id]
197
+ xml.amount(money/100.0)
198
+ end
199
+ end
200
+
201
+ def cc_stored_data_request(money, opts)
202
+ xml_document('ccStoredDataRequestV1') do |xml|
203
+ build_merchant_account(xml)
204
+ xml.merchantRefNum opts[:order_id]
205
+ xml.confirmationNumber opts[:confirmationNumber]
206
+ xml.amount(money/100.0)
207
+ end
208
+ end
209
+
210
+ # untested
211
+ #
212
+ # def cc_cancel_request(opts)
213
+ # xml_document('ccCancelRequestV1') do |xml|
214
+ # build_merchant_account(xml)
215
+ # xml.confirmationNumber opts[:confirmationNumber]
216
+ # end
217
+ # end
218
+ #
219
+ # def cc_payment_request(money, opts)
220
+ # xml_document('ccPaymentRequestV1') do |xml|
221
+ # build_merchant_account(xml)
222
+ # xml.merchantRefNum opts[:order_id]
223
+ # xml.amount(money/100.0)
224
+ # build_card(xml, opts)
225
+ # build_billing_details(xml, opts)
226
+ # end
227
+ # end
228
+ #
229
+ # def cc_authenticate_request(opts)
230
+ # xml_document('ccAuthenticateRequestV1') do |xml|
231
+ # build_merchant_account(xml)
232
+ # xml.confirmationNumber opts[:confirmationNumber]
233
+ # xml.paymentResponse 'myPaymentResponse'
234
+ # end
235
+ # end
236
+
237
+ def schema
238
+ { 'xmlns' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1',
239
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
240
+ 'xsi:schemaLocation' => 'http://www.optimalpayments.com/creditcard/xmlschema/v1'
241
+ }
242
+ end
243
+
244
+ def build_merchant_account(xml)
245
+ xml.tag! 'merchantAccount' do
246
+ xml.tag! 'accountNum' , @options[:account_number]
247
+ xml.tag! 'storeID' , @options[:store_id]
248
+ xml.tag! 'storePwd' , @options[:password]
249
+ end
250
+ end
251
+
252
+ def build_card(xml, opts)
253
+ xml.tag! 'card' do
254
+ xml.tag! 'cardNum' , @credit_card.number
255
+ xml.tag! 'cardExpiry' do
256
+ xml.tag! 'month' , @credit_card.month
257
+ xml.tag! 'year' , @credit_card.year
258
+ end
259
+ if brand = card_type(@credit_card.brand)
260
+ xml.tag! 'cardType' , brand
261
+ end
262
+ if @credit_card.verification_value
263
+ xml.tag! 'cvdIndicator' , '1' # Value Provided
264
+ xml.tag! 'cvd' , @credit_card.verification_value
265
+ end
266
+ end
267
+ end
268
+
269
+ def build_billing_details(xml, opts)
270
+ xml.tag! 'billingDetails' do
271
+ xml.tag! 'cardPayMethod', 'WEB'
272
+ build_address(xml, opts[:billing_address]) if opts[:billing_address]
273
+ xml.tag! 'email', opts[:email] if opts[:email]
274
+ end
275
+ end
276
+
277
+ def build_shipping_details(xml, opts)
278
+ xml.tag! 'shippingDetails' do
279
+ build_address(xml, opts[:shipping_address])
280
+ xml.tag! 'email', opts[:email] if opts[:email]
281
+ end if opts[:shipping_address].present?
282
+ end
283
+
284
+ def build_address(xml, addr)
285
+ if addr[:name]
286
+ xml.tag! 'firstName', addr[:name].split(' ').first
287
+ xml.tag! 'lastName' , addr[:name].split(' ').last
288
+ end
289
+ xml.tag! 'street' , addr[:address1] if addr[:address1].present?
290
+ xml.tag! 'street2', addr[:address2] if addr[:address2].present?
291
+ xml.tag! 'city' , addr[:city] if addr[:city].present?
292
+ if addr[:state].present?
293
+ state_tag = %w(US CA).include?(addr[:country]) ? 'state' : 'region'
294
+ xml.tag! state_tag, addr[:state]
295
+ end
296
+ xml.tag! 'country', addr[:country] if addr[:country].present?
297
+ xml.tag! 'zip' , addr[:zip] if addr[:zip].present?
298
+ xml.tag! 'phone' , addr[:phone] if addr[:phone].present?
299
+ end
300
+
301
+ def card_type(key)
302
+ { 'visa' => 'VI',
303
+ 'master' => 'MC',
304
+ 'american_express'=> 'AM',
305
+ 'discover' => 'DI',
306
+ 'diners_club' => 'DC',
307
+ #'switch' => '',
308
+ 'solo' => 'SO'
309
+ }[key]
310
+ end
311
+
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,834 @@
1
+ require 'active_merchant/billing/gateways/orbital/orbital_soft_descriptors'
2
+ require "rexml/document"
3
+
4
+ module ActiveMerchant #:nodoc:
5
+ module Billing #:nodoc:
6
+ # For more information on Orbital, visit the {integration center}[http://download.chasepaymentech.com]
7
+ #
8
+ # ==== Authentication Options
9
+ #
10
+ # The Orbital Gateway supports two methods of authenticating incoming requests:
11
+ # Source IP authentication and Connection Username/Password authentication
12
+ #
13
+ # In addition, these IP addresses/Connection Usernames must be affiliated with the Merchant IDs
14
+ # for which the client should be submitting transactions.
15
+ #
16
+ # This does allow Third Party Hosting service organizations presenting on behalf of other
17
+ # merchants to submit transactions. However, each time a new customer is added, the
18
+ # merchant or Third-Party hosting organization needs to ensure that the new Merchant IDs
19
+ # or Chain IDs are affiliated with the hosting companies IPs or Connection Usernames.
20
+ #
21
+ # If the merchant expects to have more than one merchant account with the Orbital
22
+ # Gateway, it should have its IP addresses/Connection Usernames affiliated at the Chain
23
+ # level hierarchy within the Orbital Gateway. Each time a new merchant ID is added, as
24
+ # long as it is placed within the same Chain, it will simply work. Otherwise, the additional
25
+ # MIDs will need to be affiliated with the merchant IPs or Connection Usernames respectively.
26
+ # For example, we generally affiliate all Salem accounts [BIN 000001] with
27
+ # their Company Number [formerly called MA #] number so all MIDs or Divisions under that
28
+ # Company will automatically be affiliated.
29
+
30
+ class OrbitalGateway < Gateway
31
+ API_VERSION = "5.6"
32
+
33
+ POST_HEADERS = {
34
+ "MIME-Version" => "1.1",
35
+ "Content-Type" => "application/PTI56",
36
+ "Content-transfer-encoding" => "text",
37
+ "Request-number" => '1',
38
+ "Document-type" => "Request",
39
+ "Interface-Version" => "Ruby|ActiveMerchant|Proprietary Gateway"
40
+ }
41
+
42
+ SUCCESS = '0'
43
+
44
+ APPROVED = [
45
+ '00', # Approved
46
+ '08', # Approved authorization, honor with ID
47
+ '11', # Approved authorization, VIP approval
48
+ '24', # Validated
49
+ '26', # Pre-noted
50
+ '27', # No reason to decline
51
+ '28', # Received and stored
52
+ '29', # Provided authorization
53
+ '31', # Request received
54
+ '32', # BIN alert
55
+ '34', # Approved for partial
56
+ '91', # Approved low fraud
57
+ '92', # Approved medium fraud
58
+ '93', # Approved high fraud
59
+ '94', # Approved fraud service unavailable
60
+ 'E7', # Stored
61
+ 'PA' # Partial approval
62
+ ]
63
+
64
+ class_attribute :secondary_test_url, :secondary_live_url
65
+
66
+ self.test_url = "https://orbitalvar1.paymentech.net/authorize"
67
+ self.secondary_test_url = "https://orbitalvar2.paymentech.net/authorize"
68
+
69
+ self.live_url = "https://orbital1.paymentech.net/authorize"
70
+ self.secondary_live_url = "https://orbital2.paymentech.net/authorize"
71
+
72
+ self.supported_countries = ["US", "CA"]
73
+ self.default_currency = "CAD"
74
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
75
+
76
+ self.display_name = 'Orbital Paymentech'
77
+ self.homepage_url = 'http://chasepaymentech.com/'
78
+
79
+ self.money_format = :cents
80
+
81
+ AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB']
82
+
83
+ CURRENCY_CODES = {
84
+ "AUD" => '036',
85
+ "CAD" => '124',
86
+ "CZK" => '203',
87
+ "DKK" => '208',
88
+ "HKD" => '344',
89
+ "ICK" => '352',
90
+ "JPY" => '392',
91
+ "MXN" => '484',
92
+ "NZD" => '554',
93
+ "NOK" => '578',
94
+ "SGD" => '702',
95
+ "SEK" => '752',
96
+ "CHF" => '756',
97
+ "GBP" => '826',
98
+ "USD" => '840',
99
+ "EUR" => '978'
100
+ }
101
+
102
+ CURRENCY_EXPONENTS = {
103
+ "AUD" => '2',
104
+ "CAD" => '2',
105
+ "CZK" => '2',
106
+ "DKK" => '2',
107
+ "HKD" => '2',
108
+ "ICK" => '2',
109
+ "JPY" => '0',
110
+ "MXN" => '2',
111
+ "NZD" => '2',
112
+ "NOK" => '2',
113
+ "SGD" => '2',
114
+ "SEK" => '2',
115
+ "CHF" => '2',
116
+ "GBP" => '2',
117
+ "USD" => '2',
118
+ "EUR" => '2'
119
+ }
120
+
121
+ # INDUSTRY TYPES
122
+ ECOMMERCE_TRANSACTION = 'EC'
123
+ RECURRING_PAYMENT_TRANSACTION = 'RC'
124
+ MAIL_ORDER_TELEPHONE_ORDER_TRANSACTION = 'MO'
125
+ INTERACTIVE_VOICE_RESPONSE = 'IV'
126
+ # INTERACTIVE_VOICE_RESPONSE = 'IN'
127
+
128
+ # Auth Only No Capture
129
+ AUTH_ONLY = 'A'
130
+ # AC - Auth and Capture = 'AC'
131
+ AUTH_AND_CAPTURE = 'AC'
132
+ # F - Force Auth No Capture and no online authorization = 'F'
133
+ FORCE_AUTH_ONLY = 'F'
134
+ # FR - Force Auth No Capture and no online authorization = 'FR'
135
+ # FC - Force Auth and Capture no online authorization = 'FC'
136
+ FORCE_AUTH_AND_CAPTURE = 'FC'
137
+ # Refund and Capture no online authorization
138
+ REFUND = 'R'
139
+
140
+ # Tax Inds
141
+ TAX_NOT_PROVIDED = 0
142
+ TAX_INCLUDED = 1
143
+ NON_TAXABLE_TRANSACTION = 2
144
+
145
+ # Customer Profile Actions
146
+ CREATE = 'C'
147
+ RETRIEVE = 'R'
148
+ UPDATE = 'U'
149
+ DELETE = 'D'
150
+
151
+ RECURRING = 'R'
152
+ DEFERRED = 'D'
153
+
154
+ # Status
155
+ # Profile Status Flag
156
+ # This field is used to set the status of a Customer Profile.
157
+ ACTIVE = 'A'
158
+ INACTIVE = 'I'
159
+ MANUAL_SUSPEND = 'MS'
160
+
161
+ # CustomerProfileOrderOverrideInd
162
+ # Defines if any Order Data can be pre-populated from
163
+ # the Customer Reference Number (CustomerRefNum)
164
+ NO_MAPPING_TO_ORDER_DATA = 'NO'
165
+ USE_CRN_FOR_ORDER_ID = 'OI'
166
+ USE_CRN_FOR_COMMENTS = 'OD'
167
+ USE_CRN_FOR_ORDER_ID_AND_COMMENTS = 'OA'
168
+
169
+ # CustomerProfileFromOrderInd
170
+ # Method to use to Generate the Customer Profile Number
171
+ # When Customer Profile Action Type = Create, defines
172
+ # what the Customer Profile Number will be:
173
+ AUTO_GENERATE = 'A' # Auto-Generate the CustomerRefNum
174
+ USE_CUSTOMER_REF_NUM = 'S' # Use CustomerRefNum field
175
+ USE_ORDER_ID = 'O' # Use OrderID field
176
+ USE_COMMENTS = 'D' # Use Comments field
177
+
178
+ SENSITIVE_FIELDS = [:account_num]
179
+
180
+ def initialize(options = {})
181
+ requires!(options, :merchant_id)
182
+ requires!(options, :login, :password) unless options[:ip_authentication]
183
+ super
184
+ end
185
+
186
+ # A – Authorization request
187
+ def authorize(money, creditcard, options = {})
188
+ order = build_new_order_xml(AUTH_ONLY, money, options) do |xml|
189
+ add_creditcard(xml, creditcard, options[:currency])
190
+ add_address(xml, creditcard, options)
191
+ if @options[:customer_profiles]
192
+ add_customer_data(xml, creditcard, options)
193
+ add_managed_billing(xml, options)
194
+ end
195
+ end
196
+ commit(order, :authorize, options[:trace_number])
197
+ end
198
+
199
+ def verify(creditcard, options = {})
200
+ MultiResponse.run(:use_first_response) do |r|
201
+ r.process { authorize(100, creditcard, options) }
202
+ r.process(:ignore_result) { void(r.authorization) }
203
+ end
204
+ end
205
+
206
+ # AC – Authorization and Capture
207
+ def purchase(money, creditcard, options = {})
208
+ order = build_new_order_xml(AUTH_AND_CAPTURE, money, options) do |xml|
209
+ add_creditcard(xml, creditcard, options[:currency])
210
+ add_address(xml, creditcard, options)
211
+ if @options[:customer_profiles]
212
+ add_customer_data(xml, creditcard, options)
213
+ add_managed_billing(xml, options)
214
+ end
215
+ end
216
+ commit(order, :purchase, options[:trace_number])
217
+ end
218
+
219
+ # MFC - Mark For Capture
220
+ def capture(money, authorization, options = {})
221
+ commit(build_mark_for_capture_xml(money, authorization, options), :capture)
222
+ end
223
+
224
+ # R – Refund request
225
+ def refund(money, authorization, options = {})
226
+ order = build_new_order_xml(REFUND, money, options.merge(:authorization => authorization)) do |xml|
227
+ add_refund(xml, options[:currency])
228
+ xml.tag! :CustomerRefNum, options[:customer_ref_num] if @options[:customer_profiles] && options[:profile_txn]
229
+ end
230
+ commit(order, :refund, options[:trace_number])
231
+ end
232
+
233
+ def credit(money, authorization, options= {})
234
+ ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
235
+ refund(money, authorization, options)
236
+ end
237
+
238
+ def void(authorization, options = {}, deprecated = {})
239
+ if(!options.kind_of?(Hash))
240
+ ActiveMerchant.deprecated("Calling the void method with an amount parameter is deprecated and will be removed in a future version.")
241
+ return void(options, deprecated.merge(:amount => authorization))
242
+ end
243
+
244
+ order = build_void_request_xml(authorization, options)
245
+ commit(order, :void, options[:trace_number])
246
+ end
247
+
248
+
249
+ # ==== Customer Profiles
250
+ # :customer_ref_num should be set unless you're happy with Orbital providing one
251
+ #
252
+ # :customer_profile_order_override_ind can be set to map
253
+ # the CustomerRefNum to OrderID or Comments. Defaults to 'NO' - no mapping
254
+ #
255
+ # 'NO' - No mapping to order data
256
+ # 'OI' - Use <CustomerRefNum> for <OrderID>
257
+ # 'OD' - Use <CustomerRefNum> for <Comments>
258
+ # 'OA' - Use <CustomerRefNum> for <OrderID> and <Comments>
259
+ #
260
+ # :order_default_description can be set optionally. 64 char max.
261
+ #
262
+ # :order_default_amount can be set optionally. integer as cents.
263
+ #
264
+ # :status defaults to Active
265
+ #
266
+ # 'A' - Active
267
+ # 'I' - Inactive
268
+ # 'MS' - Manual Suspend
269
+
270
+ def add_customer_profile(creditcard, options = {})
271
+ options.merge!(:customer_profile_action => CREATE)
272
+ order = build_customer_request_xml(creditcard, options)
273
+ commit(order, :add_customer_profile)
274
+ end
275
+
276
+ def update_customer_profile(creditcard, options = {})
277
+ options.merge!(:customer_profile_action => UPDATE)
278
+ order = build_customer_request_xml(creditcard, options)
279
+ commit(order, :update_customer_profile)
280
+ end
281
+
282
+ def retrieve_customer_profile(customer_ref_num)
283
+ options = {:customer_profile_action => RETRIEVE, :customer_ref_num => customer_ref_num}
284
+ order = build_customer_request_xml(nil, options)
285
+ commit(order, :retrieve_customer_profile)
286
+ end
287
+
288
+ def delete_customer_profile(customer_ref_num)
289
+ options = {:customer_profile_action => DELETE, :customer_ref_num => customer_ref_num}
290
+ order = build_customer_request_xml(nil, options)
291
+ commit(order, :delete_customer_profile)
292
+ end
293
+
294
+ private
295
+
296
+ def authorization_string(*args)
297
+ args.compact.join(";")
298
+ end
299
+
300
+ def split_authorization(authorization)
301
+ authorization.split(';')
302
+ end
303
+
304
+ def add_customer_data(xml, creditcard, options)
305
+ if options[:profile_txn]
306
+ xml.tag! :CustomerRefNum, options[:customer_ref_num]
307
+ else
308
+ if options[:customer_ref_num]
309
+ if creditcard
310
+ xml.tag! :CustomerProfileFromOrderInd, USE_CUSTOMER_REF_NUM
311
+ end
312
+ xml.tag! :CustomerRefNum, options[:customer_ref_num]
313
+ else
314
+ xml.tag! :CustomerProfileFromOrderInd, AUTO_GENERATE
315
+ end
316
+ xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || NO_MAPPING_TO_ORDER_DATA
317
+ end
318
+ end
319
+
320
+ def add_soft_descriptors(xml, soft_desc)
321
+ xml.tag! :SDMerchantName, soft_desc.merchant_name if soft_desc.merchant_name
322
+ xml.tag! :SDProductDescription, soft_desc.product_description if soft_desc.product_description
323
+ xml.tag! :SDMerchantCity, soft_desc.merchant_city if soft_desc.merchant_city
324
+ xml.tag! :SDMerchantPhone, soft_desc.merchant_phone if soft_desc.merchant_phone
325
+ xml.tag! :SDMerchantURL, soft_desc.merchant_url if soft_desc.merchant_url
326
+ xml.tag! :SDMerchantEmail, soft_desc.merchant_email if soft_desc.merchant_email
327
+ end
328
+
329
+ def add_address(xml, creditcard, options)
330
+ if(address = (options[:billing_address] || options[:address]))
331
+ avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
332
+
333
+ if avs_supported
334
+ xml.tag! :AVSzip, byte_limit(format_address_field(address[:zip]), 10)
335
+ xml.tag! :AVSaddress1, byte_limit(format_address_field(address[:address1]), 30)
336
+ xml.tag! :AVSaddress2, byte_limit(format_address_field(address[:address2]), 30)
337
+ xml.tag! :AVScity, byte_limit(format_address_field(address[:city]), 20)
338
+ xml.tag! :AVSstate, byte_limit(format_address_field(address[:state]), 2)
339
+ xml.tag! :AVSphoneNum, (address[:phone] ? address[:phone].scan(/\d/).join.to_s[0..13] : nil)
340
+ end
341
+ # can't look in billing address?
342
+ xml.tag! :AVSname, ((creditcard && creditcard.name) ? creditcard.name[0..29] : nil)
343
+ xml.tag! :AVScountryCode, (avs_supported ? address[:country] : '')
344
+
345
+ # Needs to come after AVScountryCode
346
+ add_destination_address(xml, address) if avs_supported
347
+ end
348
+ end
349
+
350
+ def add_destination_address(xml, address)
351
+ if address[:dest_zip]
352
+ avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:dest_country].to_s)
353
+
354
+ xml.tag! :AVSDestzip, byte_limit(format_address_field(address[:dest_zip]), 10)
355
+ xml.tag! :AVSDestaddress1, byte_limit(format_address_field(address[:dest_address1]), 30)
356
+ xml.tag! :AVSDestaddress2, byte_limit(format_address_field(address[:dest_address2]), 30)
357
+ xml.tag! :AVSDestcity, byte_limit(format_address_field(address[:dest_city]), 20)
358
+ xml.tag! :AVSDeststate, byte_limit(format_address_field(address[:dest_state]), 2)
359
+ xml.tag! :AVSDestphoneNum, (address[:dest_phone] ? address[:dest_phone].scan(/\d/).join.to_s[0..13] : nil)
360
+
361
+ xml.tag! :AVSDestname, byte_limit(address[:dest_name], 30)
362
+ xml.tag! :AVSDestcountryCode, (avs_supported ? address[:dest_country] : '')
363
+ end
364
+ end
365
+
366
+ # For Profile requests
367
+ def add_customer_address(xml, options)
368
+ if(address = (options[:billing_address] || options[:address]))
369
+ avs_supported = AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
370
+
371
+ xml.tag! :CustomerAddress1, byte_limit(format_address_field(address[:address1]), 30)
372
+ xml.tag! :CustomerAddress2, byte_limit(format_address_field(address[:address2]), 30)
373
+ xml.tag! :CustomerCity, byte_limit(format_address_field(address[:city]), 20)
374
+ xml.tag! :CustomerState, byte_limit(format_address_field(address[:state]), 2)
375
+ xml.tag! :CustomerZIP, byte_limit(format_address_field(address[:zip]), 10)
376
+ xml.tag! :CustomerEmail, byte_limit(address[:email], 50) if address[:email]
377
+ xml.tag! :CustomerPhone, (address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil)
378
+ xml.tag! :CustomerCountryCode, (avs_supported ? address[:country] : '')
379
+ end
380
+ end
381
+
382
+ def add_creditcard(xml, creditcard, currency=nil)
383
+ unless creditcard.nil?
384
+ xml.tag! :AccountNum, creditcard.number
385
+ xml.tag! :Exp, expiry_date(creditcard)
386
+ end
387
+
388
+ xml.tag! :CurrencyCode, currency_code(currency)
389
+ xml.tag! :CurrencyExponent, currency_exponents(currency)
390
+
391
+ # If you are trying to collect a Card Verification Number
392
+ # (CardSecVal) for a Visa or Discover transaction, pass one of these values:
393
+ # 1 Value is Present
394
+ # 2 Value on card but illegible
395
+ # 9 Cardholder states data not available
396
+ # If the transaction is not a Visa or Discover transaction:
397
+ # Null-fill this attribute OR
398
+ # Do not submit the attribute at all.
399
+ # - http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
400
+ unless creditcard.nil?
401
+ if %w( visa discover ).include?(creditcard.brand)
402
+ xml.tag! :CardSecValInd, (creditcard.verification_value? ? '1' : '9')
403
+ end
404
+ xml.tag! :CardSecVal, creditcard.verification_value if creditcard.verification_value?
405
+ end
406
+ end
407
+
408
+ def add_refund(xml, currency=nil)
409
+ xml.tag! :AccountNum, nil
410
+
411
+ xml.tag! :CurrencyCode, currency_code(currency)
412
+ xml.tag! :CurrencyExponent, currency_exponents(currency)
413
+ end
414
+
415
+ def add_managed_billing(xml, options)
416
+ if mb = options[:managed_billing]
417
+ ActiveMerchant.deprecated RECURRING_DEPRECATION_MESSAGE
418
+
419
+ # default to recurring (R). Other option is deferred (D).
420
+ xml.tag! :MBType, mb[:type] || RECURRING
421
+ # default to Customer Reference Number
422
+ xml.tag! :MBOrderIdGenerationMethod, mb[:order_id_generation_method] || 'IO'
423
+ # By default use MBRecurringEndDate, set to N.
424
+ # MMDDYYYY
425
+ xml.tag! :MBRecurringStartDate, mb[:start_date].scan(/\d/).join.to_s if mb[:start_date]
426
+ # MMDDYYYY
427
+ xml.tag! :MBRecurringEndDate, mb[:end_date].scan(/\d/).join.to_s if mb[:end_date]
428
+ # By default listen to any value set in MBRecurringEndDate.
429
+ xml.tag! :MBRecurringNoEndDateFlag, mb[:no_end_date_flag] || 'N' # 'Y' || 'N' (Yes or No).
430
+ xml.tag! :MBRecurringMaxBillings, mb[:max_billings] if mb[:max_billings]
431
+ xml.tag! :MBRecurringFrequency, mb[:frequency] if mb[:frequency]
432
+ xml.tag! :MBDeferredBillDate, mb[:deferred_bill_date] if mb[:deferred_bill_date]
433
+ xml.tag! :MBMicroPaymentMaxDollarValue, mb[:max_dollar_value] if mb[:max_dollar_value]
434
+ xml.tag! :MBMicroPaymentMaxBillingDays, mb[:max_billing_days] if mb[:max_billing_days]
435
+ xml.tag! :MBMicroPaymentMaxTransactions, mb[:max_transactions] if mb[:max_transactions]
436
+ end
437
+ end
438
+
439
+ def parse(body)
440
+ response = {}
441
+ xml = REXML::Document.new(body)
442
+ root = REXML::XPath.first(xml, "//Response") ||
443
+ REXML::XPath.first(xml, "//ErrorResponse")
444
+ if root
445
+ root.elements.to_a.each do |node|
446
+ recurring_parse_element(response, node)
447
+ end
448
+ end
449
+
450
+ response.delete_if { |k,_| SENSITIVE_FIELDS.include?(k) }
451
+ end
452
+
453
+ def recurring_parse_element(response, node)
454
+ if node.has_elements?
455
+ node.elements.each{|e| recurring_parse_element(response, e) }
456
+ else
457
+ response[node.name.underscore.to_sym] = node.text
458
+ end
459
+ end
460
+
461
+ def commit(order, message_type, trace_number=nil)
462
+ headers = POST_HEADERS.merge("Content-length" => order.size.to_s)
463
+ headers.merge!( "Trace-number" => trace_number.to_s,
464
+ "Merchant-Id" => @options[:merchant_id] ) if @options[:retry_logic] && trace_number
465
+ request = lambda{|url| parse(ssl_post(url, order, headers))}
466
+
467
+ # Failover URL will be attempted in the event of a connection error
468
+ response = begin
469
+ request.call(remote_url)
470
+ rescue ConnectionError
471
+ request.call(remote_url(:secondary))
472
+ end
473
+
474
+ Response.new(success?(response, message_type), message_from(response), response,
475
+ {
476
+ :authorization => authorization_string(response[:tx_ref_num], response[:order_id]),
477
+ :test => self.test?,
478
+ :avs_result => OrbitalGateway::AVSResult.new(response[:avs_resp_code]),
479
+ :cvv_result => OrbitalGateway::CVVResult.new(response[:cvv2_resp_code])
480
+ }
481
+ )
482
+ end
483
+
484
+ def remote_url(url=:primary)
485
+ if url == :primary
486
+ (self.test? ? self.test_url : self.live_url)
487
+ else
488
+ (self.test? ? self.secondary_test_url : self.secondary_live_url)
489
+ end
490
+ end
491
+
492
+ def success?(response, message_type)
493
+ if [:refund, :void].include?(message_type)
494
+ response[:proc_status] == SUCCESS
495
+ elsif response[:customer_profile_action]
496
+ response[:profile_proc_status] == SUCCESS
497
+ else
498
+ response[:proc_status] == SUCCESS &&
499
+ APPROVED.include?(response[:resp_code])
500
+ end
501
+ end
502
+
503
+ def message_from(response)
504
+ response[:resp_msg] || response[:status_msg] || response[:customer_profile_message]
505
+ end
506
+
507
+ def ip_authentication?
508
+ @options[:ip_authentication] == true
509
+ end
510
+
511
+ def build_new_order_xml(action, money, parameters = {})
512
+ requires!(parameters, :order_id)
513
+ xml = xml_envelope
514
+ xml.tag! :Request do
515
+ xml.tag! :NewOrder do
516
+ add_xml_credentials(xml)
517
+ # EC - Ecommerce transaction
518
+ # RC - Recurring Payment transaction
519
+ # MO - Mail Order Telephone Order transaction
520
+ # IV - Interactive Voice Response
521
+ # IN - Interactive Voice Response
522
+ xml.tag! :IndustryType, parameters[:industry_type] || ECOMMERCE_TRANSACTION
523
+ # A - Auth Only No Capture
524
+ # AC - Auth and Capture
525
+ # F - Force Auth No Capture and no online authorization
526
+ # FR - Force Auth No Capture and no online authorization
527
+ # FC - Force Auth and Capture no online authorization
528
+ # R - Refund and Capture no online authorization
529
+ xml.tag! :MessageType, action
530
+ add_bin_merchant_and_terminal(xml, parameters)
531
+
532
+ yield xml if block_given?
533
+
534
+ xml.tag! :OrderID, format_order_id(parameters[:order_id])
535
+ xml.tag! :Amount, amount(money)
536
+ xml.tag! :Comments, parameters[:comments] if parameters[:comments]
537
+
538
+ # CustomerAni, AVSPhoneType and AVSDestPhoneType could be added here.
539
+
540
+ if parameters[:soft_descriptors].is_a?(OrbitalSoftDescriptors)
541
+ add_soft_descriptors(xml, parameters[:soft_descriptors])
542
+ end
543
+
544
+ set_recurring_ind(xml, parameters)
545
+
546
+ # Append Transaction Reference Number at the end for Refund transactions
547
+ if action == REFUND
548
+ tx_ref_num, _ = split_authorization(parameters[:authorization])
549
+ xml.tag! :TxRefNum, tx_ref_num
550
+ end
551
+ end
552
+ end
553
+ xml.target!
554
+ end
555
+
556
+ # For Canadian transactions on PNS Tampa on New Order
557
+ # RF - First Recurring Transaction
558
+ # RS - Subsequent Recurring Transactions
559
+ def set_recurring_ind(xml, parameters)
560
+ if parameters[:recurring_ind]
561
+ raise "RecurringInd must be set to either \"RF\" or \"RS\"" unless %w(RF RS).include?(parameters[:recurring_ind])
562
+ xml.tag! :RecurringInd, parameters[:recurring_ind]
563
+ end
564
+ end
565
+
566
+ def build_mark_for_capture_xml(money, authorization, parameters = {})
567
+ tx_ref_num, order_id = split_authorization(authorization)
568
+ xml = xml_envelope
569
+ xml.tag! :Request do
570
+ xml.tag! :MarkForCapture do
571
+ add_xml_credentials(xml)
572
+ xml.tag! :OrderID, format_order_id(order_id)
573
+ xml.tag! :Amount, amount(money)
574
+ add_bin_merchant_and_terminal(xml, parameters)
575
+ xml.tag! :TxRefNum, tx_ref_num
576
+ end
577
+ end
578
+ xml.target!
579
+ end
580
+
581
+ def build_void_request_xml(authorization, parameters = {})
582
+ tx_ref_num, order_id = split_authorization(authorization)
583
+ xml = xml_envelope
584
+ xml.tag! :Request do
585
+ xml.tag! :Reversal do
586
+ add_xml_credentials(xml)
587
+ xml.tag! :TxRefNum, tx_ref_num
588
+ xml.tag! :TxRefIdx, parameters[:transaction_index]
589
+ xml.tag! :AdjustedAmt, parameters[:amount] # setting adjusted amount to nil will void entire amount
590
+ xml.tag! :OrderID, format_order_id(order_id || parameters[:order_id])
591
+ add_bin_merchant_and_terminal(xml, parameters)
592
+ xml.tag! :ReversalRetryNumber, parameters[:reversal_retry_number] if parameters[:reversal_retry_number]
593
+ xml.tag! :OnlineReversalInd, parameters[:online_reversal_ind] if parameters[:online_reversal_ind]
594
+ end
595
+ end
596
+ xml.target!
597
+ end
598
+
599
+ def currency_code(currency)
600
+ CURRENCY_CODES[(currency || self.default_currency)].to_s
601
+ end
602
+
603
+ def currency_exponents(currency)
604
+ CURRENCY_EXPONENTS[(currency || self.default_currency)].to_s
605
+ end
606
+
607
+ def expiry_date(credit_card)
608
+ "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
609
+ end
610
+
611
+ def bin
612
+ @options[:bin] || (salem_mid? ? '000001' : '000002')
613
+ end
614
+
615
+ def xml_envelope
616
+ xml = Builder::XmlMarkup.new(:indent => 2)
617
+ xml.instruct!(:xml, :version => '1.0', :encoding => 'UTF-8')
618
+ xml
619
+ end
620
+
621
+ def add_xml_credentials(xml)
622
+ unless ip_authentication?
623
+ xml.tag! :OrbitalConnectionUsername, @options[:login]
624
+ xml.tag! :OrbitalConnectionPassword, @options[:password]
625
+ end
626
+ end
627
+
628
+ def add_bin_merchant_and_terminal(xml, parameters)
629
+ xml.tag! :BIN, bin
630
+ xml.tag! :MerchantID, @options[:merchant_id]
631
+ xml.tag! :TerminalID, parameters[:terminal_id] || '001'
632
+ end
633
+
634
+ def salem_mid?
635
+ @options[:merchant_id].length == 6
636
+ end
637
+
638
+ # The valid characters include:
639
+ #
640
+ # 1. all letters and digits
641
+ # 2. - , $ @ & and a space character, though the space character cannot be the leading character
642
+ # 3. PINless Debit transactions can only use uppercase and lowercase alpha (A-Z, a-z) and numeric (0-9)
643
+ def format_order_id(order_id)
644
+ illegal_characters = /[^,$@&\- \w]/
645
+ order_id = order_id.to_s.gsub(/\./, '-')
646
+ order_id.gsub!(illegal_characters, '')
647
+ order_id.lstrip!
648
+ order_id[0...22]
649
+ end
650
+
651
+ # Address-related fields cannot contain % | ^ \ /
652
+ # Returns the value with these characters removed, or nil
653
+ def format_address_field(value)
654
+ value.gsub(/[%\|\^\\\/]/, '') if value.respond_to?(:gsub)
655
+ end
656
+
657
+ # Field lengths should be limited by byte count instead of character count
658
+ # Returns the truncated value or nil
659
+ def byte_limit(value, byte_length)
660
+ limited_value = ""
661
+
662
+ value.to_s.each_char do |c|
663
+ break if((limited_value.bytesize + c.bytesize) > byte_length)
664
+ limited_value << c
665
+ end
666
+
667
+ limited_value
668
+ end
669
+
670
+ def build_customer_request_xml(creditcard, options = {})
671
+ xml = xml_envelope
672
+ xml.tag! :Request do
673
+ xml.tag! :Profile do
674
+ xml.tag! :OrbitalConnectionUsername, @options[:login] unless ip_authentication?
675
+ xml.tag! :OrbitalConnectionPassword, @options[:password] unless ip_authentication?
676
+ xml.tag! :CustomerBin, bin
677
+ xml.tag! :CustomerMerchantID, @options[:merchant_id]
678
+ xml.tag! :CustomerName, creditcard.name if creditcard
679
+ xml.tag! :CustomerRefNum, options[:customer_ref_num] if options[:customer_ref_num]
680
+
681
+ add_customer_address(xml, options)
682
+
683
+ xml.tag! :CustomerProfileAction, options[:customer_profile_action] # C, R, U, D
684
+ # NO No mapping to order data
685
+ # OI Use <CustomerRefNum> for <OrderID>
686
+ # OD Use <CustomerReferNum> for <Comments>
687
+ # OA Use <CustomerRefNum> for <OrderID> and <Comments>
688
+ xml.tag! :CustomerProfileOrderOverrideInd, options[:customer_profile_order_override_ind] || NO_MAPPING_TO_ORDER_DATA
689
+
690
+ if options[:customer_profile_action] == CREATE
691
+ # A Auto-Generate the CustomerRefNum
692
+ # S Use CustomerRefNum field
693
+ # O Use OrderID field
694
+ # D Use Comments field
695
+ xml.tag! :CustomerProfileFromOrderInd, (options[:customer_ref_num] ? USE_CUSTOMER_REF_NUM : AUTO_GENERATE)
696
+ end
697
+
698
+ xml.tag! :OrderDefaultDescription, options[:order_default_description][0..63] if options[:order_default_description]
699
+ xml.tag! :OrderDefaultAmount, options[:order_default_amount] if options[:order_default_amount]
700
+
701
+ if [CREATE, UPDATE].include? options[:customer_profile_action]
702
+ xml.tag! :CustomerAccountType, 'CC' # Only credit card supported
703
+ xml.tag! :Status, options[:status] || ACTIVE # Active
704
+ end
705
+
706
+ xml.tag! :CCAccountNum, creditcard.number if creditcard
707
+ xml.tag! :CCExpireDate, creditcard.expiry_date.expiration.strftime("%m%y") if creditcard
708
+
709
+ # This has to come after CCExpireDate.
710
+ add_managed_billing(xml, options)
711
+ end
712
+ end
713
+ xml.target!
714
+ end
715
+
716
+ # Unfortunately, Orbital uses their own special codes for AVS responses
717
+ # that are different than the standard codes defined in
718
+ # <tt>ActiveMerchant::Billing::AVSResult</tt>.
719
+ #
720
+ # This class encapsulates the response codes shown on page 240 of their spec:
721
+ # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
722
+ #
723
+ class AVSResult < ActiveMerchant::Billing::AVSResult
724
+ CODES = {
725
+ '1' => 'No address supplied',
726
+ '2' => 'Bill-to address did not pass Auth Host edit checks',
727
+ '3' => 'AVS not performed',
728
+ '4' => 'Issuer does not participate in AVS',
729
+ '5' => 'Edit-error - AVS data is invalid',
730
+ '6' => 'System unavailable or time-out',
731
+ '7' => 'Address information unavailable',
732
+ '8' => 'Transaction Ineligible for AVS',
733
+ '9' => 'Zip Match/Zip 4 Match/Locale match',
734
+ 'A' => 'Zip Match/Zip 4 Match/Locale no match',
735
+ 'B' => 'Zip Match/Zip 4 no Match/Locale match',
736
+ 'C' => 'Zip Match/Zip 4 no Match/Locale no match',
737
+ 'D' => 'Zip No Match/Zip 4 Match/Locale match',
738
+ 'E' => 'Zip No Match/Zip 4 Match/Locale no match',
739
+ 'F' => 'Zip No Match/Zip 4 No Match/Locale match',
740
+ 'G' => 'No match at all',
741
+ 'H' => 'Zip Match/Locale match',
742
+ 'J' => 'Issuer does not participate in Global AVS',
743
+ 'JA' => 'International street address and postal match',
744
+ 'JB' => 'International street address match. Postal code not verified',
745
+ 'JC' => 'International street address and postal code not verified',
746
+ 'JD' => 'International postal code match. Street address not verified',
747
+ 'M1' => 'Cardholder name matches',
748
+ 'M2' => 'Cardholder name, billing address, and postal code matches',
749
+ 'M3' => 'Cardholder name and billing code matches',
750
+ 'M4' => 'Cardholder name and billing address match',
751
+ 'M5' => 'Cardholder name incorrect, billing address and postal code match',
752
+ 'M6' => 'Cardholder name incorrect, billing postal code matches',
753
+ 'M7' => 'Cardholder name incorrect, billing address matches',
754
+ 'M8' => 'Cardholder name, billing address and postal code are all incorrect',
755
+ 'N3' => 'Address matches, ZIP not verified',
756
+ 'N4' => 'Address and ZIP code not verified due to incompatible formats',
757
+ 'N5' => 'Address and ZIP code match (International only)',
758
+ 'N6' => 'Address not verified (International only)',
759
+ 'N7' => 'ZIP matches, address not verified',
760
+ 'N8' => 'Address and ZIP code match (International only)',
761
+ 'N9' => 'Address and ZIP code match (UK only)',
762
+ 'R' => 'Issuer does not participate in AVS',
763
+ 'UK' => 'Unknown',
764
+ 'X' => 'Zip Match/Zip 4 Match/Address Match',
765
+ 'Z' => 'Zip Match/Locale no match',
766
+ }
767
+
768
+ # Map vendor's AVS result code to a postal match code
769
+ ORBITAL_POSTAL_MATCH_CODE = {
770
+ 'Y' => %w( 9 A B C H JA JD M2 M3 M5 N5 N8 N9 X Z ),
771
+ 'N' => %w( D E F G M8 ),
772
+ 'X' => %w( 4 J R ),
773
+ nil => %w( 1 2 3 5 6 7 8 JB JC M1 M4 M6 M7 N3 N4 N6 N7 UK )
774
+ }.inject({}) do |map, (type, codes)|
775
+ codes.each { |code| map[code] = type }
776
+ map
777
+ end
778
+
779
+ # Map vendor's AVS result code to a street match code
780
+ ORBITAL_STREET_MATCH_CODE = {
781
+ 'Y' => %w( 9 B D F H JA JB M2 M4 M5 M6 M7 N3 N5 N7 N8 N9 X ),
782
+ 'N' => %w( A C E G M8 Z ),
783
+ 'X' => %w( 4 J R ),
784
+ nil => %w( 1 2 3 5 6 7 8 JC JD M1 M3 N4 N6 UK )
785
+ }.inject({}) do |map, (type, codes)|
786
+ codes.each { |code| map[code] = type }
787
+ map
788
+ end
789
+
790
+ def self.messages
791
+ CODES
792
+ end
793
+
794
+ def initialize(code)
795
+ @code = (code.blank? ? nil : code.to_s.strip.upcase)
796
+ if @code
797
+ @message = CODES[@code]
798
+ @postal_match = ORBITAL_POSTAL_MATCH_CODE[@code]
799
+ @street_match = ORBITAL_STREET_MATCH_CODE[@code]
800
+ end
801
+ end
802
+ end
803
+
804
+ # Unfortunately, Orbital uses their own special codes for CVV responses
805
+ # that are different than the standard codes defined in
806
+ # <tt>ActiveMerchant::Billing::CVVResult</tt>.
807
+ #
808
+ # This class encapsulates the response codes shown on page 255 of their spec:
809
+ # http://download.chasepaymentech.com/docs/orbital/orbital_gateway_xml_specification.pdf
810
+ #
811
+ class CVVResult < ActiveMerchant::Billing::CVVResult
812
+ MESSAGES = {
813
+ 'M' => 'Match',
814
+ 'N' => 'No match',
815
+ 'P' => 'Not processed',
816
+ 'S' => 'Should have been present',
817
+ 'U' => 'Unsupported by issuer/Issuer unable to process request',
818
+ 'I' => 'Invalid',
819
+ 'Y' => 'Invalid',
820
+ '' => 'Not applicable'
821
+ }
822
+
823
+ def self.messages
824
+ MESSAGES
825
+ end
826
+
827
+ def initialize(code)
828
+ @code = code.blank? ? '' : code.upcase
829
+ @message = MESSAGES[@code]
830
+ end
831
+ end
832
+ end
833
+ end
834
+ end