koffsite_payments 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/lib/offsite_payments.rb +48 -0
  5. data/lib/offsite_payments/action_view_helper.rb +72 -0
  6. data/lib/offsite_payments/helper.rb +119 -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 +166 -0
  15. data/lib/offsite_payments/integrations/direc_pay.rb +339 -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 +203 -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 +291 -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/mollie_ideal.rb +216 -0
  31. data/lib/offsite_payments/integrations/moneybookers.rb +199 -0
  32. data/lib/offsite_payments/integrations/nochex.rb +228 -0
  33. data/lib/offsite_payments/integrations/pag_seguro.rb +268 -0
  34. data/lib/offsite_payments/integrations/paxum.rb +114 -0
  35. data/lib/offsite_payments/integrations/pay_fast.rb +269 -0
  36. data/lib/offsite_payments/integrations/pay_u_latam.rb +246 -0
  37. data/lib/offsite_payments/integrations/paydollar.rb +142 -0
  38. data/lib/offsite_payments/integrations/payflow_link.rb +194 -0
  39. data/lib/offsite_payments/integrations/paypal.rb +362 -0
  40. data/lib/offsite_payments/integrations/paypal_payments_advanced.rb +23 -0
  41. data/lib/offsite_payments/integrations/paysbuy.rb +71 -0
  42. data/lib/offsite_payments/integrations/payu_in.rb +266 -0
  43. data/lib/offsite_payments/integrations/payu_in_paisa.rb +46 -0
  44. data/lib/offsite_payments/integrations/platron.rb +153 -0
  45. data/lib/offsite_payments/integrations/pxpay.rb +273 -0
  46. data/lib/offsite_payments/integrations/quickpay.rb +232 -0
  47. data/lib/offsite_payments/integrations/rbkmoney.rb +110 -0
  48. data/lib/offsite_payments/integrations/robokassa.rb +154 -0
  49. data/lib/offsite_payments/integrations/sage_pay_form.rb +425 -0
  50. data/lib/offsite_payments/integrations/two_checkout.rb +329 -0
  51. data/lib/offsite_payments/integrations/universal.rb +181 -0
  52. data/lib/offsite_payments/integrations/valitor.rb +200 -0
  53. data/lib/offsite_payments/integrations/verkkomaksut.rb +143 -0
  54. data/lib/offsite_payments/integrations/web_pay.rb +186 -0
  55. data/lib/offsite_payments/integrations/webmoney.rb +119 -0
  56. data/lib/offsite_payments/integrations/wirecard_checkout_page.rb +359 -0
  57. data/lib/offsite_payments/integrations/world_pay.rb +273 -0
  58. data/lib/offsite_payments/notification.rb +71 -0
  59. data/lib/offsite_payments/return.rb +37 -0
  60. data/lib/offsite_payments/version.rb +3 -0
  61. metadata +268 -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 ActiveMerchant::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 ActiveMerchant::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,291 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Klarna
