offsite_payments 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +70 -0
  4. data/lib/offsite_payments.rb +46 -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/direc_pay.rb +339 -0
  15. data/lib/offsite_payments/integrations/directebanking.rb +237 -0
  16. data/lib/offsite_payments/integrations/doku.rb +171 -0
  17. data/lib/offsite_payments/integrations/dotpay.rb +166 -0
  18. data/lib/offsite_payments/integrations/dwolla.rb +160 -0
  19. data/lib/offsite_payments/integrations/e_payment_plans.rb +146 -0
  20. data/lib/offsite_payments/integrations/easy_pay.rb +137 -0
  21. data/lib/offsite_payments/integrations/epay.rb +161 -0
  22. data/lib/offsite_payments/integrations/first_data.rb +133 -0
  23. data/lib/offsite_payments/integrations/gestpay.rb +201 -0
  24. data/lib/offsite_payments/integrations/hi_trust.rb +179 -0
  25. data/lib/offsite_payments/integrations/ipay88.rb +240 -0
  26. data/lib/offsite_payments/integrations/klarna.rb +291 -0
  27. data/lib/offsite_payments/integrations/liqpay.rb +216 -0
  28. data/lib/offsite_payments/integrations/maksuturva.rb +231 -0
  29. data/lib/offsite_payments/integrations/mollie_ideal.rb +213 -0
  30. data/lib/offsite_payments/integrations/moneybookers.rb +199 -0
  31. data/lib/offsite_payments/integrations/nochex.rb +228 -0
  32. data/lib/offsite_payments/integrations/pag_seguro.rb +255 -0
  33. data/lib/offsite_payments/integrations/paxum.rb +114 -0
  34. data/lib/offsite_payments/integrations/pay_fast.rb +269 -0
  35. data/lib/offsite_payments/integrations/paydollar.rb +142 -0
  36. data/lib/offsite_payments/integrations/payflow_link.rb +194 -0
  37. data/lib/offsite_payments/integrations/paypal.rb +362 -0
  38. data/lib/offsite_payments/integrations/paypal_payments_advanced.rb +23 -0
  39. data/lib/offsite_payments/integrations/paysbuy.rb +71 -0
  40. data/lib/offsite_payments/integrations/payu_in.rb +266 -0
  41. data/lib/offsite_payments/integrations/payu_in_paisa.rb +46 -0
  42. data/lib/offsite_payments/integrations/platron.rb +153 -0
  43. data/lib/offsite_payments/integrations/pxpay.rb +271 -0
  44. data/lib/offsite_payments/integrations/quickpay.rb +232 -0
  45. data/lib/offsite_payments/integrations/rbkmoney.rb +110 -0
  46. data/lib/offsite_payments/integrations/robokassa.rb +154 -0
  47. data/lib/offsite_payments/integrations/sage_pay_form.rb +425 -0
  48. data/lib/offsite_payments/integrations/two_checkout.rb +332 -0
  49. data/lib/offsite_payments/integrations/universal.rb +180 -0
  50. data/lib/offsite_payments/integrations/valitor.rb +200 -0
  51. data/lib/offsite_payments/integrations/verkkomaksut.rb +143 -0
  52. data/lib/offsite_payments/integrations/web_pay.rb +186 -0
  53. data/lib/offsite_payments/integrations/webmoney.rb +119 -0
  54. data/lib/offsite_payments/integrations/wirecard_checkout_page.rb +359 -0
  55. data/lib/offsite_payments/integrations/world_pay.rb +273 -0
  56. data/lib/offsite_payments/notification.rb +71 -0
  57. data/lib/offsite_payments/return.rb +37 -0
  58. data/lib/offsite_payments/version.rb +3 -0
  59. metadata +270 -0
