better_offsite_payments 2.3.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 (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