Cleanup: remove internal references and scaffolding
This commit is contained in:
parent
b4a81e0ab8
commit
812393d030
10 changed files with 72 additions and 141 deletions
|
|
@ -1,17 +1,16 @@
|
|||
"""cardano-checkout — merchant-side Cardano payment lifecycle in Python.
|
||||
|
||||
Zero-custody by design: the merchant brings a wallet xpub and an
|
||||
Zero-custody: the merchant brings a wallet xpub and an
|
||||
:class:`~cardano_checkout.store.InvoiceStore` implementation. The SDK
|
||||
owns the payment lifecycle — per-invoice receive-address bookkeeping,
|
||||
Koios UTxO polling, confirm / underpay / overpay classification, and
|
||||
time-windowed repricing against a consumer-supplied oracle.
|
||||
|
||||
**The SDK deliberately does NOT ship Cardano primitives.** Address
|
||||
derivation, transaction building, chain context, and native-script
|
||||
minting all live in `pycardano <https://github.com/Python-Cardano/pycardano>`_
|
||||
and are consumer concerns. See the README for the pairing pattern and
|
||||
for the CIP-25 v2 metadata-builder snippet (a 60-line helper that fits
|
||||
anywhere in your own code without needing a separate dep).
|
||||
Does NOT ship Cardano primitives. Address derivation, transaction
|
||||
building, chain context, and native-script minting live in
|
||||
`pycardano <https://github.com/Python-Cardano/pycardano>`_. See the
|
||||
README for the pairing pattern and the CIP-25 v2 metadata-builder
|
||||
snippet.
|
||||
|
||||
Quick start::
|
||||
|
||||
|
|
@ -20,7 +19,6 @@ Quick start::
|
|||
store = InMemoryStore() # or your SQLAlchemy / asyncpg / sqlite adapter
|
||||
|
||||
async def my_price_fn(usd: float) -> int:
|
||||
# your oracle — CoinGecko / Koios ticker / fixed rate in tests
|
||||
rate = await fetch_ada_usd_rate()
|
||||
return int(round(usd / rate * 1_000_000))
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ derived from the merchant's xpub, an expected amount in lovelace, a
|
|||
USD-denominated label, and a lifecycle state that transitions as the
|
||||
chain confirms payment.
|
||||
|
||||
The Invoice is deliberately framework-agnostic — persistence is
|
||||
delegated to an :class:`InvoiceStore` (see :mod:`cardano_checkout.store`).
|
||||
Persistence is delegated to an :class:`InvoiceStore`
|
||||
(see :mod:`cardano_checkout.store`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
|
|
@ -13,17 +13,12 @@ Koios endpoint used::
|
|||
POST https://api.koios.rest/api/v1/address_utxos
|
||||
Body: {"_addresses": ["addr1..."]}
|
||||
|
||||
Status transitions applied here::
|
||||
Status transitions::
|
||||
|
||||
PENDING ──► CONFIRMED (received >= expected * CONFIRM_TOLERANCE)
|
||||
PENDING ──► UNDERPAID (received > 0 but below tolerance)
|
||||
PENDING ──► OVERPAID (received >= expected * OVERPAY_THRESHOLD)
|
||||
PENDING ──► EXPIRED (after reprice_count exhausts — see reprice_expired_invoices)
|
||||
|
||||
Behavioral shape is identical to the original the host app ``services/cardano_monitor.py``:
|
||||
same polling intervals, same Koios URL, same 2% confirm / overpay tolerances.
|
||||
The only change is that persistence is now delegated to the store Protocol
|
||||
instead of being welded to SQLAlchemy + the ``CardanoPayment`` model.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -41,10 +36,6 @@ from cardano_checkout.store import InvoiceStore
|
|||
# returns the current-market lovelace equivalent as int. Invoked by
|
||||
# :func:`reprice_expired_invoices` to generate fresh quotes when an
|
||||
# invoice's quote window lapses without payment.
|
||||
#
|
||||
# SDK intentionally does NOT ship an oracle. Consumers wire whatever
|
||||
# price source they trust (CoinGecko, Koios ticker, their own DEX feed,
|
||||
# or a constant for tests).
|
||||
PriceFn = Callable[[float], Awaitable[int]]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -52,11 +43,11 @@ logger = logging.getLogger(__name__)
|
|||
KOIOS_URL = "https://api.koios.rest/api/v1/address_utxos"
|
||||
KOIOS_TIMEOUT = 15 # seconds
|
||||
|
||||
# Tolerance for confirming payment (2%) — unchanged from v0.1 / the host app.
|
||||
# Tolerance for confirming payment (2%).
|
||||
CONFIRM_TOLERANCE = 0.98
|
||||
OVERPAY_THRESHOLD = 1.02
|
||||
|
||||
# Default reprice cap + window (matches the host app defaults).
|
||||
# Default reprice cap + window.
|
||||
DEFAULT_MAX_REPRICINGS = 3
|
||||
DEFAULT_PAYMENT_WINDOW_MINUTES = 15
|
||||
|
||||
|
|
@ -108,8 +99,7 @@ async def check_address_utxos(
|
|||
return []
|
||||
|
||||
|
||||
# Backwards-compatible alias — monitor.py in the host app imports the private name.
|
||||
# Keeping a leading-underscore alias so the the host app shim can still reach it.
|
||||
# Leading-underscore alias kept for callers that imported the private name.
|
||||
_check_address_utxos = check_address_utxos
|
||||
|
||||
|
||||
|
|
@ -133,7 +123,7 @@ async def evaluate_utxos(
|
|||
- ``received_assets`` — ``{policy_id.asset_name_hex: quantity}``.
|
||||
- ``latest_tx_hash`` — most recent observed tx hash, or None if no UTXOs.
|
||||
|
||||
Status rules mirror the host app exactly:
|
||||
Status rules:
|
||||
|
||||
- No UTXOs → ``PENDING`` (no change)
|
||||
- ``total_value >= expected * OVERPAY_THRESHOLD`` → ``OVERPAID`` (treated as confirmed)
|
||||
|
|
@ -168,11 +158,10 @@ async def evaluate_utxos(
|
|||
if qty > 0:
|
||||
received_assets[asset_id] = received_assets.get(asset_id, 0) + qty
|
||||
|
||||
# ADA-only matching. Any native tokens landed in the same UTxOs are
|
||||
# recorded in received_assets for visibility but do NOT contribute to
|
||||
# the payment-matched total. Consumers who want to accept stablecoins
|
||||
# or other native tokens wrap this function with their own asset-to-
|
||||
# lovelace converter before comparing against expected_lovelace.
|
||||
# ADA-only matching. Native tokens in the same UTxOs are recorded in
|
||||
# received_assets for visibility but do NOT contribute to the
|
||||
# payment-matched total. Wrap this function with your own
|
||||
# asset-to-lovelace converter to accept native tokens.
|
||||
total_value = raw_lovelace
|
||||
|
||||
if expected_lovelace == 0:
|
||||
|
|
@ -190,7 +179,7 @@ async def evaluate_utxos(
|
|||
return new_status, raw_lovelace, total_value, received_assets, latest_tx_hash
|
||||
|
||||
|
||||
# Backwards-compatible alias.
|
||||
# Leading-underscore alias for callers that imported the private name.
|
||||
_evaluate_utxos = evaluate_utxos
|
||||
|
||||
|
||||
|
|
@ -313,18 +302,17 @@ async def reprice_expired_invoices(
|
|||
Args:
|
||||
store: Persistence backend.
|
||||
price_fn: Async callable that takes a USD amount and returns the
|
||||
current lovelace equivalent. Consumer-supplied — the SDK does
|
||||
not ship an oracle. A simple wiring looks like::
|
||||
current lovelace equivalent. Example::
|
||||
|
||||
from cardano_checkout.monitor import reprice_expired_invoices
|
||||
|
||||
async def my_price_fn(usd: float) -> int:
|
||||
rate = await coingecko_fetch_ada_usd() # your code
|
||||
rate = await coingecko_fetch_ada_usd()
|
||||
return int(round(usd / rate * 1_000_000))
|
||||
|
||||
await reprice_expired_invoices(store, price_fn=my_price_fn)
|
||||
window_minutes: New expiry window per reprice. the host app default 15.
|
||||
max_repricings: Give-up threshold. the host app default 3.
|
||||
window_minutes: New expiry window per reprice. Default 15.
|
||||
max_repricings: Give-up threshold. Default 3.
|
||||
limit: Max pending invoices to process per call.
|
||||
|
||||
Returns:
|
||||
|
|
|
|||
|
|
@ -6,13 +6,6 @@ The scheduler drives two jobs against a consumer-supplied
|
|||
- :func:`cardano_checkout.monitor.check_pending_invoices` — every 15 seconds
|
||||
- :func:`cardano_checkout.monitor.reprice_expired_invoices` — every 60 seconds
|
||||
|
||||
That's the *full* SDK job surface. The subscription-level + grace-period
|
||||
jobs that the original the host app scheduler shipped are the host app-specific
|
||||
(they touch ``Company``, ``Subscription``, ``SubscriptionPayment`` models
|
||||
that are merchant-specific) — those live in
|
||||
:mod:`cardano_checkout.hostapp_compat` so the host app can still import the
|
||||
exact wrappers it has always used, without polluting the generic SDK.
|
||||
|
||||
Usage::
|
||||
|
||||
from cardano_checkout.scheduler import InvoiceScheduler
|
||||
|
|
@ -54,7 +47,7 @@ class InvoiceScheduler:
|
|||
store: Persistence backend. Required.
|
||||
koios_url: Chain-query endpoint. Override for testnet / custom gateways.
|
||||
check_interval_seconds: How often to poll Koios for pending invoices.
|
||||
Defaults to 15 — identical to the host app's production cadence.
|
||||
Defaults to 15.
|
||||
reprice_interval_seconds: How often to sweep for expired invoices.
|
||||
Defaults to 60.
|
||||
payment_window_minutes: Re-expiry window when repricing.
|
||||
|
|
@ -87,9 +80,8 @@ class InvoiceScheduler:
|
|||
|
||||
async def _job_reprice_expired(self) -> None:
|
||||
if self.price_fn is None:
|
||||
# No oracle wired — skip repricing silently. Consumers that
|
||||
# don't care about the USD-lock workflow (e.g. fixed-ADA
|
||||
# invoices) will never configure a price_fn; that's fine.
|
||||
# No oracle wired — skip repricing. Fixed-ADA invoices don't
|
||||
# need one.
|
||||
return
|
||||
try:
|
||||
await reprice_expired_invoices(
|
||||
|
|
@ -149,12 +141,9 @@ class InvoiceScheduler:
|
|||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Backwards-compatible free-function API
|
||||
# Free-function API around a module-level default instance.
|
||||
# Prefer the InvoiceScheduler class for anything nontrivial.
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# Early adopters may have imported ``start_cardano_scheduler`` / ``stop_cardano_scheduler``
|
||||
# directly. Provide those as thin wrappers around a module-level default instance.
|
||||
# Using the InvoiceScheduler class is preferred for anything nontrivial.
|
||||
|
||||
_default: Optional[InvoiceScheduler] = None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
"""Persistence abstraction for Invoice objects.
|
||||
|
||||
The SDK does not prescribe a database. Consumers implement
|
||||
:class:`InvoiceStore` against whatever backend suits them — SQLAlchemy
|
||||
(the host app pattern), SQLite (example-studio pattern), Postgres raw
|
||||
(high-volume pattern), in-memory dict (tests).
|
||||
:class:`InvoiceStore` against whatever backend suits them — SQLAlchemy,
|
||||
asyncpg, SQLite, in-memory dict.
|
||||
|
||||
All methods are async so the same Protocol works cleanly for both
|
||||
All methods are async so the same Protocol works for both
|
||||
asyncpg/asyncio-sqlalchemy backends and synchronous backends wrapped
|
||||
with ``asyncio.to_thread``.
|
||||
|
||||
This module also ships :class:`InMemoryStore` — a reference implementation
|
||||
used by the test suite and useful as a drop-in for local development or
|
||||
ephemeral workflows that don't need durability.
|
||||
Also ships :class:`InMemoryStore` — a reference implementation used by
|
||||
the test suite and useful for local development.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue