easybill 0.2.17

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