4
+ mattr_accessor :service_url
5
+ self.service_url = 'https://api.hostedcheckout.io/api/v1/checkout'
6
+
7
+ def self.notification(post_body, options = {})
8
+ Notification.new(post_body, options)
9
+ end
10
+
11
+ def self.return(query_string, options = {})
12
+ Return.new(query_string, options)
13
+ end
14
+
15
+ def self.cart_items_payload(fields, cart_items)
16
+ check_required_fields!(fields)
17
+
18
+ payload = fields['purchase_country'].to_s +
19
+ fields['purchase_currency'].to_s +
20
+ fields['locale'].to_s
21
+
22
+ cart_items.each_with_index do |item, i|
23
+ payload << fields["cart_item-#{i}_type"].to_s +
24
+ fields["cart_item-#{i}_reference"].to_s +
25
+ fields["cart_item-#{i}_quantity"].to_s +
26
+ fields["cart_item-#{i}_unit_price"].to_s +
27
+ fields.fetch("cart_item-#{i}_discount_rate", '').to_s
28
+ end
29
+
30
+ payload << fields['merchant_id'].to_s +
31
+ fields['merchant_terms_uri'].to_s +
32
+ fields['merchant_checkout_uri'].to_s +
33
+ fields['merchant_base_uri'].to_s +
34
+ fields['merchant_confirmation_uri'].to_s
35
+
36
+ payload
37
+ end
38
+
39
+ def self.sign(fields, cart_items, shared_secret)
40
+ payload = cart_items_payload(fields, cart_items)
41
+
42
+ digest(payload, shared_secret)
43
+ end
44
+
45
+ def self.digest(payload, shared_secret)
46
+ Digest::SHA256.base64digest(payload + shared_secret.to_s)
47
+ end
48
+
49
+ private
50
+
51
+ def self.check_required_fields!(fields)
52
+ %w(purchase_country
53
+ purchase_currency
54
+ locale
55
+ merchant_id
56
+ merchant_terms_uri
57
+ merchant_checkout_uri
58
+ merchant_base_uri
59
+ merchant_confirmation_uri).each do |required_field|
60
+ raise ArgumentError, "Missing required field #{required_field}" if fields[required_field].nil?
61
+ end
62
+ end
63
+
64
+ class Helper < OffsitePayments::Helper
65
+ mapping :currency, 'purchase_currency'
66
+ mapping :cancel_return_url, ['merchant_terms_uri', 'merchant_checkout_uri', 'merchant_base_uri']
67
+ mapping :account, 'merchant_id'
68
+ mapping :customer, email: 'shipping_address_email'
69
+
70
+ def initialize(order, account, options = {})
71
+ super
72
+ @shared_secret = options[:credential2]
73
+ @order = order
74
+
75
+ add_field('platform_type', application_id)
76
+ add_field('test_mode', test?.to_s)
77
+ end
78
+
79
+ def notify_url(url)
80
+ url = append_order_query_param(url)
81
+ add_field('merchant_push_uri', url)
82
+ end
83
+
84
+ def return_url(url)
85
+ url = append_order_query_param(url)
86
+ add_field('merchant_confirmation_uri', url)
87
+ end
88
+
89
+ def line_item(item)
90
+ @line_items ||= []
91
+ @line_items << item
92
+
93
+ i = @line_items.size - 1
94
+
95
+ add_field("cart_item-#{i}_type", type_for(item))
96
+ add_field("cart_item-#{i}_reference", item.fetch(:reference, ''))
97
+ add_field("cart_item-#{i}_name", item.fetch(:name, ''))
98
+ add_field("cart_item-#{i}_quantity", item.fetch(:quantity, ''))
99
+ add_field("cart_item-#{i}_unit_price", tax_included_unit_price(item)).to_s
100
+ add_field("cart_item-#{i}_discount_rate", item.fetch(:discount_rate, ''))
101
+ add_field("cart_item-#{i}_tax_rate", tax_rate_for(item)).to_s
102
+
103
+ @fields
104
+ end
105
+
106
+ def billing_address(billing_fields)
107
+ country = billing_fields[:country]
108
+
109
+ add_field('purchase_country', country)
110
+ add_field('locale', guess_locale_based_on_country(country))
111
+ end
112
+
113
+ def shipping_address(shipping_fields)
114
+ add_field('shipping_address_given_name', shipping_fields[:first_name])
115
+ add_field('shipping_address_family_name', shipping_fields[:last_name])
116
+
117
+ street_address = [shipping_fields[:address1], shipping_fields[:address2]].compact.join(', ')
118
+ add_field('shipping_address_street_address', street_address)
119
+
120
+ add_field('shipping_address_postal_code', shipping_fields[:zip])
121
+ add_field('shipping_address_city', shipping_fields[:city])
122
+ add_field('shipping_address_country', shipping_fields[:country])
123
+ add_field('shipping_address_phone', shipping_fields[:phone])
124
+ end
125
+
126
+ def form_fields
127
+ sign_fields
128
+
129
+ super
130
+ end
131
+
132
+ def sign_fields
133
+ merchant_digest = Klarna.sign(@fields, @line_items, @shared_secret)
134
+ add_field('merchant_digest', merchant_digest)
135
+ end
136
+
137
+ private
138
+
139
+ def type_for(item)
140
+ case item.fetch(:type, '')
141
+ when 'shipping'
142
+ 'shipping_fee'
143
+ when 'line item'
144
+ 'physical'
145
+ when 'discount'
146
+ 'discount'
147
+ else
148
+ raise StandardError, "Unable to determine type for item #{item.to_yaml}"
149
+ end
150
+ end
151
+
152
+ def append_order_query_param(url)
153
+ u = URI.parse(url)
154
+ params = Rack::Utils.parse_nested_query(u.query)
155
+ params["order"] = @order
156
+ u.query = params.to_query
157
+
158
+ u.to_s
159
+ end
160
+
161
+ def guess_locale_based_on_country(country_code)
162
+ case country_code
163
+ when /no/i
164
+ "nb-no"
165
+ when /fi/i
166
+ "fi-fi"
167
+ when /se/i
168
+ "sv-se"
169
+ else
170
+ "sv-se"
171
+ end
172
+ end
173
+
174
+ def tax_included_unit_price(item)
175
+ item.fetch(:unit_price, '').to_i + item.fetch(:tax_amount, '').to_i
176
+ end
177
+
178
+ def tax_rate_for(item)
179
+ subtotal_price = item.fetch(:unit_price, 0).to_f * item.fetch(:quantity, 0).to_i
180
+ tax_amount = item.fetch(:tax_amount, 0).to_f
181
+
182
+ if subtotal_price > 0
183
+ tax_rate = tax_amount / subtotal_price
184
+ tax_rate = tax_rate.round(4)
185
+
186
+ percentage_to_two_decimal_precision_whole_number(tax_rate)
187
+ else
188
+ 0
189
+ end
190
+ end
191
+
192
+ def percentage_to_two_decimal_precision_whole_number(percentage)
193
+ (percentage * 10000).to_i
194
+ end
195
+ end
196
+
197
+ class Notification < OffsitePayments::Notification
198
+ def initialize(post, options = {})
199
+ super
200
+ @shared_secret = @options[:credential2]
201
+ end
202
+
203
+ def complete?
204
+ status == 'Completed'
205
+ end
206
+
207
+ def item_id
208
+ order
209
+ end
210
+
211
+ def transaction_id
212
+ params["reference"]
213
+ end
214
+
215
+ def received_at
216
+ params["completed_at"]
217
+ end
218
+
219
+ def payer_email
220
+ params["billing_address"]["email"]
221
+ end
222
+
223
+ def receiver_email
224
+ params["shipping_address"]["email"]
225
+ end
226
+
227
+ def currency
228
+ params["purchase_currency"].upcase
229
+ end
230
+
231
+ def gross
232
+ amount = Float(gross_cents) / 100
233
+ sprintf("%.2f", amount)
234
+ end
235
+
236
+ def gross_cents
237
+ params["cart"]["total_price_including_tax"]
238
+ end
239
+
240
+ def status
241
+ case params['status']
242
+ when 'checkout_complete'
243
+ 'Completed'
244
+ else
245
+ params['status']
246
+ end
247
+ end
248
+
249
+ def acknowledge(authcode = nil)
250
+ Verifier.new(@options[:authorization_header], @raw, @shared_secret).verify
251
+ end
252
+
253
+ private
254
+
255
+ def order
256
+ query = Rack::Utils.parse_nested_query(@options[:query_string])
257
+ query["order"]
258
+ end
259
+
260
+ def parse(post)
261
+ @raw = post.to_s
262
+ @params = JSON.parse(post)
263
+ end
264
+
265
+ class Verifier
266
+ attr_reader :header, :payload, :digest, :shared_secret
267
+ def initialize(header, payload, shared_secret)
268
+ @header, @payload, @shared_secret = header, payload, shared_secret
269
+
270
+ @digest = extract_digest
271
+ end
272
+
273
+ def verify
274
+ digest_matches?
275
+ end
276
+
277
+ private
278
+
279
+ def extract_digest
280
+ match = header.match(/^Klarna (?<digest>.+)$/)
281
+ match && match[:digest]
282
+ end
283
+
284
+ def digest_matches?
285
+ Klarna.digest(payload, shared_secret) == digest
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end