Skip to main content

Python SDK for the Paychainly crypto payment platform

Project description

paychainly

Python SDK for the Paychainly crypto payment platform. Accept USDT on BNB Smart Chain — manage customers, deposit addresses, payment links, invoices, and withdrawals without writing raw HTTP calls.

PyPI version Python Network


Table of contents


Installation

pip install paychainly

Requires Python 3.10+.


Setup

Create one client instance and reuse it across your app.

from paychainly import Paychainly

client = Paychainly(
    api_key="pk_live_...",                   # required — from your dashboard
    base_url="https://api.paychainly.com",  # default, optional
    timeout=30.0,                            # seconds, default 30
    retries=3,                               # retry on 5xx/network errors
    retry_delay=0.5,                         # base delay in seconds (exponential backoff)
)

Quick start

Not sure which pattern to use? Pick based on your use case:

Use case Pattern
User wallet / balance top-up (address only) Pattern 1 — Wallet
User wallet with fixed-amount hosted page Pattern 2 — Wallet + payment link
E-commerce order / SaaS subscription (logged-in) Pattern 3 — Order checkout (logged-in)
One-off payment, no account needed Pattern 4 — Guest checkout

Pattern 1 — Wallet / deposit system

Each user gets one permanent deposit address. They send USDT to it — you credit their balance. Best for top-up flows, wallets, and balance-based systems.

Step 1 — Get or create the customer

customers.create() raises ApiError with status 409 if the customer already exists. Always wrap it with a fallback to get_by_identifier() so it is safe to call on every login or page load.

from paychainly import Paychainly, ApiError

client = Paychainly(api_key="pk_live_...")

def get_or_create_customer(user_id: str, email: str = None, name: str = None):
    """Returns existing customer or creates one. Safe to call on every login."""
    try:
        return client.customers.create(
            identifier=user_id,
            email=email,
            name=name,
        )
    except ApiError as err:
        if err.status == 409:
            return client.customers.get_by_identifier(user_id)
        raise

The returned Customer object:

# Customer(
#   id=42,
#   customer_uid="dd8693ec-8b5f-43ef-b4e5-1d0a088df1a3",  # Paychainly UUID
#   identifier="user_abc123",                               # your user ID
#   email="john@example.com",
#   name="John Doe",
#   created_at="2026-06-02T10:00:00.000Z",
#   updated_at="2026-06-02T10:00:00.000Z",
# )

Step 2 — Get the wallet address

mode="reuse" returns the same address every time for this customer — call it as many times as you want.

def get_wallet_address(user_id: str):
    wallet = client.addresses.generate(
        token_symbol="USDT",
        network="BNB",
        mode="reuse",
        customer={"identifier": user_id},
    )
    return {
        "address":      wallet.address,       # "0xABC..." — show this to the user
        "network":      wallet.network,       # "BNB"
        "token_symbol": wallet.token_symbol,  # "USDT"
    }

Step 3 — Put it together

def setup_user_wallet(user_id: str, email: str = None, name: str = None):
    customer = get_or_create_customer(user_id, email, name)
    wallet   = get_wallet_address(user_id)
    return {
        "customer_id":  customer.id,
        "customer_uid": customer.customer_uid,
        "address":      wallet["address"],
        "network":      wallet["network"],
        "token_symbol": wallet["token_symbol"],
    }

# Call this whenever the user opens their wallet page
wallet = setup_user_wallet("user_abc123", email="john@example.com")
print("Deposit address:", wallet["address"])
# → "Send USDT (BEP-20) to 0xABC... on BNB Smart Chain"

Step 4 — How users send USDT

After showing wallet["address"] to your user they have three options:

  • Copy and paste — show the address as text with a copy button; user opens any wallet app and pastes it
  • QR code — encode wallet["address"] as a QR code for mobile wallets
  • Connect wallet — use MetaMask / WalletConnect on the frontend to trigger a direct USDT transfer

Pattern 2 — Wallet + payment link

Same permanent address as Pattern 1, but you also create a hosted payment page for each top-up — useful when you want a countdown timer, QR page, or a fixed-amount checkout experience.

