Skip to content

FastAPI

pip install "natcashify[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_client returns a cached NatcashClient.
  • get_async_natcash_client returns a cached AsyncNatcashClient.
  • NatcashCallback is an Annotated dependency that parses and verifies the callback query string and returns a Callback. It answers 400 on 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.