The Python Invoice model must be a faithful mirror of the Soroban smart contract struct. There are no line items — the contract models an invoice as a single description + amount + token tuple. All monetary fields (amount, amount_paid, amount_refunded) map to Python Decimal (from i128). All timestamp fields (date_created, date_paid, expires_at) map to Python datetime (converted from u64 Unix timestamps). The id field is the u64 on-chain invoice ID, mapped to Python int.
Proposed Steps:
- Create
src/shade/models/invoice.py.
- Define
InvoiceStatus as a StrEnum with values: pending, paid, expired, partially_paid, refunded.
- Define
Invoice(ShadeObject) with fields: id: int, description: str, amount: Decimal, token: str, status: InvoiceStatus, merchant_id: int, payer: Optional[str], date_created: datetime, date_paid: Optional[datetime], amount_paid: Decimal, amount_refunded: Decimal, expires_at: Optional[datetime].
- Add computed properties:
is_paid: bool, is_expired: bool, outstanding: Decimal.
- Write
_from_contract_dict(data: dict) classmethod for u64 → int and i128 → Decimal conversions.
Acceptance Criteria:
Invoice.from_dict(contract_response) populates all fields correctly.
invoice.amount is a Decimal, not an int or float.
invoice.date_created is a datetime, not an int.
invoice.payer is None for an unpaid invoice.
invoice.is_expired returns True when expires_at is in the past.
invoice.outstanding equals amount - amount_paid for a partially paid invoice.
- The model has no
line_items, customer_email, payment_url, or total fields.
The Python
Invoicemodel must be a faithful mirror of the Soroban smart contract struct. There are no line items — the contract models an invoice as a singledescription + amount + tokentuple. All monetary fields (amount,amount_paid,amount_refunded) map to PythonDecimal(from i128). All timestamp fields (date_created,date_paid,expires_at) map to Pythondatetime(converted from u64 Unix timestamps). Theidfield is the u64 on-chain invoice ID, mapped to Pythonint.Proposed Steps:
src/shade/models/invoice.py.InvoiceStatusas aStrEnumwith values:pending,paid,expired,partially_paid,refunded.Invoice(ShadeObject)with fields:id: int,description: str,amount: Decimal,token: str,status: InvoiceStatus,merchant_id: int,payer: Optional[str],date_created: datetime,date_paid: Optional[datetime],amount_paid: Decimal,amount_refunded: Decimal,expires_at: Optional[datetime].is_paid: bool,is_expired: bool,outstanding: Decimal._from_contract_dict(data: dict)classmethod for u64 → int and i128 → Decimal conversions.Acceptance Criteria:
Invoice.from_dict(contract_response)populates all fields correctly.invoice.amountis aDecimal, not anintorfloat.invoice.date_createdis adatetime, not an int.invoice.payerisNonefor an unpaid invoice.invoice.is_expiredreturnsTruewhenexpires_atis in the past.invoice.outstandingequalsamount - amount_paidfor a partially paid invoice.line_items,customer_email,payment_url, ortotalfields.