collector-ruby 0.1.1
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.
- checksums.yaml +7 -0
- data/.autotest +4 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +170 -0
- data/Rakefile +6 -0
- data/collector-ruby.gemspec +33 -0
- data/collector_browser.rb +93 -0
- data/lib/collector.rb +25 -0
- data/lib/collector/activate_invoice_request.rb +45 -0
- data/lib/collector/address.rb +28 -0
- data/lib/collector/adjust_invoice_request.rb +28 -0
- data/lib/collector/article_list_item.rb +28 -0
- data/lib/collector/base_model.rb +116 -0
- data/lib/collector/cancel_invoice_request.rb +22 -0
- data/lib/collector/client.rb +133 -0
- data/lib/collector/get_address_request.rb +22 -0
- data/lib/collector/invoice_request.rb +63 -0
- data/lib/collector/invoice_response.rb +27 -0
- data/lib/collector/invoice_row.rb +18 -0
- data/lib/collector/replace_invoice_request.rb +31 -0
- data/lib/collector/user.rb +25 -0
- data/lib/collector/version.rb +3 -0
- data/spec/address_spec.rb +45 -0
- data/spec/article_list_item_spec.rb +33 -0
- data/spec/base_model_spec.rb +42 -0
- data/spec/helpers/sandbox_objects_helper.rb +83 -0
- data/spec/invoice_request_spec.rb +27 -0
- data/spec/invoice_response_spec.rb +29 -0
- data/spec/invoice_row_spec.rb +29 -0
- data/spec/operations/activate_invoice_spec.rb +82 -0
- data/spec/operations/add_invoice_spec.rb +130 -0
- data/spec/operations/adjust_invoice_spec.rb +54 -0
- data/spec/operations/cancel_invoice_spec.rb +33 -0
- data/spec/operations/get_address_spec.rb +34 -0
- data/spec/operations/replace_invoice_spec.rb +45 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/user_spec.rb +40 -0
- data/usage_example.rb +39 -0
- metadata +253 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'representable/hash'
|
2
|
+
|
3
|
+
module Collector
|
4
|
+
class Address < BaseModel
|
5
|
+
attributes :address1, :city,
|
6
|
+
:country_code, :postal_code
|
7
|
+
attributes_opt :address2, :co_address, :cell_phone_number,
|
8
|
+
:company_name, :email, :first_name,
|
9
|
+
:last_name, :phone_number
|
10
|
+
end
|
11
|
+
|
12
|
+
class AddressRepresenter < Representable::Decorator
|
13
|
+
include Representable::Hash
|
14
|
+
|
15
|
+
property :address1, as: :Address1
|
16
|
+
property :address2, as: :Address2
|
17
|
+
property :co_address, as: :COAddress
|
18
|
+
property :city, as: :City
|
19
|
+
property :country_code, as: :CountryCode
|
20
|
+
property :postal_code, as: :PostalCode
|
21
|
+
property :cell_phone_number, as: :CellPhoneNumber
|
22
|
+
property :company_name, as: :CompanyName
|
23
|
+
property :email, as: :Email
|
24
|
+
property :first_name, as: :Firstname
|
25
|
+
property :last_name, as: :Lastname
|
26
|
+
property :phone_number, as: :PhoneNumber
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'representable/hash'
|
2
|
+
require 'representable/hash/collection'
|
3
|
+
|
4
|
+
module Collector
|
5
|
+
class AdjustInvoiceRequest < BaseModel
|
6
|
+
|
7
|
+
attributes :invoice_no, :article_id,
|
8
|
+
:description, :amount, :vat,
|
9
|
+
:store_id, :country_code
|
10
|
+
attributes_opt :correlation_id
|
11
|
+
end
|
12
|
+
|
13
|
+
class AdjustInvoiceRequestRepresenter < Representable::Decorator
|
14
|
+
include Representable::Hash
|
15
|
+
|
16
|
+
self.representation_wrap = "AdjustInvoiceRequest"
|
17
|
+
|
18
|
+
property :amount, as: "Amount"
|
19
|
+
property :article_id, as: "ArticleId"
|
20
|
+
property :description, as: "Description"
|
21
|
+
property :correlation_id, as: "CorrelationId"
|
22
|
+
property :country_code, as: "CountryCode"
|
23
|
+
property :invoice_no, as: "InvoiceNo"
|
24
|
+
property :store_id, as: "StoreId"
|
25
|
+
property :vat, as: "Vat"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Collector
|
2
|
+
class ArticleListItem < BaseModel
|
3
|
+
attributes :article_id, :quantity, :description
|
4
|
+
|
5
|
+
def initialize(hash_or_invoice_row = {})
|
6
|
+
if hash_or_invoice_row.kind_of? Hash
|
7
|
+
super(hash_or_invoice_row)
|
8
|
+
elsif hash_or_invoice_row.kind_of? InvoiceRow
|
9
|
+
hash = attributes.inject({}) do |memo, attr|
|
10
|
+
memo[attr] = hash_or_invoice_row.send(attr)
|
11
|
+
memo
|
12
|
+
end
|
13
|
+
super(hash)
|
14
|
+
else
|
15
|
+
raise ArgumentError.new("An ArticleListItem must be initialized with a hash or an InvoiceRow instance.")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ArticleListItemRepresenter < Representable::Decorator
|
21
|
+
include Representable::Hash
|
22
|
+
|
23
|
+
property :article_id, as: "ArticleId"
|
24
|
+
property :quantity, as: "Quantity"
|
25
|
+
property :description, as: "Description"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Collector
|
2
|
+
|
3
|
+
class BaseModel
|
4
|
+
@swallow_unsupported_attributes = false
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def attributes(*args)
|
8
|
+
@attributes ||= []
|
9
|
+
@attributes += args
|
10
|
+
args.each{|attr| attr_accessor attr }
|
11
|
+
end
|
12
|
+
def attributes_opt(*args)
|
13
|
+
@attributes_opt ||= []
|
14
|
+
@attributes_opt += args
|
15
|
+
args.each{|attr| attr_accessor attr }
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :attribute, :attributes
|
19
|
+
alias_method :attribute_opt, :attributes_opt
|
20
|
+
|
21
|
+
def swallow_unsupported_attributes
|
22
|
+
@swallow_unsupported_attributes = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def swallow_unsupported_attributes?
|
26
|
+
@swallow_unsupported_attributes
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes
|
31
|
+
self.class.instance_variable_get("@attributes") || []
|
32
|
+
end
|
33
|
+
|
34
|
+
def attributes_opt
|
35
|
+
self.class.instance_variable_get("@attributes_opt") || []
|
36
|
+
end
|
37
|
+
|
38
|
+
def all_attributes
|
39
|
+
attributes + attributes_opt
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate_input(hash)
|
43
|
+
unsupported_keys = hash.keys.map(&:to_sym) - all_attributes
|
44
|
+
unless unsupported_keys.empty?
|
45
|
+
raise ArgumentError.new("Unsupported attribute(s): #{unsupported_keys}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(hash = {})
|
50
|
+
validate_input(hash) unless self.class.swallow_unsupported_attributes?
|
51
|
+
all_attributes.each do |attr|
|
52
|
+
val = hash[attr] || hash[attr.to_s]
|
53
|
+
self.send("#{attr}=", val)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
self.class.attributes.each do |attr|
|
59
|
+
return false if self.send(attr) != other.send(attr)
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def has_required_attributes?
|
65
|
+
missing_attributes.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def missing_attributes
|
69
|
+
own_missing + nested_missing
|
70
|
+
end
|
71
|
+
|
72
|
+
def own_missing
|
73
|
+
attributes.select{|attr| send(attr).nil? }
|
74
|
+
end
|
75
|
+
|
76
|
+
def nested_missing
|
77
|
+
missing = []
|
78
|
+
all_attributes.each do |attr|
|
79
|
+
val = send(attr)
|
80
|
+
if !!val
|
81
|
+
if val.kind_of? BaseModel
|
82
|
+
nested_missing = val.missing_attributes
|
83
|
+
missing << {attr => nested_missing} unless nested_missing.empty?
|
84
|
+
elsif val.kind_of? Array
|
85
|
+
val.each_with_index do |nested_val, index|
|
86
|
+
if nested_val.kind_of? BaseModel
|
87
|
+
nested_missing = nested_val.missing_attributes
|
88
|
+
missing << {"#{attr}[#{index}]" => nested_missing} unless nested_missing.empty?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
missing
|
95
|
+
end
|
96
|
+
|
97
|
+
def missing_attributes_human_readable(prefix = "", missing_attr = nil)
|
98
|
+
missing_attr = missing_attributes if missing_attr.nil?
|
99
|
+
missing_key_paths = missing_attr.map.each_with_index do |item|
|
100
|
+
if item.kind_of? Hash
|
101
|
+
key = item.keys.first
|
102
|
+
missing_attributes_human_readable(prefix + "#{key}.", item[key])
|
103
|
+
else
|
104
|
+
"#{prefix}#{item}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
if prefix.empty?
|
108
|
+
"Missing attributes: " + missing_key_paths.flatten.join(", ")
|
109
|
+
else
|
110
|
+
missing_key_paths
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'representable/hash'
|
2
|
+
require 'representable/hash/collection'
|
3
|
+
|
4
|
+
module Collector
|
5
|
+
class CancelInvoiceRequest < BaseModel
|
6
|
+
|
7
|
+
attributes :invoice_no, :store_id, :country_code
|
8
|
+
attributes_opt :correlation_id
|
9
|
+
end
|
10
|
+
|
11
|
+
class CancelInvoiceRequestRepresenter < Representable::Decorator
|
12
|
+
include Representable::Hash
|
13
|
+
|
14
|
+
self.representation_wrap = "CancelInvoiceRequest"
|
15
|
+
|
16
|
+
property :correlation_id, as: "CorrelationId"
|
17
|
+
property :country_code, as: "CountryCode"
|
18
|
+
property :invoice_no, as: "InvoiceNo"
|
19
|
+
property :store_id, as: "StoreId"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'savon'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
|
4
|
+
module Collector
|
5
|
+
|
6
|
+
COLLECTOR_URL = 'https://ecommerce.collector.se/v3.0/InvoiceServiceV31.svc?wsdl'
|
7
|
+
COLLECTOR_URL_TEST = 'https://eCommerceTest.collector.se/v3.0/InvoiceServiceV31.svc?wsdl'
|
8
|
+
SERVICE_NAME = :InvoiceServiceV31
|
9
|
+
PORT_NAME = :BasicHttpBinding_IInvoiceServiceV31
|
10
|
+
|
11
|
+
class CollectorError < RuntimeError
|
12
|
+
attr_reader :faultcode
|
13
|
+
def initialize(faultcode, message)
|
14
|
+
super(message)
|
15
|
+
@faultcode = faultcode
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InvoiceNotFoundError < CollectorError ; end
|
20
|
+
class InvalidInvoiceStatusError < CollectorError ; end
|
21
|
+
class InvalidTransactionAmountError < CollectorError ; end
|
22
|
+
class AuthorizationFailedError < CollectorError ; end
|
23
|
+
|
24
|
+
class Client
|
25
|
+
def initialize(user_name, password, sandbox = false)
|
26
|
+
@header = {"ClientIpAddress" => "?",
|
27
|
+
"Username" => user_name,
|
28
|
+
"Password" => password }
|
29
|
+
url = sandbox ? COLLECTOR_URL_TEST : COLLECTOR_URL
|
30
|
+
http = Savon::HTTPClient.new
|
31
|
+
http.client.ssl_config.ssl_version = 'TLSv1'
|
32
|
+
@savon = Savon.new(url, http)
|
33
|
+
end
|
34
|
+
|
35
|
+
def operation_with_name(operation_name)
|
36
|
+
@savon.operation(SERVICE_NAME, PORT_NAME, operation_name).tap do |operation|
|
37
|
+
operation.header = @header
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def raise_error(response_hash)
|
42
|
+
fault = response_hash[:fault]
|
43
|
+
err_class = CollectorError
|
44
|
+
case fault[:faultcode]
|
45
|
+
when "s:INVOICE_NOT_FOUND"
|
46
|
+
err_class = InvoiceNotFoundError
|
47
|
+
when "s:INVALID_INVOICE_STATUS"
|
48
|
+
err_class = InvalidInvoiceStatusError
|
49
|
+
when "s:INVALID_TRANSACTION_AMOUNT"
|
50
|
+
err_class = InvalidTransactionAmountError
|
51
|
+
when "s:AUTHORIZATION_FAILED"
|
52
|
+
err_class = AuthorizationFailedError
|
53
|
+
end
|
54
|
+
faultcode = fault[:faultcode].split(":").last
|
55
|
+
raise err_class.send(:new, faultcode, fault[:faultstring])
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_attributes(request_object)
|
59
|
+
unless request_object.has_required_attributes?
|
60
|
+
raise ArgumentError.new(request_object.missing_attributes_human_readable)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# operation_name is a symbol named as in the Collector API, eg :AddInvoice
|
65
|
+
# request is a hash or an object responding to to_hash
|
66
|
+
def perform_operation(operation_name, request)
|
67
|
+
operation = operation_with_name operation_name
|
68
|
+
operation.body = request.to_hash.with_indifferent_access
|
69
|
+
response = operation.call.body
|
70
|
+
raise_error(response) unless response[:fault].nil?
|
71
|
+
namespace = response.keys.first
|
72
|
+
response[namespace].with_indifferent_access
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_invoice(invoice_request)
|
76
|
+
validate_attributes(invoice_request)
|
77
|
+
resp = perform_operation(:AddInvoice, InvoiceRequestRepresenter.new(invoice_request))
|
78
|
+
InvoiceResponse.new(resp)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_address(options)
|
82
|
+
request = GetAddressRequest.new(options.merge({country_code: "SE"}))
|
83
|
+
validate_attributes(request)
|
84
|
+
resp = perform_operation(:GetAddress, GetAddressRequestRepresenter.new(request))
|
85
|
+
user = User.new
|
86
|
+
UserRepresenter.new(user).from_hash(resp)
|
87
|
+
user
|
88
|
+
end
|
89
|
+
|
90
|
+
def cancel_invoice(options)
|
91
|
+
request = CancelInvoiceRequest.new(options)
|
92
|
+
validate_attributes(request)
|
93
|
+
resp = perform_operation(:CancelInvoice, CancelInvoiceRequestRepresenter.new(request))
|
94
|
+
resp[:correlation_id]
|
95
|
+
end
|
96
|
+
|
97
|
+
def adjust_invoice(options)
|
98
|
+
request = AdjustInvoiceRequest.new(options)
|
99
|
+
validate_attributes(request)
|
100
|
+
resp = perform_operation(:AdjustInvoice, AdjustInvoiceRequestRepresenter.new(request))
|
101
|
+
resp[:correlation_id]
|
102
|
+
end
|
103
|
+
|
104
|
+
def replace_invoice(options)
|
105
|
+
request = ReplaceInvoiceRequest.new(options)
|
106
|
+
validate_attributes(request)
|
107
|
+
resp = perform_operation(:ReplaceInvoice, ReplaceInvoiceRequestRepresenter.new(request))
|
108
|
+
InvoiceResponse.new(resp)
|
109
|
+
end
|
110
|
+
|
111
|
+
def activate_invoice(options)
|
112
|
+
request = ActivateInvoiceRequest.new(options)
|
113
|
+
validate_attributes(request)
|
114
|
+
operation = nil
|
115
|
+
if request.article_list.nil?
|
116
|
+
operation = :ActivateInvoice
|
117
|
+
request = ActivateInvoiceRequestRepresenter.new(request)
|
118
|
+
else
|
119
|
+
operation = :PartActivateInvoice
|
120
|
+
request = PartActivateInvoiceRequestRepresenter.new(request)
|
121
|
+
end
|
122
|
+
resp = perform_operation(operation, request)
|
123
|
+
InvoiceResponse.new(resp)
|
124
|
+
end
|
125
|
+
|
126
|
+
def part_activate_invoice(options)
|
127
|
+
if options[:article_list].nil?
|
128
|
+
raise ArgumentError.new("Required parameter article_list missing.")
|
129
|
+
end
|
130
|
+
activate_invoice(options)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'representable/hash'
|
2
|
+
require 'representable/hash/collection'
|
3
|
+
|
4
|
+
module Collector
|
5
|
+
class GetAddressRequest < BaseModel
|
6
|
+
|
7
|
+
attributes :reg_no, :store_id, :country_code
|
8
|
+
attributes_opt :correlation_id
|
9
|
+
end
|
10
|
+
|
11
|
+
class GetAddressRequestRepresenter < Representable::Decorator
|
12
|
+
include Representable::Hash
|
13
|
+
|
14
|
+
self.representation_wrap = "GetAddressRequest"
|
15
|
+
|
16
|
+
property :correlation_id, as: "CorrelationId"
|
17
|
+
property :country_code, as: "CountryCode"
|
18
|
+
property :reg_no, as: "RegNo"
|
19
|
+
property :store_id, as: "StoreId"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'representable/hash'
|
2
|
+
require 'representable/hash/collection'
|
3
|
+
|
4
|
+
module Collector
|
5
|
+
class InvoiceRequest < BaseModel
|
6
|
+
|
7
|
+
attributes :activation_option, :country_code, :currency,
|
8
|
+
:delivery_address, :invoice_address, :invoice_delivery_method,
|
9
|
+
:invoice_rows, :invoice_type, :order_date,
|
10
|
+
:reg_no, :store_id
|
11
|
+
|
12
|
+
attributes_opt :correlation_id, :cost_center, :credit_time, :customer_no,
|
13
|
+
:gender, :order_no, :product_code,
|
14
|
+
:purchase_type, :reference, :sales_person
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvoiceRowListRepresenter < Representable::Decorator
|
19
|
+
include Representable::Hash::Collection
|
20
|
+
self.representation_wrap = "InvoiceRow"
|
21
|
+
items extend: InvoiceRowRepresenter, class: InvoiceRow
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvoiceRequestRepresenter < Representable::Decorator
|
25
|
+
include Representable::Hash
|
26
|
+
|
27
|
+
self.representation_wrap = "AddInvoiceRequest"
|
28
|
+
|
29
|
+
property :activation_option, as: "ActivationOption"
|
30
|
+
property :correlation_id, as: "CorrelationId"
|
31
|
+
property :cost_center, as: "CostCenter"
|
32
|
+
property :country_code, as: "CountryCode"
|
33
|
+
property :credit_time, as: "CreditTime"
|
34
|
+
property :currency, as: "Currency"
|
35
|
+
property :customer_no, as: "CustomerNo"
|
36
|
+
property :delivery_address, as: "DeliveryAddress",
|
37
|
+
decorator: AddressRepresenter,
|
38
|
+
class: Address
|
39
|
+
property :gender, as: "Gender"
|
40
|
+
property :invoice_address, as: "InvoiceAddress",
|
41
|
+
decorator: AddressRepresenter,
|
42
|
+
class: Address
|
43
|
+
property :invoice_delivery_method, as: "InvoiceDeliveryMethod"
|
44
|
+
property :invoice_rows,
|
45
|
+
writer: lambda {|doc, args|
|
46
|
+
doc["InvoiceRows"] = InvoiceRowListRepresenter.new(invoice_rows).to_hash
|
47
|
+
},
|
48
|
+
reader: lambda {|doc, args|
|
49
|
+
rows = doc["InvoiceRows"]["InvoiceRow"]
|
50
|
+
self.invoice_rows = rows.map{|row| InvoiceRowRepresenter.new(InvoiceRow.new).from_hash(row) }
|
51
|
+
}
|
52
|
+
property :invoice_type, as: "InvoiceType"
|
53
|
+
property :order_date, as: "OrderDate", :type => DateTime
|
54
|
+
property :order_no, as: "OrderNo"
|
55
|
+
property :product_code, as: "ProductCode"
|
56
|
+
property :purchase_type, as: "PurchaseType"
|
57
|
+
property :reference, as: "Reference"
|
58
|
+
property :reg_no, as: "RegNo"
|
59
|
+
property :sales_person, as: "SalesPerson"
|
60
|
+
property :store_id, as: "StoreId"
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|