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,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