A clean and simple Python SDK for interacting with the Iranian Tax Organization's Moadian API
Project description
Moadian SDK
A clean and simple Python SDK for interacting with the Iranian Tax Organization's Moadian API.
Features
- ✅ Simple, intuitive API
- ✅ Automatic Tax ID generation
- ✅ Automatic invoice encryption and signing
- ✅ Date-based serial number management (resets daily)
- ✅ Optional serial storage (file or manual)
- ✅ Rate limiting
- ✅ Complete type hints
- ✅ Comprehensive error handling
- ✅ Server-friendly (no file writes by default)
- ✅ UnitCode enum for measurement units
- ✅ Header parameter overrides
Installation
pip install -e .
Quick Start
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem, UnitCode
# Initialize client
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs"
)
# Create invoice items
items = [
InvoiceItem(
sstid="1234567890123",
sstt="Product Name",
amount=1.0,
unit_fee=100000.0,
vat_rate=9.0,
mu=UnitCode.NUMBER, # Measurement unit
)
]
# Create invoice data
invoice_data = InvoiceData(
seller_tin="YOUR_TAX_ID",
buyer_tin="BUYER_TAX_ID",
items=items,
settlement_method=1, # 1 = Cash, 2 = Credit
)
# Send invoice (SDK handles everything!)
with client:
response = client.send_invoice(invoice_data)
print(f"Invoice sent! UID: {response.uid}")
print(f"Tax ID: {response.tax_id}")
print(f"Serial: {response.serial}")
How It Works
The SDK handles the complete invoice submission process:
- Serial Number Generation: Automatically generates sequential serial numbers (resets daily)
- Tax ID Generation: Creates unique Tax ID using fiscal ID, date, and serial number
- Invoice Building: Constructs invoice dictionary according to Moadian specification
- Encryption: Signs with JWS (RS256) and encrypts with JWE (RSA-OAEP-256 + AES256-GCM)
- Submission: Sends encrypted invoice to Moadian API
- Response: Returns invoice response with UID, reference number, Tax ID, and serial number
Examples
Example 1: Basic Invoice
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem, UnitCode
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
enable_serial_storage=False,
)
# Create invoice items
items = [
InvoiceItem(
sstid="1234567890123",
sstt="محصول نمونه",
amount=2.0,
unit_fee=100000.0,
vat_rate=9.0,
discount=5000.0,
mu=UnitCode.NUMBER,
)
]
# Create invoice data
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
settlement_method=1, # Cash
)
# Send invoice
with client:
response = client.send_invoice(invoice_data)
print(f"✅ Invoice sent! UID: {response.uid}")
print(f" Tax ID: {response.tax_id}")
print(f" Serial: {response.serial}")
Example 2: Using UnitCode Enum
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem, UnitCode
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
)
# Create items with different measurement units
items = [
InvoiceItem(
sstid="PROD001",
sstt="محصول بر اساس کیلوگرم",
amount=5.0,
unit_fee=50000.0,
mu=UnitCode.KILOGRAM, # Using enum
),
InvoiceItem(
sstid="PROD002",
sstt="محصول بر اساس متر",
amount=10.0,
unit_fee=20000.0,
mu=UnitCode.METER, # Using enum
),
InvoiceItem(
sstid="PROD003",
sstt="محصول بر اساس عدد (پیشفرض)",
amount=3.0,
unit_fee=15000.0,
# mu defaults to UnitCode.NUMBER
),
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
)
# You can also use string or int codes
item_with_string = InvoiceItem(
sstid="PROD004",
sstt="محصول با کد رشتهای",
amount=1.0,
unit_fee=10000.0,
mu="164", # String code
)
item_with_int = InvoiceItem(
sstid="PROD005",
sstt="محصول با کد عددی",
amount=1.0,
unit_fee=10000.0,
mu=164, # Integer code
)
# Find UnitCode by code or Persian name
unit_by_code = UnitCode.from_code(164)
if unit_by_code:
print(f"Code 164: {unit_by_code.name} = {unit_by_code.persian_name}")
unit_by_name = UnitCode.from_persian("کیلوگرم")
if unit_by_name:
print(f"Persian 'کیلوگرم': {unit_by_name.name} = {unit_by_name.code}")
Example 3: Invoice with All Optional Fields
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem, UnitCode
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
)
# Create item with all optional fields
items = [
InvoiceItem(
sstid="1234567890123",
sstt="محصول کامل با تمام فیلدها",
amount=2.0,
unit_fee=100000.0,
vat_rate=9.0,
discount=10000.0,
mu=UnitCode.NUMBER,
nw=1.5, # Net weight
cfee=1000.0, # Currency fee
cut="USD", # Currency unit type
exr=42000.0, # Exchange rate
ssrv=200000.0, # Service/Product sale rate value
sscv=200000.0, # Service/Product sale currency value
vba=180000.0, # VAT base amount
odt="DISCOUNT_TYPE", # Other discount type
odr=5.0, # Other discount rate
odam=5000.0, # Other discount amount
olt="LEVY_TYPE", # Other levy type
olr=2.0, # Other levy rate
olam=2000.0, # Other levy amount
consfee=10000.0, # Construction fee
)
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
settlement_method=1,
invoice_type=1,
invoice_pattern=1,
invoice_subject=1,
)
with client:
response = client.send_invoice(invoice_data)
Example 4: Header Parameter Overrides
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
)
items = [
InvoiceItem(
sstid="1234567890123",
sstt="محصول نمونه",
amount=1.0,
unit_fee=100000.0,
)
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
settlement_method=1, # Default: Cash
)
# Override header parameters when sending
with client:
response = client.send_invoice(
invoice_data,
# Override settlement method to Credit
settlement_method=2,
# Override type of business
tob=3,
# Add related tax ID
irtaxid="RELATED123456",
# Add other header fields
tax17=1000.0,
tinc="TAX_ID_CODE",
lno="LICENSE123",
# ... any other header parameter from build_invoice_dict()
)
Example 5: Credit Settlement with Payments
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
)
items = [
InvoiceItem(
sstid="1234567890123",
sstt="محصول با تسویه اعتباری",
amount=1.0,
unit_fee=100000.0,
)
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
settlement_method=2, # Credit
)
# For credit invoices, you can provide payment information
payments = [
{
"iinn": "INSTRUMENT_ID",
"acn": "ACCOUNT_NUMBER",
"trmn": "TERMINAL_NUMBER",
"pmt": 1, # Payment method
"trn": "TRANSACTION_NUMBER",
"pcn": "PAYMENT_CARD_NUMBER",
"pid": "PAYMENT_ID",
"pdt": 1704067200000, # Payment date timestamp
"pv": 100000.0, # Payment value
}
]
with client:
response = client.send_invoice(
invoice_data,
payments=payments,
)
Example 6: Multiple Items
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
)
# Create multiple items
items = [
InvoiceItem(
sstid="PROD001",
sstt="محصول اول",
amount=2.0,
unit_fee=50000.0,
vat_rate=9.0,
discount=5000.0,
),
InvoiceItem(
sstid="PROD002",
sstt="محصول دوم",
amount=3.0,
unit_fee=75000.0,
vat_rate=9.0,
discount=10000.0,
),
InvoiceItem(
sstid="PROD003",
sstt="محصول سوم",
amount=1.0,
unit_fee=100000.0,
vat_rate=9.0,
discount=0.0,
),
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
)
with client:
response = client.send_invoice(invoice_data)
print(f"Total items: {len(items)}")
print("Invoice totals calculated automatically")
Example 7: Error Handling
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem, MoadianException
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
)
items = [
InvoiceItem(
sstid="1234567890123",
sstt="محصول نمونه",
amount=1.0,
unit_fee=100000.0,
)
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
)
try:
with client:
response = client.send_invoice(invoice_data)
print("✅ Success:", response.uid)
except MoadianException as e:
print(f"❌ Moadian Error: {e}")
except Exception as e:
print(f"❌ Unexpected Error: {e}")
Example 8: Manual Serial Storage (Server Environment)
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem
import json
import datetime
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
enable_serial_storage=False, # No file writes
)
items = [
InvoiceItem(
sstid="1234567890123",
sstt="Product Name",
amount=1.0,
unit_fee=100000.0,
)
]
invoice_data = InvoiceData(
seller_tin="YOUR_TAX_ID",
buyer_tin="BUYER_TAX_ID",
items=items,
)
with client:
response = client.send_invoice(invoice_data)
# Store manually in your database/file
invoice_record = {
"uid": response.uid,
"tax_id": response.tax_id, # IMPORTANT: Store this!
"serial": response.serial, # IMPORTANT: Store this!
"reference_number": response.reference_number,
"timestamp": datetime.datetime.now().isoformat(),
}
# Save to database
# db.insert("invoices", invoice_record)
# Or save to file
with open("invoices.json", "a") as f:
json.dump(invoice_record, f)
f.write("\n")
Example 9: File-Based Serial Storage
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem
# Enable automatic serial storage to file
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
enable_serial_storage=True, # Enable file storage
serial_file="last_serial.txt", # Custom file path (optional)
)
items = [
InvoiceItem(
sstid="1234567890123",
sstt="Product Name",
amount=1.0,
unit_fee=100000.0,
)
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
)
# Serial numbers will be automatically saved to file
with client:
response = client.send_invoice(invoice_data)
# Serial automatically saved to last_serial.txt
Example 10: Check Invoice Status
from moadian_sdk import MoadianClient, InvoiceData, InvoiceItem
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
)
items = [
InvoiceItem(
sstid="1234567890123",
sstt="Product Name",
amount=1.0,
unit_fee=100000.0,
)
]
invoice_data = InvoiceData(
seller_tin="1234567890",
buyer_tin="0987654321",
items=items,
)
with client:
response = client.send_invoice(invoice_data)
# Check status
status_list = client.inquiry_by_uid([response.uid])
if status_list:
status = status_list[0].get("status")
print(f"Status: {status}")
if status == "SUCCESS":
print("Invoice accepted!")
elif status == "FAILED":
errors = status_list[0].get("data", {}).get("error", [])
for err in errors:
print(f"Error: {err['message']}")
Serial Number Storage Options
The SDK provides two modes for handling serial numbers:
Option 1: Automatic File Storage (Default: Disabled)
When enable_serial_storage=True, the SDK automatically saves serial numbers to a text file:
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
enable_serial_storage=True, # Enable automatic file storage
serial_file="last_serial.txt" # Optional: custom file path
)
How it works:
- Serial numbers are stored in
last_serial.txt(or custom file) - Format:
YYYY-MM-DD:serial_number(date-based, resets daily) - Automatically loads last serial for today on startup
- Automatically saves after each invoice submission
- File format example:
# Date-based serial numbers (YYYY-MM-DD:serial) 2024-01-15:42 2024-01-14:150 2024-01-13:89
Use when:
- You have write access to the filesystem
- You want automatic persistence across restarts
- Single-instance application
Option 2: Manual Storage (Default: Enabled)
When enable_serial_storage=False (default), the SDK uses in-memory serial management:
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
enable_serial_storage=False # Default - no file writes
)
How it works:
- Serial numbers are managed in-memory only
- Resets to 0 each day automatically
- You must store serial and tax_id manually after each invoice
- Serial numbers reset on application restart
Use when:
- Server environments without write access
- Multi-instance applications (load-balanced)
- You want to store serials in your database
- You need custom storage logic
Certificate Setup
Place your certificates in a certs/ directory:
certs/
├── private_key.pem # Private key (PEM format)
└── certificate.pem # Certificate (PEM format)
The SDK will automatically find these files. If you use different filenames:
client = MoadianClient(
client_id="YOUR_FISCAL_ID",
certs_dir="certs",
private_key_file="my_key.pem",
certificate_file="my_cert.pem",
)
API Reference
MoadianClient
Main client class for interacting with Moadian API.
Initialization
MoadianClient(
client_id: str, # Required: Fiscal memory ID
certs_dir: str = "certs", # Certificate directory
private_key_file: str = "private_key.pem",
certificate_file: str = "certificate.pem",
password: Optional[bytes] = None, # Private key password
enable_serial_storage: bool = False, # Enable file storage (default: False)
serial_file: str = "last_serial.txt", # Serial file path
)
Methods
-
send_invoice(invoice_data, force_serial=None, min_delay=15.0, **header_overrides)- Send invoice (high-level)- Returns:
InvoiceResponsewithuid,reference_number,tax_id,serial - Handles: Tax ID generation, encryption, submission
**header_overrides: Optional keyword arguments to override header/invoice parameters- Any parameter accepted by
build_invoice_dict()can be passed here - Examples:
settlement_method,tob,irtaxid,tax17,tinc,lno, etc.
- Any parameter accepted by
- Returns:
-
send_invoices(invoices, tax_id=None, serial=None)- Send invoices (low-level)- For advanced use cases
-
get_server_information()- Get server info and public keys- Returns:
ServerInfoobject
- Returns:
-
inquiry_by_uid(uid_list, fiscal_id=None)- Check invoice status by UID- Returns: List of status dictionaries
-
inquiry_by_reference_id(reference_ids)- Check invoice status by reference- Returns: List of status dictionaries
-
inquiry_by_time_range(start, end, page_number=1, page_size=10, status=None)- Query invoices by date range -
get_fiscal_information(memory_id)- Get fiscal memory information -
get_taxpayer_information(economic_code)- Get taxpayer information
Models
InvoiceResponse
@dataclass
class InvoiceResponse:
uid: str # Invoice UID (requestTraceId)
reference_number: str # Moadian reference number
tax_id: str # Generated Tax ID (22 characters)
serial: int # Serial number (decimal format)
packet_type: Optional[str]
data: Optional[Any]
InvoiceData
@dataclass
class InvoiceData:
seller_tin: str # Seller Tax ID
buyer_tin: str # Buyer Tax ID
items: List[InvoiceItem] # Invoice items
settlement_method: int = 1 # 1=Cash, 2=Credit
invoice_type: int = 1
invoice_pattern: int = 1
invoice_subject: int = 1
InvoiceItem
@dataclass
class InvoiceItem:
sstid: str # Service/Product ID
sstt: str # Service/Product Title
amount: float # Quantity (double)
unit_fee: float # Unit price (double)
vat_rate: float = 10.0 # VAT rate percentage (double)
discount: float = 0.0 # Discount amount (double)
mu: Union[UnitCode, str, int] = None # Measurement unit (default: UnitCode.NUMBER)
# Optional fields
nw: Optional[float] = None # Net weight
cfee: Optional[float] = None # Currency fee
cut: Optional[str] = None # Currency unit type
exr: Optional[float] = None # Exchange rate
ssrv: Optional[float] = None # Service/Product sale rate value
sscv: Optional[float] = None # Service/Product sale currency value
vba: Optional[float] = None # VAT base amount
odt: Optional[str] = None # Other discount type
odr: Optional[float] = None # Other discount rate
odam: Optional[float] = None # Other discount amount
olt: Optional[str] = None # Other levy type
olr: Optional[float] = None # Other levy rate
olam: Optional[float] = None # Other levy amount
consfee: Optional[float] = None # Construction fee
UnitCode
Enumeration for measurement units (واحدهای سنجش):
class UnitCode(Enum):
NUMBER = (1612, "عدد") # Default
KILOGRAM = (164, "کیلوگرم")
METER = (1627, "متر")
LITER = (1637, "لیتر")
# ... and many more
# Methods
@classmethod
def from_code(cls, code: int) -> Optional[UnitCode]
@classmethod
def from_persian(cls, persian_name: str) -> Optional[UnitCode]
InvoiceStatus
class InvoiceStatus(Enum):
SUCCESS = "SUCCESS"
FAILED = "FAILED"
IN_PROGRESS = "IN_PROGRESS"
NOT_FOUND = "NOT_FOUND"
TIMEOUT = "TIMEOUT"
Important Notes
Serial Number Management
- Serial numbers reset daily - Each day starts from 0 (or 1)
- Must be unique per day - The SDK ensures this automatically
- Storage is optional - Choose based on your environment:
- File storage: Use when you have write access and single instance
- Manual storage: Use in server environments or multi-instance setups
Tax ID and Serial Storage
When enable_serial_storage=False (default):
- You MUST store
tax_idandserialfrom the response - These are critical for invoice tracking and compliance
- Store them in your database or file system
- The SDK provides these values in
InvoiceResponsefor your convenience
When enable_serial_storage=True:
- Serial numbers are automatically saved to file
- You should still store
tax_idfor your records - Serial file format:
YYYY-MM-DD:serial_number
Server Environments
For production server environments:
- Set
enable_serial_storage=False(default) - Store
tax_idandserialin your database after each invoice - Use your database to track serial numbers if needed
- The SDK handles serial generation, you handle persistence
Measurement Units
The SDK provides a UnitCode enum for measurement units. You can:
- Use the enum directly:
mu=UnitCode.KILOGRAM - Use string codes:
mu="164" - Use integer codes:
mu=164 - Find by code:
UnitCode.from_code(164) - Find by Persian name:
UnitCode.from_persian("کیلوگرم")
Default measurement unit is UnitCode.NUMBER (1612, "عدد").
Header Parameter Overrides
You can override any header parameter when sending an invoice:
response = client.send_invoice(
invoice_data,
settlement_method=2, # Override to Credit
tob=3, # Override type of business
irtaxid="RELATED123", # Add related tax ID
tax17=1000.0, # Add tax17
# ... any other header parameter
)
Error Handling
The SDK raises custom exceptions:
MoadianException- Base exceptionAuthenticationException- Authentication failuresAPIException- API errorsCertificateException- Certificate loading errorsInvoiceException- Invoice generation errors
from moadian_sdk import MoadianClient, MoadianException, AuthenticationException
try:
with client:
response = client.send_invoice(invoice_data)
except AuthenticationException as e:
print(f"Auth failed: {e}")
except MoadianException as e:
print(f"Error: {e}")
Project Structure
moadian-sdk/
├── moadian_sdk/ # SDK package
│ ├── __init__.py # Public API
│ ├── client.py # Main API client
│ ├── crypto.py # Encryption/signing
│ ├── invoice.py # Invoice building & Tax ID generation
│ ├── models.py # Data models
│ ├── exceptions.py # Custom exceptions
│ └── utils.py # Certificate utilities
├── example.py # Comprehensive examples
├── pyproject.toml # Project configuration
└── README.md # This file
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moadian_sdk-0.2.0.tar.gz.
File metadata
- Download URL: moadian_sdk-0.2.0.tar.gz
- Upload date:
- Size: 34.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8205a150488cc481776a39a5ed5811084bb90737073679c7c1f1c104fd48a80b
|
|
| MD5 |
682d46312448e193599f226106947336
|
|
| BLAKE2b-256 |
9245725975b16e2aa96f031f2ccb94901b9988639350103d7e5066e3e35462cd
|
File details
Details for the file moadian_sdk-0.2.0-py3-none-any.whl.
File metadata
- Download URL: moadian_sdk-0.2.0-py3-none-any.whl
- Upload date:
- Size: 25.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.18
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78c0a7571e44be8fe3d2f66958bae2a9aedd3145f55b20db884f03ce7008c861
|
|
| MD5 |
e380cdf5910fe9a188412f7125d4a930
|
|
| BLAKE2b-256 |
4bb3bb69dde6fde54ceda36198519cf72821db72c329d207862a389d5e101862
|