better_offsite_payments 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +76 -0
  4. data/lib/offsite_payments.rb +39 -0
  5. data/lib/offsite_payments/action_view_helper.rb +72 -0
  6. data/lib/offsite_payments/helper.rb +120 -0
  7. data/lib/offsite_payments/integrations.rb +14 -0
  8. data/lib/offsite_payments/integrations/a1agregator.rb +245 -0
  9. data/lib/offsite_payments/integrations/authorize_net_sim.rb +580 -0
  10. data/lib/offsite_payments/integrations/bit_pay.rb +150 -0
  11. data/lib/offsite_payments/integrations/bogus.rb +32 -0
  12. data/lib/offsite_payments/integrations/chronopay.rb +283 -0
  13. data/lib/offsite_payments/integrations/citrus.rb +227 -0
  14. data/lib/offsite_payments/integrations/coinbase.rb +172 -0
  15. data/lib/offsite_payments/integrations/direc_pay.rb +332 -0
  16. data/lib/offsite_payments/integrations/directebanking.rb +237 -0
  17. data/lib/offsite_payments/integrations/doku.rb +171 -0
  18. data/lib/offsite_payments/integrations/dotpay.rb +166 -0
  19. data/lib/offsite_payments/integrations/dwolla.rb +160 -0
  20. data/lib/offsite_payments/integrations/e_payment_plans.rb +146 -0
  21. data/lib/offsite_payments/integrations/easy_pay.rb +137 -0
  22. data/lib/offsite_payments/integrations/epay.rb +161 -0
  23. data/lib/offsite_payments/integrations/first_data.rb +133 -0
  24. data/lib/offsite_payments/integrations/gestpay.rb +205 -0
  25. data/lib/offsite_payments/integrations/hi_trust.rb +179 -0
  26. data/lib/offsite_payments/integrations/ipay88.rb +251 -0
  27. data/lib/offsite_payments/integrations/klarna.rb +275 -0
  28. data/lib/offsite_payments/integrations/liqpay.rb +216 -0
  29. data/lib/offsite_payments/integrations/maksuturva.rb +231 -0
  30. data/lib/offsite_payments/integrations/megakassa.rb +184 -0
  31. data/lib/offsite_payments/integrations/mollie.rb +32 -0
  32. data/lib/offsite_payments/integrations/mollie_ideal.rb +194 -0
  33. data/lib/offsite_payments/integrations/mollie_mistercash.rb +143 -0
  34. data/lib/offsite_payments/integrations/molpay.rb +193 -0
  35. data/lib/offsite_payments/integrations/moneybookers.rb +199 -0
  36. data/lib/offsite_payments/integrations/nochex.rb +228 -0
  37. data/lib/offsite_payments/integrations/pag_seguro.rb +268 -0
  38. data/lib/offsite_payments/integrations/paxum.rb +114 -0
  39. data/lib/offsite_payments/integrations/pay_fast.rb +269 -0
  40. data/lib/offsite_payments/integrations/paydollar.rb +142 -0
  41. data/lib/offsite_payments/integrations/payflow_link.rb +194 -0
  42. data/lib/offsite_payments/integrations/paypal.rb +362 -0
  43. data/lib/offsite_payments/integrations/paypal_payments_advanced.rb +23 -0
  44. data/lib/offsite_payments/integrations/paysbuy.rb +71 -0
  45. data/lib/offsite_payments/integrations/payu_in.rb +276 -0
  46. data/lib/offsite_payments/integrations/payu_in_paisa.rb +46 -0
  47. data/lib/offsite_payments/integrations/platron.rb +153 -0
  48. data/lib/offsite_payments/integrations/pxpay.rb +273 -0
  49. data/lib/offsite_payments/integrations/quickpay.rb +232 -0
  50. data/lib/offsite_payments/integrations/rbkmoney.rb +110 -0
  51. data/lib/offsite_payments/integrations/realex_offsite.rb +317 -0
  52. data/lib/offsite_payments/integrations/robokassa.rb +154 -0
  53. data/lib/offsite_payments/integrations/sage_pay_form.rb +431 -0
  54. data/lib/offsite_payments/integrations/two_checkout.rb +329 -0
  55. data/lib/offsite_payments/integrations/universal.rb +190 -0
  56. data/lib/offsite_payments/integrations/valitor.rb +200 -0
  57. data/lib/offsite_payments/integrations/verkkomaksut.rb +143 -0
  58. data/lib/offsite_payments/integrations/web_pay.rb +186 -0
  59. data/lib/offsite_payments/integrations/webmoney.rb +119 -0
  60. data/lib/offsite_payments/integrations/wirecard_checkout_page.rb +359 -0
  61. data/lib/offsite_payments/integrations/world_pay.rb +280 -0
  62. data/lib/offsite_payments/integrations/yandex_money.rb +175 -0
  63. data/lib/offsite_payments/notification.rb +71 -0
  64. data/lib/offsite_payments/return.rb +37 -0
  65. data/lib/offsite_payments/version.rb +3 -0
  66. metadata +297 -0
