collector-ruby 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a3ee1393ab7c170b016813a73ea4762cb136bc60
4
+ data.tar.gz: c77ea79dba484ad8f9591068000bc68f9580cd34
5
+ SHA512:
6
+ metadata.gz: bcfff7ccb4d15c5b732285e892a571c169acaa42227755689fa5a599ac464dcfe85c2f0355afab2932136b478e199c27d389c7648b0d6725b3d5e718d22dc397
7
+ data.tar.gz: bb5039c39b3e83f602fb3a74c89f78c2e9e86f1a0080467f8f86133d8e30d9dd53f077b3321f693dee3d842e439eb205cb7de754f3de4d90ec4486bc04df4155
@@ -0,0 +1,4 @@
1
+ Autotest.add_hook(:initialize) do |at|
2
+ %w{.git coverage log fixtures}.each {|exception| at.add_exception(exception)}
3
+ at.prefix = "bundle exec "
4
+ end
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
19
+ fixtures/vcr_cassettes
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # TODO: Move to .gemspec once Savon 3 is released
4
+ gem 'savon', :github => "savonrb/savon", :branch => "master"
5
+
6
+ # Specify your gem's dependencies in collector-ruby.gemspec
7
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Felix Holmgren
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,170 @@
1
+ # Collector::Ruby
2
+
3
+ This gem provides an interface to the [Collector SOAP API](https://commerce.collector.se/en/Integration/API/) for payments handling.
4
+
5
+ __Important__: `collector-ruby` depends on the unfinished version 3 of Savon, and therefore users of this gem must also include this in their Gemfile:
6
+
7
+ gem 'savon', :github => "savonrb/savon", :branch => "master"
8
+
9
+ *NOTE: `collector-ruby` will soon be updated to use Savon's successor.*
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'collector-ruby'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself:
22
+
23
+ $ gem install collector-ruby
24
+
25
+
26
+ Inside of your Ruby program, require collector-ruby:
27
+
28
+ require 'collector-ruby'
29
+
30
+ __Important__: See note above about adding Savon to your app's dependencies.
31
+
32
+ # Usage
33
+ ## Initializing the client
34
+ All calls to the Collector API are performed by instances of the `Collector::Client` class. The client has no state apart from the configuration parameters passed when initializing it. The initializer requires the user name and password, and an optional third parameter specifies whether to use the sandbox environment.
35
+
36
+ regular_client = Collector.new(user_name, password)
37
+ sandbox_client = Collector.new(user_name, password, true)
38
+
39
+ ## Errors
40
+ The `Client` object performs basic validation of all requests (checking that all required parameters are present), and raises an `ArgumentError` if the validation fails.
41
+
42
+ If a call to the Collector API fails, an instance of `CollectorError` will be raised. This class has a property called `faultcode` which contains the error code as it appears in [the Collector documentation](https://commerce.collector.se/en/Integration/API/General/Error-codes/), e.g. `INVALID_REGISTRATION_NUMBER`. The full description returned by Collector is set as the error object's `message`.
43
+
44
+ For some common Collector API errors, custom error classes are implemented. Currently these errors may be raised.
45
+
46
+ InvoiceNotFoundError
47
+ InvalidInvoiceStatusError
48
+ InvalidTransactionAmountError
49
+
50
+ These are subclasses of `CollectorError` and thus also have the `faultcode` property.
51
+
52
+ ## Add Invoice
53
+
54
+ require 'collector'
55
+
56
+ address = Collector::Address.new(
57
+ address1: "GATUADRESSAKT211",
58
+ city: "UMEÅ",
59
+ country_code: "SE",
60
+ postal_code: "90737",
61
+ first_name: "FÖRNAMNAKT211",
62
+ last_name: "EFTERNAMNAKT211" )
63
+
64
+ invoice_row = Collector::InvoiceRow.new(
65
+ article_id: 12,
66
+ description: "A wonderful thing",
67
+ quantity: "2",
68
+ unit_price: 12.0,
69
+ vat: 2.0 )
70
+
71
+ invoice_request = Collector::InvoiceRequest.new(activation_option: 0,
72
+ country_code: "SE",
73
+ currency: "SEK",
74
+ delivery_address: address,
75
+ invoice_address: address,
76
+ invoice_delivery_method: 1,
77
+ invoice_rows: [invoice_row],
78
+ invoice_type: 0,
79
+ order_date: DateTime.now,
80
+ reg_no: "1602079954",
81
+ store_id: "355" )
82
+
83
+ user_name = "" # Your Collector user name
84
+ password = "" # Your Collector password
85
+
86
+ collector = Collector.new(user_name, password)
87
+ response = collector.add_invoice(invoice_request)
88
+
89
+ puts "Response: #{response.inspect}"
90
+ # => <Collector::AddInvoiceResponse:0x007f9c358c13c8 @available_reservation_amount="0", @correlation_id=nil, @due_date=nil, @invoice_no="125434", @invoice_status="1", @invoice_url=nil, @lowest_amount_to_pay=nil, @payment_reference=nil, @total_amount=nil>
91
+
92
+ ## Activate Invoice
93
+ These methods return instances of `Collector::InvoiceResponse`, which has the the following attributes:
94
+ `:available_reservation_amount, :correlation_id,`
95
+ `:due_date, :invoice_no, :invoice_status, :invoice_url,`
96
+ `:lowest_amount_to_pay, :payment_reference, :total_amount,`
97
+ `:new_invoice_no`
98
+
99
+ (`:new_invoice_no` is only returned when partially activating an invoice.)
100
+
101
+
102
+ ### Full Activation
103
+ response = @client.activate_invoice(invoice_no: invoice_no,
104
+ store_id: "355",
105
+ country_code: "SE",
106
+ correlation_id: "optional_unique_string")
107
+
108
+
109
+
110
+ ### Part Activation
111
+ Part activation of an invoice can be performed either by calling the same `#activate_invoice` method as for full activation, but including `article_list` among the parameters, or more explicitly by calling `#part_activate_invoice`.
112
+
113
+ Whichever method is used, the same parameters are expected and the response is identical. The only difference is that when `#part_activate_invoice` is called without an `article_list` parameter, an `ArgumentError` will be raised.
114
+
115
+ item = Collector::ArticleListItem.new(article_id: 101, quantity: 1, description: "A fine piece")
116
+ item2 = Collector::ArticleListItem.new(article_id: 102, quantity: 1, description: "Another fine piece")
117
+ response = @client.part_activate_invoice(
118
+ invoice_no: invoice_no,
119
+ store_id: "355",
120
+ country_code: "SE",
121
+ article_list: [item, item2])
122
+
123
+ `ArticleListItem` instances can also be initialized from `InvoiceRow` objects:
124
+
125
+ item = Collector::ArticleListItem.new(invoice_row)
126
+
127
+ ## Replace Invoice
128
+
129
+ Also returns an instance of `Collector::InvoiceResponse`, but with values for only the following fields: `CorrelationId`, `AvailableReservationAmount`, `TotalAmount`, `InvoiceStatus`. This operation does **not** create a new invoice number.
130
+
131
+ new_invoice_row = Collector::InvoiceRow.new(
132
+ article_id: 123,
133
+ description: "A new shiny gadget",
134
+ quantity: "2",
135
+ unit_price: 250.0,
136
+ vat: 2.0)
137
+ response = @client.replace_invoice(invoice_no: invoice_no,
138
+ store_id: "355",
139
+ country_code: "SE",
140
+ correlation_id: "testing_replace_invoice",
141
+ invoice_rows: [new_row])
142
+
143
+ ## Adjust Invoice
144
+
145
+ correlation_id = @client.adjust_invoice(invoice_no: invoice_no,
146
+ article_id: 101,
147
+ description: "You are our 100th customer!",
148
+ amount: "-20.0",
149
+ vat: "2.0",
150
+ store_id: "355",
151
+ country_code: "SE",
152
+ correlation_id: "optional_unique_string")
153
+ The same invoice can be adjusted to a lower amount several times, and may also be adjusted by a positive amount, but only up to the original amount of the invoice. If an attempt is made to adjust the invoice beyond its original amount, an `InvalidTransactionAmountError` will be raised.
154
+
155
+ ## Cancel Invoice
156
+ correlation_id = @client.cancel_invoice(invoice_no: invoice_no,
157
+ store_id: "355",
158
+ country_code: "SE",
159
+ correlation_id: "optional_unique_string" )
160
+
161
+ Attempts to cancel an already canceled invoice will result in an `InvalidInvoiceStatusError`.
162
+
163
+ ## Get Address
164
+ user = @client.get_address(reg_no: "1602079954", store_id: "355")
165
+
166
+ The returned `User` object has the following attributes:
167
+
168
+ :first_name, :last_name, :reg_no, :addresses
169
+
170
+ `addresses` is an array, but currently contains at most one address. This operation is only available in Sweden, and an agreement with Collector is required for its use.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task :default => :spec
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'collector/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "collector-ruby"
8
+ spec.version = Collector::VERSION
9
+ spec.authors = ["Felix Holmgren"]
10
+ spec.email = ["felix.holmgren@gmail.com"]
11
+ spec.description = %q{Client for the Collector API}
12
+ spec.summary = %q{Just starting out}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "virtus", ">= 0.5.0"
22
+ spec.add_runtime_dependency "representable"
23
+ spec.add_runtime_dependency "activesupport"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec", "~> 2.13"
28
+ spec.add_development_dependency "require_all"
29
+ spec.add_development_dependency "webmock"
30
+ spec.add_development_dependency "vcr"
31
+ spec.add_development_dependency 'commander'
32
+ spec.add_development_dependency 'terminal-table'
33
+ end
@@ -0,0 +1,93 @@
1
+ require 'commander/import'
2
+
3
+ program :version, '0.0.1'
4
+ program :name, 'Collector browser'
5
+ program :description, 'Simple tool to browse the Collector API.'
6
+
7
+ require 'savon'
8
+ require 'terminal-table'
9
+
10
+ CollectorUrl = 'https://eCommerceTest.collector.se/v3.0/InvoiceServiceV31.svc?wsdl'
11
+ ServiceName = :InvoiceServiceV31
12
+ PortName = :BasicHttpBinding_IInvoiceServiceV31
13
+
14
+ default_command :menu
15
+
16
+ def client
17
+ return @savon if @savon
18
+ http = Savon::HTTPClient.new
19
+ http.client.ssl_config.ssl_version = 'TLSv1'
20
+ @savon = Savon.new(CollectorUrl, http)
21
+ end
22
+
23
+ def operations
24
+ @operations ||= client.operations(ServiceName, PortName)
25
+ end
26
+
27
+ def build_operation_info(parts)
28
+ memo = {}
29
+
30
+ parts.each do |element|
31
+ name = element.name.to_sym
32
+
33
+ case
34
+ when element.simple_type?
35
+ base_type_local = element.base_type.split(':').last
36
+ memo[name] = {type: base_type_local, simple: true, singular: element.singular?}
37
+
38
+ when element.complex_type?
39
+ value = build_operation_info(element.children)
40
+ memo[name] = {type: :complex, sub_part: value, simple: false, singular: element.singular?}
41
+ end
42
+ end
43
+
44
+ memo
45
+ end
46
+
47
+ def clear_screen
48
+ system ("clear")
49
+ end
50
+
51
+ def print_example(operation_name, path = [0])
52
+ clear_screen
53
+ operation = client.wsdl.operation(ServiceName.to_s, PortName.to_s, operation_name.to_s)
54
+ info = build_operation_info operation.input.body_parts
55
+ request_name = info.keys.first
56
+ hash = info
57
+ path.each{|index| key = hash.keys[index] ; hash = hash[key][:sub_part] }
58
+ rows = hash.keys.map.each_with_index do |key, idx|
59
+ value = hash[key]
60
+ [value[:simple] ? "-" : idx + 1,
61
+ key,
62
+ value[:type],
63
+ value[:singular]]
64
+ end
65
+ puts Terminal::Table.new :title => request_name, :headings => ['#', 'Param', 'Type', 'Singular?'], rows: rows
66
+ choice = ask("Choose nested structure, (b)ack, or e(x)it")
67
+ case choice
68
+ when "x"
69
+ abort
70
+ when "b"
71
+ if path.count == 1
72
+ print_menu
73
+ else
74
+ path.pop
75
+ print_example(operation_name, path)
76
+ end
77
+ else
78
+ print_example(operation_name, path << (choice.to_i - 1))
79
+ end
80
+ end
81
+
82
+ def print_menu
83
+ clear_screen
84
+ rows = operations.map.each_with_index{|name, idx| [idx + 1, name] }
85
+ table = Terminal::Table.new :title => "Operations", :headings => ['#', 'Operation'], rows: rows
86
+ puts table
87
+ choice = ask("Operation?")
88
+ print_example(operations[choice.to_i - 1])
89
+ end
90
+
91
+ command :menu do
92
+ print_menu
93
+ end
@@ -0,0 +1,25 @@
1
+ require "collector/version"
2
+ require "collector/base_model"
3
+ require "collector/client"
4
+ require "collector/address"
5
+ require "collector/user"
6
+ require "collector/invoice_row"
7
+ require "collector/article_list_item"
8
+
9
+ require "collector/invoice_request"
10
+ require "collector/cancel_invoice_request"
11
+ require "collector/adjust_invoice_request"
12
+ require "collector/replace_invoice_request"
13
+ require "collector/activate_invoice_request"
14
+ require "collector/get_address_request"
15
+ require "collector/invoice_response"
16
+
17
+ module Collector
18
+
19
+ class << self
20
+ def new(*options)
21
+ Client.new(*options)
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,45 @@
1
+ require 'representable/hash'
2
+ require 'representable/hash/collection'
3
+
4
+ module Collector
5
+ class ActivateInvoiceRequest < BaseModel
6
+
7
+ attributes :invoice_no, :country_code, :store_id
8
+ attributes_opt :correlation_id, :article_list
9
+ end
10
+
11
+ class ActivateInvoiceRequestRepresenter < Representable::Decorator
12
+ include Representable::Hash
13
+
14
+ self.representation_wrap = "ActivateInvoiceRequest"
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
+ class ArticleListRepresenter < Representable::Decorator
23
+ include Representable::Hash::Collection
24
+ self.representation_wrap = "Article"
25
+ items extend: ArticleListItemRepresenter, class: ArticleListItem
26
+ end
27
+
28
+ # TODO: Figure out how to make subclassing work
29
+ class PartActivateInvoiceRequestRepresenter < Representable::Decorator
30
+ include Representable::Hash
31
+
32
+ self.representation_wrap = "PartActivateInvoiceRequest"
33
+
34
+ property :article_list,
35
+ writer: lambda {|doc, args|
36
+ doc["ArticleList"] = ArticleListRepresenter.new(article_list).to_hash
37
+ }
38
+
39
+ property :correlation_id, as: "CorrelationId"
40
+ property :country_code, as: "CountryCode"
41
+ property :invoice_no, as: "InvoiceNo"
42
+ property :store_id, as: "StoreId"
43
+ end
44
+
45
+ end