cetustek 0.2.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 722407201f6462aed26f91e61295dc87fcfdc970db2b55adf8aa73f4a4840f1a
4
+ data.tar.gz: ab37a4849d55a493854202d343c2cf082fb118813eea9d491e09970209317066
5
+ SHA512:
6
+ metadata.gz: db34fb0378f5fd87b4c42cec44306b69afaa09ceb9ade280993f1618ed487e0c92ea6e61f3645747277e4a1e548b736f7806ced8e659c4a84a0d1a98da3efbe8
7
+ data.tar.gz: 5164f077cb9fff85c4879ea31e0f51710d03b6b621545c2b757deac19f3833e7f50802470577812ca95cfccb8742b271dc71c418625a8e9b5947f08e8c22bf2a
data/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2025-01-22
9
+
10
+ ### Added
11
+ - Environment-specific configuration support (sandbox/production)
12
+ - Service-oriented architecture implementation
13
+ - XML builder service for invoice generation
14
+ - SOAP service wrapper
15
+ - Response handler with improved error handling
16
+ - Data transfer objects for invoice data
17
+
18
+ ### Changed
19
+ - Refactored CreateInvoice class to use service pattern
20
+ - Unified configuration system
21
+ - Improved code organization and maintainability
22
+ - Enhanced error handling and logging
23
+
24
+ ### Removed
25
+ - Deprecated Config class in favor of Configuration
26
+
27
+ ## [0.1.0] - 2025-01-22
28
+
29
+ ### Added
30
+ - Initial release
31
+ - Electronic invoice cancellation functionality
32
+ - Integrated SOAP Web Services
33
+ - Added `ox` gem for XML processing
34
+ - Added `savon` gem for SOAP service integration
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Cetustek
2
+
3
+ Cetustek is a Ruby gem designed for handling electronic invoice operations, including invoice cancellation. It communicates with the e-invoice system through SOAP Web Services.
4
+
5
+ ## Features
6
+
7
+ - Electronic invoice cancellation
8
+ - XML format generation
9
+ - SOAP Web Services integration
10
+ - Environment-specific configuration (sandbox/production)
11
+ - Service-oriented architecture
12
+ - Robust error handling
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'cetustek'
20
+ ```
21
+
22
+ Then execute:
23
+
24
+ ```bash
25
+ bundle install
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ Configure Cetustek in your application:
31
+
32
+ ```ruby
33
+ # config/initializers/cetustek.rb
34
+ Cetustek.configure do |config|
35
+ # Set environment (:production or :sandbox)
36
+ config.environment = Rails.env.production? ? :production : :sandbox
37
+
38
+ # Set authentication credentials
39
+ config.site_id = ENV['CETUSTEK_SITE_ID']
40
+ config.username = ENV['CETUSTEK_USERNAME']
41
+ config.password = ENV['CETUSTEK_PASSWORD']
42
+ end
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ### Cancel an Invoice
48
+
49
+ ```ruby
50
+ invoice = YourInvoiceModel.find(invoice_id)
51
+ invoice_data = Cetustek::Models::InvoiceData.new(
52
+ order_id: invoice.order_id,
53
+ order_date: Time.zone.today,
54
+ buyer_identifier: invoice.receipt,
55
+ buyer_name: invoice.name,
56
+ buyer_email: invoice.email,
57
+ items: invoice.items.map { |item|
58
+ Cetustek::Models::InvoiceItem.new(
59
+ code: item.sku,
60
+ name: item.name,
61
+ quantity: item.quantity,
62
+ unit_price: item.price
63
+ )
64
+ }
65
+ )
66
+
67
+ result = Cetustek::CreateInvoice.new(invoice_data).execute
68
+ ```
69
+
70
+ ## Development
71
+
72
+ 1. Clone this repository
73
+ 2. Run `bin/setup` to install dependencies
74
+ 3. Run `bin/console` for an interactive prompt to experiment
75
+
76
+ ## Requirements
77
+
78
+ - Ruby >= 2.7.0
79
+ - `ox` gem for XML processing
80
+ - `savon` gem for SOAP services
81
+
82
+ ## Contributing
83
+
84
+ 1. Fork this project
85
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
86
+ 3. Commit your changes (`git commit -am 'Add some amazing feature'`)
87
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
88
+ 5. Open a Pull Request
89
+
90
+ ## Versioning
91
+
92
+ This project follows [Semantic Versioning](https://semver.org/). See the [CHANGELOG.md](CHANGELOG.md) file for version details.
93
+
94
+ ## License
95
+
96
+ This gem is available as open source under the terms of the MIT License.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,54 @@
1
+ module Cetustek
2
+ class CancelInvoice
3
+ def initialize(invoice)
4
+ @invoice = invoice
5
+ end
6
+
7
+ def execute
8
+ generate_xml
9
+ perform
10
+ analize_response
11
+ end
12
+
13
+ private
14
+
15
+ def perform
16
+ url = Cetustek.config.url
17
+ client = Savon.client(
18
+ wsdl: url,
19
+ open_timeout: 300,
20
+ read_timeout: 300
21
+ )
22
+
23
+ @response = client.call(:cancel_invoice, message:
24
+ { invoicexml: @xml,
25
+ source: Cetustek.config.site_id + Cetustek.config.password,
26
+ rentid: Cetustek.config.username })
27
+ end
28
+
29
+ def generate_xml
30
+ doc = Ox::Document.new
31
+
32
+ instruct = Ox::Instruct.new(:xml)
33
+ instruct[:version] = '1.0'
34
+ instruct[:encoding] = 'UTF-8'
35
+ instruct[:standalone] = 'yes'
36
+ doc << instruct
37
+
38
+ invoice = Ox::Element.new('Invoice')
39
+ invoice[:XSDVersion] = '2.8'
40
+ doc << invoice
41
+
42
+ invoice << Ox::Raw.new("<InvoiceNumber>#{@invoice.number}</InvoiceNumber>")
43
+ invoice << Ox::Raw.new("<InvoiceYear>#{@invoice.created_at.year}</InvoiceYear>")
44
+ invoice << Ox::Raw.new('<Remark>退貨</Remark>')
45
+ @xml = Ox.dump(doc)
46
+ end
47
+
48
+ def analize_response
49
+ return unless @response.body[:cancel_invoice_response][:return] == 'C0'
50
+
51
+ @invoice.update(canceled: true)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cetustek
4
+ class Configuration
5
+ attr_accessor :environment, :site_id, :username, :password
6
+
7
+ def initialize
8
+ @environment = :sandbox
9
+ end
10
+
11
+ def url
12
+ if @environment == :production
13
+ 'https://www.ei.com.tw/InvoiceMultiWeb/InvoiceAPI?wsdl'
14
+ else
15
+ 'https://invoice.cetustek.com.tw/InvoiceMultiWeb/InvoiceAPI?wsdl'
16
+ end
17
+ end
18
+
19
+ def production?
20
+ @environment == :production
21
+ end
22
+
23
+ def sandbox?
24
+ @environment == :sandbox
25
+ end
26
+ end
27
+
28
+ class << self
29
+ def configure
30
+ yield(config)
31
+ end
32
+
33
+ def config
34
+ @config ||= Configuration.new
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'models/invoice_data'
4
+ require_relative 'services/invoice_xml_builder'
5
+ require_relative 'services/invoice_service'
6
+ require_relative 'services/response_handler'
7
+
8
+ module Cetustek
9
+ class CreateInvoice
10
+ def initialize(invoice_data)
11
+ @invoice_data = invoice_data
12
+ end
13
+
14
+ def execute
15
+ xml = Services::InvoiceXmlBuilder.new(@invoice_data).build
16
+ response = Services::InvoiceService.new(xml, @invoice_data.order_id).create
17
+ result = Services::ResponseHandler.new(response, @invoice_data, xml).process
18
+
19
+ if defined?(Rails) && result[:number] && result[:random_number]
20
+ update_invoice_info(result)
21
+ end
22
+
23
+ result
24
+ end
25
+
26
+ private
27
+
28
+ def update_invoice_info(result)
29
+ return unless @invoice_data.respond_to?(:invoice_info)
30
+
31
+ @invoice_data.invoice_info.update(
32
+ number: result[:number],
33
+ random_number: result[:random_number],
34
+ created_at: Time.zone.today
35
+ )
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cetustek
4
+ module Models
5
+ class InvoiceData
6
+ attr_reader :order_id, :order_date, :buyer_identifier, :buyer_name,
7
+ :buyer_email, :donate_mark, :carrier_type, :carrier_id,
8
+ :npo_ban, :items, :payment_type, :total_discount,
9
+ :coupon_discount, :delivery_fee, :handling_fee
10
+
11
+ def initialize(attributes = {})
12
+ @order_id = attributes[:order_id]
13
+ @order_date = attributes[:order_date]
14
+ @buyer_identifier = attributes[:buyer_identifier]
15
+ @buyer_name = attributes[:buyer_name]
16
+ @buyer_email = attributes[:buyer_email]
17
+ @donate_mark = attributes[:donate_mark]
18
+ @carrier_type = attributes[:carrier_type]
19
+ @carrier_id = attributes[:carrier_id]
20
+ @npo_ban = attributes[:npo_ban]
21
+ @items = attributes[:items] || []
22
+ @payment_type = attributes[:payment_type]
23
+ @total_discount = attributes[:total_discount] || 0
24
+ @coupon_discount = attributes[:coupon_discount] || 0
25
+ @delivery_fee = attributes[:delivery_fee] || 0
26
+ @handling_fee = attributes[:handling_fee] || 0
27
+ end
28
+ end
29
+
30
+ class InvoiceItem
31
+ attr_reader :code, :name, :quantity, :unit_price
32
+
33
+ def initialize(attributes = {})
34
+ @code = attributes[:code]
35
+ @name = attributes[:name]
36
+ @quantity = attributes[:quantity]
37
+ @unit_price = attributes[:unit_price]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ module Cetustek
2
+ class QueryInvoiceByOrderId
3
+ def self.query(order_id)
4
+ url = Cetustek.config.url
5
+ client = Savon.client(
6
+ wsdl: url,
7
+ open_timeout: 300,
8
+ read_timeout: 300
9
+ )
10
+
11
+ @response = client.call(:query_invoice_by_orderid, message:
12
+ {
13
+ orderid: order_id,
14
+ source: Cetustek.config.site_id + Cetustek.config.password,
15
+ rentid: Cetustek.config.username
16
+ })
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'savon'
4
+ require 'logger'
5
+
6
+ module Cetustek
7
+ module Services
8
+ class InvoiceService
9
+ def initialize(xml, order_id = nil)
10
+ @xml = xml
11
+ @order_id = order_id
12
+ end
13
+
14
+ def create
15
+ client = build_soap_client
16
+ response = call_create_invoice(client)
17
+ log_response(response)
18
+ response
19
+ end
20
+
21
+ private
22
+
23
+ def build_soap_client
24
+ Savon.client(
25
+ wsdl: Cetustek.config.url,
26
+ open_timeout: 300,
27
+ read_timeout: 300
28
+ )
29
+ end
30
+
31
+ def call_create_invoice(client)
32
+ client.call(:create_invoice_v3, message: {
33
+ invoicexml: @xml,
34
+ source: Cetustek.config.site_id + Cetustek.config.password,
35
+ rentid: Cetustek.config.username,
36
+ hastax: 1
37
+ })
38
+ end
39
+
40
+ def log_response(response)
41
+ return unless defined?(Rails)
42
+
43
+ logger = Logger.new(Rails.root.join('log/invoice.log'))
44
+ logger.debug("#{@order_id} - #{response.body}") if @order_id
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ox'
4
+ require 'cgi'
5
+
6
+ module Cetustek
7
+ module Services
8
+ class InvoiceXmlBuilder
9
+ def initialize(invoice_data)
10
+ @data = invoice_data
11
+ end
12
+
13
+ def build
14
+ doc = Ox::Document.new
15
+ doc << create_xml_instruct
16
+ doc << create_invoice_element
17
+
18
+ Ox.dump(doc)
19
+ end
20
+
21
+ private
22
+
23
+ def create_xml_instruct
24
+ instruct = Ox::Instruct.new(:xml)
25
+ instruct[:version] = '1.0'
26
+ instruct[:encoding] = 'UTF-8'
27
+ instruct
28
+ end
29
+
30
+ def create_invoice_element
31
+ invoice = Ox::Element.new('Invoice')
32
+ invoice[:XSDVersion] = '2.8'
33
+
34
+ add_basic_info(invoice)
35
+ add_buyer_info(invoice)
36
+ add_invoice_type_info(invoice)
37
+ add_details(invoice)
38
+
39
+ invoice
40
+ end
41
+
42
+ def add_basic_info(invoice)
43
+ invoice << Ox::Raw.new("<OrderId>#{@data.order_id}</OrderId>")
44
+ invoice << Ox::Raw.new("<OrderDate>#{@data.order_date.strftime('%Y/%m/%d')}</OrderDate>")
45
+ end
46
+
47
+ def add_buyer_info(invoice)
48
+ invoice << Ox::Raw.new("<BuyerIdentifier>#{@data.buyer_identifier}</BuyerIdentifier>")
49
+ invoice << Ox::Raw.new("<BuyerName>#{CGI.escapeHTML(@data.buyer_name)}</BuyerName>")
50
+ invoice << Ox::Raw.new("<BuyerEmailAddress>#{@data.buyer_email}</BuyerEmailAddress>")
51
+ end
52
+
53
+ def add_invoice_type_info(invoice)
54
+ invoice << Ox::Raw.new("<DonateMark>#{@data.donate_mark}</DonateMark>")
55
+ invoice << Ox::Raw.new('<InvoiceType>07</InvoiceType>')
56
+ invoice << Ox::Raw.new("<CarrierType>#{@data.carrier_type}</CarrierType>")
57
+ invoice << Ox::Raw.new("<CarrierId1>#{@data.carrier_id}</CarrierId1>")
58
+ invoice << Ox::Raw.new("<CarrierId2>#{@data.carrier_id2}</CarrierId2>")
59
+ invoice << Ox::Raw.new("<NPOBAN>#{@data.npo_ban}</NPOBAN>")
60
+ invoice << Ox::Raw.new('<TaxType>1</TaxType>')
61
+ invoice << Ox::Raw.new("<PayWay>#{@data.payment_type}</PayWay>")
62
+ end
63
+
64
+ def add_details(invoice)
65
+ details = Ox::Element.new('Details')
66
+ invoice << details
67
+
68
+ @data.items.each do |item|
69
+ details << create_product_item(item)
70
+ end
71
+
72
+ add_additional_items(details)
73
+ end
74
+
75
+ def create_product_item(item)
76
+ product = Ox::Element.new('ProductItem')
77
+ product << Ox::Raw.new("<ProductionCode>#{item.code}</ProductionCode>")
78
+ product << Ox::Raw.new("<Description>#{CGI.escapeHTML(item.name)}</Description>")
79
+ product << Ox::Raw.new("<Quantity>#{item.quantity}</Quantity>")
80
+ product << Ox::Raw.new("<UnitPrice>#{item.unit_price}</UnitPrice>")
81
+ product
82
+ end
83
+
84
+ def add_additional_items(details)
85
+ add_discount_item(details) if @data.total_discount.positive?
86
+ add_coupon_item(details) if @data.coupon_discount.positive?
87
+ add_delivery_fee_item(details) if @data.delivery_fee.positive?
88
+ add_handling_fee_item(details) if @data.handling_fee.positive?
89
+ end
90
+
91
+ def add_discount_item(details)
92
+ discount = Ox::Element.new('ProductItem')
93
+ discount << Ox::Raw.new('<ProductionCode>DISCOUNT</ProductionCode>')
94
+ discount << Ox::Raw.new('<Description>折抵金額</Description>')
95
+ discount << Ox::Raw.new('<Quantity>1</Quantity>')
96
+ discount << Ox::Raw.new("<UnitPrice>#{@data.total_discount * -1}</UnitPrice>")
97
+ details << discount
98
+ end
99
+
100
+ def add_coupon_item(details)
101
+ coupon = Ox::Element.new('ProductItem')
102
+ coupon << Ox::Raw.new('<ProductionCode>COUPON</ProductionCode>')
103
+ coupon << Ox::Raw.new('<Description>分享折讓</Description>')
104
+ coupon << Ox::Raw.new('<Quantity>1</Quantity>')
105
+ coupon << Ox::Raw.new("<UnitPrice>#{@data.coupon_discount * -1}</UnitPrice>")
106
+ details << coupon
107
+ end
108
+
109
+ def add_delivery_fee_item(details)
110
+ delivery = Ox::Element.new('ProductItem')
111
+ delivery << Ox::Raw.new('<ProductionCode>DELIVERY_FEE</ProductionCode>')
112
+ delivery << Ox::Raw.new('<Description>運費</Description>')
113
+ delivery << Ox::Raw.new('<Quantity>1</Quantity>')
114
+ delivery << Ox::Raw.new("<UnitPrice>#{@data.delivery_fee}</UnitPrice>")
115
+ details << delivery
116
+ end
117
+
118
+ def add_handling_fee_item(details)
119
+ handling = Ox::Element.new('ProductItem')
120
+ handling << Ox::Raw.new('<ProductionCode>HANDLING_FEE</ProductionCode>')
121
+ handling << Ox::Raw.new('<Description>手續費</Description>')
122
+ handling << Ox::Raw.new('<Quantity>1</Quantity>')
123
+ handling << Ox::Raw.new("<UnitPrice>#{@data.handling_fee}</UnitPrice>")
124
+ details << handling
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Cetustek
6
+ module Services
7
+ class ResponseHandler
8
+ class InvalidResponseError < StandardError; end
9
+
10
+ def initialize(response, invoice_data, xml = nil)
11
+ @response = response
12
+ @invoice_data = invoice_data
13
+ @xml = xml
14
+ end
15
+
16
+ def process
17
+ response_body = @response.body[:create_invoice_v3_response][:return]
18
+ number, random_number = response_body.split(';')
19
+
20
+ unless random_number
21
+ log_error
22
+ raise InvalidResponseError, "Invalid response: #{response_body}"
23
+ end
24
+
25
+ { number: number, random_number: random_number }
26
+ end
27
+
28
+ private
29
+
30
+ def log_error
31
+ return unless defined?(Rails) && @xml
32
+
33
+ logger = Logger.new(Rails.root.join('log/invoice_xml.log'))
34
+ logger.debug("#{@invoice_data.order_id} - #{@xml.force_encoding('UTF-8')}")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cetustek
4
+ VERSION = "0.2.0"
5
+ end
data/lib/cetustek.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cetustek/version"
4
+ require_relative "cetustek/configuration"
5
+ require_relative "cetustek/create_invoice"
6
+ require_relative "cetustek/cancel_invoice"
7
+ require_relative "cetustek/query_invoice_by_order_id"
8
+
9
+ module Cetustek
10
+ class Error < StandardError; end
11
+
12
+ class << self
13
+ def configure
14
+ yield(config)
15
+ end
16
+
17
+ def config
18
+ @config ||= Configuration.new
19
+ end
20
+ end
21
+ end
data/sig/cetustek.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Cetustek
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cetustek
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Zac
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-01-22 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: ox
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.14'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.14'
26
+ - !ruby/object:Gem::Dependency
27
+ name: savon
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.14'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.14'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ description: Cetustek is a Ruby gem designed for handling electronic invoice operations,
55
+ including invoice cancellation. It communicates with the e-invoice system through
56
+ SOAP Web Services.
57
+ email:
58
+ - 579103+7a6163@users.noreply.github.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - CHANGELOG.md
64
+ - README.md
65
+ - Rakefile
66
+ - lib/cetustek.rb
67
+ - lib/cetustek/cancel_invoice.rb
68
+ - lib/cetustek/configuration.rb
69
+ - lib/cetustek/create_invoice.rb
70
+ - lib/cetustek/models/invoice_data.rb
71
+ - lib/cetustek/query_invoice_by_order_id.rb
72
+ - lib/cetustek/services/invoice_service.rb
73
+ - lib/cetustek/services/invoice_xml_builder.rb
74
+ - lib/cetustek/services/response_handler.rb
75
+ - lib/cetustek/version.rb
76
+ - sig/cetustek.rbs
77
+ homepage: https://github.com/7a6163/cetustek
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ homepage_uri: https://github.com/7a6163/cetustek
82
+ source_code_uri: https://github.com/7a6163/cetustek
83
+ changelog_uri: https://github.com/7a6163/cetustek/blob/main/CHANGELOG.md
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 3.0.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.6.2
99
+ specification_version: 4
100
+ summary: A Ruby gem for handling electronic invoice operations
101
+ test_files: []