koffsite_payments 3.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 (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