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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +4 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +170 -0
  8. data/Rakefile +6 -0
  9. data/collector-ruby.gemspec +33 -0
  10. data/collector_browser.rb +93 -0
  11. data/lib/collector.rb +25 -0
  12. data/lib/collector/activate_invoice_request.rb +45 -0
  13. data/lib/collector/address.rb +28 -0
  14. data/lib/collector/adjust_invoice_request.rb +28 -0
  15. data/lib/collector/article_list_item.rb +28 -0
  16. data/lib/collector/base_model.rb +116 -0
  17. data/lib/collector/cancel_invoice_request.rb +22 -0
  18. data/lib/collector/client.rb +133 -0
  19. data/lib/collector/get_address_request.rb +22 -0
  20. data/lib/collector/invoice_request.rb +63 -0
  21. data/lib/collector/invoice_response.rb +27 -0
  22. data/lib/collector/invoice_row.rb +18 -0
  23. data/lib/collector/replace_invoice_request.rb +31 -0
  24. data/lib/collector/user.rb +25 -0
  25. data/lib/collector/version.rb +3 -0
  26. data/spec/address_spec.rb +45 -0
  27. data/spec/article_list_item_spec.rb +33 -0
  28. data/spec/base_model_spec.rb +42 -0
  29. data/spec/helpers/sandbox_objects_helper.rb +83 -0
  30. data/spec/invoice_request_spec.rb +27 -0
  31. data/spec/invoice_response_spec.rb +29 -0
  32. data/spec/invoice_row_spec.rb +29 -0
  33. data/spec/operations/activate_invoice_spec.rb +82 -0
  34. data/spec/operations/add_invoice_spec.rb +130 -0
  35. data/spec/operations/adjust_invoice_spec.rb +54 -0
  36. data/spec/operations/cancel_invoice_spec.rb +33 -0
  37. data/spec/operations/get_address_spec.rb +34 -0
  38. data/spec/operations/replace_invoice_spec.rb +45 -0
  39. data/spec/spec_helper.rb +46 -0
  40. data/spec/user_spec.rb +40 -0
  41. data/usage_example.rb +39 -0
  42. 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