offsite_payments 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +5 -6
  5. data/lib/offsite_payments.rb +1 -8
  6. data/lib/offsite_payments/helper.rb +2 -2
  7. data/lib/offsite_payments/integrations/a1agregator.rb +1 -1
  8. data/lib/offsite_payments/integrations/chronopay.rb +2 -2
  9. data/lib/offsite_payments/integrations/coinbase.rb +166 -0
  10. data/lib/offsite_payments/integrations/direc_pay.rb +2 -2
  11. data/lib/offsite_payments/integrations/dotpay.rb +2 -2
  12. data/lib/offsite_payments/integrations/e_payment_plans.rb +1 -1
  13. data/lib/offsite_payments/integrations/gestpay.rb +2 -0
  14. data/lib/offsite_payments/integrations/ipay88.rb +17 -6
  15. data/lib/offsite_payments/integrations/mollie_ideal.rb +7 -4
  16. data/lib/offsite_payments/integrations/nochex.rb +1 -1
  17. data/lib/offsite_payments/integrations/pag_seguro.rb +25 -14
  18. data/lib/offsite_payments/integrations/pay_fast.rb +1 -1
  19. data/lib/offsite_payments/integrations/payflow_link.rb +1 -1
  20. data/lib/offsite_payments/integrations/paypal.rb +1 -1
  21. data/lib/offsite_payments/integrations/payu_in.rb +13 -3
  22. data/lib/offsite_payments/integrations/pxpay.rb +5 -3
  23. data/lib/offsite_payments/integrations/realex.rb +294 -0
  24. data/lib/offsite_payments/integrations/sage_pay_form.rb +3 -3
  25. data/lib/offsite_payments/integrations/two_checkout.rb +0 -3
  26. data/lib/offsite_payments/integrations/universal.rb +4 -3
  27. data/lib/offsite_payments/integrations/valitor.rb +1 -1
  28. data/lib/offsite_payments/notification.rb +1 -1
  29. data/lib/offsite_payments/version.rb +1 -1
  30. metadata +44 -16
  31. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6ceed474a3b7ef686e45078b9d45216ef5ab4e0b
4
- data.tar.gz: f8002c4b3cf915ffb6569bd0b89d08e19b31ef90
3
+ metadata.gz: 657879b046bcb541ea2225eee85af0f5916a4d79
4
+ data.tar.gz: 73e5e75b10f09b131966a268697dd641dfc1bd93
5
5
  SHA512:
6
- metadata.gz: 72f336afee267844d2220db70f4eeedb429b9b4e098f73795c767f4a97e3a2218d61be15ee92bb704d994ca92cf59c718d338f8006c864090664fb588e510cb9
7
- data.tar.gz: bb29af4399b7c1ed0633b6ccb848b57cd3f2e2c9042336ee5f00807499211ec6639baa1b007afecdfc2991d2b5098be2844d86ba52e40b108840a25917a9a27e
6
+ metadata.gz: 697047cf3a0639a5093c1f1ba0c5a19c9040a4694405ea7ca515b0a2bcabb3a52527ac97e49919b8fcd42fd1c27b7f10ead8d55c9aea4597eb4cdcbc20308bc3
7
+ data.tar.gz: 22c3e6d5e428a00fd8943d15ad95ae3e811716d8cbbdf778cb7f881f16665b952a63223a786274b616c6741118ab1f00feef0dd87dbabb04d012ab966b4a3440
Binary file
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -35,6 +35,7 @@ Or, if you're using Bundler, just add the following to your Gemfile:
35
35
  * [A1Agregator](http://a1agregator.ru/) - RU
36
36
  * [Authorize.Net SIM](http://developer.authorize.net/api/sim/) - US
37
37
  * [Banca Sella GestPay](https://www.gestpay.it/)
38
+ * [BitPay](https://bitpay.com/)
38
39
  * [Chronopay](http://www.chronopay.com)
39
40
  * [DirecPay](http://www.timesofmoney.com/direcpay/jsp/home.jsp)
40
41
  * [Direct-eBanking / sofortueberweisung.de by Payment-Networks AG](https://www.payment-network.com/deb_com_en/merchantarea/home) - DE, AT, CH, BE, UK, NL
@@ -49,6 +50,7 @@ Or, if you're using Bundler, just add the following to your Gemfile:
49
50
  * [PagSeguro](http://www.pagseguro.com.br/) - BR
50
51
  * [Paxum](https://www.paxum.com/)
51
52
  * [PayPal Website Payments Standard](https://www.paypal.com/cgi-bin/webscr?cmd#_wp-standard-overview-outside)
53
+ * [PayDollar](http://www.paydollar.com)
52
54
  * [Paysbuy](https://www.paysbuy.com/) - TH
53
55
  * [Platron](https://www.platron.ru/) - RU
54
56
  * [RBK Money](https://rbkmoney.ru/) - RU
@@ -61,10 +63,7 @@ Or, if you're using Bundler, just add the following to your Gemfile:
61
63
  * [WebPay](http://webpay.by/)
62
64
  * [WorldPay](http://www.worldpay.com)
63
65
 
64
- ## Contributing
66
+ ## Misc.
65
67
 
66
- The source code is hosted at [GitHub](http://github.com/Shopify/offsite_payments), and can be fetched using:
67
-
68
- git clone https://github.com/Shopify/offsite_payments.git
69
-
70
- Please don't touch the CHANGELOG in your pull requests, we'll add the appropriate CHANGELOG entries at release time.
68
+ - This library is MIT licensed.
69
+ - We will gladly accept contributions. See **CONTRIBUTING.md** for more information.
@@ -5,14 +5,7 @@ require "socket"
5
5
 
6
6
  require 'active_support/core_ext/class/delegating_attributes'
7
7
 
8
- require 'active_utils/common/network_connection_retries'
9
- require 'active_utils/common/connection'
10
- require 'active_utils/common/requires_parameters'
11
- require 'active_utils/common/country'
12
- require 'active_utils/common/error'
13
- require 'active_utils/common/post_data'
14
- require 'active_utils/common/posts_data'
15
- require 'active_utils/common/currency_code'
8
+ require 'active_utils'
16
9
 
17
10
  require "offsite_payments/helper"
18
11
  require "offsite_payments/notification"
@@ -90,9 +90,9 @@ module OffsitePayments #:nodoc:
90
90
  end
91
91
 
92
92
  def lookup_country_code(name_or_code, format = country_format)
93
- country = ActiveMerchant::Country.find(name_or_code)
93
+ country = ActiveUtils::Country.find(name_or_code)
94
94
  country.code(format).to_s
95
- rescue ActiveMerchant::InvalidCountryCodeError
95
+ rescue ActiveUtils::InvalidCountryCodeError
96
96
  name_or_code
97
97
  end
98
98
 
@@ -213,7 +213,7 @@ module OffsitePayments #:nodoc:
213
213
  end
214
214
 
215
215
  class Status
216
- include ActiveMerchant::PostsData
216
+ include ActiveUtils::PostsData
217
217
 
218
218
  STATUS_TEST_URL = 'https://partner.a1pay.ru/a1lite/info/'
219
219
 
@@ -117,10 +117,10 @@ module OffsitePayments #:nodoc:
117
117
  private
118
118
 
119
119
  def checkout_language_from_country(country_code)
120
- country = ActiveMerchant::Country.find(country_code)
120
+ country = ActiveUtils::Country.find(country_code)
121
121
  short_code = country.code(:alpha2).to_s
122
122
  LANG_FOR_COUNTRY[short_code]
123
- rescue ActiveMerchant::InvalidCountryCodeError
123
+ rescue ActiveUtils::InvalidCountryCodeError
124
124
  'EN'
125
125
  end
126
126
  end
@@ -0,0 +1,166 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Coinbase
4
+ mattr_accessor :service_url
5
+ self.service_url = 'https://www.coinbase.com/checkouts/redirect'
6
+
7
+ mattr_accessor :buttoncreate_url
8
+ self.buttoncreate_url = 'https://api.coinbase.com/v1/buttons'
9
+
10
+ mattr_accessor :notification_confirmation_url
11
+ self.notification_confirmation_url = 'https://api.coinbase.com/v1/orders/%s'
12
+
13
+ # options should be { credential1: "your API key", credential2: "your API secret" }
14
+ def self.notification(post, options = {})
15
+ Notification.new(post, options)
16
+ end
17
+
18
+ def self.return(query_string, options = {})
19
+ Return.new(query_string, options)
20
+ end
21
+
22
+ class Helper < OffsitePayments::Helper
23
+ # account should be a Coinbase API key; see https://coinbase.com/account/integrations
24
+ # options[:credential2] should be the corresponding API secret
25
+ def initialize(order_id, account, options)
26
+ super
27
+
28
+ @order = order_id
29
+ @account = account
30
+ @options = options
31
+ @options[:credential1] ||= ''
32
+ @options[:credential2] ||= ''
33
+ end
34
+
35
+ mapping :notify_url, 'notify_url'
36
+ mapping :return_url, 'return_url'
37
+ mapping :cancel_return_url, 'cancel_return_url'
38
+
39
+ def form_fields
40
+ uri = URI.parse(Coinbase.buttoncreate_url)
41
+
42
+ request_body = {
43
+ 'button[auto_redirect]' => true,
44
+ 'button[name]' => @options[:description] || "Your Order",
45
+ 'button[price_string]' => @options[:amount],
46
+ 'button[price_currency_iso]' => @options[:currency],
47
+ 'button[custom]' => @order,
48
+ 'button[callback_url]' => @fields['notify_url'],
49
+ 'button[success_url]' => @fields['return_url'],
50
+ 'button[cancel_url]' => @fields['cancel_return_url'],
51
+ 'api_key' => @account
52
+ }.to_query
53
+
54
+ data = Coinbase.do_request(uri, @account, @options[:credential2], request_body)
55
+ json = JSON.parse(data)
56
+
57
+ raise ActionViewHelperError, "Error occured while contacting gateway : #{json['error']}" if json['error']
58
+
59
+ {'id' => json['button']['code']}
60
+ rescue JSON::ParserError
61
+ raise ActionViewHelperError, 'Invalid response from gateway. Please try again.'
62
+ end
63
+ end
64
+
65
+ class Notification < OffsitePayments::Notification
66
+
67
+ def complete?
68
+ status == "Completed"
69
+ end
70
+
71
+ def item_id
72
+ params['custom']
73
+ end
74
+
75
+ def transaction_id
76
+ params['id']
77
+ end
78
+
79
+ def received_at
80
+ Time.iso8601(params['created_at']).to_time.to_i
81
+ end
82
+
83
+ def gross
84
+ "%.2f" % (params['total_native']['cents'].to_f / 100)
85
+ end
86
+
87
+ def currency
88
+ params['total_native']['currency_iso']
89
+ end
90
+
91
+ def status
92
+ case params['status']
93
+ when "completed"
94
+ "Completed"
95
+ else
96
+ "Failed"
97
+ end
98
+ end
99
+
100
+ # Acknowledge the transaction to Coinbase. This method has to be called after a new
101
+ # apc arrives. Coinbase will verify that all the information we received are correct
102
+ # and will return a ok or a fail.
103
+ def acknowledge(authcode = {})
104
+
105
+ uri = URI.parse(Coinbase.notification_confirmation_url % transaction_id)
106
+
107
+ response = Coinbase.do_request(uri, @options[:credential1], @options[:credential2])
108
+ return false if response.nil?
109
+
110
+ posted_order = @params
111
+ parse(response)
112
+
113
+ %w(id custom total_native status).all? { |param| posted_order[param] == @params[param] }
114
+ end
115
+
116
+ private
117
+
118
+ def parse(post)
119
+ @raw = post.to_s
120
+ @params = JSON.parse(post)['order']
121
+ end
122
+ end
123
+
124
+ class Return < OffsitePayments::Return
125
+ def initialize(query_string, options = {})
126
+ super
127
+ @notification = Notification.new(@params.to_json, options)
128
+ end
129
+
130
+ def parse(query_string)
131
+ parsed_hash = Rack::Utils.parse_nested_query(query_string)
132
+
133
+ if native_cents = parsed_hash['order'] && parsed_hash['order']['total_native'] && parsed_hash['order']['total_native']['cents']
134
+ parsed_hash['order']['total_native']['cents'] = native_cents.to_i
135
+ end
136
+
137
+ parsed_hash
138
+ end
139
+ end
140
+
141
+ protected
142
+
143
+ def self.do_request(uri, api_key, api_secret, post_body = nil)
144
+ nonce = (Time.now.to_f * 1e6).to_i
145
+ hmac_message = nonce.to_s + uri.to_s
146
+
147
+ if post_body
148
+ request = Net::HTTP::Post.new(uri.request_uri)
149
+ request.body = post_body
150
+ hmac_message = hmac_message + request.body
151
+ else
152
+ request = Net::HTTP::Get.new(uri.path)
153
+ end
154
+
155
+ http = Net::HTTP.new(uri.host, uri.port)
156
+ http.use_ssl = true
157
+
158
+ request['ACCESS_KEY'] = api_key
159
+ request['ACCESS_SIGNATURE'] = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), api_secret, hmac_message)
160
+ request['ACCESS_NONCE'] = nonce.to_s
161
+
162
+ http.request(request).body
163
+ end
164
+ end
165
+ end
166
+ end
@@ -309,7 +309,7 @@ module OffsitePayments #:nodoc:
309
309
  end
310
310
 
311
311
  class Status
312
- include ActiveMerchant::PostsData
312
+ include ActiveUtils::PostsData
313
313
 
314
314
  STATUS_TEST_URL = 'https://test.direcpay.com/direcpay/secure/dpMerchantTransaction.jsp'
315
315
  STATUS_LIVE_URL = 'https://www.timesofmoney.com/direcpay/secure/dpPullMerchAtrnDtls.jsp'
@@ -324,7 +324,7 @@ module OffsitePayments #:nodoc:
324
324
  def update(authorization, notification_url)
325
325
  url = test? ? STATUS_TEST_URL : STATUS_LIVE_URL
326
326
  parameters = [ authorization, account, notification_url ]
327
- data = ActiveMerchant::PostData.new
327
+ data = ActiveUtils::PostData.new
328
328
  data[:requestparams] = parameters.join('|')
329
329
 
330
330
  response = ssl_get("#{url}?#{data.to_post_data}")
@@ -75,9 +75,9 @@ module OffsitePayments #:nodoc:
75
75
  private
76
76
 
77
77
  def lookup_country_code(name_or_code, format = country_format)
78
- country = ActiveMerchant::Country.find(name_or_code)
78
+ country = ActiveUtils::Country.find(name_or_code)
79
79
  country.code(format).to_s
80
- rescue ActiveMerchant::InvalidCountryCodeError
80
+ rescue ActiveUtils::InvalidCountryCodeError
81
81
  name_or_code
82
82
  end
83
83
  end
@@ -67,7 +67,7 @@ module OffsitePayments #:nodoc:
67
67
  end
68
68
 
69
69
  class Notification < OffsitePayments::Notification
70
- include ActiveMerchant::PostsData
70
+ include ActiveUtils::PostsData
71
71
  def complete?
72
72
  status == "Completed"
73
73
  end
@@ -50,6 +50,8 @@ module OffsitePayments #:nodoc:
50
50
  site.use_ssl = true
51
51
  site.verify_mode = OpenSSL::SSL::VERIFY_NONE
52
52
  site.get(path).body
53
+ rescue Timeout::Error, Errno::ECONNRESET, Errno::ETIMEDOUT
54
+ raise ActionViewHelperError, "Error occured while contacting payment gateway. Please try again."
53
55
  end
54
56
  end
55
57
 
@@ -1,6 +1,8 @@
1
1
  module OffsitePayments #:nodoc:
2
2
  module Integrations #:nodoc:
3
3
  module Ipay88
4
+ CANCELLED_ERROR_DESCRIPTION = 'Customer Cancel Transaction'
5
+
4
6
  def self.service_url
5
7
  "https://www.mobile88.com/epayment/entry.asp"
6
8
  end
@@ -13,8 +15,12 @@ module OffsitePayments #:nodoc:
13
15
  Return.new(query_string, options)
14
16
  end
15
17
 
18
+ def self.notification(post, options = {})
19
+ Notification.new(post, options)
20
+ end
21
+
16
22
  class Helper < OffsitePayments::Helper
17
- include ActiveMerchant::RequiresParameters
23
+ include ActiveUtils::RequiresParameters
18
24
 
19
25
  # Currencies supported
20
26
  # MYR (Malaysian Ringgit - for all payment methods except China Union Pay and PayPal)
@@ -108,6 +114,7 @@ module OffsitePayments #:nodoc:
108
114
  mapping :language, "Lang"
109
115
  mapping :payment, "PaymentId"
110
116
  mapping :return_url, "ResponseURL"
117
+ mapping :notify_url, "BackendURL"
111
118
  mapping :signature, "Signature"
112
119
 
113
120
  protected
@@ -123,10 +130,14 @@ module OffsitePayments #:nodoc:
123
130
  end
124
131
 
125
132
  class Notification < OffsitePayments::Notification
126
- include ActiveMerchant::PostsData
133
+ include ActiveUtils::PostsData
127
134
 
128
135
  def status
129
- params["Status"] == '1' ? 'Completed' : 'Failed'
136
+ if params["Status"] == '1'
137
+ 'Completed'
138
+ else
139
+ error == CANCELLED_ERROR_DESCRIPTION ? 'Cancelled' : 'Failed'
140
+ end
130
141
  end
131
142
 
132
143
  def complete?
@@ -138,7 +149,7 @@ module OffsitePayments #:nodoc:
138
149
  end
139
150
 
140
151
  def gross
141
- params["Amount"]
152
+ params["Amount"].try(:gsub, /,(?=\d{3}\b)/, '')
142
153
  end
143
154
 
144
155
  def currency
@@ -182,7 +193,7 @@ module OffsitePayments #:nodoc:
182
193
  end
183
194
 
184
195
  def acknowledge
185
- secure? && success? && requery == "00"
196
+ secure? && (!success? || requery == "00")
186
197
  end
187
198
 
188
199
  protected
@@ -228,7 +239,7 @@ module OffsitePayments #:nodoc:
228
239
  end
229
240
 
230
241
  def cancelled?
231
- params["ErrDesc"] == 'Customer Cancel Transaction'
242
+ params["ErrDesc"] == CANCELLED_ERROR_DESCRIPTION
232
243
  end
233
244
 
234
245
  def message
@@ -2,7 +2,7 @@ module OffsitePayments #:nodoc:
2
2
  module Integrations #:nodoc:
3
3
  module MollieIdeal
4
4
  class API
5
- include ActiveMerchant::PostsData
5
+ include ActiveUtils::PostsData
6
6
 
7
7
  attr_reader :token
8
8
 
@@ -25,7 +25,7 @@ module OffsitePayments #:nodoc:
25
25
  end
26
26
  end
27
27
 
28
- RedirectError = Class.new(ActiveMerchant::ActiveMerchantError)
28
+ RedirectError = Class.new(ActiveUtils::ActiveUtilsError)
29
29
 
30
30
  MOLLIE_API_V1_URI = 'https://api.mollie.nl/v1/'.freeze
31
31
 
@@ -137,10 +137,13 @@ module OffsitePayments #:nodoc:
137
137
 
138
138
  def request_redirect
139
139
  MollieIdeal.create_payment(token, redirect_paramaters)
140
- rescue ActiveMerchant::ResponseError => e
141
- if %w(401 403 422).include?(e.response.code)
140
+ rescue ActiveUtils::ResponseError => e
141
+ case e.response.code
142
+ when '401', '403', '422'
142
143
  error = JSON.parse(e.response.body)['error']['message']
143
144
  raise ActionViewHelperError, error
145
+ when '503'
146
+ raise ActionViewHelperError, 'Service temporarily unavailable. Please try again.'
144
147
  else
145
148
  raise
146
149
  end
@@ -139,7 +139,7 @@ module OffsitePayments #:nodoc:
139
139
 
140
140
  # Parser and handler for incoming Automatic Payment Confirmations from Nochex.
141
141
  class Notification < OffsitePayments::Notification
142
- include ActiveMerchant::PostsData
142
+ include ActiveUtils::PostsData
143
143
 
144
144
  def complete?
145
145
  status == 'Completed'
@@ -62,17 +62,18 @@ module OffsitePayments #:nodoc:
62
62
 
63
63
  mapping :order, 'reference'
64
64
 
65
- mapping :billing_address, :city => 'shippingAddressCity',
66
- :address1 => 'shippingAddressStreet',
67
- :address2 => 'shippingAddressNumber',
68
- :state => 'shippingAddressState',
69
- :zip => 'shippingAddressPostalCode',
70
- :country => 'shippingAddressCountry'
71
-
72
65
  mapping :notify_url, 'notificationURL'
73
66
  mapping :return_url, 'redirectURL'
74
67
  mapping :description, 'itemDescription1'
75
68
 
69
+ def shipping_address(params = {})
70
+ add_field('shippingAddressCity', params[:city].slice(0, 60)) if params[:city]
71
+ add_field('shippingAddressStreet', params[:address1].slice(0, 80)) if params[:address1]
72
+ add_field('shippingAddressComplement', params[:address2].slice(0, 40)) if params[:address2]
73
+ add_field('shippingAddressState', params[:state])
74
+ add_field('shippingAddressPostalCode', params[:zip].delete("^0-9").slice(0, 8)) if params[:zip]
75
+ end
76
+
76
77
  def form_fields
77
78
  invoice_id = fetch_token
78
79
 
@@ -84,11 +85,13 @@ module OffsitePayments #:nodoc:
84
85
  end
85
86
 
86
87
  def customer(params = {})
87
- phone = area_code_and_number(params[:phone])
88
88
  full_name = remove_excessive_whitespace("#{params[:first_name]} #{params[:last_name]}")
89
89
 
90
- add_field("senderAreaCode", phone[0])
91
- add_field("senderPhone", phone[1])
90
+ if phone = area_code_and_number(params[:phone])
91
+ add_field("senderAreaCode", phone[0])
92
+ add_field("senderPhone", phone[1])
93
+ end
94
+
92
95
  add_field("senderEmail", params[:email])
93
96
  add_field('senderName', full_name)
94
97
  end
@@ -108,11 +111,12 @@ module OffsitePayments #:nodoc:
108
111
  check_for_errors(response, xml)
109
112
 
110
113
  extract_token(xml)
111
- rescue Timeout::Error, Errno::ECONNRESET => e
114
+ rescue Timeout::Error, Errno::ECONNRESET, Errno::ETIMEDOUT => e
112
115
  raise ActionViewHelperError, "Erro ao conectar-se ao PagSeguro. Por favor, tente novamente."
113
116
  end
114
117
 
115
118
  def area_code_and_number(phone)
119
+ return if phone.nil?
116
120
  phone.gsub!(/[^\d]/, '')
117
121
 
118
122
  ddd = phone.slice(0..1)
@@ -130,7 +134,7 @@ module OffsitePayments #:nodoc:
130
134
  when "401"
131
135
  raise ActionViewHelperError, "Token do PagSeguro inválido."
132
136
  else
133
- raise ActiveMerchant::ResponseError, response
137
+ raise ActiveUtils::ResponseError, response
134
138
  end
135
139
  end
136
140
 
@@ -161,13 +165,19 @@ module OffsitePayments #:nodoc:
161
165
  end
162
166
 
163
167
  class Notification < OffsitePayments::Notification
168
+ class NotificationError < StandardError; end
169
+
164
170
  def initialize(post, options = {})
171
+ @acknowledge = true
165
172
  notify_code = parse_http_query(post)["notificationCode"]
166
173
  email = options[:credential1]
167
174
  token = options[:credential2]
168
175
 
169
176
  uri = URI.join(PagSeguro.notification_url, notify_code)
170
177
  parse_xml(web_get(uri, email: email, token: token))
178
+
179
+ rescue NotificationError
180
+ @acknowledge = false
171
181
  end
172
182
 
173
183
  def complete?
@@ -223,9 +233,8 @@ module OffsitePayments #:nodoc:
223
233
  end
224
234
  end
225
235
 
226
- # There's no acknowledge for PagSeguro
227
236
  def acknowledge
228
- true
237
+ @acknowledge
229
238
  end
230
239
 
231
240
  private
@@ -234,6 +243,8 @@ module OffsitePayments #:nodoc:
234
243
  uri.query = URI.encode_www_form(params)
235
244
 
236
245
  response = Net::HTTP.get_response(uri)
246
+ raise NotificationError if response.code.to_i > 200
247
+
237
248
  response.body
238
249
  end
239
250
 
@@ -172,7 +172,7 @@ module OffsitePayments #:nodoc:
172
172
  # end
173
173
  # end
174
174
  class Notification < OffsitePayments::Notification
175
- include ActiveMerchant::PostsData
175
+ include ActiveUtils::PostsData
176
176
  include Common
177
177
 
178
178
  # Was the transaction complete?
@@ -13,7 +13,7 @@ module OffsitePayments #:nodoc:
13
13
  end
14
14
 
15
15
  class Helper < OffsitePayments::Helper
16
- include ActiveMerchant::PostsData
16
+ include ActiveUtils::PostsData
17
17
 
18
18
  def initialize(order, account, options = {})
19
19
  super
@@ -180,7 +180,7 @@ module OffsitePayments #:nodoc:
180
180
  # end
181
181
  # end
182
182
  class Notification < OffsitePayments::Notification
183
- include ActiveMerchant::PostsData
183
+ include ActiveUtils::PostsData
184
184
 
185
185
  def initialize(post, options = {})
186
186
  super
@@ -116,7 +116,7 @@ module OffsitePayments #:nodoc:
116
116
 
117
117
  # Order amount should be equal to gross - discount
118
118
  def amount_ok?( order_amount, order_discount = BigDecimal.new( '0.0' ) )
119
- BigDecimal.new( gross ) == order_amount && BigDecimal.new( discount.to_s ) == order_discount
119
+ BigDecimal.new( original_gross ) == order_amount && BigDecimal.new( discount.to_s ) == order_discount
120
120
  end
121
121
 
122
122
  # Status of transaction return from the PayU. List of possible values:
@@ -162,10 +162,14 @@ module OffsitePayments #:nodoc:
162
162
  end
163
163
 
164
164
  # original amount send by merchant
165
- def gross
165
+ def original_gross
166
166
  params['amount']
167
167
  end
168
168
 
169
+ def gross
170
+ parse_and_round_gross_amount(params['amount'])
171
+ end
172
+
169
173
  # This is discount given to user - based on promotion set by merchants.
170
174
  def discount
171
175
  params['discount']
@@ -225,7 +229,7 @@ module OffsitePayments #:nodoc:
225
229
  end
226
230
 
227
231
  def checksum_ok?
228
- checksum_fields = [transaction_status, *user_defined.reverse, customer_email, customer_first_name, product_info, gross, invoice]
232
+ checksum_fields = [transaction_status, *user_defined.reverse, customer_email, customer_first_name, product_info, original_gross, invoice]
229
233
 
230
234
  unless Digest::SHA512.hexdigest([@secret_key, *checksum_fields, @merchant_id].join("|")) == checksum
231
235
  @message = 'Return checksum not matching the data provided'
@@ -233,6 +237,12 @@ module OffsitePayments #:nodoc:
233
237
  end
234
238
  true
235
239
  end
240
+
241
+ private
242
+ def parse_and_round_gross_amount(amount)
243
+ rounded_amount = (amount.to_f * 100.0).round
244
+ sprintf("%.2f", rounded_amount / 100.00)
245
+ end
236
246
  end
237
247
 
238
248
  class Return < OffsitePayments::Return
@@ -16,7 +16,7 @@ module OffsitePayments #:nodoc:
16
16
  end
17
17
 
18
18
  class Helper < OffsitePayments::Helper
19
- include ActiveMerchant::PostsData
19
+ include ActiveUtils::PostsData
20
20
 
21
21
  attr_reader :token_parameters, :redirect_parameters
22
22
 
@@ -58,6 +58,8 @@ module OffsitePayments #:nodoc:
58
58
  end
59
59
 
60
60
  url.to_s
61
+ rescue ActiveUtils::ConnectionError
62
+ raise ActionViewHelperError, "A connection error occurred while contacting the payment gateway. Please try again."
61
63
  end
62
64
 
63
65
  def form_method
@@ -102,8 +104,8 @@ module OffsitePayments #:nodoc:
102
104
  end
103
105
 
104
106
  class Notification < OffsitePayments::Notification
105
- include ActiveMerchant::PostsData
106
- include ActiveMerchant::RequiresParameters
107
+ include ActiveUtils::PostsData
108
+ include ActiveUtils::RequiresParameters
107
109
 
108
110
  def initialize(query_string, options={})
109
111
  # PxPay appends ?result=...&userid=... to whatever return_url was specified, even if that URL ended with a ?query.
@@ -0,0 +1,294 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Realex
4
+ mattr_accessor :production_url
5
+ mattr_accessor :test_url
6
+ self.production_url = 'https://hpp.realexpayments.com/pay'
7
+ self.test_url = 'https://hpp.sandbox.realexpayments.com/pay'
8
+
9
+ def self.helper(order, account, options={})
10
+ Helper.new(order, account, options)
11
+ end
12
+
13
+ def self.notification(query_string, options={})
14
+ Notification.new(query_string, options)
15
+ end
16
+
17
+ def self.return(query_string, options={})
18
+ Return.new(query_string, options)
19
+ end
20
+
21
+ def self.service_url
22
+ mode = OffsitePayments.mode
23
+ case mode
24
+ when :production
25
+ self.production_url
26
+ when :test
27
+ self.test_url
28
+ else
29
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
30
+ end
31
+ end
32
+
33
+ module Common
34
+ CURRENCY_SPECIAL_MINOR_UNITS = {
35
+ 'BIF' => 0,
36
+ 'BYR' => 0,
37
+ 'CLF' => 0,
38
+ 'CLP' => 0,
39
+ 'CVE' => 0,
40
+ 'DJF' => 0,
41
+ 'GNF' => 0,
42
+ 'HUF' => 0,
43
+ 'ISK' => 0,
44
+ 'JPY' => 0,
45
+ 'KMF' => 0,
46
+ 'KRW' => 0,
47
+ 'PYG' => 0,
48
+ 'RWF' => 0,
49
+ 'UGX' => 0,
50
+ 'UYI' => 0,
51
+ 'VND' => 0,
52
+ 'VUV' => 0,
53
+ 'XAF' => 0,
54
+ 'XOF' => 0,
55
+ 'XPF' => 0,
56
+ 'BHD' => 3,
57
+ 'IQD' => 3,
58
+ 'JOD' => 3,
59
+ 'KWD' => 3,
60
+ 'LYD' => 3,
61
+ 'OMR' => 3,
62
+ 'TND' => 3,
63
+ 'COU' => 4
64
+ }
65
+
66
+ def create_signature(fields, secret)
67
+ data = fields.join('.')
68
+ digest = Digest::SHA1.hexdigest(data)
69
+ signed = "#{digest}.#{secret}"
70
+ Digest::SHA1.hexdigest(signed)
71
+ end
72
+
73
+ # Realex accepts currency amounts as an integer in the lowest value
74
+ # e.g.
75
+ # format_amount(110.56, 'GBP')
76
+ # => 11056
77
+ def format_amount(amount, currency)
78
+ units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2
79
+ multiple = 10**units
80
+ return (amount.to_f * multiple.to_f).to_i
81
+ end
82
+
83
+ # Realex returns currency amount as an integer
84
+ def format_amount_as_float(amount, currency)
85
+ units = CURRENCY_SPECIAL_MINOR_UNITS[currency] || 2
86
+ divisor = 10**units
87
+ return (amount.to_f / divisor.to_f)
88
+ end
89
+
90
+ def extract_digits(value)
91
+ value.scan(/\d+/).join('')
92
+ end
93
+
94
+ def extract_avs_code(params={})
95
+ [extract_digits(params[:zip]), extract_digits(params[:address1])].join('|')
96
+ end
97
+
98
+ end
99
+
100
+ class Helper < OffsitePayments::Helper
101
+ include Common
102
+
103
+ def initialize(order, account, options = {})
104
+ @timestamp = Time.now.strftime('%Y%m%d%H%M%S')
105
+ @currency = options[:currency]
106
+ @merchant_id = account
107
+ @sub_account = options[:credential2]
108
+ @secret = options[:credential3]
109
+ super
110
+ # Credentials
111
+ add_field 'MERCHANT_ID', @merchant_id
112
+ add_field 'ACCOUNT', @sub_account
113
+ # Defaults
114
+ add_field 'AUTO_SETTLE_FLAG', '1'
115
+ add_field 'RETURN_TSS', '1'
116
+ add_field 'TIMESTAMP', @timestamp
117
+ # Realex does not send back CURRENCY param in response
118
+ # however it does echo any other param so we send it twice.
119
+ add_field 'X-CURRENCY', @currency
120
+ end
121
+
122
+ def form_fields
123
+ sign_fields
124
+ end
125
+
126
+ def amount=(amount)
127
+ add_field 'AMOUNT', format_amount(amount, @currency)
128
+ end
129
+
130
+ def billing_address(params={})
131
+ add_field(mappings[:billing_address][:zip], extract_avs_code(params))
132
+ add_field(mappings[:billing_address][:country], lookup_country_code(params[:country]))
133
+ end
134
+
135
+ def shipping_address(params={})
136
+ add_field(mappings[:shipping_address][:zip], extract_avs_code(params))
137
+ add_field(mappings[:shipping_address][:country], lookup_country_code(params[:country]))
138
+ end
139
+
140
+ def sign_fields
141
+ @fields.merge!('SHA1HASH' => generate_signature)
142
+ end
143
+
144
+ def generate_signature
145
+ fields_to_sign = []
146
+ ['TIMESTAMP', 'MERCHANT_ID', 'ORDER_ID', 'AMOUNT', 'CURRENCY'].each do |field|
147
+ fields_to_sign << @fields[field]
148
+ end
149
+
150
+ create_signature(fields_to_sign, @secret)
151
+ end
152
+
153
+ # Realex Required Fields
154
+ mapping :currency, 'CURRENCY'
155
+
156
+ mapping :order, 'ORDER_ID'
157
+ mapping :amount, 'AMOUNT'
158
+ mapping :return_url, 'MERCHANT_RESPONSE_URL'
159
+
160
+ # Realex Optional fields
161
+ mapping :customer, :email => 'CUST_NUM'
162
+
163
+ mapping :shipping_address, :zip => 'SHIPPING_CODE',
164
+ :country => 'SHIPPING_CO'
165
+ mapping :billing_address, :zip => 'BILLING_CODE',
166
+ :country => 'BILLING_CO'
167
+ end
168
+
169
+ class Notification < OffsitePayments::Notification
170
+ include Common
171
+ def initialize(post, options={})
172
+ super
173
+ @secret = options[:credential3]
174
+ end
175
+
176
+ # Required Notification methods to define
177
+ def status
178
+ if result == '00'
179
+ 'Completed'
180
+ else
181
+ 'Invalid'
182
+ end
183
+ end
184
+
185
+ # Realex does not send back the currency param by default
186
+ # we have sent this additional parameter
187
+ def currency
188
+ params['X-CURRENCY']
189
+ end
190
+
191
+ def gross
192
+ format_amount_as_float(params['AMOUNT'], currency)
193
+ end
194
+
195
+ def complete?
196
+ verified? && status == 'Completed'
197
+ end
198
+
199
+ # Fields for Realex signature verification
200
+ def timestamp
201
+ params['TIMESTAMP']
202
+ end
203
+
204
+ def merchant_id
205
+ params['MERCHANT_ID']
206
+ end
207
+
208
+ def order_id
209
+ params['ORDER_ID']
210
+ end
211
+
212
+ def result
213
+ params['RESULT']
214
+ end
215
+
216
+ def message
217
+ params['MESSAGE']
218
+ end
219
+
220
+ def pasref
221
+ params['PASREF']
222
+ end
223
+
224
+ def authcode
225
+ params['AUTHCODE']
226
+ end
227
+
228
+ def signature
229
+ params['SHA1HASH']
230
+ end
231
+
232
+ def calculated_signature
233
+ fields = [timestamp, merchant_id, order_id, result, message, pasref, authcode]
234
+ create_signature(fields, @secret)
235
+ end
236
+
237
+ def verified?
238
+ signature == calculated_signature
239
+ end
240
+
241
+ # Extra data (available from Realex)
242
+ def cvn_result
243
+ params['CVNRESULT']
244
+ end
245
+
246
+ def avs_postcode_result
247
+ params['AVSPOSTCODERESULT']
248
+ end
249
+
250
+ def avs_address_result
251
+ params['AVSADDRESSRESULT']
252
+ end
253
+
254
+ def pasref
255
+ params['PASREF']
256
+ end
257
+
258
+ def eci
259
+ params['ECI']
260
+ end
261
+
262
+ def cavv
263
+ params['CAVV']
264
+ end
265
+
266
+ def xid
267
+ params['XID']
268
+ end
269
+
270
+ end
271
+
272
+ class Return < OffsitePayments::Return
273
+ def initialize(data, options)
274
+ super
275
+ @notification = Notification.new(data, options)
276
+ end
277
+
278
+ def success?
279
+ notification.complete?
280
+ end
281
+
282
+ # TODO: realex does not provide a separate cancelled endpoint
283
+ def cancelled?
284
+ false
285
+ end
286
+
287
+ def message
288
+ notification.message
289
+ end
290
+ end
291
+
292
+ end
293
+ end
294
+ end
@@ -131,6 +131,8 @@ module OffsitePayments #:nodoc:
131
131
  fields['BillingPostCode'] ||= "0000"
132
132
  fields['DeliveryPostCode'] ||= "0000"
133
133
 
134
+ fields['ReferrerID'] = referrer_id if referrer_id
135
+
134
136
  crypt_skip = ['Vendor', 'EncryptKey', 'SendEmail']
135
137
  crypt_skip << 'BillingState' unless fields['BillingCountry'] == 'US'
136
138
  crypt_skip << 'DeliveryState' unless fields['DeliveryCountry'] == 'US'
@@ -138,14 +140,12 @@ module OffsitePayments #:nodoc:
138
140
  key = fields['EncryptKey']
139
141
  @crypt ||= create_crypt_field(fields.except(*crypt_skip), key)
140
142
 
141
- result = {
143
+ {
142
144
  'VPSProtocol' => '3.00',
143
145
  'TxType' => 'PAYMENT',
144
146
  'Vendor' => @fields['Vendor'],
145
147
  'Crypt' => @crypt
146
148
  }
147
- result['ReferrerID'] = referrer_id if referrer_id
148
- result
149
149
  end
150
150
 
151
151
  private
@@ -82,9 +82,6 @@ module OffsitePayments #:nodoc:
82
82
  # Overrides Approved URL for return process redirects
83
83
  mapping :return_url, 'x_receipt_link_url'
84
84
 
85
- # notifications are sent via static URLs in the Instant Notification Settings of 2Checkout admin
86
- mapping :notify_url, 'notify_url'
87
-
88
85
  # Allow seller to indicate the step of the checkout page
89
86
  # Possible values: ‘review-cart’, ‘shipping-information’, ‘shipping-method’, ‘billing-information’ and ‘payment-method’
90
87
  mapping :purchase_step, 'purchase_step'
@@ -10,7 +10,7 @@ module OffsitePayments #:nodoc:
10
10
  end
11
11
 
12
12
  def self.sign(fields, key)
13
- Digest::HMAC.hexdigest(fields.sort.join, key, Digest::SHA256)
13
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, key, fields.sort.join)
14
14
  end
15
15
 
16
16
  class Helper < OffsitePayments::Helper
@@ -129,7 +129,7 @@ module OffsitePayments #:nodoc:
129
129
  end
130
130
 
131
131
  def acknowledge(authcode = nil)
132
- signature = @params.delete('x_signature')
132
+ signature = @params['x_signature']
133
133
  signature && signature.casecmp(generate_signature) == 0
134
134
  end
135
135
 
@@ -161,7 +161,8 @@ module OffsitePayments #:nodoc:
161
161
  private
162
162
 
163
163
  def generate_signature
164
- Universal.sign(@params, @key)
164
+ signature_params = @params.select { |k| k.start_with? 'x_' }.reject { |k| k == 'x_signature' }
165
+ Universal.sign(signature_params, @key)
165
166
  end
166
167
  end
167
168
 
@@ -24,7 +24,7 @@ module OffsitePayments #:nodoc:
24
24
  end
25
25
 
26
26
  class Helper < OffsitePayments::Helper
27
- include ActiveMerchant::RequiresParameters
27
+ include ActiveUtils::RequiresParameters
28
28
 
29
29
  DEFAULT_SUCCESS_TEXT = "The transaction has been completed."
30
30
 
@@ -54,7 +54,7 @@ module OffsitePayments #:nodoc:
54
54
  end
55
55
 
56
56
  def iso_currency
57
- ActiveMerchant::CurrencyCode.standardize(currency)
57
+ ActiveUtils::CurrencyCode.standardize(currency)
58
58
  end
59
59
 
60
60
  private
@@ -1,3 +1,3 @@
1
1
  module OffsitePayments
2
- VERSION = "2.0.1"
2
+ VERSION = "2.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: offsite_payments
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Luetke
@@ -30,7 +30,7 @@ cert_chain:
30
30
  fl3hbtVFTqbOlwL9vy1fudXcolIE/ZTcxQ+er07ZFZdKCXayR9PPs64heamfn0fp
31
31
  TConQSX2BnZdhIEYW+cKzEC/bLc=
32
32
  -----END CERTIFICATE-----
33
- date: 2014-06-06 00:00:00.000000000 Z
33
+ date: 2015-01-17 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: activesupport
@@ -70,6 +70,9 @@ dependencies:
70
70
  name: money
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 5.0.0
73
76
  - - "<"
74
77
  - !ruby/object:Gem::Version
75
78
  version: 7.0.0
@@ -77,6 +80,9 @@ dependencies:
77
80
  prerelease: false
78
81
  version_requirements: !ruby/object:Gem::Requirement
79
82
  requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 5.0.0
80
86
  - - "<"
81
87
  - !ruby/object:Gem::Version
82
88
  version: 7.0.0
@@ -101,47 +107,53 @@ dependencies:
101
107
  - !ruby/object:Gem::Version
102
108
  version: 4.0.0
103
109
  - !ruby/object:Gem::Dependency
104
- name: json
110
+ name: active_utils
105
111
  requirement: !ruby/object:Gem::Requirement
106
112
  requirements:
107
113
  - - "~>"
108
114
  - !ruby/object:Gem::Version
109
- version: '1.7'
115
+ version: 3.0.0
110
116
  type: :runtime
111
117
  prerelease: false
112
118
  version_requirements: !ruby/object:Gem::Requirement
113
119
  requirements:
114
120
  - - "~>"
115
121
  - !ruby/object:Gem::Version
116
- version: '1.7'
122
+ version: 3.0.0
117
123
  - !ruby/object:Gem::Dependency
118
- name: active_utils
124
+ name: nokogiri
119
125
  requirement: !ruby/object:Gem::Requirement
120
126
  requirements:
121
127
  - - "~>"
122
128
  - !ruby/object:Gem::Version
123
- version: 2.2.0
129
+ version: '1.4'
124
130
  type: :runtime
125
131
  prerelease: false
126
132
  version_requirements: !ruby/object:Gem::Requirement
127
133
  requirements:
128
134
  - - "~>"
129
135
  - !ruby/object:Gem::Version
130
- version: 2.2.0
136
+ version: '1.4'
131
137
  - !ruby/object:Gem::Dependency
132
- name: nokogiri
138
+ name: actionpack
133
139
  requirement: !ruby/object:Gem::Requirement
134
140
  requirements:
135
- - - "~>"
141
+ - - ">="
136
142
  - !ruby/object:Gem::Version
137
- version: '1.4'
143
+ version: 3.2.20
144
+ - - "<"
145
+ - !ruby/object:Gem::Version
146
+ version: 5.0.0
138
147
  type: :runtime
139
148
  prerelease: false
140
149
  version_requirements: !ruby/object:Gem::Requirement
141
150
  requirements:
142
- - - "~>"
151
+ - - ">="
143
152
  - !ruby/object:Gem::Version
144
- version: '1.4'
153
+ version: 3.2.20
154
+ - - "<"
155
+ - !ruby/object:Gem::Version
156
+ version: 5.0.0
145
157
  - !ruby/object:Gem::Dependency
146
158
  name: rake
147
159
  requirement: !ruby/object:Gem::Requirement
@@ -156,20 +168,34 @@ dependencies:
156
168
  - - ">="
157
169
  - !ruby/object:Gem::Version
158
170
  version: '0'
171
+ - !ruby/object:Gem::Dependency
172
+ name: test-unit
173
+ requirement: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - "~>"
176
+ - !ruby/object:Gem::Version
177
+ version: '3.0'
178
+ type: :development
179
+ prerelease: false
180
+ version_requirements: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - "~>"
183
+ - !ruby/object:Gem::Version
184
+ version: '3.0'
159
185
  - !ruby/object:Gem::Dependency
160
186
  name: mocha
161
187
  requirement: !ruby/object:Gem::Requirement
162
188
  requirements:
163
189
  - - "~>"
164
190
  - !ruby/object:Gem::Version
165
- version: 0.13.0
191
+ version: '1.0'
166
192
  type: :development
167
193
  prerelease: false
168
194
  version_requirements: !ruby/object:Gem::Requirement
169
195
  requirements:
170
196
  - - "~>"
171
197
  - !ruby/object:Gem::Version
172
- version: 0.13.0
198
+ version: '1.0'
173
199
  - !ruby/object:Gem::Dependency
174
200
  name: rails
175
201
  requirement: !ruby/object:Gem::Requirement
@@ -220,6 +246,7 @@ files:
220
246
  - lib/offsite_payments/integrations/bogus.rb
221
247
  - lib/offsite_payments/integrations/chronopay.rb
222
248
  - lib/offsite_payments/integrations/citrus.rb
249
+ - lib/offsite_payments/integrations/coinbase.rb
223
250
  - lib/offsite_payments/integrations/direc_pay.rb
224
251
  - lib/offsite_payments/integrations/directebanking.rb
225
252
  - lib/offsite_payments/integrations/doku.rb
@@ -252,6 +279,7 @@ files:
252
279
  - lib/offsite_payments/integrations/pxpay.rb
253
280
  - lib/offsite_payments/integrations/quickpay.rb
254
281
  - lib/offsite_payments/integrations/rbkmoney.rb
282
+ - lib/offsite_payments/integrations/realex.rb
255
283
  - lib/offsite_payments/integrations/robokassa.rb
256
284
  - lib/offsite_payments/integrations/sage_pay_form.rb
257
285
  - lib/offsite_payments/integrations/two_checkout.rb
@@ -285,7 +313,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
313
  version: '0'
286
314
  requirements: []
287
315
  rubyforge_project:
288
- rubygems_version: 2.2.0
316
+ rubygems_version: 2.2.2
289
317
  signing_key:
290
318
  specification_version: 4
291
319
  summary: Framework and tools for dealing with offsite (hosted) payment pages.
metadata.gz.sig CHANGED
Binary file