def create_wallet_topup(user_id: str, amount: str, order_id: str):
    # 1. Ensure the permanent wallet address exists
    wallet = client.addresses.generate(
        token_symbol="USDT",
        network="BNB",
        mode="reuse",
        customer={"identifier": user_id},
    )

    # 2. Create a payment link tied to that address
    link = client.payment_links.create_for_address(
        wallet.address,
        amount=amount,                          # e.g. "50.00"
        memo=f"Top-up #{order_id}",             # shown on the checkout page
        expiry_hours=24,
        metadata={"order_id": order_id, "user_id": user_id},
    )

    return {
        "address":    wallet.address,
        "pay_url":    link.pay_url,             # hosted page with QR + timer
        "expires_at": link.expires_at,
    }

topup = create_wallet_topup("user_abc123", amount="50.00", order_id="topup_001")
# Redirect user to topup["pay_url"] or show topup["address"] yourself

Pattern 3 — Order checkout (logged-in user)

Each order gets its own checkout: a fresh deposit address + hosted payment page, linked to the logged-in customer. payment_links.create() does everything in one call.

def create_checkout(order_id: str, amount: str, user_id: str,
                    memo: str = None, note: str = None, metadata: dict = None):
    link = client.payment_links.create(
        unique_id=order_id,          # idempotency key — safe to retry with the same order_id
        token_symbol="USDT",
        network="BNB",
        amount=amount,               # e.g. "49.99" — omit for open-amount
        memo=memo or f"Order #{order_id}",
        note=note,
        expiry_hours=24,
        metadata={"order_id": order_id, **(metadata or {})},
        customer={"identifier": user_id},
    )
    return link

link = create_checkout(
    order_id="order_001",
    amount="49.99",
    memo="Premium Plan",
    user_id="user_abc123",
)

The returned PaymentLink object:

# PaymentLink(
#   id=77,
#   slug="abc123xyz",
#   unique_id="order_001",
#   address="0xDEPOSIT...",
#   pay_url="https://paychainly.com/pay/abc123xyz",
#   amount="49.99",
#   token_symbol="USDT",
#   network="BNB",
#   status="active",
#   expires_at="2026-06-03T10:00:00.000Z",
#   metadata={"order_id": "order_001"},
# )

You have three options from the same response — pick one:

# Option 1 — redirect to Paychainly's hosted checkout page
#   Includes QR code, countdown timer, MetaMask connect, and payment confirmation
redirect_url = link.pay_url
# → https://paychainly.com/pay/abc123xyz

# Option 2 — show in your own custom UI
print(f"Send {link.amount} {link.token_symbol} to: {link.address}")
print(f"Expires at: {link.expires_at}")

# Option 3 — QR-encode link.address yourself (any QR library works)

Pattern 4 — Guest checkout (no account)

Identical to Pattern 3 but no customer field — no customer record is created.

def create_guest_checkout(order_id: str, amount: str, memo: str = None):
    link = client.payment_links.create(
        unique_id=order_id,
        token_symbol="USDT",
        network="BNB",
        amount=amount,
        memo=memo or f"Order #{order_id}",
        expiry_hours=24,
        # no customer= field
    )
    return link

link = create_guest_checkout(order_id="order_002", amount="29.99")

# Redirect guest to the hosted checkout page
redirect_url = link.pay_url
# → https://paychainly.com/pay/xyz456abc

Receiving payments

Webhooks (recommended)

Set your webhook URL in the dashboard. Paychainly calls your endpoint the moment USDT arrives.

Important: Read the raw request body before JSON-parsing. Signature verification requires the unmodified bytes.

Flask — wallet pattern (credit user balance):

from flask import Flask, request, abort
from paychainly import Webhooks, WebhookSignatureError

app = Flask(__name__)
WEBHOOK_SECRET = "whsec_..."

