sendregning 0.1.1 → 0.2.2

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.
@@ -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