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 +7 -0
- data/CHANGELOG.md +34 -0
- data/README.md +96 -0
- data/Rakefile +4 -0
- data/lib/cetustek/cancel_invoice.rb +54 -0
- data/lib/cetustek/configuration.rb +37 -0
- data/lib/cetustek/create_invoice.rb +38 -0
- data/lib/cetustek/models/invoice_data.rb +41 -0
- data/lib/cetustek/query_invoice_by_order_id.rb +19 -0
- data/lib/cetustek/services/invoice_service.rb +48 -0
- data/lib/cetustek/services/invoice_xml_builder.rb +128 -0
- data/lib/cetustek/services/response_handler.rb +38 -0
- data/lib/cetustek/version.rb +5 -0
- data/lib/cetustek.rb +21 -0
- data/sig/cetustek.rbs +4 -0
- metadata +101 -0
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,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
|
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
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: []
|