offsite_payments 2.0.1 → 2.1.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 (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