@@ -0,0 +1,251 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Ipay88
4
+ CANCELLED_ERROR_DESCRIPTION = 'Customer Cancel Transaction'
5
+
6
+ def self.service_url
7
+ "https://www.mobile88.com/epayment/entry.asp"
8
+ end
9
+
10
+ def self.requery_url
11
+ "https://www.mobile88.com/epayment/enquiry.asp"
12
+ end
13
+
14
+ def self.return(query_string, options={})
15
+ Return.new(query_string, options)
16
+ end
17
+
18
+ def self.notification(post, options = {})
19
+ Notification.new(post, options)
20
+ end
21
+
22
+ class Helper < OffsitePayments::Helper
23
+ include ActiveUtils::RequiresParameters
24
+
25
+ # Currencies supported
26
+ # MYR (Malaysian Ringgit - for all payment methods except China Union Pay and PayPal)
27
+ # USD (US Dollar - only for PayPal)
28
+ # CNY (Yuan Renminbi - only for China Union Pay)
29
+ SUPPORTED_CURRENCIES = %w[MYR USD CNY]
30
+
31
+ # Languages supported
32
+ # ISO-8859-1 (English)
33
+ # UTF-8 (Unicode)
34
+ # GB2312 (Chinese Simplified)
35
+ # GD18030 (Chinese Simplified)
36
+ # BIG5 (Chinese Traditional)
37
+ SUPPORTED_LANGS = %w[ISO-8859-1 UTF-8 GB2312 GD18030 BIG5]
38
+
39
+ # Payment methods supported
40
+ # 8 (Alliance Online Transfer)
41
+ # 10 (AmBank)
42
+ # 21 (China Union Pay)
43
+ # 20 (CIMB Click)
44
+ # 2 (Credit Card MYR)
45
+ # 16 (FPX)
46
+ # 15 (Hong Leong Bank Transfer)
47
+ # 6 (Maybank2u.com)
48
+ # 23 (MEPS Cash)
49
+ # 17 (Mobile Money)
50
+ # 33 (PayPal)
51
+ # 14 (RHB)
52
+ PAYMENT_METHODS = %w[8 10 21 20 2 16 15 6 23 17 33 14]
53
+
54
+ attr_reader :amount_in_cents, :merchant_key
55
+
56
+ def initialize(order, account, options = {})
57
+ requires!(options, :amount, :currency, :credential2)
58
+ @merchant_key = options[:credential2]
59
+ @amount_in_cents = options[:amount]
60
+ super
61
+ add_field mappings[:signature], signature
62
+ end
63
+
64
+ def amount_in_dollars
65
+ sprintf("%.2f", @amount_in_cents.to_f/100)
66
+ end
67
+
68
+ def amount=(money)
69
+ @amount_in_cents = money.respond_to?(:cents) ? money.cents : money
70
+ raise ArgumentError, "amount must be a Money object or an integer" if money.is_a?(String)
71
+ raise ActionViewHelperError, "amount must be greater than $0.00" if @amount_in_cents.to_i <= 0
72
+
73
+ add_field mappings[:amount], amount_in_dollars
74
+ end
75
+
76
+ def currency(symbol)
77
+ raise ArgumentError, "unsupported currency" unless SUPPORTED_CURRENCIES.include?(symbol)
78
+ add_field mappings[:currency], symbol
79
+ end
80
+
81
+ def language(lang)
82
+ raise ArgumentError, "unsupported language" unless SUPPORTED_LANGS.include?(lang)
83
+ add_field mappings[:language], lang
84
+ end
85
+
86
+ def payment(pay_method)
87
+ raise ArgumentError, "unsupported payment method" unless PAYMENT_METHODS.include?(pay_method.to_s)
88
+ add_field mappings[:payment], pay_method
89
+ end
90
+
91
+ def customer(params = {})
92
+ add_field(mappings[:customer][:name], "#{params[:first_name]} #{params[:last_name]}")
93
+ add_field(mappings[:customer][:email], params[:email])
94
+ add_field(mappings[:customer][:phone], params[:phone])
95
+ end
96
+
97
+ def self.sign(str)
98
+ [Digest::SHA1.digest(str)].pack("m").chomp
99
+ end
100
+
101
+ def signature
102
+ self.class.sign(self.sig_components)
103
+ end
104
+
105
+ mapping :account, "MerchantCode"
106
+ mapping :amount, "Amount"
107
+ mapping :currency, "Currency"
108
+ mapping :order, "RefNo"
109
+ mapping :description, "ProdDesc"
110
+ mapping :customer, :name => "UserName",
111
+ :email => "UserEmail",
112
+ :phone => "UserContact"
113
+ mapping :remark, "Remark"
114
+ mapping :language, "Lang"
115
+ mapping :payment, "PaymentId"
116
+ mapping :return_url, "ResponseURL"
117
+ mapping :notify_url, "BackendURL"
118
+ mapping :signature, "Signature"
119
+
120
+ protected
121
+
122
+ def sig_components
123
+ components = [merchant_key]
124
+ components << fields[mappings[:account]]
125
+ components << fields[mappings[:order]]
126
+ components << amount_in_dollars.gsub(/[.,]/, '')
127
+ components << fields[mappings[:currency]]
128
+ components.join
129
+ end
130
+ end
131
+
132
+ class Notification < OffsitePayments::Notification
133
+ include ActiveUtils::PostsData
134
+
135
+ def status
136
+ if params["Status"] == '1'
137
+ 'Completed'
138
+ else
139
+ error == CANCELLED_ERROR_DESCRIPTION ? 'Cancelled' : 'Failed'
140
+ end
141
+ end
142
+
143
+ def complete?
144
+ status == 'Completed'
145
+ end
146
+
147
+ def item_id
148
+ params["RefNo"]
149
+ end
150
+
151
+ def gross
152
+ params["Amount"].try(:gsub, /,(?=\d{3}\b)/, '')
153
+ end
154
+
155
+ def currency
156
+ params["Currency"]
157
+ end
158
+
159
+ def account
160
+ params["MerchantCode"]
161
+ end
162
+
163
+ def payment
164
+ params["PaymentId"].to_i
165
+ end
166
+
167
+ def remark
168
+ params["Remark"]
169
+ end
170
+
171
+ def transaction_id
172
+ params["TransId"]
173
+ end
174
+
175
+ def auth_code
176
+ params["AuthCode"]
177
+ end
178
+
179
+ def error
180
+ params["ErrDesc"]
181
+ end
182
+
183
+ def signature
184
+ params["Signature"]
185
+ end
186
+
187
+ def secure?
188
+ generated_signature == signature
189
+ end
190
+
191
+ def success?
192
+ status == 'Completed'
193
+ end
194
+
195
+ def acknowledge
196
+ secure? && (!success? || requery == "00")
197
+ end
198
+
199
+ protected
200
+
201
+ def generated_signature
202
+ Helper.sign(sig_components)
203
+ end
204
+
205
+ def sig_components
206
+ components = [@options[:credential2]]
207
+ [:account, :payment, :item_id, :amount_in_cents, :currency].each do |i|
208
+ components << send(i)
209
+ end
210
+ components << params["Status"]
211
+ components.join
212
+ end
213
+
214
+ def requery
215
+ data = { "MerchantCode" => account, "RefNo" => item_id, "Amount" => gross }
216
+ params = parameterize(data)
217
+ ssl_post Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" }
218
+ end
219
+
220
+ private
221
+
222
+ def parameterize(params)
223
+ params.reject { |k, v| v.blank? }.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&")
224
+ end
225
+
226
+ def amount_in_cents
227
+ @amount_in_cents ||= (gross || "").gsub(/[.,]/, "")
228
+ end
229
+ end
230
+
231
+ class Return < OffsitePayments::Return
232
+ def initialize(query_string, options = {})
233
+ super
234
+ @notification = Notification.new(query_string, options)
235
+ end
236
+
237
+ def success?
238
+ params["Status"] == "1"
239
+ end
240
+
241
+ def cancelled?
242
+ params["ErrDesc"] == CANCELLED_ERROR_DESCRIPTION
243
+ end
244
+
245
+ def message
246
+ params["ErrDesc"]
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,275 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Klarna
4
+ mattr_accessor :service_url
5
+ self.service_url = 'https://api.hostedcheckout.io/shopify/payment'
6
+
7
+ REQUIRED_FIELDS = %w(amount checkout_token merchant_base_uri merchant_checkout_uri merchant_confirmation_uri merchant_id merchant_terms_uri purchase_currency)
8
+
9
+ def self.notification(post_body, options = {})
10
+ Notification.new(post_body, options)
11
+ end
12
+
13
+ def self.return(query_string, options = {})
14
+ Return.new(query_string, options)
15
+ end
16
+
17
+ def self.cart_items_payload(fields, cart_items)
18
+ check_required_fields!(fields)
19
+
20
+ payload = ""
21
+ REQUIRED_FIELDS.sort.each do |field|
22
+ payload << fields[field].to_s
23
+ end
24
+
25
+ payload
26
+ end
27
+
28
+ def self.sign(fields, cart_items, shared_secret)
29
+ payload = cart_items_payload(fields, cart_items)
30
+
31
+ digest(payload, shared_secret)
32
+ end
33
+
34
+ def self.digest(payload, shared_secret)
35
+ Digest::SHA256.base64digest(payload + shared_secret.to_s)
36
+ end
37
+
38
+ private
39
+
40
+ def self.check_required_fields!(fields)
41
+ REQUIRED_FIELDS.each do |required_field|
42
+ raise ArgumentError, "Missing required field #{required_field}" if fields[required_field].nil?
43
+ end
44
+ end
45
+
46
+ class Helper < OffsitePayments::Helper
47
+ mapping :currency, 'purchase_currency'
48
+ mapping :cancel_return_url, ['merchant_terms_uri', 'merchant_checkout_uri', 'merchant_base_uri']
49
+ mapping :account, 'merchant_id'
50
+ mapping :customer, email: 'shipping_address_email'
51
+ mapping :checkout_token, 'checkout_token'
52
+ mapping :amount, 'amount'
53
+
54
+ def initialize(order, account, options = {})
55
+ super
56
+ @shared_secret = options[:credential2]
57
+ @order = order
58
+
59
+ add_field('platform_type', application_id)
60
+ add_field('test_mode', test?.to_s)
61
+ end
62
+
63
+ def notify_url(url)
64
+ url = append_order_query_param(url)
65
+ add_field('merchant_push_uri', url)
66
+ end
67
+
68
+ def return_url(url)
69
+ url = append_order_query_param(url)
70
+ add_field('merchant_confirmation_uri', url)
71
+ end
72
+
73
+ def line_item(item)
74
+ @line_items ||= []
75
+ @line_items << item
76
+
77
+ i = @line_items.size - 1
78
+
79
+ add_field("cart_item-#{i}_type", type_for(item))
80
+ add_field("cart_item-#{i}_reference", item.fetch(:reference, ''))
81
+ add_field("cart_item-#{i}_name", item.fetch(:name, ''))
82
+ add_field("cart_item-#{i}_quantity", item.fetch(:quantity, ''))
83
+ add_field("cart_item-#{i}_unit_price", tax_included_unit_price(item)).to_s
84
+ add_field("cart_item-#{i}_discount_rate", item.fetch(:discount_rate, ''))
85
+ add_field("cart_item-#{i}_tax_rate", tax_rate_for(item)).to_s
86
+
87
+ @fields
88
+ end
89
+
90
+ def billing_address(billing_fields)
91
+ country = billing_fields[:country]
92
+
93
+ add_field('purchase_country', country)
94
+ add_field('locale', guess_locale_based_on_country(country))
95
+ end
96
+
97
+ def shipping_address(shipping_fields)
98
+ add_field('shipping_address_given_name', shipping_fields[:first_name])
99
+ add_field('shipping_address_family_name', shipping_fields[:last_name])
100
+
101
+ street_address = [shipping_fields[:address1], shipping_fields[:address2]].compact.join(', ')
102
+ add_field('shipping_address_street_address', street_address)
103
+
104
+ add_field('shipping_address_postal_code', shipping_fields[:zip])
105
+ add_field('shipping_address_city', shipping_fields[:city])
106
+ add_field('shipping_address_country', shipping_fields[:country])
107
+ add_field('shipping_address_phone', shipping_fields[:phone])
108
+ end
109
+
110
+ def form_fields
111
+ sign_fields
112
+
113
+ super
114
+ end
115
+
116
+ def sign_fields
117
+ merchant_digest = Klarna.sign(@fields, @line_items, @shared_secret)
118
+ add_field('merchant_digest', merchant_digest)
119
+ end
120
+
121
+ private
122
+
123
+ def type_for(item)
124
+ case item.fetch(:type, '')
125
+ when 'shipping'
126
+ 'shipping_fee'
127
+ when 'line item'
128
+ 'physical'
129
+ when 'discount'
130
+ 'discount'
131
+ else
132
+ raise StandardError, "Unable to determine type for item #{item.to_yaml}"
133
+ end
134
+ end
135
+
136
+ def append_order_query_param(url)
137
+ u = URI.parse(url)
138
+ params = Rack::Utils.parse_nested_query(u.query)
139
+ params["order"] = @order
140
+ u.query = params.to_query
141
+
142
+ u.to_s
143
+ end
144
+
145
+ def guess_locale_based_on_country(country_code)
146
+ case country_code
147
+ when /no/i
148
+ "nb-no"
149
+ when /fi/i
150
+ "fi-fi"
151
+ when /se/i
152
+ "sv-se"
153
+ else
154
+ "sv-se"
155
+ end
156
+ end
157
+
158
+ def tax_included_unit_price(item)
159
+ item.fetch(:unit_price, '').to_i + item.fetch(:tax_amount, '').to_i
160
+ end
161
+
162
+ def tax_rate_for(item)
163
+ subtotal_price = item.fetch(:unit_price, 0).to_f * item.fetch(:quantity, 0).to_i
164
+ tax_amount = item.fetch(:tax_amount, 0).to_f
165
+
166
+ if subtotal_price > 0
167
+ tax_rate = tax_amount / subtotal_price
168
+ tax_rate = tax_rate.round(4)
169
+
170
+ percentage_to_two_decimal_precision_whole_number(tax_rate)
171
+ else
172
+ 0
173
+ end
174
+ end
175
+
176
+ def percentage_to_two_decimal_precision_whole_number(percentage)
177
+ (percentage * 10000).to_i
178
+ end
179
+ end
180
+
181
+ class Notification < OffsitePayments::Notification
182
+ def initialize(post, options = {})
183
+ super
184
+ @shared_secret = @options[:credential2]
185
+ end
186
+
187
+ def complete?
188
+ status == 'Completed'
189
+ end
190
+
191
+ def item_id
192
+ order
193
+ end
194
+
195
+ def transaction_id
196
+ params["reference"]
197
+ end
198
+
199
+ def received_at
200
+ params["completed_at"]
201
+ end
202
+
203
+ def payer_email
204
+ params["billing_address"]["email"]
205
+ end
206
+
207
+ def receiver_email
208
+ params["shipping_address"]["email"]
209
+ end
210
+
211
+ def currency
212
+ params["purchase_currency"].upcase
213
+ end
214
+
215
+ def gross
216
+ amount = Float(gross_cents) / 100
217
+ sprintf("%.2f", amount)
218
+ end
219
+
220
+ def gross_cents
221
+ params["order_amount"]
222
+ end
223
+
224
+ def status
225
+ case params['status']
226
+ when 'checkout_complete'
227
+ 'Completed'
228
+ else
229
+ params['status']
230
+ end
231
+ end
232
+
233
+ def acknowledge(authcode = nil)
234
+ Verifier.new(@options[:authorization_header], @raw, @shared_secret).verify
235
+ end
236
+
237
+ private
238
+
239
+ def order
240
+ query = Rack::Utils.parse_nested_query(@options[:query_string])
241
+ query["order"]
242
+ end
243
+
244
+ def parse(post)
245
+ @raw = post.to_s
246
+ @params = JSON.parse(post)
247
+ end
248
+
249
+ class Verifier
250
+ attr_reader :header, :payload, :digest, :shared_secret
251
+ def initialize(header, payload, shared_secret)
252
+ @header, @payload, @shared_secret = header, payload, shared_secret
253
+
254
+ @digest = extract_digest
255
+ end
256
+
257
+ def verify
258
+ digest_matches?
259
+ end
260
+
261
+ private
262
+
263
+ def extract_digest
264
+ match = header.match(/^Klarna (?<digest>.+)$/)
265
+ match && match[:digest]
266
+ end
267
+
268
+ def digest_matches?
269
+ Klarna.digest(payload, shared_secret) == digest
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end