collector-ruby 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -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
|
data/.autotest
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/lib/collector.rb
ADDED
@@ -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
|