@app.post("/webhooks/paychainly")
def paychainly_webhook():
    raw_body  = request.get_data()          # raw bytes — do NOT call request.json first
    signature = request.headers.get("X-Paychainly-Signature", "")

    try:
        event = Webhooks.verify(raw_body, signature, WEBHOOK_SECRET)
    except WebhookSignatureError:
        abort(400)

    if event.event == "deposit_detected":
        customer = client.customers.get_by_deposit_address(event.to_address)
        credit_user_balance(customer.identifier, event.amount)
        print(f"+{event.amount} USDT credited to {customer.identifier}")
        print(f"TX: {event.tx_hash} — block {event.block_number}")

    return "", 200   # always 200 — prevents retries

FastAPI — checkout pattern (fulfil an order):

from fastapi import FastAPI, Request, HTTPException
from paychainly import Webhooks, WebhookSignatureError

app = FastAPI()
WEBHOOK_SECRET = "whsec_..."

@app.post("/webhooks/paychainly")
async def paychainly_webhook(request: Request):
    raw_body  = await request.body()
    signature = request.headers.get("x-paychainly-signature", "")

    try:
        event = Webhooks.verify(raw_body, signature, WEBHOOK_SECRET)
    except WebhookSignatureError:
        raise HTTPException(status_code=400)

    if event.event == "deposit_detected":
        links = client.payment_links.get_by_address(event.to_address)
        if links:
            order_id = links[0].metadata.get("order_id")
            await fulfill_order(order_id, event.amount, event.tx_hash)

    return {"status": "ok"}

The webhook event object:

# PaychainlyEvent(
#   event="deposit_detected",
#   tx_hash="0xabc123...",
#   from_address="0xUSER_WALLET...",   # who sent the USDT
#   to_address="0xDEPOSIT...",         # your user's deposit address
#   amount="50.00",
#   block_number=48123456,
#   timestamp=1748862000,
#   user_id="user_abc123",             # identifier you set on the customer
# )

Polling (development only)

def check_for_deposit(address: str):
    result = client.transactions.list_by_address(address, limit=5)
    if result.total > 0:
        tx = result.data[0]
        print(f"{tx.amount} USDT — {tx.status}{tx.tx_hash}")
        return tx
    return None

Error handling

from paychainly import ApiError

try:
    client.customers.create(identifier="user_123")
except ApiError as err:
    print(err.status)   # HTTP status code, e.g. 409
    print(err.code)     # machine-readable code, e.g. "DUPLICATE_IDENTIFIER"
    print(err.message)  # human-readable description
Status Code Cause
409 DUPLICATE_IDENTIFIER Customer already exists. Use get_by_identifier() instead.
400 INVALID_SIGNATURE Webhook signature mismatch. Check your webhook secret.
401 Missing or invalid API key.
404 Resource not found.

Async usage

Every method has an async equivalent. Use AsyncPaychainly in FastAPI, async Django, or any asyncio app.

import asyncio
from paychainly import AsyncPaychainly, ApiError

async def main():
    async with AsyncPaychainly(api_key="pk_live_...") as client:

        # Pattern 1 — wallet
        try:
            customer = await client.customers.create(identifier="user_abc123")
        except ApiError as err:
            if err.status == 409:
                customer = await client.customers.get_by_identifier("user_abc123")
            else:
                raise

        wallet = await client.addresses.generate(
            token_symbol="USDT",
            network="BNB",
            mode="reuse",
            customer={"identifier": customer.identifier},
        )
        print("Wallet address:", wallet.address)

        # Pattern 3 — order checkout (logged-in)
        link = await client.payment_links.create(
            unique_id="order_001",
            token_symbol="USDT",
            network="BNB",
            amount="49.99",
            customer={"identifier": customer.identifier},
        )
        print("Pay URL:", link.pay_url)

asyncio.run(main())

Pattern comparison

# Pattern Customer Address mode Payment link Best for
1 Wallet — address only Required reuse — permanent Not needed Open-ended deposits, balance top-ups
2 Wallet — with payment link Required reuse — same address create_for_address() per top-up Fixed-amount top-ups with hosted page
3 Order checkout — logged-in user Required generate_new per order payment_links.create() per order E-commerce, invoices, subscriptions
4 Guest checkout — no account Not needed generate_new per order payment_links.create() per order One-off payments, anonymous checkout

API reference

client.customers

