activemerchant 1.1.0 → 1.2.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 (178) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +226 -0
  3. data/CONTRIBUTERS +52 -0
  4. data/README +34 -24
  5. data/Rakefile +152 -0
  6. data/gem-public_cert.pem +20 -0
  7. data/init.rb +3 -0
  8. data/lib/active_merchant.rb +3 -1
  9. data/lib/active_merchant/billing/credit_card.rb +21 -17
  10. data/lib/active_merchant/billing/credit_card_methods.rb +17 -19
  11. data/lib/active_merchant/billing/gateway.rb +160 -44
  12. data/lib/active_merchant/billing/gateways.rb +2 -15
  13. data/lib/active_merchant/billing/gateways/authorize_net.rb +21 -21
  14. data/lib/active_merchant/billing/gateways/bogus.rb +6 -6
  15. data/lib/active_merchant/billing/gateways/brain_tree.rb +191 -0
  16. data/lib/active_merchant/billing/gateways/card_stream.rb +207 -0
  17. data/lib/active_merchant/billing/gateways/cyber_source.rb +402 -0
  18. data/lib/active_merchant/billing/gateways/data_cash.rb +41 -97
  19. data/lib/active_merchant/billing/gateways/efsnet.rb +256 -0
  20. data/lib/active_merchant/billing/gateways/eway.rb +77 -29
  21. data/lib/active_merchant/billing/gateways/exact.rb +230 -0
  22. data/lib/active_merchant/billing/gateways/linkpoint.rb +6 -33
  23. data/lib/active_merchant/billing/gateways/moneris.rb +155 -125
  24. data/lib/active_merchant/billing/gateways/net_registry.rb +257 -0
  25. data/lib/active_merchant/billing/gateways/pay_junction.rb +407 -0
  26. data/lib/active_merchant/billing/gateways/payflow.rb +163 -25
  27. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +56 -38
  28. data/lib/active_merchant/billing/gateways/payflow/payflow_express_response.rb +10 -1
  29. data/lib/active_merchant/billing/gateways/payflow/payflow_response.rb +9 -0
  30. data/lib/active_merchant/billing/gateways/payflow_express.rb +36 -11
  31. data/lib/active_merchant/billing/gateways/payflow_express_uk.rb +6 -0
  32. data/lib/active_merchant/billing/gateways/payflow_uk.rb +7 -3
  33. data/lib/active_merchant/billing/gateways/payment_express.rb +261 -0
  34. data/lib/active_merchant/billing/gateways/paypal.rb +18 -4
  35. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +31 -15
  36. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +8 -0
  37. data/lib/active_merchant/billing/gateways/paypal_express.rb +33 -8
  38. data/lib/active_merchant/billing/gateways/plugnpay.rb +300 -0
  39. data/lib/active_merchant/billing/gateways/protx.rb +285 -0
  40. data/lib/active_merchant/billing/gateways/psigate.rb +13 -12
  41. data/lib/active_merchant/billing/gateways/psl_card.rb +297 -0
  42. data/lib/active_merchant/billing/gateways/quickpay.rb +197 -0
  43. data/lib/active_merchant/billing/gateways/realex.rb +212 -0
  44. data/lib/active_merchant/billing/gateways/secure_pay.rb +31 -0
  45. data/lib/active_merchant/billing/gateways/trans_first.rb +136 -0
  46. data/lib/active_merchant/billing/gateways/trust_commerce.rb +43 -20
  47. data/lib/active_merchant/billing/gateways/usa_epay.rb +6 -5
  48. data/lib/active_merchant/billing/gateways/verifi.rb +235 -0
  49. data/lib/active_merchant/billing/gateways/viaklix.rb +171 -0
  50. data/lib/active_merchant/billing/integrations/gestpay/helper.rb +0 -2
  51. data/lib/active_merchant/billing/integrations/helper.rb +8 -1
  52. data/lib/active_merchant/billing/integrations/nochex.rb +62 -1
  53. data/lib/active_merchant/billing/integrations/nochex/notification.rb +9 -16
  54. data/lib/active_merchant/billing/integrations/notification.rb +1 -1
  55. data/lib/active_merchant/billing/integrations/paypal/helper.rb +59 -46
  56. data/lib/active_merchant/billing/integrations/paypal/notification.rb +14 -47
  57. data/lib/active_merchant/lib/error.rb +4 -0
  58. data/lib/active_merchant/lib/post_data.rb +22 -0
  59. data/lib/active_merchant/lib/posts_data.rb +23 -5
  60. data/lib/active_merchant/lib/requires_parameters.rb +2 -2
  61. data/lib/active_merchant/lib/validateable.rb +1 -1
  62. data/lib/support/gateway_support.rb +45 -0
  63. data/lib/tasks/cia.rb +1 -1
  64. data/script/generate +14 -0
  65. data/script/generator/base.rb +45 -0
  66. data/script/generator/generator.rb +24 -0
  67. data/script/generator/generators/gateway/gateway_generator.rb +14 -0
  68. data/script/generator/generators/gateway/templates/gateway.rb +73 -0
  69. data/script/generator/generators/gateway/templates/gateway_test.rb +41 -0
  70. data/script/generator/generators/gateway/templates/remote_gateway_test.rb +56 -0
  71. data/script/generator/generators/integration/integration_generator.rb +25 -0
  72. data/script/generator/generators/integration/templates/helper.rb +34 -0
  73. data/script/generator/generators/integration/templates/helper_test.rb +54 -0
  74. data/script/generator/generators/integration/templates/integration.rb +18 -0
  75. data/script/generator/generators/integration/templates/module_test.rb +9 -0
  76. data/script/generator/generators/integration/templates/notification.rb +100 -0
  77. data/script/generator/generators/integration/templates/notification_test.rb +41 -0
  78. data/script/generator/manifest.rb +20 -0
  79. data/test/extra/binding_of_caller.rb +80 -0
  80. data/test/extra/breakpoint.rb +547 -0
  81. data/test/fixtures.yml +251 -0
  82. data/test/remote_tests/remote_authorize_net_test.rb +113 -0
  83. data/test/remote_tests/remote_brain_tree_test.rb +78 -0
  84. data/test/remote_tests/remote_card_stream_test.rb +160 -0
  85. data/test/remote_tests/remote_cyber_source_test.rb +130 -0
  86. data/test/remote_tests/remote_data_cash_test.rb +155 -0
  87. data/test/remote_tests/remote_efsnet_test.rb +93 -0
  88. data/test/remote_tests/remote_eway_test.rb +71 -0
  89. data/test/remote_tests/remote_exact_test.rb +59 -0
  90. data/test/remote_tests/remote_gestpay_integration_test.rb +37 -0
  91. data/test/remote_tests/remote_linkpoint_test.rb +144 -0
  92. data/test/remote_tests/remote_moneris_test.rb +110 -0
  93. data/test/remote_tests/remote_net_registry_test.rb +120 -0
  94. data/test/remote_tests/remote_pay_junction_test.rb +162 -0
  95. data/test/remote_tests/remote_payflow_express_test.rb +50 -0
  96. data/test/remote_tests/remote_payflow_test.rb +241 -0
  97. data/test/remote_tests/remote_payflow_uk_test.rb +172 -0
  98. data/test/remote_tests/remote_payment_express_test.rb +136 -0
  99. data/test/remote_tests/remote_paypal_express_test.rb +49 -0
  100. data/test/remote_tests/remote_paypal_integration_test.rb +14 -0
  101. data/test/remote_tests/remote_paypal_test.rb +163 -0
  102. data/test/remote_tests/remote_plugnpay_test.rb +70 -0
  103. data/test/remote_tests/remote_protx_test.rb +184 -0
  104. data/test/remote_tests/remote_psigate_test.rb +87 -0
  105. data/test/remote_tests/remote_psl_card_test.rb +105 -0
  106. data/test/remote_tests/remote_quickpay_test.rb +182 -0
  107. data/test/remote_tests/remote_realex_test.rb +227 -0
  108. data/test/remote_tests/remote_secure_pay_test.rb +36 -0
  109. data/test/remote_tests/remote_trans_first_test.rb +37 -0
  110. data/test/remote_tests/remote_trust_commerce_test.rb +136 -0
  111. data/test/remote_tests/remote_usa_epay_test.rb +57 -0
  112. data/test/remote_tests/remote_verifi_test.rb +107 -0
  113. data/test/remote_tests/remote_viaklix_test.rb +53 -0
  114. data/test/test_helper.rb +132 -0
  115. data/test/unit/base_test.rb +61 -0
  116. data/test/unit/country_code_test.rb +33 -0
  117. data/test/unit/country_test.rb +64 -0
  118. data/test/unit/credit_card_formatting_test.rb +24 -0
  119. data/test/unit/credit_card_methods_test.rb +37 -0
  120. data/test/unit/credit_card_test.rb +365 -0
  121. data/test/unit/gateways/authorize_net_test.rb +140 -0
  122. data/test/unit/gateways/bogus_test.rb +43 -0
  123. data/test/unit/gateways/brain_tree_test.rb +77 -0
  124. data/test/unit/gateways/card_stream_test.rb +37 -0
  125. data/test/unit/gateways/cyber_source_test.rb +151 -0
  126. data/test/unit/gateways/data_cash_test.rb +23 -0
  127. data/test/unit/gateways/efsnet_test.rb +70 -0
  128. data/test/unit/gateways/eway_test.rb +105 -0
  129. data/test/unit/gateways/exact_test.rb +118 -0
  130. data/test/unit/gateways/gateway_test.rb +24 -0
  131. data/test/unit/gateways/linkpoint_test.rb +165 -0
  132. data/test/unit/gateways/moneris_test.rb +167 -0
  133. data/test/unit/gateways/net_registry_test.rb +478 -0
  134. data/test/unit/gateways/pay_junction_test.rb +61 -0
  135. data/test/unit/gateways/payflow_express_test.rb +165 -0
  136. data/test/unit/gateways/payflow_express_uk_test.rb +14 -0
  137. data/test/unit/gateways/payflow_test.rb +230 -0
  138. data/test/unit/gateways/payflow_uk_test.rb +68 -0
  139. data/test/unit/gateways/payment_express_test.rb +215 -0
  140. data/test/unit/gateways/paypal_express_test.rb +222 -0
  141. data/test/unit/gateways/paypal_test.rb +241 -0
  142. data/test/unit/gateways/plugnpay_test.rb +79 -0
  143. data/test/unit/gateways/protx_test.rb +110 -0
  144. data/test/unit/gateways/psigate_test.rb +110 -0
  145. data/test/unit/gateways/psl_card_test.rb +51 -0
  146. data/test/unit/gateways/quickpay_test.rb +125 -0
  147. data/test/unit/gateways/realex_test.rb +150 -0
  148. data/test/unit/gateways/secure_pay_test.rb +78 -0
  149. data/test/unit/gateways/trans_first_test.rb +125 -0
  150. data/test/unit/gateways/trust_commerce_test.rb +57 -0
  151. data/test/unit/gateways/usa_epay_test.rb +117 -0
  152. data/test/unit/gateways/verifi_test.rb +91 -0
  153. data/test/unit/gateways/viaklix_test.rb +72 -0
  154. data/test/unit/integrations/action_view_helper_test.rb +54 -0
  155. data/test/unit/integrations/bogus_module_test.rb +16 -0
  156. data/test/unit/integrations/chronopay_module_test.rb +9 -0
  157. data/test/unit/integrations/gestpay_module_test.rb +10 -0
  158. data/test/unit/integrations/helpers/bogus_helper_test.rb +28 -0
  159. data/test/unit/integrations/helpers/chronopay_helper_test.rb +67 -0
  160. data/test/unit/integrations/helpers/gestpay_helper_test.rb +100 -0
  161. data/test/unit/integrations/helpers/nochex_helper_test.rb +53 -0
  162. data/test/unit/integrations/helpers/paypal_helper_test.rb +162 -0
  163. data/test/unit/integrations/helpers/two_checkout_helper_test.rb +92 -0
  164. data/test/unit/integrations/nochex_module_test.rb +9 -0
  165. data/test/unit/integrations/notifications/chronopay_notification_test.rb +66 -0
  166. data/test/unit/integrations/notifications/gestpay_notification_test.rb +60 -0
  167. data/test/unit/integrations/notifications/nochex_notification_test.rb +51 -0
  168. data/test/unit/integrations/notifications/notification_test.rb +41 -0
  169. data/test/unit/integrations/notifications/paypal_notification_test.rb +85 -0
  170. data/test/unit/integrations/notifications/two_checkout_notification_test.rb +55 -0
  171. data/test/unit/integrations/paypal_module_test.rb +24 -0
  172. data/test/unit/integrations/two_checkout_module_test.rb +9 -0
  173. data/test/unit/post_data_test.rb +55 -0
  174. data/test/unit/response_test.rb +14 -0
  175. data/test/unit/validateable_test.rb +56 -0
  176. metadata +160 -7
  177. metadata.gz.sig +0 -0
  178. data/lib/active_merchant/billing/gateways/payflow/f73e89fd.0 +0 -17