@@ -0,0 +1,240 @@
1
+ module OffsitePayments #:nodoc:
2
+ module Integrations #:nodoc:
3
+ module Ipay88
4
+ def self.service_url
5
+ "https://www.mobile88.com/epayment/entry.asp"
6
+ end
7
+
8
+ def self.requery_url
9
+ "https://www.mobile88.com/epayment/enquiry.asp"
10
+ end
11
+
12
+ def self.return(query_string, options={})
13
+ Return.new(query_string, options)
14
+ end
15
+
16
+ class Helper < OffsitePayments::Helper
17
+ include ActiveMerchant::RequiresParameters
18
+
19
+ # Currencies supported
20
+ # MYR (Malaysian Ringgit - for all payment methods except China Union Pay and PayPal)
21
+ # USD (US Dollar - only for PayPal)
22
+ # CNY (Yuan Renminbi - only for China Union Pay)
23
+ SUPPORTED_CURRENCIES = %w[MYR USD CNY]
24
+
25
+ # Languages supported
26
+ # ISO-8859-1 (English)
27
+ # UTF-8 (Unicode)
28
+ # GB2312 (Chinese Simplified)
29
+ # GD18030 (Chinese Simplified)
30
+ # BIG5 (Chinese Traditional)
31
+ SUPPORTED_LANGS = %w[ISO-8859-1 UTF-8 GB2312 GD18030 BIG5]
32
+
33
+ # Payment methods supported
34
+ # 8 (Alliance Online Transfer)
35
+ # 10 (AmBank)
36
+ # 21 (China Union Pay)
37
+ # 20 (CIMB Click)
38
+ # 2 (Credit Card MYR)
39
+ # 16 (FPX)
40
+ # 15 (Hong Leong Bank Transfer)
41
+ # 6 (Maybank2u.com)
42
+ # 23 (MEPS Cash)
43
+ # 17 (Mobile Money)
44
+ # 33 (PayPal)
45
+ # 14 (RHB)
46
+ PAYMENT_METHODS = %w[8 10 21 20 2 16 15 6 23 17 33 14]
47
+
48
+ attr_reader :amount_in_cents, :merchant_key
49
+
50
+ def initialize(order, account, options = {})
51
+ requires!(options, :amount, :currency, :credential2)
52
+ @merchant_key = options[:credential2]
53
+ @amount_in_cents = options[:amount]
54
+ super
55
+ add_field mappings[:signature], signature
56
+ end
57
+
58
+ def amount_in_dollars
59
+ sprintf("%.2f", @amount_in_cents.to_f/100)
60
+ end
61
+
62
+ def amount=(money)
63
+ @amount_in_cents = money.respond_to?(:cents) ? money.cents : money
64
+ raise ArgumentError, "amount must be a Money object or an integer" if money.is_a?(String)
65
+ raise ActionViewHelperError, "amount must be greater than $0.00" if @amount_in_cents.to_i <= 0
66
+
67
+ add_field mappings[:amount], amount_in_dollars
68
+ end
69
+
70
+ def currency(symbol)
71
+ raise ArgumentError, "unsupported currency" unless SUPPORTED_CURRENCIES.include?(symbol)
72
+ add_field mappings[:currency], symbol
73
+ end
74
+
75
+ def language(lang)
76
+ raise ArgumentError, "unsupported language" unless SUPPORTED_LANGS.include?(lang)
77
+ add_field mappings[:language], lang
78
+ end
79
+
80
+ def payment(pay_method)
81
+ raise ArgumentError, "unsupported payment method" unless PAYMENT_METHODS.include?(pay_method.to_s)
82
+ add_field mappings[:payment], pay_method
83
+ end
84
+
85
+ def customer(params = {})
86
+ add_field(mappings[:customer][:name], "#{params[:first_name]} #{params[:last_name]}")
87
+ add_field(mappings[:customer][:email], params[:email])
88
+ add_field(mappings[:customer][:phone], params[:phone])
89
+ end
90
+
91
+ def self.sign(str)
92
+ [Digest::SHA1.digest(str)].pack("m").chomp
93
+ end
94
+
95
+ def signature
96
+ self.class.sign(self.sig_components)
97
+ end
98
+
99
+ mapping :account, "MerchantCode"
100
+ mapping :amount, "Amount"
101
+ mapping :currency, "Currency"
102
+ mapping :order, "RefNo"
103
+ mapping :description, "ProdDesc"
104
+ mapping :customer, :name => "UserName",
105
+ :email => "UserEmail",
106
+ :phone => "UserContact"
107
+ mapping :remark, "Remark"
108
+ mapping :language, "Lang"
109
+ mapping :payment, "PaymentId"
110
+ mapping :return_url, "ResponseURL"
111
+ mapping :signature, "Signature"
112
+
113
+ protected
114
+
115
+ def sig_components
116
+ components = [merchant_key]
117
+ components << fields[mappings[:account]]
118
+ components << fields[mappings[:order]]
119
+ components << amount_in_dollars.gsub(/[.,]/, '')
120
+ components << fields[mappings[:currency]]
121
+ components.join
122
+ end
123
+ end
124
+
125
+ class Notification < OffsitePayments::Notification
126
+ include ActiveMerchant::PostsData
127
+
128
+ def status
129
+ params["Status"] == '1' ? 'Completed' : 'Failed'
130
+ end
131
+
132
+ def complete?
133
+ status == 'Completed'
134
+ end
135
+
136
+ def item_id
137
+ params["RefNo"]
138
+ end
139
+
140
+ def gross
141
+ params["Amount"]
142
+ end
143
+
144
+ def currency
145
+ params["Currency"]
146
+ end
147
+
148
+ def account
149
+ params["MerchantCode"]
150
+ end
151
+
152
+ def payment
153
+ params["PaymentId"].to_i
154
+ end
155
+
156
+ def remark
157
+ params["Remark"]
158
+ end
159
+
160
+ def transaction_id
161
+ params["TransId"]
162
+ end
163
+
164
+ def auth_code
165
+ params["AuthCode"]
166
+ end
167
+
168
+ def error
169
+ params["ErrDesc"]
170
+ end
171
+
172
+ def signature
173
+ params["Signature"]
174
+ end
175
+
176
+ def secure?
177
+ generated_signature == signature
178
+ end
179
+
180
+ def success?
181
+ status == 'Completed'
182
+ end
183
+
184
+ def acknowledge
185
+ secure? && success? && requery == "00"
186
+ end
187
+
188
+ protected
189
+
190
+ def generated_signature
191
+ Helper.sign(sig_components)
192
+ end
193
+
194
+ def sig_components
195
+ components = [@options[:credential2]]
196
+ [:account, :payment, :item_id, :amount_in_cents, :currency].each do |i|
197
+ components << send(i)
198
+ end
199
+ components << params["Status"]
200
+ components.join
201
+ end
202
+
203
+ def requery
204
+ data = { "MerchantCode" => account, "RefNo" => item_id, "Amount" => gross }
205
+ params = parameterize(data)
206
+ ssl_post Ipay88.requery_url, params, { "Content-Length" => params.size.to_s, "User-Agent" => "Active Merchant -- http://activemerchant.org" }
207
+ end
208
+
209
+ private
210
+
211
+ def parameterize(params)
212
+ params.reject { |k, v| v.blank? }.keys.sort.collect { |key| "#{key}=#{CGI.escape(params[key].to_s)}" }.join("&")
213
+ end
214
+
215
+ def amount_in_cents
216
+ @amount_in_cents ||= (gross || "").gsub(/[.,]/, "")
217
+ end
218
+ end
219
+
220
+ class Return < OffsitePayments::Return
221
+ def initialize(query_string, options = {})
222
+ super
223
+ @notification = Notification.new(query_string, options)
224
+ end
225
+
226
+ def success?
227
+ params["Status"] == "1"
228
+ end
229
+
230
+ def cancelled?
231
+ params["ErrDesc"] == 'Customer Cancel Transaction'
232
+ end
233
+
234
+ def message
235
+ params["ErrDesc"]
236
+ end
237
+ end
238
+ end
239
+ end
240
+ 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