Method Description
create(identifier, ...) Create a new customer. Raises ApiError(409) if identifier exists.
get(id) Get customer by Paychainly numeric ID.
get_by_identifier(identifier) Get customer by your own user ID.
get_by_email(email) Get customer by email address.
get_by_uid(customer_uid) Get customer by Paychainly UUID.
get_by_deposit_address(address) Look up which customer owns a deposit address.
list(...) List customers with optional pagination.
list_all(...) Fetch all customers across pages automatically.
update_by_identifier(identifier, ...) Update customer fields by your user ID.
update_by_email(email, ...) Update customer fields by email.

client.addresses

Method Description
generate(token_symbol, network, ...) Generate a deposit address. Use mode="reuse" for permanent wallets.
get(id) Get address by numeric ID.
get_by_address(address) Get address record by on-chain address string.
list(...) List all deposit addresses.
list_all(...) Fetch all addresses across pages automatically.
revoke(id) Revoke a deposit address by ID.
revoke_by_address(address) Revoke a deposit address by its on-chain address string.

client.payment_links

Method Description
create(unique_id, token_symbol, network, ...) Create a payment link with a fresh deposit address and hosted page.
get(id) Get payment link by numeric ID.
get_by_slug(slug) Get payment link by URL slug.
get_by_unique_id(unique_id) Get payment link by your order/reference ID.
get_by_address(address) Get payment links for a deposit address.
create_for_address(address, ...) Create a payment link for an existing deposit address (Pattern 2).
list(...) List all payment links.
list_all(...) Fetch all payment links across pages automatically.

client.transactions

Method Description
get(id) Get transaction by numeric ID.
get_by_hash(tx_hash) Get transaction by on-chain hash.
list(...) List all transactions with optional filters.
list_all(...) Fetch all transactions across pages automatically.
list_by_address(address, ...) List transactions for a specific deposit address.

client.withdrawals

Method Description
create(idempotency_key, network, to_address, amount, fee_mode, ...) Initiate a USDT withdrawal.
get(id) Get withdrawal by numeric ID.
list(...) List all withdrawals.
list_all(...) Fetch all withdrawals across pages automatically.
list_by_address(address, ...) List withdrawals for a specific address.
cancel(id) Cancel a pending withdrawal.

client.invoices

Method Description
get(tx_id_or_hash) Get invoice by transaction ID or hash (auto-detected).
get_by_id(id) Get invoice by numeric transaction ID.
get_by_hash(tx_hash) Get invoice by on-chain transaction hash.

client.sandbox

Method Description
credit(address, amount) Simulate a USDT deposit in sandbox mode. Triggers the full webhook + sweep flow.

client.system

Method Description
health() Check API health status (DB, RPC, gas wallet).

Webhooks (static class)

Method Description
Webhooks.verify(raw_body, signature, secret) Verify a webhook signature. Raises WebhookSignatureError if invalid. Returns PaychainlyEvent.
Webhooks.sign(payload, secret) Generate an HMAC-SHA256 signature for a payload dict. Useful for testing.

Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paychainly-1.0.1.tar.gz (18.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paychainly-1.0.1-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file paychainly-1.0.1.tar.gz.

File metadata

  • Download URL: paychainly-1.0.1.tar.gz
  • Upload date:
  • Size: 18.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for paychainly-1.0.1.tar.gz
Algorithm Hash digest
SHA256 ff617644cd02784987ec336a19056aad1a59ceb21b5124cb9b957966b1eec69b
MD5 a913f5d704f34c07ce1ad0e05f680076
BLAKE2b-256 6c73580d9d45cc6b3d2afc945f9624398eb4614550760b2a798ee2e7da309b46

See more details on using hashes here.

File details

Details for the file paychainly-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: paychainly-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 18.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for paychainly-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0d46af099155161bc88d89ab602ede5a47b932a3f3f41f9be6bfbf5d39f36f36
MD5 cb8f78f1165206d75d7c8dc08cb7f612
BLAKE2b-256 ed94ff3fe6b155cfd25c65cbf8c10110147b5a9b5834f409c1b32e4212db04f1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page