FastAPI¶
FastAPI 0.115 or newer.
Configuration¶
The dependencies build the client with from_env(), so set the NATCASH_* environment variables
(see Getting started). The sandbox is the default.
Dependencies¶
get_natcash_clientreturns a cachedNatcashClient.get_async_natcash_clientreturns a cachedAsyncNatcashClient.NatcashCallbackis anAnnotateddependency that parses and verifies the callback query string and returns aCallback. It answers400on a bad signature.
from decimal import Decimal
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
from natcashify import AsyncNatcashClient
from natcashify.fastapi import NatcashCallback, get_async_natcash_client
app = FastAPI()
Client = Annotated[AsyncNatcashClient, Depends(get_async_natcash_client)]
@app.post("/checkout")
async def checkout(client: Client) -> dict[str, str]:
payment = await client.create_payment("ORDER-001", Decimal("250"))
return {"url": payment.checkout_url}
@app.get("/natcash/callback")
async def callback(callback: NatcashCallback, client: Client) -> dict[str, str]:
order = orders.get(callback.order_number)
if order is None:
raise HTTPException(status_code=404)
if order.is_paid:
return {"status": "paid"}
transaction = await client.get_transaction(callback.order_number)
if transaction.is_successful and transaction.amount == order.amount:
order.mark_paid(transaction.transaction_id)
return {"status": "paid"}
return {"status": "not paid"}
The dependency never calls get_transaction itself: the callback reaches your shop through the
payer's browser, so confirm with the gateway and compare the amount before fulfilling. The
browser replays the callback on refresh, so skip orders that are already paid, see
Callbacks.
Testing¶
Override the dependency with a client that uses a fake transport:
from natcashify.core.testing import AsyncFakeTransport
app.dependency_overrides[get_async_natcash_client] = lambda: AsyncNatcashClient.from_env(
environ=ENV, transport=AsyncFakeTransport()
)
Sign test callbacks with sign_callback, see
Getting started.