@@ -0,0 +1,407 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+
4
+ # PayJunction Gateway
5
+ #
6
+ # This gateway accepts the following arguments:
7
+ # :login => your PayJunction username
8
+ # :password => your PayJunction pass
9
+ #
10
+ # Example use:
11
+ #
12
+ # gateway = ActiveMerchant::Billing::Base.gateway(:pay_junction).new(
13
+ # :login => "my_account",
14
+ # :password => "my_pass" )
15
+ #
16
+ # # set up credit card obj as in main ActiveMerchant example
17
+ # creditcard = ActiveMerchant::Billing::CreditCard.new(
18
+ # :type => 'visa',
19
+ # :number => '4242424242424242',
20
+ # :month => 8,
21
+ # :year => 2009,
22
+ # :first_name => 'Bob',
23
+ # :last_name => 'Bobsen'
24
+ # )
25
+ #
26
+ # # optionally specify address if using AVS
27
+ # address = { :address1 => '101 Test Ave', :city => 'Test', :state => 'TS',
28
+ # :zipcode => '10101', :country => 'US' }
29
+ #
30
+ # # run request
31
+ # response = gateway.purchase(1000, creditcard, :address => address) # charge 10 dollars
32
+ #
33
+ # 1) Check whether the transaction was successful
34
+ #
35
+ # response.success?
36
+ #
37
+ # 2) Retrieve the message returned by PayJunction
38
+ #
39
+ # response.message
40
+ #
41
+ # 3) Retrieve the unique transaction ID returned by PayJunction
42
+ #
43
+ # response.authorization
44
+ #
45
+ # This gateway supports "instant" transactions. These transactions allow you
46
+ # to execute an operation on a previously run card without card information
47
+ # provided you have the transaction id from a previous transaction with the
48
+ # same card. All functions that take a credit card object for this gateway
49
+ # can take a transaction id string instead.
50
+ #
51
+ # Test Transactions
52
+ #
53
+ # See the source for initialize() for test account information. Note that
54
+ # PayJunction does not allow test transactions on your account, so if the
55
+ # gateway is running in :test mode or the :test_request option has been
56
+ # been specified and is true, your transaction will be run against
57
+ # PayJunction's global test account and will not show up in your account.
58
+ #
59
+ # Transactions ran on this account go through a test processor, so there is no
60
+ # need to void or otherwise cancel transactions. However, for further safety,
61
+ # please use the special card numbers 4433221111223344 or 4444333322221111 and
62
+ # keep transaction amounts below $4.00 when testing.
63
+ #
64
+ # Also note, transactions ran for an amount between $0.00 and $1.99 will likely
65
+ # result in denial. To demonstrate approvals, use amounts between $2.00 and $4.00.
66
+ #
67
+ # Test transactions can be checked by logging into
68
+ # PayJunction Web Login with username 'pj-cm-01' and password 'pj-cm-01p'
69
+ #
70
+ # Usage Details
71
+ #
72
+ # Below is a map of values accepted by PayJunction and how you should submit
73
+ # each to ActiveMerchant
74
+ #
75
+ # PayJunction Field ActiveMerchant Use
76
+ #
77
+ # dc_logon provide as :login value to gateway instantation
78
+ # dc_password provide as :password value to gateway instantiation
79
+ #
80
+ # dc_name will be retrieved from credit_card.name
81
+ # dc_first_name :first_name on CreditCard object instantation
82
+ # dc_last_name :last_name on CreditCard object instantation
83
+ # dc_number :number on CreditCard object instantation
84
+ # dc_expiration_month :month on CreditCard object instantation
85
+ # dc_expiration_year :year on CreditCard object instantation
86
+ # dc_verification_number :verification_value on CC object instantation
87
+ #
88
+ # dc_transaction_amount include as argument to method for your transaction type
89
+ # dc_transaction_type do nothing, set by your transaction type
90
+ # dc_version do nothing, always "1.2"
91
+ #
92
+ # dc_transaction_id submit as a string in place of CreditCard obj for
93
+ # "instant" transactions.
94
+ #
95
+ # dc_invoice :order_id in options for transaction method
96
+ # dc_notes :notes in options for transaction method
97
+ #
98
+ # See example use above for address AVS fields
99
+ # See #recurring for periodic transaction fields
100
+ #
101
+
102
+
103
+ class PayJunctionGateway < Gateway
104
+ API_VERSION = '1.2'
105
+ LIVE_URL = 'https://payjunction.com/quick_link' # also handles test requests
106
+
107
+ SUCCESS_CODES = ["00", "85"]
108
+
109
+ DECLINE_CODES = {
110
+ "AE" => 'Address verification failed because address did not match.',
111
+ 'ZE' => 'Address verification failed because zip did not match.',
112
+ 'XE' => 'Address verification failed because zip and address did not match.',
113
+ 'YE' => 'Address verification failed because zip and address did not match.',
114
+ 'OE' => 'Address verification failed because address or zip did not match.',
115
+ 'UE' => 'Address verification failed because cardholder address unavailable.',
116
+ 'RE' => 'Address verification failed because address verification system is not working.',
117
+ 'SE' => 'Address verification failed because address verification system is unavailable.',
118
+ 'EE' => 'Address verification failed because transaction is not a mail or phone order.',
119
+ 'GE' => 'Address verification failed because international support is unavailable.',
120
+ 'CE' => 'Declined because CVV2/CVC2 code did not match.',
121
+ '04' => 'Declined. Pick up card.',
122
+ '07' => 'Declined. Pick up card (Special Condition).',
123
+ '41' => 'Declined. Pick up card (Lost).',
124
+ '43' => 'Declined. Pick up card (Stolen).',
125
+ '13' => 'Declined because of the amount is invalid.',
126
+ '14' => 'Declined because the card number is invalid.',
127
+ '80' => 'Declined because of an invalid date.',
128
+ '05' => 'Declined. Do not honor.',
129
+ '51' => 'Declined because of insufficient funds.',
130
+ 'N4' => 'Declined because the amount exceeds issuer withdrawal limit.',
131
+ '61' => 'Declined because the amount exceeds withdrawal limit.',
132
+ '62' => 'Declined because of an invalid service code (restricted).',
133
+ '65' => 'Declined because the card activity limit exceeded.',
134
+ '93' => 'Declined because there a violation (the transaction could not be completed).',
135
+ '06' => 'Declined because address verification failed.',
136
+ '54' => 'Declined because the card has expired.',
137
+ '15' => 'Declined because there is no such issuer.',
138
+ '96' => 'Declined because of a system error.',
139
+ 'N7' => 'Declined because of a CVV2/CVC2 mismatch.',
140
+ 'M4' => 'Declined.'
141
+
142
+ }
143
+
144
+ BADDATA_CODES = {
145
+ "FE" => "There was a format error with your Trinity Gateway Service (API) request."
146
+ }
147
+
148
+ ERROR_CODES = {
149
+ "LE" => "Could not log you in (problem with dc_logon and/or dc_password).",
150
+ 'NL' => 'Aborted because of a system error, please try again later. ',
151
+ 'AB' => 'Aborted because of an upstream system error, please try again later.'
152
+ }
153
+
154
+ self.supported_cardtypes = [:visa, :master, :american_express, :discover]
155
+ self.supported_countries = ['US']
156
+
157
+ self.homepage_url = 'http://www.payjunction.com/'
158
+ self.display_name = 'PayJunction'
159
+
160
+ attr_reader :url, :response, :options
161
+
162
+ def initialize(options = {})
163
+ requires!(options, :login, :password)
164
+ @options = options
165
+ super
166
+ end
167
+
168
+ # The first half of the preauth(authorize)/postauth(capture) model.
169
+ # Checks to make sure funds are available for a transaction, and returns a
170
+ # transaction_id that can be used later to postauthorize (capture) the funds.
171
+ def authorize(money, creditcard_or_billing_id, options = {})
172
+ parameters = {
173
+ :transaction_amount => amount(money),
174
+ }
175
+
176
+ add_payment_source(parameters, creditcard_or_billing_id)
177
+ add_address(parameters, options)
178
+ add_optional_fields(parameters, options)
179
+ commit('AUTHORIZATION', parameters)
180
+ end
181
+
182
+ # A simple sale, capturing funds immediately.
183
+ # Execute authorization and capture in a single step.
184
+ def purchase(money, creditcard_or_billing_id, options = {})
185
+ parameters = {
186
+ :transaction_amount => amount(money),
187
+ }
188
+
189
+ add_payment_source(parameters, creditcard_or_billing_id)
190
+ add_address(parameters, options)
191
+ add_optional_fields(parameters, options)
192
+ commit('AUTHORIZATION_CAPTURE', parameters)
193
+ end
194
+
195
+ # The second half of the preauth(authorize)/postauth(capture) model.
196
+ # Retrieve funds that have been previously authorized with _authorization_
197
+ def capture(money, authorization, options = {})
198
+ parameters = {
199
+ :transaction_id => authorization,
200
+ :posture => 'capture'
201
+ }
202
+
203
+ add_optional_fields(parameters, options)
204
+ commit('update', parameters)
205
+ end
206
+
207
+ # Return money to a card that was previously billed.
208
+ # _authorization_ should be the transaction id of the transaction we are returning.
209
+ def credit(money, authorization, options = {})
210
+ parameters = {
211
+ :transaction_amount => amount(money),
212
+ :transaction_id => authorization
213
+ }
214
+
215
+ commit('CREDIT', parameters)
216
+ end
217
+
218
+ # Cancel a transaction that has been charged but has not yet made it
219
+ # through the batch process.
220
+ def void(money, authorization, options = {})
221
+ parameters = {
222
+ :transaction_id => authorization,
223
+ :posture => 'void'
224
+ }
225
+
226
+ add_optional_fields(parameters, options)
227
+ commit('update', parameters)
228
+ end
229
+
230
+ # Set up a sale that will be made on a regular basis for the same amount
231
+ # (ex. $20 a month for 12 months)
232
+ #
233
+ # The parameter :periodicity should be specified as either :monthly, :weekly, or :daily
234
+ # The parameter :payments should be the number of payments to be made
235
+ #
236
+ # gateway.recurring('2000', creditcard, :periodicity => :monthly, :payments => 12)
237
+ #
238
+ # The optional parameter :starting_at takes a date or time argument or a string in
239
+ # YYYYMMDD format and can be used to specify when the first charge will be made.
240
+ # If omitted the first charge will be immediate.
241
+ def recurring(money, creditcard_or_billing_id, options = {})
242
+ requires!(options, [:periodicity, :monthly, :weekly, :daily], :payments)
243
+
244
+ periodic_type = case options[:periodicity]
245
+ when :monthly
246
+ 'month'
247
+ when :weekly
248
+ 'week'
249
+ when :daily
250
+ 'day'
251
+ end
252
+
253
+ if options[:starting_at].nil?
254
+ start_date = Time.now.strftime('%Y-%m-%d')
255
+ elsif options[:starting_at].is_a?(String)
256
+ sa = options[:starting_at]
257
+ start_date = "#{sa[0..3]}-#{sa[4..5]}-#{sa[6..7]}"
258
+ else
259
+ start_date = options[:starting_at].strftime('%Y-%m-%d')
260
+ end
261
+
262
+ parameters = {
263
+ :transaction_amount => amount(money),
264
+ :schedule_periodic_type => periodic_type,
265
+ :schedule_create => 'true',
266
+ :schedule_limit => options[:payments].to_i > 1 ? options[:payments] : 1,
267
+ :schedule_periodic_number => 1,
268
+ :schedule_start => start_date
269
+ }
270
+
271
+ add_payment_source(parameters, creditcard_or_billing_id)
272
+ add_optional_fields(parameters, options)
273
+ add_address(parameters, options)
274
+ commit('AUTHORIZATION_CAPTURE', parameters)
275
+ end
276
+
277
+ def test?
278
+ @options[:test] || super
279
+ end
280
+
281
+ private
282
+
283
+ # add fields depending on payment source selected (cc or transaction id)
284
+ def add_payment_source(params, source)
285
+ if source.is_a?(String)
286
+ add_billing_id(params, source)
287
+ else
288
+ add_creditcard(params, source)
289
+ end
290
+ end
291
+
292
+ # add fields for credit card
293
+ def add_creditcard(params, creditcard)
294
+ params[:name] = creditcard.name
295
+ params[:number] = creditcard.number
296
+ params[:expiration_month] = creditcard.month
297
+ params[:expiration_year] = creditcard.year
298
+ params[:verification_number] = creditcard.verification_value if creditcard.verification_value?
299
+ end
300
+
301
+ # add field for "instant" transaction, using previous transaction id
302
+ def add_billing_id(params, billingid)
303
+ params[:transaction_id] = billingid
304
+ end
305
+
306
+ # add address fields if present
307
+ def add_address(params, options)
308
+ address = options[:billing_address] || options[:address]
309
+
310
+ if address
311
+ params[:address] = address[:address1] unless address[:address1].blank?
312
+ params[:city] = address[:city] unless address[:city].blank?
313
+ params[:state] = address[:state] unless address[:state].blank?
314
+ params[:zipcode] = address[:zip] unless address[:zip].blank?
315
+ params[:country] = address[:country] unless address[:country].blank?
316
+ end
317
+ end
318
+
319
+ def add_optional_fields(params, options)
320
+ params[:notes] = options[:description] unless options[:description].blank?
321
+ params[:invoice] = options[:order_id].to_s.gsub(/[^-\/\w.,']/, '') unless options[:order_id].blank?
322
+ end
323
+
324
+ def clean_and_stringify_params(parameters)
325
+ # PayJunction uses an HTTPS POST request to submit key-pairs. Here we're going
326
+ # to convert all symbols in the params to strings and remove any pairs that
327
+ # have a nil value to keep the gateway happy.
328
+ to_submit = {}
329
+ parameters.keys.reverse.each do |key|
330
+ if parameters[key]
331
+ to_submit[key.to_s] = parameters[key]
332
+ end
333
+ end
334
+ to_submit
335
+ end
336
+
337
+ def commit(action, parameters)
338
+ if test?
339
+ # test requests must use global test account
340
+ parameters[:logon] = 'pj-ql-01'
341
+ parameters[:password] = 'pj-ql-01p'
342
+ else
343
+ parameters[:logon] = @options[:login]
344
+ parameters[:password] = @options[:password]
345
+ end
346
+ parameters[:version] = API_VERSION
347
+ parameters[:transaction_type] = action
348
+
349
+ submission = clean_and_stringify_params(parameters)
350
+
351
+ if result = test_result_from_cc_number(parameters[:number])
352
+ return result
353
+ end
354
+
355
+ post_args = submission.collect { |key, value| "dc_#{key}=#{CGI.escape(value.to_s)}" }.join("&")
356
+ headers = {'Content-type' => 'application/x-www-form-urlencoded '}
357
+
358
+ data = ssl_post(LIVE_URL, post_args, headers) # execute transaction
359
+ @response = parse(data)
360
+
361
+ unless action == 'update'
362
+ success = SUCCESS_CODES.include?(@response[:response_code])
363
+ message = @response[:response_message]
364
+ authorization = @response[:transaction_id]
365
+ else
366
+ success = @response[:query_status] == true
367
+ message = 'Transaction update request.' # no message returned with this trans. type
368
+ authorization = parameters[:transaction_id] # maintain trans ID since it isn't returned
369
+ end
370
+
371
+ Response.new(success, message, @response,
372
+ :test => test?,
373
+ :authorization => authorization
374
+ )
375
+ end
376
+
377
+ def parse(body)
378
+ # PayJunction uses the Field Separator ASCII character to separate key/val
379
+ # pairs in the response. The <FS> character's octal value is 034.
380
+ #
381
+ # Sample response:
382
+ #
383
+ # transaction_id=44752<FS>response_code=M4<FS>response_message=Declined (INV TEST CARD).
384
+
385
+ pairs = body.chomp.split("\034")
386
+ response = {}
387
+ pairs.each do |pair|
388
+ key, val = pair.split('=')
389
+ response[key[3..-1].to_sym] = val ? normalize(val) : nil
390
+ end
391
+ response
392
+ end
393
+
394
+ # Make a ruby type out of the response string
395
+ def normalize(field)
396
+ case field
397
+ when "true" then true
398
+ when "false" then false
399
+ when "" then nil
400
+ when "null" then nil
401
+ else field
402
+ end
403
+ end
404
+
405
+ end
406
+ end
407
+ end
@@ -1,40 +1,105 @@
1
1
  require File.dirname(__FILE__) + '/payflow/payflow_common_api'
2
+ require File.dirname(__FILE__) + '/payflow/payflow_response'
3
+ require File.dirname(__FILE__) + '/payflow_express'
2
4
 
3
5
  module ActiveMerchant #:nodoc:
4
6
  module Billing #:nodoc:
5
7
  class PayflowGateway < Gateway
6
8
  include PayflowCommonAPI
7
9
 
8
- def authorize(money, credit_card, options = {})
9
- if result = test_result_from_cc_number(credit_card.number)
10
- return result
11
- end
12
-
13
- request = build_sale_or_authorization_request('Authorization', money, credit_card, options)
10
+ RECURRING_ACTIONS = Set.new([:add, :modify, :cancel, :inquiry, :reactivate, :payment])
11
+
12
+ self.supported_cardtypes = [:visa, :master, :american_express, :jcb, :discover, :diners_club]
13
+ self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_payflow-pro-overview-outside'
14
+ self.display_name = 'PayPal Payflow Pro'
15
+
16
+ def authorize(money, credit_card_or_reference, options = {})
17
+ request = build_sale_or_authorization_request(:authorization, money, credit_card_or_reference, options)
18
+
19
+ commit(request)
20
+ end
21
+
22
+ def purchase(money, credit_card_or_reference, options = {})
23
+ request = build_sale_or_authorization_request(:purchase, money, credit_card_or_reference, options)
24
+
14
25
  commit(request)
15
26
  end
16
27
 
17
- def purchase(money, credit_card, options = {})
18
- if result = test_result_from_cc_number(credit_card.number)
19
- return result
28
+ def credit(money, identification_or_credit_card, options = {})
29
+ if identification_or_credit_card.is_a?(String)
30
+ # Perform referenced credit
31
+ request = build_reference_request(:credit, money, identification_or_credit_card, options)
32
+ else
33
+ # Perform non-referenced credit
34
+ request = build_credit_card_request(:credit, money, identification_or_credit_card, options)
20
35
  end
21
36
 
22
- request = build_sale_or_authorization_request('Sale', money, credit_card, options)
23
37
  commit(request)
24
- end
38
+ end
39
+
40
+ # Adds or modifies a recurring Payflow profile. See the Payflow Pro Recurring Billing Guide for more details:
41
+ # https://www.paypal.com/en_US/pdf/PayflowPro_RecurringBilling_Guide.pdf
42
+ #
43
+ # Several options are available to customize the recurring profile:
44
+ #
45
+ # * <tt>profile_id</tt> - is only required for editing a recurring profile
46
+ # * <tt>starting_at</tt> - takes a Date, Time, or string in mmddyyyy format. The date must be in the future.
47
+ # * <tt>name</tt> - The name of the customer to be billed. If not specified, the name from the credit card is used.
48
+ # * <tt>periodicity</tt> - The frequency that the recurring payments will occur at. Can be one of
49
+ # :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly
50
+ # * <tt>payments</tt> - The term, or number of payments that will be made
51
+ # * <tt>comment</tt> - A comment associated with the profile
52
+ def recurring(money, credit_card, options = {})
53
+ options[:name] = credit_card.name if options[:name].blank? && credit_card
54
+ request = build_recurring_request(options[:profile_id] ? :modify : :add, money, options) do |xml|
55
+ add_credit_card(xml, credit_card) if credit_card
56
+ end
57
+ commit(request, :recurring)
58
+ end
59
+
60
+ def cancel_recurring(profile_id)
61
+ request = build_recurring_request(:cancel, 0, :profile_id => profile_id)
62
+ commit(request, :recurring)
63
+ end
64
+
65
+ def recurring_inquiry(profile_id)
66
+ request = build_recurring_request(:inquiry, nil, :profile_id => profile_id)
67
+ commit(request, :recurring)
68
+ end
25
69
 
26
70
  def express
27
71
  @express ||= PayflowExpressGateway.new(@options)
28
72
  end
29
73
 
30
- def self.supported_cardtypes
31
- [:visa, :master, :american_express, :jcb, :discover, :diners_club]
74
+ private
75
+ def build_sale_or_authorization_request(action, money, credit_card_or_reference, options)
76
+ if credit_card_or_reference.is_a?(String)
77
+ build_reference_sale_or_authorization_request(action, money, credit_card_or_reference, options)
78
+ else
79
+ build_credit_card_request(action, money, credit_card_or_reference, options)
80
+ end
32
81
  end
33
82
 
34
- private
35
- def build_sale_or_authorization_request(action, money, credit_card, options)
83
+ def build_reference_sale_or_authorization_request(action, money, reference, options)
36
84
  xml = Builder::XmlMarkup.new :indent => 2
37
- xml.tag! action do
85
+ xml.tag! TRANSACTIONS[action] do
86
+ xml.tag! 'PayData' do
87
+ xml.tag! 'Invoice' do
88
+ xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
89
+ end
90
+ xml.tag! 'Tender' do
91
+ xml.tag! 'Card' do
92
+ xml.tag! 'ExtData', 'Name' => 'ORIGID', 'Value' => reference
93
+ end
94
+ end
95
+ end
96
+ end
97
+ xml.target!
98
+ end
99
+
100
+ def build_credit_card_request(action, money, credit_card, options)
101
+ xml = Builder::XmlMarkup.new :indent => 2
102
+ xml.tag! TRANSACTIONS[action] do
38
103
  xml.tag! 'PayData' do
39
104
  xml.tag! 'Invoice' do
40
105
  xml.tag! 'CustIP', options[:ip] unless options[:ip].blank?
@@ -47,9 +112,7 @@ module ActiveMerchant #:nodoc:
47
112
  add_address(xml, 'BillTo', billing_address, options)
48
113
  add_address(xml, 'ShipTo', shipping_address, options)
49
114
 
50
- xml.tag! 'TotalAmt', amount(money), 'Currency' => currency(money)
51
-
52
-
115
+ xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
53
116
  end
54
117
 
55
118
  xml.tag! 'Tender' do
@@ -59,22 +122,28 @@ module ActiveMerchant #:nodoc:
59
122
  end
60
123
  xml.target!
61
124
  end
62
-
125
+
63
126
  def add_credit_card(xml, credit_card)
64
127
  xml.tag! 'Card' do
65
- xml.tag! 'CardType', CARD_MAPPING[credit_card.type.to_sym]
128
+ xml.tag! 'CardType', credit_card_type(credit_card)
66
129
  xml.tag! 'CardNum', credit_card.number
67
130
  xml.tag! 'ExpDate', expdate(credit_card)
68
131
  xml.tag! 'NameOnCard', credit_card.name
69
132
  xml.tag! 'CVNum', credit_card.verification_value if credit_card.verification_value?
70
133
 
71
- if [ 'switch', 'solo' ].include?(credit_card.type.to_s)
134
+ if requires_start_date_or_issue_number?(credit_card)
72
135
  xml.tag!('ExtData', 'Name' => 'CardStart', 'Value' => startdate(credit_card)) unless credit_card.start_month.blank? || credit_card.start_year.blank?
73
- xml.tag!('ExtData', 'Name' => 'CardIssue', 'Value' => credit_card.issue_number) unless credit_card.issue_number.blank?
136
+ xml.tag!('ExtData', 'Name' => 'CardIssue', 'Value' => format(credit_card.issue_number, :two_digits)) unless credit_card.issue_number.blank?
74
137
  end
75
138
  end
76
139
  end
77
140
 
141
+ def credit_card_type(credit_card)
142
+ return '' if credit_card.type.blank?
143
+
144
+ CARD_MAPPING[credit_card.type.to_sym]
145
+ end
146
+
78
147
  def expdate(creditcard)
79
148
  year = sprintf("%.4i", creditcard.year)
80
149
  month = sprintf("%.2i", creditcard.month)
@@ -89,8 +158,77 @@ module ActiveMerchant #:nodoc:
89
158
  "#{month}#{year}"
90
159
  end
91
160
 
92
- def build_response(success, message, response, options = {})
93
- Response.new(success, message, response, options)
161
+ def build_recurring_request(action, money, options)
162
+ unless RECURRING_ACTIONS.include?(action)
163
+ raise StandardError, "Invalid Recurring Profile Action: #{action}"
164
+ end
165
+
166
+ xml = Builder::XmlMarkup.new :indent => 2
167
+ xml.tag! 'RecurringProfiles' do
168
+ xml.tag! 'RecurringProfile' do
169
+ xml.tag! action.to_s.capitalize do
170
+ unless [:cancel, :inquiry].include?(action)
171
+ xml.tag! 'RPData' do
172
+ xml.tag! 'Name', options[:name] unless options[:name].nil?
173
+ xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
174
+ xml.tag! 'PayPeriod', get_pay_period(options)
175
+ xml.tag! 'Term', options[:payments] unless options[:payments].nil?
176
+ xml.tag! 'Comment', options[:comment] unless options[:comment].nil?
177
+
178
+
179
+ if initial_tx = options[:initial_transaction]
180
+ requires!(initial_tx, [:type, :authorization, :purchase])
181
+ requires!(initial_tx, :amount) if initial_tx[:type] == :purchase
182
+
183
+ xml.tag! 'OptionalTrans', TRANSACTIONS[initial_tx[:type]]
184
+ xml.tag! 'OptionalTransAmt', amount(initial_tx[:amount]) unless initial_tx[:amount].blank?
185
+ end
186
+
187
+ xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1 )
188
+ xml.tag! 'EMail', options[:email] unless options[:email].nil?
189
+
190
+ billing_address = options[:billing_address] || options[:address]
191
+ shipping_address = options[:shipping_address] || billing_address
192
+
193
+ add_address(xml, 'BillTo', billing_address, options)
194
+ add_address(xml, 'ShipTo', shipping_address, options)
195
+ end
196
+ xml.tag! 'Tender' do
197
+ yield xml
198
+ end
199
+ end
200
+ if action != :add
201
+ xml.tag! "ProfileID", options[:profile_id]
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ def get_pay_period(options)
209
+ requires!(options, [:periodicity, :bimonthly, :monthly, :biweekly, :weekly, :yearly, :daily, :semimonthly, :quadweekly, :quarterly, :semiyearly])
210
+ case options[:periodicity]
211
+ when :weekly then 'Weekly'
212
+ when :biweekly then 'Bi-weekly'
213
+ when :semimonthly then 'Semi-monthly'
214
+ when :quadweekly then 'Every four weeks'
215
+ when :monthly then 'Monthly'
216
+ when :quarterly then 'Quarterly'
217
+ when :semiyearly then 'Semi-yearly'
218
+ when :yearly then 'Yearly'
219
+ end
220
+ end
221
+
222
+ def format_rp_date(time)
223
+ case time
224
+ when Time, Date then time.strftime("%m%d%Y")
225
+ else
226
+ time.to_s
227
+ end
228
+ end
229
+
230
+ def build_response(success, message, response, options = {})
231
+ PayflowResponse.new(success, message, response, options)
94
232
  end
95
233
  end
96
234
  end