offsite_payments 2.0.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 (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