easybill 0.2.17
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/.gitignore +24 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +74 -0
- data/easybill.gemspec +28 -0
- data/lib/easybill.rb +52 -0
- data/lib/easybill/client.rb +116 -0
- data/lib/easybill/configuration.rb +43 -0
- data/lib/easybill/customer.rb +4 -0
- data/lib/easybill/document.rb +34 -0
- data/lib/easybill/error.rb +3 -0
- data/lib/easybill/payment.rb +4 -0
- data/lib/easybill/railtie.rb +12 -0
- data/lib/easybill/version.rb +3 -0
- data/lib/sekken_patches/importer.rb +8 -0
- data/spec/easybill/client_spec.rb +189 -0
- data/spec/easybill/configuration_spec.rb +30 -0
- data/spec/easybill/document_spec.rb +41 -0
- data/spec/easybill_spec.rb +14 -0
- data/spec/fixtures/fixtures.yml +98 -0
- data/spec/fixtures/vcr_cassettes/add_document_payment_-_adds_a_payment_.yml +1677 -0
- data/spec/fixtures/vcr_cassettes/create_document_-_raises_Easybill_Error_if_unsuccessful_.yml +1669 -0
- data/spec/fixtures/vcr_cassettes/create_document_-_returns_an_Easybill_Document_on_success_.yml +1677 -0
- data/spec/fixtures/vcr_cassettes/create_dunning_-_creates_a_warning_.yml +1742 -0
- data/spec/fixtures/vcr_cassettes/find_documents_by_document_number_-_finds_documents_.yml +1671 -0
- data/spec/fixtures/vcr_cassettes/get_customer_-_raises_Easybill_Error_if_unsuccessful_.yml +1678 -0
- data/spec/fixtures/vcr_cassettes/get_customer_-_returns_an_Easybill_Customer_on_success_.yml +1727 -0
- data/spec/fixtures/vcr_cassettes/get_customer_by_customer_number_-_raises_Easybill_Error_if_unsuccessful_.yml +1678 -0
- data/spec/fixtures/vcr_cassettes/get_customer_by_customer_number_-_returns_an_Easybill_Customer_on_success_.yml +1671 -0
- data/spec/fixtures/vcr_cassettes/get_document_payments_-_returns_an_array_of_Easybill_Payment_on_success_.yml +1727 -0
- data/spec/fixtures/vcr_cassettes/get_document_pdf_-_returns_a_document_PDF_.yml +1727 -0
- data/spec/fixtures/vcr_cassettes/get_document_sent_-_receives_sent_date_.yml +1727 -0
- data/spec/fixtures/vcr_cassettes/get_documents_-_returns_an_Easybill_Document_array_on_success_.yml +1729 -0
- data/spec/fixtures/vcr_cassettes/get_documents_-_returns_an_empty_array_if_there_are_no_documents_.yml +1675 -0
- data/spec/fixtures/vcr_cassettes/local_copy_5c31767ec9fc284da7e4858f3580fb50.yml +1177 -0
- data/spec/fixtures/vcr_cassettes/search_customers_-_returns_an_array_of_matching_Easybill_Customers_.yml +2754 -0
- data/spec/fixtures/vcr_cassettes/search_customers_-_returns_an_empty_array_when_no_customers_are_found_.yml +1671 -0
- data/spec/fixtures/vcr_cassettes/set_customer_-_raises_Easybill_Error_if_unsuccessful_.yml +1700 -0
- data/spec/fixtures/vcr_cassettes/set_customer_-_returns_an_Easybill_Customer_on_success_.yml +1687 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/support/fixture.rb +16 -0
- data/spec/support/matchers.rb +6 -0
- data/spec/support/method_interceptor.rb +56 -0
- data/wsdl/soap.companyposition.xsd +235 -0
- data/wsdl/soap.customer.xsd +773 -0
- data/wsdl/soap.document.xsd +799 -0
- data/wsdl/soap.easybill.wsdl +1087 -0
- metadata +219 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: db9c7e5fbf2f0dce0f22b7d1f39e084c8f9de86b
|
4
|
+
data.tar.gz: 4da8f71c973ed2478394a282f1565854783df254
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 08e76ed6811ec5901323ec690aeafc8ea1bf6522c467dc15ab334c7e388be54950f83f3000d6ecba22ee00364e75af81bdadab804e94274a6db51740f9f6e3f5
|
7
|
+
data.tar.gz: d792eee33d7edd5bf772d6ee96f323602d9dbaf0524863200a51c5ddd600dedac4195729f1f701c0c6bc2717edc725c9bb4783c88e0e1590a5f724205630cc8c
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Gemfile.lock
|
2
|
+
*.gem
|
3
|
+
*.rbc
|
4
|
+
.bundle
|
5
|
+
.config
|
6
|
+
coverage
|
7
|
+
InstalledFiles
|
8
|
+
lib/bundler/man
|
9
|
+
pkg
|
10
|
+
rdoc
|
11
|
+
spec/reports
|
12
|
+
test/tmp
|
13
|
+
test/version_tmp
|
14
|
+
tmp
|
15
|
+
log
|
16
|
+
|
17
|
+
# YARD artifacts
|
18
|
+
.yardoc
|
19
|
+
_yardoc
|
20
|
+
doc/
|
21
|
+
|
22
|
+
apikey
|
23
|
+
|
24
|
+
*~
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.4
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Lars Henrik Mai
|
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,74 @@
|
|
1
|
+
# Easybill
|
2
|
+
|
3
|
+
A wrapper for the [Easybill](http://easybill.de) online invoice service.
|
4
|
+
Create invoices, manage customer information and their payments using
|
5
|
+
the SOAP API.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'easybill'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install easybill
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
client = Easybill::Client.new(key: <your apikey>)
|
24
|
+
# => <Easybill::Client @key=<asdf>>
|
25
|
+
# Note: the 'key' argument can be left out if you store the key in a
|
26
|
+
# file name 'apikey' in the project root.
|
27
|
+
customer = client.get_customer_by_customer_number(700)
|
28
|
+
# => <Easybill::Customer ... > # a [OpenStruct](http://www.ruby-doc.org/stdlib-2.0/libdoc/ostruct/rdoc/OpenStruct.html)
|
29
|
+
client.search_customers("Foo")
|
30
|
+
# => [<Easybill::Customer>...] # Array[OpenStruct]
|
31
|
+
customer.company_name
|
32
|
+
# => "ACME Inc."
|
33
|
+
|
34
|
+
See lib/easybill/client.rb for currently supported operations.
|
35
|
+
|
36
|
+
If you need access to unmapped SOAP operations you can always use the
|
37
|
+
underlying savonrb client:
|
38
|
+
|
39
|
+
client.client.class
|
40
|
+
# => Savon::Client
|
41
|
+
client.client.operations
|
42
|
+
# => [:get_customer, :get_customer_by_customer_number, :set_customer ... ]
|
43
|
+
client.client.call(<operation>, message: <hash>)
|
44
|
+
|
45
|
+
For exploration of the API and ease of development, there are some
|
46
|
+
fixtures in place in fixtures.yaml:
|
47
|
+
|
48
|
+
require 'easybill/fixture'
|
49
|
+
include Easybill::Fixture
|
50
|
+
# provides "customer", "document" and "position" helper methods
|
51
|
+
invoice = document(:basic)
|
52
|
+
# => {...} # corresponding hash from the yaml fixtures
|
53
|
+
|
54
|
+
# the customer_id in the fixtures is probably not valid anymore:
|
55
|
+
customer_id = client.get_customer_by_customer_number(10001).customer_id
|
56
|
+
invoice["customerID"] = customer_id
|
57
|
+
|
58
|
+
client.client.call(:create_document, message: invoice)
|
59
|
+
# (lots of debug output)
|
60
|
+
|
61
|
+
## Testing
|
62
|
+
|
63
|
+
If you need to update the vcr cassettes you can pass a valid api key to
|
64
|
+
your test setting the EASYBILL_APIKEY environment variable.
|
65
|
+
|
66
|
+
EASYBILL_APIKEY='120931...' bundle exec rspec spec
|
67
|
+
|
68
|
+
## Contributing
|
69
|
+
|
70
|
+
1. Fork it
|
71
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
73
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
74
|
+
5. Create new Pull Request
|
data/easybill.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'easybill/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'easybill'
|
8
|
+
gem.version = Easybill::VERSION
|
9
|
+
gem.authors = ['ad2games GmbH']
|
10
|
+
gem.email = ['developers@ad2games.com']
|
11
|
+
gem.description = 'Create invoices with Easybill'
|
12
|
+
gem.summary = 'A client library to the SOAP API of Easybill.de'
|
13
|
+
gem.homepage = ''
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
gem.add_runtime_dependency 'sekken'
|
21
|
+
gem.add_runtime_dependency 'php-serialize'
|
22
|
+
gem.add_runtime_dependency 'local_copy'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'pry'
|
25
|
+
gem.add_development_dependency 'rspec', '>= 3.0.0'
|
26
|
+
gem.add_development_dependency 'vcr'
|
27
|
+
gem.add_development_dependency 'webmock'
|
28
|
+
end
|
data/lib/easybill.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'sekken'
|
2
|
+
require 'sekken_patches/importer'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
require 'easybill/configuration'
|
6
|
+
require 'easybill/version'
|
7
|
+
require 'easybill/error'
|
8
|
+
require 'easybill/client'
|
9
|
+
require 'easybill/document'
|
10
|
+
require 'easybill/customer'
|
11
|
+
require 'easybill/payment'
|
12
|
+
|
13
|
+
require 'easybill/railtie' if defined?(Rails::Railtie)
|
14
|
+
|
15
|
+
module Easybill
|
16
|
+
class << self
|
17
|
+
# The Easybill configuration object. Must act like a hash and return sensible
|
18
|
+
# values for all Easybill configuration options. See Easybill::Configuration.
|
19
|
+
attr_writer :configuration
|
20
|
+
|
21
|
+
# Public: Call this method to modify defaults in your initializers.
|
22
|
+
#
|
23
|
+
# Examples:
|
24
|
+
#
|
25
|
+
# Easybill.configure do |config|
|
26
|
+
# config.api_key = '1234678...'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Yields Easybill configuration
|
30
|
+
def configure
|
31
|
+
yield(configuration)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public: The configuration object.
|
35
|
+
#
|
36
|
+
# Returns Easybill configuration
|
37
|
+
def configuration
|
38
|
+
@configuration ||= Configuration.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def wsdl
|
42
|
+
return @wsdl if @wsdl
|
43
|
+
wsdl_path = File.join(root_path, 'wsdl')
|
44
|
+
@wsdl = File.read(File.join(root_path, 'wsdl/soap.easybill.wsdl'))
|
45
|
+
.gsub('WSDL_PATH', wsdl_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def root_path
|
49
|
+
File.expand_path(File.join(__FILE__, '../..'))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'local_copy'
|
2
|
+
|
3
|
+
module Easybill
|
4
|
+
class Client
|
5
|
+
# default is 60 seconds which is too low
|
6
|
+
HTTP_RECEIVE_TIMEOUT = 300
|
7
|
+
|
8
|
+
CLIENT_METHODS = {
|
9
|
+
get_customer: { op_name: 'getCustomer', result_type: 'customer' },
|
10
|
+
get_customer_by_customer_number: { op_name: 'getCustomerByCustomerNumber', result_type: 'customer' },
|
11
|
+
set_customer: { op_name: 'setCustomer', result_type: 'customer' },
|
12
|
+
create_document: { op_name: 'createDocument', result_type: 'document' },
|
13
|
+
get_documents: { op_name: 'getDocuments', result_type: 'document' },
|
14
|
+
add_document_payment: { op_name: 'addDocumentPayment', result_type: nil },
|
15
|
+
get_document_payments: { op_name: 'getDocumentPayments', result_type: 'payment' },
|
16
|
+
find_documents_by_document_number: { op_name: 'findDocumentsByDocumentNumber', result_type: 'document' },
|
17
|
+
get_document_pdf: { op_name: 'getDocumentPDF', result_type: 'document' },
|
18
|
+
create_dunning: { op_name: 'createDunning', result_type: 'document' },
|
19
|
+
get_document_sent: { op_name: 'getDocumentSent', result_type: 'document' },
|
20
|
+
}
|
21
|
+
|
22
|
+
def initialize(options = {})
|
23
|
+
@options = Easybill.configuration.merge(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def client
|
27
|
+
sekken = Sekken.new(Easybill.wsdl)
|
28
|
+
sekken.http.receive_timeout = HTTP_RECEIVE_TIMEOUT
|
29
|
+
sekken
|
30
|
+
end
|
31
|
+
|
32
|
+
CLIENT_METHODS.each do |method_name, method_details|
|
33
|
+
define_method method_name do |arg|
|
34
|
+
response = save_call do
|
35
|
+
operation = client.operation('easybillService', 'easybillPort', method_details[:op_name])
|
36
|
+
operation.header = { 'UserAuthKey' => @options[:api_key] }
|
37
|
+
operation.body = { "#{method_details[:op_name][0].upcase}#{method_details[:op_name][1..-1]}Request".to_sym => arg }
|
38
|
+
|
39
|
+
operation.call
|
40
|
+
end
|
41
|
+
build_proper_response(method_name, method_details[:result_type], response)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def search_customers(term)
|
46
|
+
response = save_call do
|
47
|
+
operation = client.operation('easybillService', 'easybillPort', :searchCustomers)
|
48
|
+
operation.header = { 'UserAuthKey' => @options[:api_key] }
|
49
|
+
operation.body = { SearchCustomersRequest: term }
|
50
|
+
|
51
|
+
operation.call
|
52
|
+
end
|
53
|
+
return [] if response.body[:search_customers_response].nil?
|
54
|
+
|
55
|
+
records = response.body[:search_customers_response][:search_customer]
|
56
|
+
records = [records] if records.is_a?(Hash)
|
57
|
+
records.map { |hsh| Easybill::Customer.new(hsh) }
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def save_call
|
63
|
+
response = yield
|
64
|
+
|
65
|
+
begin
|
66
|
+
if response.body.empty? || (response.body.is_a?(Hash) && response.body.key?(:fault))
|
67
|
+
fail Easybill::Error, response.body[:fault][:faultstring]
|
68
|
+
end
|
69
|
+
rescue NoMethodError
|
70
|
+
raise Easybill::Error, 'Could not parse response: ' \
|
71
|
+
"#{response.respond_to?(:raw) ? response.raw : ''}"
|
72
|
+
end
|
73
|
+
|
74
|
+
response
|
75
|
+
rescue HTTPClient::ConnectTimeoutError
|
76
|
+
raise Easybill::Error, 'Timeout, please switch to different billing service.'
|
77
|
+
end
|
78
|
+
|
79
|
+
def response_array(result, result_type, klass)
|
80
|
+
return [] if result.blank?
|
81
|
+
|
82
|
+
if result.is_a?(Hash) && result[result_type]
|
83
|
+
if result[result_type].is_a?(Hash)
|
84
|
+
[klass.new(result[result_type])]
|
85
|
+
else
|
86
|
+
result[result_type].map { |element| klass.new(element) }
|
87
|
+
end
|
88
|
+
else
|
89
|
+
[klass.new(result)]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def build_proper_response(method_name, result_type, response)
|
94
|
+
result = response.body[:"#{method_name}_response"]
|
95
|
+
|
96
|
+
if method_name == :get_documents
|
97
|
+
response_array(result, result_type.to_sym, Easybill::Document)
|
98
|
+
elsif method_name == :get_document_payments
|
99
|
+
response_array(result, result_type.to_sym, Easybill::Payment)
|
100
|
+
elsif method_name == :find_documents_by_document_number
|
101
|
+
response_array(result, result_type.to_sym, Easybill::Document)
|
102
|
+
else
|
103
|
+
case result_type
|
104
|
+
when 'customer'
|
105
|
+
Easybill::Customer.new(result)
|
106
|
+
when 'document'
|
107
|
+
Easybill::Document.new(result)
|
108
|
+
when 'payment'
|
109
|
+
Easybill::Payment.new(result)
|
110
|
+
else
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Easybill
|
2
|
+
class Configuration
|
3
|
+
OPTIONS = [:api_key, :logger].freeze
|
4
|
+
|
5
|
+
# The API key for your user taken from the easybill website.
|
6
|
+
attr_accessor :api_key
|
7
|
+
|
8
|
+
# The logger used by Easybill
|
9
|
+
attr_accessor :logger
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
OPTIONS.each do |option|
|
13
|
+
env_key = "EASYBILL_#{option.upcase}"
|
14
|
+
send("#{option}=", ENV[env_key]) if ENV[env_key]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: Returns a hash of all configurable options.
|
19
|
+
def to_hash
|
20
|
+
Hash[OPTIONS.map do |option|
|
21
|
+
[option, send(option)]
|
22
|
+
end]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Allows config options to be read like a hash.
|
26
|
+
#
|
27
|
+
# key - Name of the option to retrieve
|
28
|
+
#
|
29
|
+
# Returns the value of the requested attribute
|
30
|
+
def[](key)
|
31
|
+
send(key) if OPTIONS.include?(key)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Public
|
35
|
+
#
|
36
|
+
# hash - A set of configuration options that will take precedence over the defaults
|
37
|
+
#
|
38
|
+
# Returns a hash of all configurable options merged with +hash+
|
39
|
+
def merge(hash)
|
40
|
+
to_hash.merge(hash)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'php_serialize'
|
2
|
+
|
3
|
+
module Easybill
|
4
|
+
class Document < OpenStruct
|
5
|
+
def service_period
|
6
|
+
plain_service_date = PHP.unserialize(service_date)
|
7
|
+
|
8
|
+
if plain_service_date.nil?
|
9
|
+
''
|
10
|
+
else
|
11
|
+
if plain_service_date.key?('serviceDateFrom')
|
12
|
+
"#{parse_date(plain_service_date['serviceDateFrom'])} - #{parse_date(plain_service_date['serviceDateThru'])}"
|
13
|
+
else
|
14
|
+
plain_service_date['serviceDateString']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def service_period_end
|
20
|
+
service_period_dates = service_period.split(' - ')
|
21
|
+
service_period_dates.length == 2 ? Date.parse(service_period_dates[1]) : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def cancellation?
|
25
|
+
document_type == 'STORNO'
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_date(date)
|
31
|
+
Date.parse(date).strftime('%d.%m.%Y')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|