easybill 0.2.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|