cetustek 0.1.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
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.
- cetustek/__init__.py +16 -2
- cetustek/client.py +147 -9
- cetustek/models.py +52 -0
- cetustek-1.0.0.dist-info/METADATA +288 -0
- cetustek-1.0.0.dist-info/RECORD +8 -0
- cetustek-0.1.0.dist-info/METADATA +0 -111
- cetustek-0.1.0.dist-info/RECORD +0 -8
- {cetustek-0.1.0.dist-info → cetustek-1.0.0.dist-info}/WHEEL +0 -0
- {cetustek-0.1.0.dist-info → cetustek-1.0.0.dist-info}/licenses/LICENSE +0 -0
cetustek/__init__.py
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
"""Cetustek Taiwan e-invoice SDK."""
|
|
2
2
|
|
|
3
|
-
from cetustek.client import Cetustek
|
|
3
|
+
from cetustek.client import Cetustek, CetustekError, CetustekAPIError
|
|
4
4
|
from cetustek.models import (
|
|
5
|
+
# Input models
|
|
5
6
|
CancelInvoiceInput,
|
|
6
7
|
CreateInvoiceInput,
|
|
7
8
|
InvoiceItem,
|
|
8
9
|
QueryInvoiceInput,
|
|
10
|
+
# Response models
|
|
11
|
+
CancelInvoiceResponse,
|
|
12
|
+
CreateInvoiceResponse,
|
|
13
|
+
QueryInvoiceResponse,
|
|
9
14
|
)
|
|
10
15
|
|
|
11
16
|
__all__ = [
|
|
17
|
+
# Client
|
|
12
18
|
"Cetustek",
|
|
19
|
+
# Exceptions
|
|
20
|
+
"CetustekError",
|
|
21
|
+
"CetustekAPIError",
|
|
22
|
+
# Input models
|
|
13
23
|
"CreateInvoiceInput",
|
|
14
24
|
"CancelInvoiceInput",
|
|
15
25
|
"QueryInvoiceInput",
|
|
16
26
|
"InvoiceItem",
|
|
27
|
+
# Response models
|
|
28
|
+
"CreateInvoiceResponse",
|
|
29
|
+
"CancelInvoiceResponse",
|
|
30
|
+
"QueryInvoiceResponse",
|
|
17
31
|
]
|
|
18
32
|
|
|
19
|
-
__version__ = "
|
|
33
|
+
__version__ = "1.0.0"
|
cetustek/client.py
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
import html
|
|
2
|
+
import re
|
|
3
|
+
from typing import Optional
|
|
2
4
|
from xml.sax.saxutils import escape
|
|
3
5
|
|
|
6
|
+
import requests
|
|
7
|
+
|
|
4
8
|
from cetustek.models import (
|
|
5
9
|
CreateInvoiceInput,
|
|
10
|
+
CreateInvoiceResponse,
|
|
6
11
|
CancelInvoiceInput,
|
|
12
|
+
CancelInvoiceResponse,
|
|
7
13
|
QueryInvoiceInput,
|
|
14
|
+
QueryInvoiceResponse,
|
|
8
15
|
)
|
|
9
16
|
|
|
10
17
|
|
|
18
|
+
class CetustekError(Exception):
|
|
19
|
+
"""Base exception for Cetustek API errors."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CetustekAPIError(CetustekError):
|
|
24
|
+
"""API returned an error response."""
|
|
25
|
+
def __init__(self, code: str, message: Optional[str] = None):
|
|
26
|
+
self.code = code
|
|
27
|
+
self.message = message
|
|
28
|
+
super().__init__(f"API Error: {code}" + (f" - {message}" if message else ""))
|
|
29
|
+
|
|
30
|
+
|
|
11
31
|
class Cetustek:
|
|
12
32
|
def __init__(self, *, endpoint: str, rent_id: str, api_password: str, site_code: str):
|
|
13
33
|
self.endpoint = endpoint
|
|
@@ -18,7 +38,16 @@ class Cetustek:
|
|
|
18
38
|
# API methods
|
|
19
39
|
# -------------------------
|
|
20
40
|
|
|
21
|
-
def createInvoice(self, data: CreateInvoiceInput) ->
|
|
41
|
+
def createInvoice(self, data: CreateInvoiceInput) -> CreateInvoiceResponse:
|
|
42
|
+
"""
|
|
43
|
+
Create a new e-invoice.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
CreateInvoiceResponse with invoice_number and random_code.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
CetustekAPIError: If the API returns an error.
|
|
50
|
+
"""
|
|
22
51
|
items_xml = ""
|
|
23
52
|
for item in data.items:
|
|
24
53
|
items_xml += f"""
|
|
@@ -61,10 +90,20 @@ class Cetustek:
|
|
|
61
90
|
<source>{self.source}</source>
|
|
62
91
|
"""
|
|
63
92
|
|
|
64
|
-
|
|
93
|
+
response_xml = self._post_soap(self._wrap_soap("CreateInvoiceV3", inner))
|
|
94
|
+
return self._parse_create_response(response_xml)
|
|
95
|
+
|
|
96
|
+
def cancelInvoice(self, data: CancelInvoiceInput, no_check: bool = False) -> CancelInvoiceResponse:
|
|
97
|
+
"""
|
|
98
|
+
Cancel (void) an existing invoice.
|
|
65
99
|
|
|
100
|
+
Args:
|
|
101
|
+
data: CancelInvoiceInput with invoice details.
|
|
102
|
+
no_check: If True, skip validation checks.
|
|
66
103
|
|
|
67
|
-
|
|
104
|
+
Returns:
|
|
105
|
+
CancelInvoiceResponse with success status and code.
|
|
106
|
+
"""
|
|
68
107
|
invoice_xml = f"""
|
|
69
108
|
<Invoice XSDVersion="2.8">
|
|
70
109
|
<InvoiceNumber>{data.invoice_number}</InvoiceNumber>
|
|
@@ -81,13 +120,18 @@ class Cetustek:
|
|
|
81
120
|
"""
|
|
82
121
|
|
|
83
122
|
action = "CancelInvoiceNoCheck" if no_check else "CancelInvoice"
|
|
123
|
+
response_xml = self._post_soap(self._wrap_soap(action, inner))
|
|
124
|
+
return self._parse_cancel_response(response_xml)
|
|
84
125
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def queryInvoice(self, data: QueryInvoiceInput) -> str:
|
|
126
|
+
def queryInvoice(self, data: QueryInvoiceInput) -> QueryInvoiceResponse:
|
|
88
127
|
"""
|
|
89
128
|
Query invoice by invoice number and year.
|
|
90
|
-
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
QueryInvoiceResponse with invoice details.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
CetustekAPIError: If the API returns an error.
|
|
91
135
|
"""
|
|
92
136
|
inner = f"""
|
|
93
137
|
<invoicenumber>{data.invoice_number}</invoicenumber>
|
|
@@ -95,12 +139,106 @@ class Cetustek:
|
|
|
95
139
|
<rentid>{self.rent_id}</rentid>
|
|
96
140
|
<source>{self.source}</source>
|
|
97
141
|
"""
|
|
98
|
-
|
|
142
|
+
response_xml = self._post_soap(self._wrap_soap("QueryInvoice", inner))
|
|
143
|
+
return self._parse_query_response(response_xml, data.invoice_number)
|
|
99
144
|
|
|
145
|
+
# -------------------------
|
|
146
|
+
# Response parsers
|
|
147
|
+
# -------------------------
|
|
148
|
+
|
|
149
|
+
def _parse_create_response(self, response_xml: str) -> CreateInvoiceResponse:
|
|
150
|
+
"""Parse createInvoice response XML."""
|
|
151
|
+
return_value = self._extract_return_value(response_xml)
|
|
152
|
+
|
|
153
|
+
if ";" not in return_value:
|
|
154
|
+
raise CetustekAPIError(return_value)
|
|
155
|
+
|
|
156
|
+
parts = return_value.split(";")
|
|
157
|
+
if len(parts) != 2:
|
|
158
|
+
raise CetustekAPIError(return_value, "Unexpected response format")
|
|
159
|
+
|
|
160
|
+
invoice_number, random_code = parts
|
|
161
|
+
return CreateInvoiceResponse(
|
|
162
|
+
invoice_number=invoice_number,
|
|
163
|
+
random_code=random_code,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def _parse_cancel_response(self, response_xml: str) -> CancelInvoiceResponse:
|
|
167
|
+
"""Parse cancelInvoice response XML."""
|
|
168
|
+
return_value = self._extract_return_value(response_xml)
|
|
169
|
+
|
|
170
|
+
success = return_value == "C0"
|
|
171
|
+
return CancelInvoiceResponse(
|
|
172
|
+
success=success,
|
|
173
|
+
code=return_value,
|
|
174
|
+
message=None if success else return_value,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def _parse_query_response(self, response_xml: str, invoice_number: str) -> QueryInvoiceResponse:
|
|
178
|
+
"""Parse queryInvoice response XML."""
|
|
179
|
+
return_value = self._extract_return_value(response_xml, dotall=True)
|
|
180
|
+
|
|
181
|
+
# Unescape HTML entities
|
|
182
|
+
invoice_xml = html.unescape(return_value)
|
|
183
|
+
|
|
184
|
+
# Check if response is an error (not XML)
|
|
185
|
+
if not invoice_xml.strip().startswith("<"):
|
|
186
|
+
raise CetustekAPIError(invoice_xml)
|
|
187
|
+
|
|
188
|
+
# Parse invoice fields from XML
|
|
189
|
+
return QueryInvoiceResponse(
|
|
190
|
+
invoice_number=invoice_number,
|
|
191
|
+
invoice_date=self._extract_xml_value(invoice_xml, "InvoiceDate"),
|
|
192
|
+
invoice_time=self._extract_xml_value(invoice_xml, "InvoiceTime"),
|
|
193
|
+
order_id=self._extract_xml_value(invoice_xml, "OrderID"),
|
|
194
|
+
random_code=self._extract_xml_value(invoice_xml, "RandomNumber"),
|
|
195
|
+
buyer_identifier=self._extract_xml_value(invoice_xml, "BuyerIdentifier"),
|
|
196
|
+
buyer_name=self._extract_xml_value(invoice_xml, "BuyerName"),
|
|
197
|
+
seller_identifier=self._extract_xml_value(invoice_xml, "SellerIdentifier"),
|
|
198
|
+
seller_name=self._extract_xml_value(invoice_xml, "SellerName"),
|
|
199
|
+
invoice_status=self._extract_xml_value(invoice_xml, "InvoiceStatus"),
|
|
200
|
+
donate_mark=self._extract_xml_value(invoice_xml, "DonateMark"),
|
|
201
|
+
carrier_type=self._extract_xml_value(invoice_xml, "CarrierType"),
|
|
202
|
+
carrier_id=self._extract_xml_value(invoice_xml, "CarrierId1"),
|
|
203
|
+
npoban=self._extract_xml_value(invoice_xml, "NPOBAN"),
|
|
204
|
+
tax_type=self._extract_xml_value(invoice_xml, "TaxType"),
|
|
205
|
+
sales_amount=self._extract_xml_float(invoice_xml, "SalesAmount"),
|
|
206
|
+
tax_amount=self._extract_xml_float(invoice_xml, "TaxAmount"),
|
|
207
|
+
total_amount=self._extract_xml_float(invoice_xml, "TotalAmount"),
|
|
208
|
+
raw_xml=invoice_xml,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def _extract_return_value(self, response_xml: str, dotall: bool = False) -> str:
|
|
212
|
+
"""Extract the <return> value from SOAP response."""
|
|
213
|
+
flags = re.DOTALL if dotall else 0
|
|
214
|
+
match = re.search(r"<return>(.*?)</return>", response_xml, flags)
|
|
215
|
+
if not match:
|
|
216
|
+
raise CetustekError(f"Invalid response: missing <return> tag")
|
|
217
|
+
return match.group(1).strip()
|
|
218
|
+
|
|
219
|
+
def _extract_xml_value(self, xml: str, tag: str) -> Optional[str]:
|
|
220
|
+
"""Extract value from XML tag (case-insensitive)."""
|
|
221
|
+
pattern = rf"<{tag}>(.*?)</{tag}>"
|
|
222
|
+
match = re.search(pattern, xml, re.IGNORECASE | re.DOTALL)
|
|
223
|
+
if match:
|
|
224
|
+
value = match.group(1).strip()
|
|
225
|
+
return value if value else None
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
def _extract_xml_float(self, xml: str, tag: str) -> Optional[float]:
|
|
229
|
+
"""Extract float value from XML tag."""
|
|
230
|
+
value = self._extract_xml_value(xml, tag)
|
|
231
|
+
if value:
|
|
232
|
+
try:
|
|
233
|
+
return float(value)
|
|
234
|
+
except ValueError:
|
|
235
|
+
return None
|
|
236
|
+
return None
|
|
100
237
|
|
|
101
238
|
# -------------------------
|
|
102
239
|
# Internal helpers
|
|
103
240
|
# -------------------------
|
|
241
|
+
|
|
104
242
|
def _post_soap(self, body: str) -> str:
|
|
105
243
|
headers = {
|
|
106
244
|
"Content-Type": "text/xml; charset=utf-8",
|
cetustek/models.py
CHANGED
|
@@ -2,6 +2,10 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import Optional, List
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
# -------------------------
|
|
6
|
+
# Input Models
|
|
7
|
+
# -------------------------
|
|
8
|
+
|
|
5
9
|
@dataclass
|
|
6
10
|
class InvoiceItem:
|
|
7
11
|
production_code: str # ProductionCode
|
|
@@ -55,3 +59,51 @@ class CancelInvoiceInput:
|
|
|
55
59
|
class QueryInvoiceInput:
|
|
56
60
|
invoice_number: str
|
|
57
61
|
invoice_year: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# -------------------------
|
|
65
|
+
# Response Models
|
|
66
|
+
# -------------------------
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CreateInvoiceResponse:
|
|
70
|
+
"""Response from createInvoice API."""
|
|
71
|
+
invoice_number: str # 10-character invoice number (e.g., "WP20260002")
|
|
72
|
+
random_code: str # 4-digit random code (e.g., "6827")
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def invoice_year(self) -> str:
|
|
76
|
+
"""Extract year from invoice number (positions 2-5)."""
|
|
77
|
+
return self.invoice_number[2:6]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class QueryInvoiceResponse:
|
|
82
|
+
"""Response from queryInvoice API."""
|
|
83
|
+
invoice_number: str
|
|
84
|
+
invoice_date: Optional[str] = None
|
|
85
|
+
invoice_time: Optional[str] = None
|
|
86
|
+
order_id: Optional[str] = None
|
|
87
|
+
random_code: Optional[str] = None
|
|
88
|
+
buyer_identifier: Optional[str] = None
|
|
89
|
+
buyer_name: Optional[str] = None
|
|
90
|
+
seller_identifier: Optional[str] = None
|
|
91
|
+
seller_name: Optional[str] = None
|
|
92
|
+
invoice_status: Optional[str] = None
|
|
93
|
+
donate_mark: Optional[str] = None
|
|
94
|
+
carrier_type: Optional[str] = None
|
|
95
|
+
carrier_id: Optional[str] = None
|
|
96
|
+
npoban: Optional[str] = None
|
|
97
|
+
tax_type: Optional[str] = None
|
|
98
|
+
sales_amount: Optional[float] = None
|
|
99
|
+
tax_amount: Optional[float] = None
|
|
100
|
+
total_amount: Optional[float] = None
|
|
101
|
+
raw_xml: Optional[str] = None # Original XML for additional parsing
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class CancelInvoiceResponse:
|
|
106
|
+
"""Response from cancelInvoice API."""
|
|
107
|
+
success: bool
|
|
108
|
+
code: str # "C0" for success, error code otherwise
|
|
109
|
+
message: Optional[str] = None
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cetustek
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK for Cetustek Taiwan e-invoice API
|
|
5
|
+
Project-URL: Homepage, https://github.com/vincelee888/cetustek
|
|
6
|
+
Project-URL: Repository, https://github.com/vincelee888/cetustek
|
|
7
|
+
Project-URL: Issues, https://github.com/vincelee888/cetustek/issues
|
|
8
|
+
Author: Vince Lee
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cetustek,e-invoice,einvoice,invoice,taiwan
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: requests>=2.25.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Cetustek
|
|
32
|
+
|
|
33
|
+
[](https://badge.fury.io/py/cetustek)
|
|
34
|
+
[](https://pypi.org/project/cetustek/)
|
|
35
|
+
[](https://opensource.org/licenses/MIT)
|
|
36
|
+
|
|
37
|
+
A Python SDK for the [Cetustek](https://www.cetustek.com.tw/) Taiwan e-invoice API.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- Create, query, and cancel e-invoices
|
|
42
|
+
- Type-safe with dataclass request/response models
|
|
43
|
+
- Comprehensive error handling with custom exceptions
|
|
44
|
+
- Full support for B2B and B2C invoices
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install cetustek
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Requirements:** Python 3.9+
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from cetustek import Cetustek, CreateInvoiceInput, InvoiceItem
|
|
58
|
+
|
|
59
|
+
client = Cetustek(
|
|
60
|
+
endpoint="https://invoice.cetustek.com.tw/InvoiceMultiWeb/InvoiceAPI",
|
|
61
|
+
rent_id="YOUR_RENT_ID",
|
|
62
|
+
site_code="YOUR_SITE_CODE",
|
|
63
|
+
api_password="YOUR_API_PASSWORD",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
result = client.createInvoice(CreateInvoiceInput(
|
|
67
|
+
order_id="12345678",
|
|
68
|
+
order_date="2026/01/15",
|
|
69
|
+
donate_mark="0",
|
|
70
|
+
invoice_type="07",
|
|
71
|
+
tax_type="1",
|
|
72
|
+
pay_way="1",
|
|
73
|
+
items=[InvoiceItem(
|
|
74
|
+
production_code="PROD001",
|
|
75
|
+
description="Product",
|
|
76
|
+
quantity=1,
|
|
77
|
+
unit_price=1000,
|
|
78
|
+
)],
|
|
79
|
+
))
|
|
80
|
+
|
|
81
|
+
print(result.invoice_number) # "WP20260002"
|
|
82
|
+
print(result.random_code) # "6827"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
### Create Invoice
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from cetustek import CreateInvoiceInput, InvoiceItem, CetustekAPIError
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
result = client.createInvoice(CreateInvoiceInput(
|
|
94
|
+
order_id="12345678",
|
|
95
|
+
order_date="2026/01/15",
|
|
96
|
+
donate_mark="0", # 0: No donation, 1: Donate, 2: Donate to NPO
|
|
97
|
+
invoice_type="07", # 07: B2B, 08: B2C
|
|
98
|
+
tax_type="1", # 1: Taxable, 2: Zero-rated, 3: Tax-free
|
|
99
|
+
pay_way="1",
|
|
100
|
+
items=[InvoiceItem(
|
|
101
|
+
production_code="PROD001",
|
|
102
|
+
description="Product description",
|
|
103
|
+
quantity=1,
|
|
104
|
+
unit_price=1000,
|
|
105
|
+
unit="件", # Optional
|
|
106
|
+
)],
|
|
107
|
+
# Optional buyer info
|
|
108
|
+
buyer_identifier="12345678", # Tax ID (統一編號)
|
|
109
|
+
buyer_name="Company Name",
|
|
110
|
+
buyer_email="email@example.com",
|
|
111
|
+
tax_rate=0.05, # Default: 5%
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
print(result.invoice_number) # "WP20260002"
|
|
115
|
+
print(result.random_code) # "6827"
|
|
116
|
+
print(result.invoice_year) # "2026" (derived property)
|
|
117
|
+
|
|
118
|
+
except CetustekAPIError as e:
|
|
119
|
+
print(f"Error: {e.code}")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Query Invoice
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from cetustek import QueryInvoiceInput
|
|
126
|
+
|
|
127
|
+
result = client.queryInvoice(QueryInvoiceInput(
|
|
128
|
+
invoice_number="WP20260002",
|
|
129
|
+
invoice_year="2026",
|
|
130
|
+
))
|
|
131
|
+
|
|
132
|
+
print(result.order_id)
|
|
133
|
+
print(result.buyer_name)
|
|
134
|
+
print(result.total_amount)
|
|
135
|
+
print(result.invoice_status)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Cancel Invoice
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from cetustek import CancelInvoiceInput
|
|
142
|
+
|
|
143
|
+
result = client.cancelInvoice(CancelInvoiceInput(
|
|
144
|
+
invoice_number="WP20260002",
|
|
145
|
+
invoice_year="2026",
|
|
146
|
+
remark="Cancellation reason",
|
|
147
|
+
))
|
|
148
|
+
|
|
149
|
+
if result.success:
|
|
150
|
+
print("Invoice cancelled")
|
|
151
|
+
else:
|
|
152
|
+
print(f"Failed: {result.code}")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## API Reference
|
|
156
|
+
|
|
157
|
+
### Client
|
|
158
|
+
|
|
159
|
+
| Parameter | Description |
|
|
160
|
+
|-----------|-------------|
|
|
161
|
+
| `endpoint` | Cetustek API endpoint URL |
|
|
162
|
+
| `rent_id` | Rent ID (統一編號) |
|
|
163
|
+
| `site_code` | Site code |
|
|
164
|
+
| `api_password` | API password |
|
|
165
|
+
|
|
166
|
+
### Response Models
|
|
167
|
+
|
|
168
|
+
**CreateInvoiceResponse**
|
|
169
|
+
|
|
170
|
+
| Field | Type | Description |
|
|
171
|
+
|-------|------|-------------|
|
|
172
|
+
| `invoice_number` | str | Invoice number (e.g., "WP20260002") |
|
|
173
|
+
| `random_code` | str | 4-digit random code |
|
|
174
|
+
| `invoice_year` | str | Year (derived from invoice_number) |
|
|
175
|
+
|
|
176
|
+
**QueryInvoiceResponse**
|
|
177
|
+
|
|
178
|
+
| Field | Type | Description |
|
|
179
|
+
|-------|------|-------------|
|
|
180
|
+
| `invoice_number` | str | Invoice number |
|
|
181
|
+
| `order_id` | str | Order ID |
|
|
182
|
+
| `random_code` | str | Random code |
|
|
183
|
+
| `buyer_identifier` | str | Buyer tax ID |
|
|
184
|
+
| `buyer_name` | str | Buyer name |
|
|
185
|
+
| `seller_identifier` | str | Seller tax ID |
|
|
186
|
+
| `seller_name` | str | Seller name |
|
|
187
|
+
| `invoice_status` | str | Status |
|
|
188
|
+
| `sales_amount` | float | Sales amount |
|
|
189
|
+
| `tax_amount` | float | Tax amount |
|
|
190
|
+
| `total_amount` | float | Total amount |
|
|
191
|
+
| `raw_xml` | str | Raw XML response |
|
|
192
|
+
|
|
193
|
+
**CancelInvoiceResponse**
|
|
194
|
+
|
|
195
|
+
| Field | Type | Description |
|
|
196
|
+
|-------|------|-------------|
|
|
197
|
+
| `success` | bool | Whether cancellation succeeded |
|
|
198
|
+
| `code` | str | Response code ("C0" = success) |
|
|
199
|
+
| `message` | str | Error message (if failed) |
|
|
200
|
+
|
|
201
|
+
### Input Models
|
|
202
|
+
|
|
203
|
+
**InvoiceItem**
|
|
204
|
+
|
|
205
|
+
| Field | Type | Required | Description |
|
|
206
|
+
|-------|------|:--------:|-------------|
|
|
207
|
+
| `production_code` | str | Yes | Product code |
|
|
208
|
+
| `description` | str | Yes | Description |
|
|
209
|
+
| `quantity` | float | Yes | Quantity |
|
|
210
|
+
| `unit_price` | float | Yes | Unit price |
|
|
211
|
+
| `unit` | str | No | Unit (e.g., "件") |
|
|
212
|
+
|
|
213
|
+
**CreateInvoiceInput**
|
|
214
|
+
|
|
215
|
+
| Field | Type | Required | Description |
|
|
216
|
+
|-------|------|:--------:|-------------|
|
|
217
|
+
| `order_id` | str | Yes | Order ID |
|
|
218
|
+
| `order_date` | str | Yes | Date (yyyy/MM/dd) |
|
|
219
|
+
| `donate_mark` | str | Yes | 0/1/2 |
|
|
220
|
+
| `invoice_type` | str | Yes | 07 (B2B) / 08 (B2C) |
|
|
221
|
+
| `pay_way` | str | Yes | Payment method |
|
|
222
|
+
| `tax_type` | str | Yes | 1/2/3/4/5/9 |
|
|
223
|
+
| `items` | List | Yes | Invoice items |
|
|
224
|
+
| `buyer_identifier` | str | No | Buyer tax ID |
|
|
225
|
+
| `buyer_name` | str | No | Buyer name |
|
|
226
|
+
| `buyer_email` | str | No | Buyer email |
|
|
227
|
+
| `tax_rate` | float | No | Tax rate (default: 0.05) |
|
|
228
|
+
| `carrier_type` | str | No | Carrier type |
|
|
229
|
+
| `carrier_id1` | str | No | Carrier ID |
|
|
230
|
+
| `npoban` | str | No | NPO code |
|
|
231
|
+
|
|
232
|
+
## Error Handling
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
from cetustek import CetustekError, CetustekAPIError
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
result = client.createInvoice(invoice_input)
|
|
239
|
+
except CetustekAPIError as e:
|
|
240
|
+
print(f"API Error: {e.code} - {e.message}")
|
|
241
|
+
except CetustekError as e:
|
|
242
|
+
print(f"SDK Error: {e}")
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Example
|
|
246
|
+
|
|
247
|
+
See [example.py](example.py) for a complete working example.
|
|
248
|
+
|
|
249
|
+
## Getting Started
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
pip install cetustek
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from cetustek import Cetustek, CreateInvoiceInput, InvoiceItem
|
|
257
|
+
|
|
258
|
+
# Initialize client
|
|
259
|
+
client = Cetustek(
|
|
260
|
+
endpoint="https://invoice.cetustek.com.tw/InvoiceMultiWeb/InvoiceAPI",
|
|
261
|
+
rent_id="YOUR_RENT_ID",
|
|
262
|
+
site_code="YOUR_SITE_CODE",
|
|
263
|
+
api_password="YOUR_API_PASSWORD",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Create an invoice
|
|
267
|
+
result = client.createInvoice(CreateInvoiceInput(
|
|
268
|
+
order_id="12345678",
|
|
269
|
+
order_date="2026/01/15",
|
|
270
|
+
donate_mark="0",
|
|
271
|
+
invoice_type="07",
|
|
272
|
+
tax_type="1",
|
|
273
|
+
pay_way="1",
|
|
274
|
+
items=[InvoiceItem(
|
|
275
|
+
production_code="PROD001",
|
|
276
|
+
description="Product",
|
|
277
|
+
quantity=1,
|
|
278
|
+
unit_price=1000,
|
|
279
|
+
)],
|
|
280
|
+
))
|
|
281
|
+
|
|
282
|
+
print(result.invoice_number) # e.g., "WP20260002"
|
|
283
|
+
print(result.random_code) # e.g., "6827"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
cetustek/__init__.py,sha256=8aaph2X7ksWwPXzSki85NEIoU1f2xi4Y553U3PMT6E0,701
|
|
2
|
+
cetustek/client.py,sha256=S56MgkTvXJV0yfRDF98m9Q5hinhLtwenFdqNlv8MWUw,9399
|
|
3
|
+
cetustek/models.py,sha256=ZXm8-EonV2bB2R7M3FHVXRbl6bg0si7a7JlL1JKXuIo,3119
|
|
4
|
+
cetustek/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
cetustek-1.0.0.dist-info/METADATA,sha256=LuPY18Jpzwku3A2uBX2qAW-tqTxftloSgclz3PFE7F8,7885
|
|
6
|
+
cetustek-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
7
|
+
cetustek-1.0.0.dist-info/licenses/LICENSE,sha256=3Zyxxlid2vbpfK2L93sfxat307kl3auQ32KXigeRGiE,1066
|
|
8
|
+
cetustek-1.0.0.dist-info/RECORD,,
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: cetustek
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Python SDK for Cetustek Taiwan e-invoice API
|
|
5
|
-
Project-URL: Homepage, https://github.com/vincelee888/cetustek
|
|
6
|
-
Project-URL: Repository, https://github.com/vincelee888/cetustek
|
|
7
|
-
Project-URL: Issues, https://github.com/vincelee888/cetustek/issues
|
|
8
|
-
Author: Vince Lee
|
|
9
|
-
License-Expression: MIT
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Keywords: cetustek,e-invoice,einvoice,invoice,taiwan
|
|
12
|
-
Classifier: Development Status :: 4 - Beta
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
-
Classifier: Topic :: Office/Business :: Financial
|
|
23
|
-
Classifier: Typing :: Typed
|
|
24
|
-
Requires-Python: >=3.9
|
|
25
|
-
Requires-Dist: requests>=2.25.0
|
|
26
|
-
Provides-Extra: dev
|
|
27
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
28
|
-
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
|
|
31
|
-
# Cetustek
|
|
32
|
-
|
|
33
|
-
Python SDK for Cetustek Taiwan e-invoice API.
|
|
34
|
-
|
|
35
|
-
## Installation
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
pip install cetustek
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Usage
|
|
42
|
-
|
|
43
|
-
```python
|
|
44
|
-
from cetustek import Cetustek, CreateInvoiceInput, InvoiceItem
|
|
45
|
-
|
|
46
|
-
# Initialize the client
|
|
47
|
-
client = Cetustek(
|
|
48
|
-
endpoint="https://invoice.cetustek.com.tw/InvoiceMultiWeb/InvoiceAPI",
|
|
49
|
-
rent_id="YOUR_RENT_ID",
|
|
50
|
-
site_code="YOUR_SITE_CODE",
|
|
51
|
-
api_password="YOUR_API_PASSWORD",
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
# Create an invoice
|
|
55
|
-
invoice_input = CreateInvoiceInput(
|
|
56
|
-
order_id="12345678",
|
|
57
|
-
order_date="2026/01/15",
|
|
58
|
-
buyer_identifier="12345678",
|
|
59
|
-
buyer_name="Company Name",
|
|
60
|
-
buyer_email="email@example.com",
|
|
61
|
-
donate_mark="0",
|
|
62
|
-
invoice_type="07",
|
|
63
|
-
tax_type="1",
|
|
64
|
-
tax_rate=0.05,
|
|
65
|
-
pay_way="1",
|
|
66
|
-
items=[
|
|
67
|
-
InvoiceItem(
|
|
68
|
-
production_code="PROD001",
|
|
69
|
-
description="Product description",
|
|
70
|
-
quantity=1,
|
|
71
|
-
unit_price=1000,
|
|
72
|
-
unit="件",
|
|
73
|
-
)
|
|
74
|
-
],
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
result = client.createInvoice(invoice_input)
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Query an Invoice
|
|
81
|
-
|
|
82
|
-
```python
|
|
83
|
-
from cetustek import QueryInvoiceInput
|
|
84
|
-
|
|
85
|
-
query_input = QueryInvoiceInput(
|
|
86
|
-
invoice_number="AA12345678",
|
|
87
|
-
invoice_year="2026",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
result = client.queryInvoice(query_input)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Cancel an Invoice
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
from cetustek import CancelInvoiceInput
|
|
97
|
-
|
|
98
|
-
cancel_input = CancelInvoiceInput(
|
|
99
|
-
invoice_number="AA12345678",
|
|
100
|
-
invoice_year="2026",
|
|
101
|
-
remark="Cancellation reason",
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
result = client.cancelInvoice(cancel_input)
|
|
105
|
-
# Or use no_check=True to skip validation
|
|
106
|
-
result = client.cancelInvoice(cancel_input, no_check=True)
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## License
|
|
110
|
-
|
|
111
|
-
MIT
|
cetustek-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
cetustek/__init__.py,sha256=IDmudJOsB1z-P6kdq3AXlAldzq5ycUrjiFViZmpZnYM,345
|
|
2
|
-
cetustek/client.py,sha256=dpY7SO4PudPe3O8hgHXvEk9iD5eExc260W2wrkeOxqw,3911
|
|
3
|
-
cetustek/models.py,sha256=Z0Pd9ujiF7fmkMMLHZgnxekvDZYdxE3IbxAiWjVrM3s,1565
|
|
4
|
-
cetustek/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
cetustek-0.1.0.dist-info/METADATA,sha256=5S3FSlwCAuxHiv493w1R7mJcm-ez8hdk5-fo8rG_VoU,2765
|
|
6
|
-
cetustek-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
7
|
-
cetustek-0.1.0.dist-info/licenses/LICENSE,sha256=3Zyxxlid2vbpfK2L93sfxat307kl3auQ32KXigeRGiE,1066
|
|
8
|
-
cetustek-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|