sendregning 0.1.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8b771593610a6f4f488681d49b9779c8770dc347329f445ef53d3a376f8dd831
4
+ data.tar.gz: 14d3fe0461d33d1e0269b6c87deeee857bd6fe37a03165e3de5c971b6fa1b311
5
+ SHA512:
6
+ metadata.gz: bc21bc5404eec80df91cea1657fff4d3084f08ff6e34c38ab7adb72fbf48cdc53a1b3f1f0d634e1014bce7339202f48d0c8d66f709573f160ccb0cfcc5d259e6
7
+ data.tar.gz: 65159551c9c6a8d8c13e6ebf86e249f99a46377d0c39f9173e6d7a80ab85e74c989967bad29536acca096c8d42548acb8a4c79f84bb141f1221ac741415b70f5
data/.gitignore CHANGED
@@ -1,21 +1 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC
1
+ *.gem
@@ -0,0 +1,23 @@
1
+ require: rubocop-rspec
2
+
3
+ inherit_from:
4
+ - .rubocop_todo.yml
5
+
6
+ AllCops:
7
+ NewCops: enable
8
+
9
+ Style/AsciiComments:
10
+ Enabled: false
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Style/StringLiterals:
16
+ EnforcedStyle: double_quotes
17
+
18
+ Layout/LineLength:
19
+ AutoCorrect: true
20
+ Max: 80
21
+
22
+ Layout/MultilineOperationIndentation:
23
+ EnforcedStyle: aligned
File without changes
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem "pry"
9
+ gem "pry-stack_explorer"
10
+ gem "rubocop"
11
+ gem "rubocop-rails"
12
+ gem "rubocop-rspec"
13
+ end
@@ -0,0 +1,93 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sendregning (0.2.2)
5
+ builder (~> 3.1)
6
+ httmultiparty (~> 0.3)
7
+ httparty (~> 0.13)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (6.0.3.1)
13
+ concurrent-ruby (~> 1.0, >= 1.0.2)
14
+ i18n (>= 0.7, < 2)
15
+ minitest (~> 5.1)
16
+ tzinfo (~> 1.1)
17
+ zeitwerk (~> 2.2, >= 2.2.2)
18
+ ast (2.4.0)
19
+ binding_of_caller (0.8.0)
20
+ debug_inspector (>= 0.0.1)
21
+ builder (3.2.4)
22
+ coderay (1.1.3)
23
+ concurrent-ruby (1.1.6)
24
+ debug_inspector (0.0.3)
25
+ httmultiparty (0.3.16)
26
+ httparty (>= 0.7.3)
27
+ mimemagic
28
+ multipart-post
29
+ httparty (0.18.0)
30
+ mime-types (~> 3.0)
31
+ multi_xml (>= 0.5.2)
32
+ i18n (1.8.2)
33
+ concurrent-ruby (~> 1.0)
34
+ method_source (1.0.0)
35
+ mime-types (3.3.1)
36
+ mime-types-data (~> 3.2015)
37
+ mime-types-data (3.2020.0512)
38
+ mimemagic (0.3.5)
39
+ minitest (5.14.1)
40
+ multi_xml (0.6.0)
41
+ multipart-post (2.1.1)
42
+ parallel (1.19.1)
43
+ parser (2.7.1.3)
44
+ ast (~> 2.4.0)
45
+ pry (0.13.1)
46
+ coderay (~> 1.1)
47
+ method_source (~> 1.0)
48
+ pry-stack_explorer (0.5.1)
49
+ binding_of_caller (~> 0.7)
50
+ pry (~> 0.13)
51
+ rack (2.2.2)
52
+ rainbow (3.0.0)
53
+ rake (13.0.1)
54
+ regexp_parser (1.7.0)
55
+ rexml (3.2.4)
56
+ rubocop (0.85.0)
57
+ parallel (~> 1.10)
58
+ parser (>= 2.7.0.1)
59
+ rainbow (>= 2.2.2, < 4.0)
60
+ regexp_parser (>= 1.7)
61
+ rexml
62
+ rubocop-ast (>= 0.0.3)
63
+ ruby-progressbar (~> 1.7)
64
+ unicode-display_width (>= 1.4.0, < 2.0)
65
+ rubocop-ast (0.0.3)
66
+ parser (>= 2.7.0.1)
67
+ rubocop-rails (2.5.2)
68
+ activesupport
69
+ rack (>= 1.1)
70
+ rubocop (>= 0.72.0)
71
+ rubocop-rspec (1.39.0)
72
+ rubocop (>= 0.68.1)
73
+ ruby-progressbar (1.10.1)
74
+ thread_safe (0.3.6)
75
+ tzinfo (1.2.7)
76
+ thread_safe (~> 0.1)
77
+ unicode-display_width (1.7.0)
78
+ zeitwerk (2.3.0)
79
+
80
+ PLATFORMS
81
+ ruby
82
+
83
+ DEPENDENCIES
84
+ pry
85
+ pry-stack_explorer
86
+ rake
87
+ rubocop
88
+ rubocop-rails
89
+ rubocop-rspec
90
+ sendregning!
91
+
92
+ BUNDLED WITH
93
+ 2.1.4
@@ -0,0 +1,49 @@
1
+ # Sendregning
2
+
3
+ [![Code Climate](https://codeclimate.com/github/elektronaut/sendregning.png)](https://codeclimate.com/github/elektronaut/sendregning)
4
+
5
+ Ruby client for the SendRegning Web Service.
6
+
7
+ ## Getting started
8
+
9
+ Install with RubyGems:
10
+
11
+ gem install sendregning
12
+
13
+ Now start sending invoices:
14
+
15
+ # Create a new client
16
+ client = Sendregning::Client.new('my@email.com', 'myawesomepassword')
17
+
18
+ # Start a new email invoice
19
+ invoice = client.new_invoice(
20
+ name: 'My Client',
21
+ zip: '0123',
22
+ city: 'Oslo',
23
+ shipment: :email,
24
+ emailaddresses: 'my@email.com'
25
+ )
26
+
27
+ # Add an item
28
+ invoice.add_line qty: 1, desc: 'Bananaphone', unitPrice: '500,00'
29
+
30
+ # Send it away!
31
+ invoice.send!
32
+
33
+ # Get the invoice number for future reference
34
+ id = invoice.invoiceNo
35
+
36
+ Let's check how we're doing!
37
+
38
+ invoice = client.find_invoice(id)
39
+ invoice.paid? # => true
40
+
41
+ Pass `test: true` to the constructor to use the test API
42
+
43
+ # Create a new client
44
+ client = Sendregning::Client.new('my@email.com', 'myawesomepassword', test: true)
45
+
46
+
47
+ ## Copyright
48
+
49
+ Copyright (c) 2010 Inge Jørgensen. See LICENSE for details.
@@ -1,13 +1,20 @@
1
- require 'rubygems'
2
- require 'builder'
3
- require 'net/http/post/multipart'
4
- require 'httparty'
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "builder"
5
+ require "httparty"
6
+ require "httmultiparty"
5
7
 
6
8
  dir = Pathname(__FILE__).dirname.expand_path
7
9
 
8
- require dir + 'sendregning/client'
9
- require dir + 'sendregning/invoice'
10
- require dir + 'sendregning/line'
10
+ require dir + "sendregning/xml_serializer"
11
+ require dir + "sendregning/client"
12
+ require dir + "sendregning/invoice"
13
+ require dir + "sendregning/invoice_parser"
14
+ require dir + "sendregning/invoice_serializer"
15
+ require dir + "sendregning/line"
16
+ require dir + "sendregning/line_serializer"
17
+ require dir + "sendregning/version"
11
18
 
12
19
  module Sendregning
13
- end
20
+ end
@@ -1,118 +1,87 @@
1
- require 'yaml'
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
2
4
 
3
5
  module Sendregning
6
+ # Client for the SendRegning Web Services.
7
+ #
8
+ # Usage example:
9
+ #
10
+ # client = SendRegning::Client.new(my_username, my_password)
11
+
12
+ class Client
13
+ include HTTMultiParty
4
14
 
5
- # Client for the SendRegning Web Services.
6
- #
7
- # Usage example:
8
- #
9
- # client = SendRegning::Client.new(my_username, my_password)
10
-
11
- class Client
12
- include HTTParty
15
+ base_uri "https://www.sendregning.no"
16
+ format :xml
13
17
 
14
- base_uri 'https://www.sendregning.no'
15
- format :xml
18
+ def initialize(username, password, options = {})
19
+ @auth = { username: username, password: password }
20
+ @test = true if options[:test]
21
+ end
16
22
 
17
- def initialize(username, password, options={})
18
- @auth = {:username => username, :password => password}
19
- @test = true if options[:test]
20
- end
21
-
22
- public
23
+ # Performs a GET request
24
+ def get(query = {})
25
+ self.class.get("/ws/butler.do", query_options(query))
26
+ end
23
27
 
24
- # Performs a GET request
25
- def get(query={})
26
- self.class.get('/ws/butler.do', query_options(query))
27
- end
28
+ # Performs a POST request
29
+ def post(query = nil)
30
+ self.class.post("/ws/butler.do", query_options(query))
31
+ end
28
32
 
29
- # Performs a POST request
30
- def post(query=nil)
31
- self.class.post('/ws/butler.do', query_options(query))
32
- end
33
-
34
- # Performs a POST request with an XML payload
35
- def post_xml(xml, body={})
36
- # Prepare the request
37
- url = URI.parse("#{self.class.base_uri}/ws/butler.do")
38
- body[:xml] = UploadIO.new(StringIO.new(xml), 'text/xml', 'request.xml')
39
- body[:test] = true if @test
40
- request = Net::HTTP::Post::Multipart.new(url.path, body)
41
- request.basic_auth @auth[:username], @auth[:password]
42
-
43
- # Perform request
44
- http = Net::HTTP.new(url.host, url.port)
45
- http.use_ssl = true
46
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
47
- res = http.start{|http| http.request(request)}
33
+ def post_xml(xml, query = {})
34
+ query[:xml] = xml_file(xml)
35
+ self.class.post("/ws/butler.do",
36
+ query_options(query).merge(detect_mime_type: true))
37
+ end
48
38
 
49
- # Parse the response
50
- response = Response.new(res, self.class.parser.call(res.body, :xml))
51
- end
52
-
53
- # Returns a list of recipients
54
- def recipients
55
- response = get(:action => 'select', :type => 'recipient')
56
- response['recipients']['recipient']
57
- end
58
-
59
- # Instances a new invoice
60
- def new_invoice(attributes={})
61
- Sendregning::Invoice.new(attributes.merge({:client => self}))
62
- end
63
-
64
- # Sends an invoice
65
- def send_invoice(invoice)
66
- response = post_xml(invoice.to_xml, {:action => 'send', :type => 'invoice'})
67
- parse_invoice(response['invoices']['invoice'], invoice)
68
- end
69
-
70
- # Finds an invoice by invoice number
71
- def find_invoice(invoice_id)
72
- builder = Builder::XmlMarkup.new(:indent=>2)
73
- builder.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
74
- request_xml = builder.select do |select|
75
- select.invoiceNumbers do |numbers|
76
- numbers.invoiceNumber invoice_id
77
- end
78
- end
79
- response = post_xml(request_xml, {:action => 'select', :type => 'invoice'})
80
- parse_invoice(response['invoices']['invoice']) rescue nil
81
- end
82
-
39
+ # Returns a list of recipients
40
+ def recipients
41
+ response = get(action: "select", type: "recipient")
42
+ response["recipients"]["recipient"]
43
+ end
83
44
 
84
- protected
45
+ # Instances a new invoice
46
+ def new_invoice(attributes = {})
47
+ Sendregning::Invoice.new(attributes.merge({ client: self }))
48
+ end
85
49
 
86
- def flatten_xml_hash(hash)
87
- new_hash = {}
88
- hash.each do |key, value|
89
- new_hash[key] = "#{value}"
90
- end
91
- new_hash
92
- end
50
+ # Sends an invoice
51
+ def send_invoice(invoice)
52
+ response = post_xml(invoice.to_xml,
53
+ { action: "send", type: "invoice" })
54
+ InvoiceParser.parse(response, invoice)
55
+ end
93
56
 
94
- def parse_invoice(response, invoice=Sendregning::Invoice.new)
95
- attributes = response.dup
96
- if attributes['optional']
97
- attributes = attributes.merge(attributes['optional'])
98
- attributes.delete('optional')
99
- end
100
- if attributes['shipment']
101
- attributes = attributes.merge(attributes['shipment'])
102
- attributes.delete('shipment')
103
- end
104
- lines = attributes['lines']['line']; attributes.delete('lines')
57
+ # Finds an invoice by invoice number
58
+ def find_invoice(invoice_id)
59
+ builder = Builder::XmlMarkup.new(indent: 2)
60
+ builder.instruct! :xml, version: "1.0", encoding: "UTF-8"
61
+ request_xml = builder.select do |select|
62
+ select.invoiceNumbers do |numbers|
63
+ numbers.invoiceNumber invoice_id
64
+ end
65
+ end
66
+ response = post_xml(request_xml,
67
+ { action: "select", type: "invoice" })
68
+ begin
69
+ InvoiceParser.parse(response)
70
+ rescue StandardError
71
+ nil
72
+ end
73
+ end
105
74
 
106
- invoice.update(flatten_xml_hash(attributes))
107
- invoice.lines = lines.map{|l| Sendregning::Line.new(flatten_xml_hash(l))}
108
- invoice
109
- end
75
+ protected
110
76
 
111
- # Prepares options for a request
112
- def query_options(query={})
113
- query[:test] = 'true' if @test
114
- {:basic_auth => @auth, :query => query}
115
- end
77
+ # Prepares options for a request
78
+ def query_options(query = {})
79
+ query[:test] = "true" if @test
80
+ { basic_auth: @auth, query: query }
81
+ end
116
82
 
117
- end
118
- end
83
+ def xml_file(xml)
84
+ UploadIO.new(StringIO.new(xml), "text/xml", "request.xml")
85
+ end
86
+ end
87
+ end
@@ -1,147 +1,98 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sendregning
4
+ class Invoice
5
+ OPTIONAL_ATTRIBUTES = [
6
+ # Request
7
+ :invoiceType, :creditedId, :orderNo, :invoiceDate, :orderNo,
8
+ :invoiceDate, :dueDate, :orderDate, :recipientNo, :address1, :address2,
9
+ :country, :email, :ourRef, :yourRef, :comment, :invoiceText, :printDunningInfo,
10
+ :itemTaxInclude,
11
+
12
+ # Response
13
+ :tax, :dueDate, :dunningFee, :invoiceNo, :total, :accountNo, :orgNrSuffix, :kid, :orgNo, :interestRate, :state
14
+ ].freeze
15
+
16
+ SHIPMENT_ATTRIBUTES = %i[
17
+ shipment emailaddresses copyaddresses
18
+ ].freeze
19
+
20
+ SHIPMENT_MODES = {
21
+ paper: "PAPER",
22
+ email: "EMAIL",
23
+ paper_and_email: "PAPER_AND_EMAIL"
24
+ }.freeze
25
+
26
+ attr_accessor :client
27
+ attr_accessor :name, :zip, :city
28
+ attr_accessor :optional, :shipment
29
+ attr_accessor :lines
30
+
31
+ def initialize(attributes = {})
32
+ update(attributes)
33
+ @lines = []
34
+ end
35
+
36
+ def update(attributes = {})
37
+ @client = attributes[:client] if attributes[:client]
38
+ @name = attributes[:name] if attributes[:name]
39
+ @zip = attributes[:zip] if attributes[:zip]
40
+ @city = attributes[:city] if attributes[:city]
41
+ self.optional = attributes
42
+ self.shipment = attributes
43
+ end
44
+
45
+ def add_line(line)
46
+ line = Sendregning::Line.new(line) unless line.is_a?(Sendregning::Line)
47
+ @lines << line
48
+ line
49
+ end
50
+
51
+ # Sends an invoice
52
+ def send!
53
+ client.send_invoice(self)
54
+ end
55
+
56
+ def paid?
57
+ state == "paid"
58
+ end
59
+
60
+ def shipment_mode
61
+ mode = (@shipment[:shipment] || :paper).to_sym
62
+ raise "Invalid shipment mode!" unless SHIPMENT_MODES.keys.include?(mode)
63
+
64
+ SHIPMENT_MODES[mode]
65
+ end
66
+
67
+ # Renders invoice to XML
68
+ def to_xml(options = {})
69
+ InvoiceSerializer.build(self, options)
70
+ end
71
+
72
+ protected
73
+
74
+ def filter_attributes(attributes, filter)
75
+ attributes.dup.delete_if { |k, _v| !filter.include?(k.to_sym) }
76
+ end
77
+
78
+ def optional=(attributes)
79
+ @optional ||= {}
80
+ @optional.merge!(filter_attributes(attributes, OPTIONAL_ATTRIBUTES))
81
+ end
82
+
83
+ def shipment=(attributes)
84
+ @shipment ||= {}
85
+ @shipment.merge!(filter_attributes(attributes, SHIPMENT_ATTRIBUTES))
86
+ end
2
87
 
3
- class Invoice
4
-
5
- OPTIONAL_ATTRIBUTES = [
6
- # Request
7
- :invoiceType, :creditedId, :orderNo, :invoiceDate, :orderNo,
8
- :invoiceDate, :dueDate, :orderDate, :recipientNo, :address1, :address2,
9
- :country, :email, :ourRef, :yourRef, :comment, :invoiceText, :printDunningInfo,
10
- :itemTaxInclude,
11
-
12
- # Response
13
- :tax, :dueDate, :dunningFee, :invoiceNo, :total, :accountNo, :orgNrSuffix, :kid, :orgNo, :interestRate, :state
14
- ]
15
-
16
- SHIPMENT_ATTRIBUTES = [
17
- :shipment, :emailaddresses, :copyaddresses
18
- ]
19
-
20
- SHIPMENT_MODES = {
21
- :paper => 'PAPER',
22
- :email => 'EMAIL',
23
- :paper_and_email => 'PAPER_AND_EMAIL'
24
- }
25
-
26
- attr_accessor :client
27
- attr_accessor :name, :zip, :city
28
- attr_accessor :optional, :shipment
29
- attr_accessor :lines
30
-
31
- def initialize(attributes={})
32
- self.update(attributes)
33
- @lines = []
34
- end
35
-
36
- def update(attributes={})
37
- @client = attributes[:client] if attributes[:client]
38
- @name = attributes[:name] if attributes[:name]
39
- @zip = attributes[:zip] if attributes[:zip]
40
- @city = attributes[:city] if attributes[:city]
41
- self.optional = attributes
42
- self.shipment = attributes
43
- end
44
-
45
- def add_line(line)
46
- line = Sendregning::Line.new(line) unless line.kind_of?(Sendregning::Line)
47
- @lines << line
48
- line
49
- end
50
-
51
- # Sends an invoice
52
- def send!
53
- self.client.send_invoice(self)
54
- end
55
-
56
- def paid?
57
- state == 'paid'
58
- end
59
-
60
- # Renders invoice to XML
61
- def to_xml(options={})
62
- if options[:builder]
63
- xml = options[:builder]
64
- else
65
- xml = Builder::XmlMarkup.new(:indent=>2)
66
- xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
67
- end
68
- xml.invoices do |invoices|
69
- invoices.invoice do |invoice|
70
- invoice.name @name
71
- invoice.zip @zip
72
- invoice.city @city
73
-
74
- # Lines
75
- if @lines.length > 0
76
- invoice.lines do |line_builder|
77
- @lines.each{|l| l.to_xml(:builder => line_builder)}
78
- end
79
- end
80
-
81
- # Optional attributes
82
- if @optional.length > 0
83
- invoice.optional do |optional|
84
- @optional.each do |key, value|
85
- key = key.to_sym
86
- if value.kind_of?(Date) || value.kind_of(Time)
87
- value = value.strftime("%d.%m.%y")
88
- end
89
- optional.tag! key, value
90
- end
91
- end
92
- end
93
-
94
- # Shipment attributes
95
- if @shipment.length > 0
96
- invoice.shipment do |shipment|
97
- shipment_mode = (@shipment[:shipment] || :paper).to_sym
98
- raise 'Invalid shipment mode!' unless SHIPMENT_MODES.keys.include?(shipment_mode)
99
- shipment_mode = SHIPMENT_MODES[shipment_mode]
100
-
101
- shipment.text! shipment_mode
102
- @shipment.each do |key, values|
103
- key = key.to_sym
104
- unless key == :shipment
105
- values = [values] unless values.kind_of?(Enumerable)
106
- shipment.tag! key do |emails|
107
- values.each{|v| emails.email v}
108
- end
109
- end
110
- end
111
- end
112
- end
113
- end
114
- end
115
- end
116
-
117
- protected
118
-
119
- def optional=(attributes)
120
- @optional ||= {}
121
- attributes.each do |key, value|
122
- @optional[key.to_sym] = value if OPTIONAL_ATTRIBUTES.include?(key.to_sym)
123
- end
124
- @optional
125
- end
126
-
127
- def shipment=(attributes)
128
- @shipment ||= {}
129
- attributes.each do |key, value|
130
- @shipment[key.to_sym] = value if SHIPMENT_ATTRIBUTES.include?(key.to_sym)
131
- end
132
- @shipment
133
- end
134
-
135
- def method_missing(method, *args)
136
- if OPTIONAL_ATTRIBUTES.include?(method)
137
- @optional[method]
138
- elsif SHIPPING_ATTRIBUTES.include?(method)
139
- @shipping[method]
140
- else
141
- super
142
- end
143
- end
144
-
145
- end
146
-
147
- end
88
+ def method_missing(method, *args)
89
+ if OPTIONAL_ATTRIBUTES.include?(method)
90
+ @optional[method]
91
+ elsif SHIPMENT_ATTRIBUTES.include?(method)
92
+ @shipment[method]
93
+ else
94
+ super
95
+ end
96
+ end
97
+ end
98
+ end