klarna 0.1.3
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.
- data/.gitignore +9 -0
- data/.travis.yml +13 -0
- data/Gemfile +14 -0
- data/Guardfile +16 -0
- data/MIT-LICENSE +20 -0
- data/README.textile +139 -0
- data/Rakefile +22 -0
- data/TODO +30 -0
- data/examples/Gemfile +8 -0
- data/examples/config/initializer.rb +15 -0
- data/examples/console.rb +71 -0
- data/examples/public/images/klarna.png +0 -0
- data/examples/public/images/ruby.png +0 -0
- data/examples/views/_address.haml +22 -0
- data/examples/views/_articles.haml +21 -0
- data/examples/views/checkout_page_example.haml +2 -0
- data/examples/views/essential/add_transaction/_form.haml +32 -0
- data/examples/views/essential/add_transaction/result.haml +7 -0
- data/examples/views/essential/calculate_monthly_cost/_form.haml +16 -0
- data/examples/views/essential/calculate_monthly_cost/result.haml +7 -0
- data/examples/views/essential/get_addresses/_form.haml +9 -0
- data/examples/views/essential/get_addresses/result.haml +8 -0
- data/examples/views/index.haml +296 -0
- data/examples/views/layout.haml +48 -0
- data/examples/views/payment_terms_example.haml +102 -0
- data/examples/views/product_page_example.haml +2 -0
- data/examples/views/reservation/activate_reservation/_form.haml +54 -0
- data/examples/views/reservation/activate_reservation/result.haml +7 -0
- data/examples/views/reservation/cancel_reservation/_form.haml +8 -0
- data/examples/views/reservation/cancel_reservation/result.haml +7 -0
- data/examples/views/reservation/change_reservation/_form.haml +10 -0
- data/examples/views/reservation/change_reservation/result.haml +7 -0
- data/examples/views/reservation/reserve_amount/_form.haml +58 -0
- data/examples/views/reservation/reserve_amount/result.haml +7 -0
- data/examples/views/reservation/reserve_ocr_numbers/_form.haml +8 -0
- data/examples/views/reservation/reserve_ocr_numbers/result.haml +7 -0
- data/examples/views/reservation/split_reservation/_form.haml +14 -0
- data/examples/views/reservation/split_reservation/result.haml +7 -0
- data/examples/views/special/get_pclasses/_form.haml +8 -0
- data/examples/views/special/get_pclasses/result.haml +7 -0
- data/examples/views/special/invoice_address/_form.haml +8 -0
- data/examples/views/special/invoice_address/result.haml +7 -0
- data/examples/views/special/invoice_amount/_form.haml +8 -0
- data/examples/views/special/invoice_amount/result.haml +7 -0
- data/examples/views/special/is_invoice_paid/_form.haml +8 -0
- data/examples/views/special/is_invoice_paid/result.haml +7 -0
- data/examples/views/special/update_charge_amount/_form.haml +15 -0
- data/examples/views/special/update_charge_amount/result.haml +7 -0
- data/examples/views/special/update_goods_quantity/_form.haml +17 -0
- data/examples/views/special/update_goods_quantity/result.haml +7 -0
- data/examples/views/special/update_order_number/_form.haml +10 -0
- data/examples/views/special/update_order_number/result.haml +7 -0
- data/examples/views/useful/activate_invoice/_form.haml +9 -0
- data/examples/views/useful/activate_invoice/result.haml +7 -0
- data/examples/views/useful/credit_invoice/_form.haml +11 -0
- data/examples/views/useful/credit_invoice/result.haml +7 -0
- data/examples/views/useful/delete_invoice/_form.haml +9 -0
- data/examples/views/useful/delete_invoice/result.haml +7 -0
- data/examples/views/useful/email_invoice/_form.haml +9 -0
- data/examples/views/useful/email_invoice/result.haml +7 -0
- data/examples/views/useful/has_account/_form.haml +9 -0
- data/examples/views/useful/has_account/result.haml +7 -0
- data/examples/views/useful/return_amount/_form.haml +15 -0
- data/examples/views/useful/return_amount/result.haml +7 -0
- data/examples/views/useful/send_invoice/_form.haml +9 -0
- data/examples/views/useful/send_invoice/result.haml +7 -0
- data/examples/web.rb +349 -0
- data/klarna.gemspec +34 -0
- data/lib/klarna.rb +175 -0
- data/lib/klarna/api.rb +170 -0
- data/lib/klarna/api/client.rb +128 -0
- data/lib/klarna/api/constants.rb +638 -0
- data/lib/klarna/api/errors.rb +154 -0
- data/lib/klarna/api/methods.rb +16 -0
- data/lib/klarna/api/methods/cost_calculations.rb +134 -0
- data/lib/klarna/api/methods/invoicing.rb +304 -0
- data/lib/klarna/api/methods/reservation.rb +149 -0
- data/lib/klarna/api/methods/standard.rb +123 -0
- data/lib/klarna/version.rb +5 -0
- data/test/fixtures/api/companies.yml +97 -0
- data/test/fixtures/api/pclasses.yml +37 -0
- data/test/fixtures/api/persons.yml +144 -0
- data/test/fixtures/api/stores.yml +6 -0
- data/test/fixtures/klarna.yml +10 -0
- data/test/klarna/api/client_test.rb +272 -0
- data/test/klarna/api/errors_test.rb +46 -0
- data/test/klarna/api/methods/cost_calculations_test.rb +78 -0
- data/test/klarna/api/methods/invoicing_test.rb +409 -0
- data/test/klarna/api/methods/reservation_test.rb +66 -0
- data/test/klarna/api/methods/standard_test.rb +244 -0
- data/test/klarna/api_test.rb +137 -0
- data/test/klarna_test.rb +204 -0
- data/test/support/assertions_helper.rb +40 -0
- data/test/test_helper.rb +55 -0
- metadata +312 -0
data/lib/klarna.rb
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# require 'rubygems'
|
|
3
|
+
# require 'bundler'
|
|
4
|
+
# Bundler.require
|
|
5
|
+
require 'i18n'
|
|
6
|
+
require 'active_support/all'
|
|
7
|
+
|
|
8
|
+
module Klarna
|
|
9
|
+
|
|
10
|
+
autoload :API, 'klarna/api'
|
|
11
|
+
autoload :VERSION, 'klarna/version'
|
|
12
|
+
|
|
13
|
+
module API
|
|
14
|
+
autoload :Client, 'klarna/api/client'
|
|
15
|
+
autoload :Constants, 'klarna/api/constants'
|
|
16
|
+
autoload :Errors, 'klarna/api/errors'
|
|
17
|
+
|
|
18
|
+
module Methods
|
|
19
|
+
autoload :Standard, 'klarna/api/methods/standard'
|
|
20
|
+
autoload :Invoicing, 'klarna/api/methods/invoicing'
|
|
21
|
+
autoload :Reservation, 'klarna/api/methods/reservation'
|
|
22
|
+
autoload :CostCalculations, 'klarna/api/methods/cost_calculations'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class KlarnaConfigError < ::StandardError
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
DEFAULT_STORE_CONFIG_FILE = File.join(ENV['HOME'], '.klarna.yml') unless defined?(::Klarna::DEFAULT_STORE_CONFIG_FILE)
|
|
30
|
+
VALID_COUNTRIES = [:SE, :NO, :FI, :DK] unless defined?(::Klarna::VALID_COUNTRIES)
|
|
31
|
+
DEFAULT_COUNTRY = VALID_COUNTRIES.first unless defined?(::Klarna::DEFAULT_COUNTRY)
|
|
32
|
+
DEFAULT_MODE = :test unless defined?(::Klarna::DEFAULT_MODE)
|
|
33
|
+
|
|
34
|
+
# Specifies running mode: +:test+ (alla actions gets virtual) or +:production+ (live)
|
|
35
|
+
# Default: +:test+
|
|
36
|
+
mattr_accessor :mode
|
|
37
|
+
@@mode = :test
|
|
38
|
+
|
|
39
|
+
# Country used to ensure that params sent to Klarna service is correct.
|
|
40
|
+
# Default: +:SE+
|
|
41
|
+
mattr_accessor :country
|
|
42
|
+
@@country = ::Klarna::DEFAULT_COUNTRY
|
|
43
|
+
|
|
44
|
+
# Klarna e-store ID (a.k.a. "eid") used for Klarna API authentication.
|
|
45
|
+
# Default: +nil+
|
|
46
|
+
# NOTE: If +nil+, Klarna will look for credentials in file specified by +Devise.store_config_file+.
|
|
47
|
+
mattr_accessor :store_id
|
|
48
|
+
@@store_id = nil
|
|
49
|
+
|
|
50
|
+
# Klarna e-store shared secret token used for Klarna API authentication.
|
|
51
|
+
# Default: +nil+
|
|
52
|
+
# NOTE: If +nil+, Klarna will look for credentials in file specified by +Devise.credentials_file+.
|
|
53
|
+
mattr_accessor :store_secret
|
|
54
|
+
@@store_secret = nil
|
|
55
|
+
|
|
56
|
+
# Klarna e-store-specific pclasses (a.k.a. campaigns).
|
|
57
|
+
# Default: +nil+
|
|
58
|
+
#
|
|
59
|
+
# NOTE:
|
|
60
|
+
# * Required for campaigns
|
|
61
|
+
# * This should maybe be initialized from database to make more dynamic (e.g. admin inteface).
|
|
62
|
+
#
|
|
63
|
+
mattr_accessor :store_pclasses
|
|
64
|
+
@@store_pclasses = nil
|
|
65
|
+
|
|
66
|
+
# Path to a YAML file containing API credentials - used only if +store_id+ and +store_secret+ are not set.
|
|
67
|
+
# Default: +"~/.klarna.yml"+
|
|
68
|
+
mattr_accessor :store_config_file
|
|
69
|
+
@@store_config_file = ::Klarna::DEFAULT_STORE_CONFIG_FILE
|
|
70
|
+
|
|
71
|
+
# The logger to use in log mode.
|
|
72
|
+
# Default: +::Logger.new(::STDOUT)+
|
|
73
|
+
mattr_accessor :logger
|
|
74
|
+
@@logger = ::Logger.new(::STDOUT)
|
|
75
|
+
|
|
76
|
+
# Sets if activity should be logged or not - for high-level debugging.
|
|
77
|
+
# Default: +false+
|
|
78
|
+
mattr_accessor :logging
|
|
79
|
+
@@logging = false
|
|
80
|
+
|
|
81
|
+
# Sets if Net::HTTP activity should be logged or not - for low-level debugging.
|
|
82
|
+
# Default: +false+
|
|
83
|
+
mattr_accessor :http_logging
|
|
84
|
+
@@http_logging = false
|
|
85
|
+
|
|
86
|
+
class << self
|
|
87
|
+
|
|
88
|
+
# Configuration DSL helper method.
|
|
89
|
+
#
|
|
90
|
+
# == Usage/Example:
|
|
91
|
+
#
|
|
92
|
+
# Klarna::Setup do |config|
|
|
93
|
+
# config.country = :SE
|
|
94
|
+
# # etc.
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
97
|
+
def setup
|
|
98
|
+
yield self
|
|
99
|
+
self.load_credentials_from_file unless self.store_id || self.store_secret
|
|
100
|
+
end
|
|
101
|
+
alias :configure :setup
|
|
102
|
+
|
|
103
|
+
# Reset to defaults - mostly usable in specs.
|
|
104
|
+
#
|
|
105
|
+
def reset!
|
|
106
|
+
self.mode = ::Klarna::DEFAULT_MODE
|
|
107
|
+
self.country = ::Klarna::DEFAULT_COUNTRY
|
|
108
|
+
self.store_id = nil
|
|
109
|
+
self.store_secret = nil
|
|
110
|
+
self.store_pclasses = nil
|
|
111
|
+
self.store_config_file = ::Klarna::DEFAULT_STORE_CONFIG_FILE
|
|
112
|
+
self.logger = ::Logger.new(::STDOUT)
|
|
113
|
+
self.logging = false
|
|
114
|
+
self.http_logging = false
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Logging helper for debugging purposes.
|
|
118
|
+
#
|
|
119
|
+
def log(message, level = :info)
|
|
120
|
+
return unless self.logging?
|
|
121
|
+
level = :info if level.blank?
|
|
122
|
+
self.logger ||= ::Logger.new(::STDOUT)
|
|
123
|
+
self.logger.send(level.to_sym, "[klarna:] #{level.to_s.upcase} #{message}")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Logging helper for debugging "log return value"-cases.
|
|
127
|
+
#
|
|
128
|
+
def log_result(label, &block)
|
|
129
|
+
result = block.call
|
|
130
|
+
self.log label % result.inspect
|
|
131
|
+
result
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Optional: Try to load credentials from a system file.
|
|
135
|
+
#
|
|
136
|
+
def load_credentials_from_file(force = false)
|
|
137
|
+
begin
|
|
138
|
+
store_config = File.open(self.store_config_file) { |file| YAML.load(file).with_indifferent_access }
|
|
139
|
+
self.store_id = store_config[self.mode][:store_id] if force || self.store_id.nil?
|
|
140
|
+
self.store_secret = store_config[self.mode][:store_secret] if force || self.store_secret.nil?
|
|
141
|
+
self.store_pclasses = store_config[self.mode][:store_pclasses] if force || self.store_pclasses.nil?
|
|
142
|
+
rescue
|
|
143
|
+
raise KlarnaConfigError, "Could not load store details from: #{self.store_config_file.inspect}"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def store_id=(value)
|
|
148
|
+
@@store_id = value ? value.to_i : value
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def store_secret=(value)
|
|
152
|
+
@@store_secret = value ? value.to_s.strip : value
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def country=(value)
|
|
156
|
+
@@country = value.to_s.upcase.to_sym rescue ::Klarna::DEFAULT_COUNTRY
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def mode=(value)
|
|
160
|
+
@@mode = value.to_s.downcase.to_sym rescue ::Klarna::DEFAULT_MODE
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def valid_countries
|
|
164
|
+
::Klarna::VALID_COUNTRIES
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def default_country
|
|
168
|
+
::Klarna::DEFAULT_COUNTRY
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
alias :logging? :logging
|
|
172
|
+
alias :http_logging? :http_logging
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
data/lib/klarna/api.rb
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'iconv'
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
require 'digest/sha2'
|
|
5
|
+
require 'xmlrpc/base64'
|
|
6
|
+
|
|
7
|
+
require 'klarna/api/constants'
|
|
8
|
+
require 'klarna/api/errors'
|
|
9
|
+
|
|
10
|
+
module Klarna
|
|
11
|
+
module API
|
|
12
|
+
|
|
13
|
+
# == Reference:
|
|
14
|
+
#
|
|
15
|
+
# * http://integration.klarna.com/api-functions
|
|
16
|
+
#
|
|
17
|
+
# == Note:
|
|
18
|
+
#
|
|
19
|
+
# All input data strings should be coded in accordance with ISO-8859-1.
|
|
20
|
+
#
|
|
21
|
+
# This implementation is not 1-to-1 implementation of the Klarna API functions.
|
|
22
|
+
# The methods are refactored/cleaned up to meet Ruby code standards; avoid
|
|
23
|
+
# unessecary code-repetition and spaghetti-code like the other implementations.
|
|
24
|
+
# Same functionality can be achieved though; see the RDocs.
|
|
25
|
+
#
|
|
26
|
+
# To obtain pclasses - or download the pclasses file:
|
|
27
|
+
#
|
|
28
|
+
# 1. Log in to Kreditor Online.
|
|
29
|
+
# 2. Click on "Display store"
|
|
30
|
+
# 3. Click on "Click here to view campaigns".
|
|
31
|
+
#
|
|
32
|
+
# ...or use the API-method +::Klarna::API.get_pclasses+.
|
|
33
|
+
#
|
|
34
|
+
|
|
35
|
+
include ::Klarna::API::Constants
|
|
36
|
+
include ::Klarna::API::Errors
|
|
37
|
+
|
|
38
|
+
@@client = nil
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
|
|
42
|
+
# Re-use or (re-)initialize a new Klarna XML-RPC API client.
|
|
43
|
+
#
|
|
44
|
+
def client(force_new = false)
|
|
45
|
+
begin
|
|
46
|
+
if force_new || @@client.nil?
|
|
47
|
+
@@client = ::Klarna::API::Client.new
|
|
48
|
+
end
|
|
49
|
+
rescue => e
|
|
50
|
+
::Klarna.log e, :error
|
|
51
|
+
end
|
|
52
|
+
@@client
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def key_for(kind, value)
|
|
56
|
+
begin
|
|
57
|
+
kind = validated_kind(kind)
|
|
58
|
+
rescue => e
|
|
59
|
+
raise e
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
begin
|
|
63
|
+
is_correct_format = !(value.to_s =~ /^\d+$/)
|
|
64
|
+
constants = ::Klarna::API.const_get(kind)
|
|
65
|
+
key = is_correct_format ? value : constants.invert[value.to_i] # BUG: Should lookup the value's key.
|
|
66
|
+
rescue
|
|
67
|
+
raise ::Klarna::API::KlarnaArgumentError, "Invalid '#{kind}': #{value.inspect}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
key ? key.to_sym : nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def id_for(kind, value)
|
|
74
|
+
begin
|
|
75
|
+
kind = validated_kind(kind)
|
|
76
|
+
rescue => e
|
|
77
|
+
raise e
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
begin
|
|
81
|
+
is_correct_format = (value.to_s =~ /^\d+$/)
|
|
82
|
+
constants = ::Klarna::API.const_get(kind)
|
|
83
|
+
id = is_correct_format ? value.to_i : constants[value.to_s.upcase.to_sym]
|
|
84
|
+
rescue
|
|
85
|
+
raise ::Klarna::API::KlarnaArgumentError, "Invalid '#{kind}': #{value.inspect}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
id ? id.to_i : nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Validate if specified +kind+ is a valid constant.
|
|
92
|
+
#
|
|
93
|
+
def validated_kind(kind)
|
|
94
|
+
valid_kinds = [:country, :currency, :language, :pno_format, :address_format, :shipment_type, :pclass, :mobile, :invoice, :goods, :monthly_cost]
|
|
95
|
+
valid_kinds.collect! do |valid_kind|
|
|
96
|
+
[valid_kind.to_s.singularize.to_sym, valid_kind.to_s.pluralize.to_sym]
|
|
97
|
+
end
|
|
98
|
+
valid_kinds.flatten!
|
|
99
|
+
kind = kind.to_s.pluralize.to_sym
|
|
100
|
+
|
|
101
|
+
unless kind.is_a?(String) || kind.is_a?(Symbol)
|
|
102
|
+
raise ::Klarna::API::KlarnaArgumentError, "Not a valid kind: #{kind.inspect}. Expects a symbol or a string: #{valid_kinds.join(', ')}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
unless valid_kinds.include?(kind.to_s.downcase.to_sym)
|
|
106
|
+
raise ::Klarna::API::KlarnaArgumentError, "Not a valid kind: #{kind.inspect}. Valid kinds: #{valid_kinds.join(', ')}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
kind.to_s.upcase.to_sym
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Parse, validate, and cast a method argument before RPC-call.
|
|
113
|
+
#
|
|
114
|
+
def validate_arg(value, cast_to, format_expression, strip_expression = nil, &block)
|
|
115
|
+
raise ::Klarna::API::KlarnaArgumentError,
|
|
116
|
+
"Argument cast_to should be Symbol, but was #{cast_to.class.name}." unless cast_to.is_a?(Symbol)
|
|
117
|
+
raise ::Klarna::API::KlarnaArgumentError,
|
|
118
|
+
"Argument regexp should be Regexp, but was #{format_expression.class.name}." unless format_expression.is_a?(Regexp)
|
|
119
|
+
raise ::Klarna::API::KlarnaArgumentError,
|
|
120
|
+
"Argument strip should be Regexp, but was #{strip_expression.class.name}." unless strip_expression.is_a?(Regexp)
|
|
121
|
+
|
|
122
|
+
value = value.to_s.gsub(strip_expression, '') if strip_expression
|
|
123
|
+
|
|
124
|
+
unless value.to_s =~ format_expression
|
|
125
|
+
raise ::Klarna::API::KlarnaArgumentError, "Invalid argument: #{value.inspect}. Expected format: #{format_expression.inspect}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Pass value to block - for type casting, etc. - if given.
|
|
129
|
+
value = block.call(value) if block_given?
|
|
130
|
+
|
|
131
|
+
value.tap do |v|
|
|
132
|
+
case cast_to
|
|
133
|
+
when :string then v.to_s
|
|
134
|
+
when :integer then v.to_i
|
|
135
|
+
when :decimal then v.to_f # number of decimals?
|
|
136
|
+
when :date then v # TODO
|
|
137
|
+
else
|
|
138
|
+
raise ::Klarna::API::KlarnaArgumentError, "Invalid cast_to value: #{cast_to.inspect}. "
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def parse_flags(constant_name, flags)
|
|
144
|
+
if flags.is_a?(Hash)
|
|
145
|
+
flags = flags.sum { |k,v|
|
|
146
|
+
::Klarna::API.const_get(constant_name.to_s.upcase.to_sym)[k.to_s.upcase.to_sym]
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
flags.to_i
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def digest(*args)
|
|
153
|
+
string = args.join(':')
|
|
154
|
+
iso_value = self.encode(string)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
hex_md5_digest = [*::Digest::MD5.hexdigest(iso_value)].pack('H*')
|
|
158
|
+
base64_digest = ::XMLRPC::Base64.encode(hex_md5_digest).strip
|
|
159
|
+
hex_sha512_digest = [*Digest::SHA512.hexdigest(iso_value)].pack('H*')
|
|
160
|
+
base64_digest = ::XMLRPC::Base64.encode(hex_sha512_digest).strip
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def encode(string, from_encoding = 'utf-8')
|
|
164
|
+
::Iconv.conv(::Klarna::API::PROTOCOL_ENCODING, from_encoding, string)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'xmlrpc/client'
|
|
3
|
+
|
|
4
|
+
require 'klarna/api/methods'
|
|
5
|
+
|
|
6
|
+
module Klarna
|
|
7
|
+
module API
|
|
8
|
+
class Client < ::XMLRPC::Client
|
|
9
|
+
|
|
10
|
+
include ::Klarna::API::Methods
|
|
11
|
+
|
|
12
|
+
attr_accessor :store_id,
|
|
13
|
+
:store_secret,
|
|
14
|
+
:mode,
|
|
15
|
+
:timeout,
|
|
16
|
+
:last_request_headers,
|
|
17
|
+
:last_request_params,
|
|
18
|
+
:last_response,
|
|
19
|
+
:client_ip,
|
|
20
|
+
:protocol,
|
|
21
|
+
:host,
|
|
22
|
+
:port
|
|
23
|
+
|
|
24
|
+
def initialize(*args)
|
|
25
|
+
self.store_id = args.shift || ::Klarna.store_id
|
|
26
|
+
self.store_secret = args.shift || ::Klarna.store_secret
|
|
27
|
+
|
|
28
|
+
if self.store_id.blank? || self.store_secret.blank?
|
|
29
|
+
raise ::Klarna::API::KlarnaCredentialsError, "Both STORE_ID or STORE_SECRET must be set."
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
options = args.extract_options!
|
|
33
|
+
self.client_ip = options[:client_ip].presence || '127.0.0.1'
|
|
34
|
+
self.mode = options.key?(:mode) ? options[:mode] : ::Klarna.mode
|
|
35
|
+
self.timeout = options.key?(:timeout) ? options[:timeout] : 10 # seconds
|
|
36
|
+
|
|
37
|
+
unless ::Klarna::API::END_POINT.keys.include?(self.mode)
|
|
38
|
+
raise "No such mode: #{self.mode.inspect}. " <<
|
|
39
|
+
"Valid modes: #{::Klarna::API::END_POINT.keys.collect(&:inspect).join(', ')}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
begin
|
|
43
|
+
::Klarna.log "Endpoint URI: %s" % self.endpoint_uri.inspect
|
|
44
|
+
|
|
45
|
+
super(self.host, '/', self.port, nil, nil, nil, nil, self.use_ssl?, self.timeout)
|
|
46
|
+
|
|
47
|
+
self.http_header_extra ||= {}
|
|
48
|
+
self.http_header_extra.merge!(self.content_type_headers)
|
|
49
|
+
|
|
50
|
+
self.instance_variable_get(:@http).set_debug_output(::Klarna.logger) if ::Klarna.http_logging?
|
|
51
|
+
rescue ::XMLRPC::FaultException => e
|
|
52
|
+
raise ::Klarna::API::KlarnaServiceError.new(e.faultCode, e.faultString)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def call(service_method, *args)
|
|
57
|
+
args.collect! { |arg| arg.is_a?(String) ? ::Iconv.conv('utf-8', ::Klarna::API::PROTOCOL_ENCODING, arg) : arg }
|
|
58
|
+
::Klarna.log "Method: #{service_method}"
|
|
59
|
+
::Klarna.log "Params: %s" % self.add_meta_params(*args).inspect
|
|
60
|
+
|
|
61
|
+
self.last_request_headers = http_header_extra
|
|
62
|
+
|
|
63
|
+
begin
|
|
64
|
+
::Klarna.log_result("Result: %s") do
|
|
65
|
+
params = self.add_meta_params(*args)
|
|
66
|
+
self.last_request_params = params
|
|
67
|
+
self.last_response = super(service_method, *params)
|
|
68
|
+
end
|
|
69
|
+
rescue ::XMLRPC::FaultException => e
|
|
70
|
+
raise ::Klarna::API::KlarnaServiceError.new(e.faultCode, e.faultString)
|
|
71
|
+
rescue ::Timeout::Error => e
|
|
72
|
+
raise ::Klarna::API::KlarnaServiceError.new(-1, e.message)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def ssl?
|
|
77
|
+
self.protocol == 'https'
|
|
78
|
+
end
|
|
79
|
+
alias :use_ssl? :ssl?
|
|
80
|
+
|
|
81
|
+
def protocol
|
|
82
|
+
@protocol = ::Klarna::API::END_POINT[self.mode][:protocol]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def host
|
|
86
|
+
@host = ::Klarna::API::END_POINT[self.mode][:host]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def port
|
|
90
|
+
@port = ::Klarna::API::END_POINT[self.mode][:port]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def endpoint_uri
|
|
94
|
+
@endpoint_uri = "#{self.protocol}://#{self.host}:#{self.port}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
protected
|
|
98
|
+
|
|
99
|
+
# Request content-type headers.
|
|
100
|
+
#
|
|
101
|
+
def content_type_headers
|
|
102
|
+
{
|
|
103
|
+
:'Accept-Encoding' => 'deflate,gzclient_ip',
|
|
104
|
+
:'Content-Type' => "text/xml;charset=#{::Klarna::API::PROTOCOL_ENCODING}",
|
|
105
|
+
:'Accept-Charset' => 'iso-8859-1', # REVISIT: 'UTF-8,ISO-8859-1,US-ASCII',
|
|
106
|
+
:'Connection' => 'close',
|
|
107
|
+
:'User-Agent' => 'ruby/xmlrpc' # Note: Default "User-Agent" gives 400-error.
|
|
108
|
+
}.with_indifferent_access
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Ensure that the required client info params get sent with each Klarna API request.
|
|
112
|
+
# Without these the Klarna API will get a service error response.
|
|
113
|
+
#
|
|
114
|
+
def add_meta_params(*args)
|
|
115
|
+
args.unshift *[::Klarna::API::PROTOCOL_VERSION, ::XMLRPC::Client::USER_AGENT]
|
|
116
|
+
args
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Pass additional required digest args to the raw digest method.
|
|
120
|
+
#
|
|
121
|
+
def digest(*args)
|
|
122
|
+
options = args.extract_options!
|
|
123
|
+
::Klarna::API.digest(*[(self.store_id unless options[:store_id] == false), args, self.store_secret].compact.flatten)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|