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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +74 -0
  8. data/easybill.gemspec +28 -0
  9. data/lib/easybill.rb +52 -0
  10. data/lib/easybill/client.rb +116 -0
  11. data/lib/easybill/configuration.rb +43 -0
  12. data/lib/easybill/customer.rb +4 -0
  13. data/lib/easybill/document.rb +34 -0
  14. data/lib/easybill/error.rb +3 -0
  15. data/lib/easybill/payment.rb +4 -0
  16. data/lib/easybill/railtie.rb +12 -0
  17. data/lib/easybill/version.rb +3 -0
  18. data/lib/sekken_patches/importer.rb +8 -0
  19. data/spec/easybill/client_spec.rb +189 -0
  20. data/spec/easybill/configuration_spec.rb +30 -0
  21. data/spec/easybill/document_spec.rb +41 -0
  22. data/spec/easybill_spec.rb +14 -0
  23. data/spec/fixtures/fixtures.yml +98 -0
  24. data/spec/fixtures/vcr_cassettes/add_document_payment_-_adds_a_payment_.yml +1677 -0
  25. data/spec/fixtures/vcr_cassettes/create_document_-_raises_Easybill_Error_if_unsuccessful_.yml +1669 -0
  26. data/spec/fixtures/vcr_cassettes/create_document_-_returns_an_Easybill_Document_on_success_.yml +1677 -0
  27. data/spec/fixtures/vcr_cassettes/create_dunning_-_creates_a_warning_.yml +1742 -0
  28. data/spec/fixtures/vcr_cassettes/find_documents_by_document_number_-_finds_documents_.yml +1671 -0
  29. data/spec/fixtures/vcr_cassettes/get_customer_-_raises_Easybill_Error_if_unsuccessful_.yml +1678 -0
  30. data/spec/fixtures/vcr_cassettes/get_customer_-_returns_an_Easybill_Customer_on_success_.yml +1727 -0
  31. data/spec/fixtures/vcr_cassettes/get_customer_by_customer_number_-_raises_Easybill_Error_if_unsuccessful_.yml +1678 -0
  32. data/spec/fixtures/vcr_cassettes/get_customer_by_customer_number_-_returns_an_Easybill_Customer_on_success_.yml +1671 -0
  33. data/spec/fixtures/vcr_cassettes/get_document_payments_-_returns_an_array_of_Easybill_Payment_on_success_.yml +1727 -0
  34. data/spec/fixtures/vcr_cassettes/get_document_pdf_-_returns_a_document_PDF_.yml +1727 -0
  35. data/spec/fixtures/vcr_cassettes/get_document_sent_-_receives_sent_date_.yml +1727 -0
  36. data/spec/fixtures/vcr_cassettes/get_documents_-_returns_an_Easybill_Document_array_on_success_.yml +1729 -0
  37. data/spec/fixtures/vcr_cassettes/get_documents_-_returns_an_empty_array_if_there_are_no_documents_.yml +1675 -0
  38. data/spec/fixtures/vcr_cassettes/local_copy_5c31767ec9fc284da7e4858f3580fb50.yml +1177 -0
  39. data/spec/fixtures/vcr_cassettes/search_customers_-_returns_an_array_of_matching_Easybill_Customers_.yml +2754 -0
  40. data/spec/fixtures/vcr_cassettes/search_customers_-_returns_an_empty_array_when_no_customers_are_found_.yml +1671 -0
  41. data/spec/fixtures/vcr_cassettes/set_customer_-_raises_Easybill_Error_if_unsuccessful_.yml +1700 -0
  42. data/spec/fixtures/vcr_cassettes/set_customer_-_returns_an_Easybill_Customer_on_success_.yml +1687 -0
  43. data/spec/spec_helper.rb +54 -0
  44. data/spec/support/fixture.rb +16 -0
  45. data/spec/support/matchers.rb +6 -0
  46. data/spec/support/method_interceptor.rb +56 -0
  47. data/wsdl/soap.companyposition.xsd +235 -0
  48. data/wsdl/soap.customer.xsd +773 -0
  49. data/wsdl/soap.document.xsd +799 -0
  50. data/wsdl/soap.easybill.wsdl +1087 -0
  51. metadata +219 -0
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1 @@
1
+ 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -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.
@@ -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
@@ -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
@@ -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,4 @@
1
+ module Easybill
2
+ class Customer < OpenStruct
3
+ end
4
+ 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
@@ -0,0 +1,3 @@
1
+ module Easybill
2
+ class Error < Sekken::Error; end
3
+ end
@@ -0,0 +1,4 @@
1
+ module Easybill
2
+ class Payment < OpenStruct
3
+ end
4
+ end
@@ -0,0 +1,12 @@
1
+ require 'easybill'
2
+ require 'rails'
3
+
4
+ module Easybill
5
+ class Railtie < Rails::Railtie
6
+ config.before_initialize do
7
+ Easybill.configure do |config|
8
+ config.logger = Rails.logger
9
+ end
10
+ end
11
+ end
12
+ end