Skip to main content

payitgo for Python

Accept crypto payments with payitgo from Python 3.8+. No dependencies.

Full reference, with every method, option, type and error: api.payitgo.co/docs/sdk-python.

pip install payitgo

The package is payitgo on PyPI; you import it as payitgo.

Accept a payment in three steps

import os
import payitgo
from flask import Flask, abort, redirect, request

client = payitgo.Client(
    os.environ["PAYITGO_KEY"],
    base_url=os.environ["PAYITGO_BASE_URL"],
    webhook_secret=os.environ["PAYITGO_WEBHOOK_SECRET"],  # the signing secret: see Webhooks (IPN) below
)
app = Flask(__name__)

# 1. Create a payment and send the customer to the checkout page.
@app.post("/checkout")
def checkout():
    payment = client.payments.create(
        order_id=request.form["order_id"],   # your order id: safe to retry
        amount_usd=request.form["total"],    # e.g. "19.99"
        # token="USDT_TRC20",                # optional: without it the customer picks the coin
        success_url="https://shop.example/thanks",
    )
    return redirect(payment.checkout_url)

# 2. Receive the notification (the complete handler is under Webhooks (IPN) below).
#    Use the raw body: the signature covers its exact bytes.
@app.post("/payitgo/ipn")
def ipn():
    try:
        event = client.webhooks.construct_event(request.get_data(), request.headers.get("X-Webhook-Signature"))
    except payitgo.SignatureVerificationError:
        abort(400)
    # 3. Fulfil the order once the payment is final.
    if event.type == "payment.confirmed":
        mark_order_paid(event.data.order_id)  # make this idempotent: events can arrive twice
    return "", 200

Start with a test key, where you simulate what the customer pays; switch to a live key when the account owner has turned live mode on. A key does not say its mode (the dashboard shows a Test or Live badge next to it): client.is_live() asks payitgo, once.

Prices in other currencies

To price a payment in your own currency, give price_amount and price_currency (an ISO 4217 code such as EUR, GBP or NGN) instead of amount_usd. payitgo converts the price into dollars at the rate of the moment, and the payment keeps both:

payment = client.payments.create(
    order_id="ORD-1002",
    price_amount="45.50",    # sent as str(price_amount): a str or a Decimal, not a float
    price_currency="EUR",    # any case
)
# payment.price_amount "45.5", payment.price_currency "EUR",
# payment.price_rate "1.082" (dollars per euro), payment.usd_amount "49.23"

Everything else (usd_amount, crypto_amount, fees, minimums) stays in dollars; a payment priced in dollars has no price_* keys (read them with .get()). Give amount_usd or the pair, not both (400). When no exchange rate can be had, create raises a PayitgoError with status 503 (exchange rate unavailable for price_currency: try again shortly) once the client's retries are spent: try again shortly.

Your own payment page

checkout_url is only for payitgo's hosted page. To show the payment on your own page, render the payment your server gets back (deposit_address, crypto_amount, destination_tag for XRP, a QR code of qr_payload, expires_at); no checkout calls are needed.

  • You know the coin: create the payment with token and render the payment it returns.
  • The customer chooses it: create the payment without token, show payment.token_options on your page, then call client.payments.select_token(payment.id, chosen) from your server and render the payment it returns: it now has its coin, address, amount, locked rate and a deadline 15 minutes away.
payment = client.payments.create(order_id="ORD-1", amount_usd=10)
# your page shows payment.token_options; the customer picks one
chosen = client.payments.select_token(payment.id, "USDT_TRC20")  # or your order id
# render chosen.deposit_address, chosen.crypto_amount, chosen.qr_payload, chosen.expires_at

The coin is chosen once: choosing again is a 409 (payments.retrieve then returns the payment with its coin). A coin outside token_options, or a payment that can no longer be paid, is a 400.

Test mode

A test payment gets an address like a live one, but no coins are involved: you choose an outcome and payitgo plays its deposits, status changes and webhooks a few seconds apart (Test mode).

# Choose the outcome when you create the payment (with token it starts at once,
# without it once the coin is chosen)…
client.payments.create(order_id="TEST-1", amount_usd=10, token="USDT_TRC20", simulate="confirmed")

# …or later, on a payment you already have (its id or your order id).
payment = client.payments.simulate("TEST-2", "partially_paid")
# payment.simulation: {"outcome": "partially_paid", "state": "running"}

Outcomes (payitgo.SIMULATION_OUTCOMES): confirmed, overpaid, partially_paid, underpaid, expired, failed, wrong_asset, paid_late. Simulated deposits have "simulated": True; their tx_id names no transaction on any chain. A live key gets 400; a payment whose simulation is still running, 409.

Webhooks (IPN)

payitgo POSTs a signed notification for every change of a payment (payment.confirmed, payment.expired, …), of a direct settlement and of a payout. Each mode has one webhook: a test key manages the test one, a live key the live one.

Where the notifications go. Set the URL once per mode (https:// on a public host, accepting POST requests), or give a payment its own when you create it:

# Once per mode (safe to repeat):
client.webhooks.set("https://shop.example/payitgo/ipn")

# Or per payment: its events go there instead of the webhook's URL.
client.payments.create(order_id="ORD-1", amount_usd=10, webhook_url="https://shop.example/orders/ipn")

# Or as the client's default for every payment it creates (a payment's own webhook_url wins):
shop = payitgo.Client(os.environ["PAYITGO_KEY"], webhook_url="https://shop.example/orders/ipn")

A storefront's key cannot set the account's URL (403): pass webhook_url instead. Payout events always go to the webhook's URL.

The signing secret. Every notification of a mode is signed with that mode's secret, whichever URL it goes to. Copy it from the dashboard or read it with client.webhooks.get().secret, and give it to the client once:

client = payitgo.Client(os.environ["PAYITGO_KEY"], webhook_secret=os.environ["PAYITGO_WEBHOOK_SECRET"])

Receiving them (Flask). Verify the raw body, handle each event once, and answer 2xx within 10 seconds; otherwise payitgo retries after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h.

@app.post("/payitgo/ipn")
def ipn():
    try:
        # Checks the signature with the client's webhook_secret and refuses anything older than 5 minutes.
        event = client.webhooks.construct_event(request.get_data(), request.headers.get("X-Webhook-Signature"))
    except payitgo.SignatureVerificationError:
        abort(400)
    # The same event can arrive more than once: handle each event id once,
    # e.g. by inserting it into a table with a unique key.
    if not record_event_once(event.id):
        return "", 200
    if event.type == "payment.confirmed":
        jobs.enqueue("fulfil", event.data.order_id)  # slow work off the request
    elif event.type == "payment.expired":
        jobs.enqueue("release_stock", event.data.order_id)
    return "", 200

Testing it. client.webhooks.test() queues a signed ping event to the webhook's URL and returns its delivery (400 when no URL is set).

API

client = payitgo.Client(api_key, base_url="https://api.example.com",  # or PAYITGO_BASE_URL
                      timeout=30, max_retries=2,                    # network errors, 429, 5xx
                      webhook_secret=secret,                        # for webhooks.construct_event
                      webhook_url="https://…")                      # default webhook_url of payments.create

client.is_live()                                   # False for a test key (asks payitgo once)
client.payments.create(order_id, amount_usd, token, webhook_url=None, description=None, metadata=None,
                       customer_email=None, success_url=None, cancel_url=None, simulate=None)
client.payments.create(order_id, price_amount=…, price_currency=…, …)  # priced in another currency
client.payments.select_token(payment_id, token)    # or your order id: the payment with its coin
client.payments.simulate(payment_id, "confirmed")  # test keys: plays the outcome
client.payments.retrieve(payment_id)               # or your order id
client.payments.list(status="confirmed", store_id=store_id, limit=50, offset=0) # every filter optional
client.tokens.list()                               # [{token, chain, min_usd, network_fee_usd, fee_note}]
client.tokens.list(amount_usd="49.90")             # the coins a $49.90 payment offers, with amount_due_usd
client.checkout.retrieve(checkout_token)           # public payer view
client.webhooks.get()                              # {livemode, url, secret, created_at, updated_at}
client.webhooks.set(url)                           # the webhook
client.webhooks.remove()                           # the webhook, url None (the secret stays)
client.webhooks.test()                             # the delivery of a "ping" event
client.webhooks.construct_event(raw_body, signature_header, secret=None, tolerance=300)
payitgo.Webhook.construct_event(raw_body, signature_header, secret, tolerance=300)  # no client needed

Responses are dicts that also allow attribute access: payment["status"] or payment.status. Field names are the API's own (see the payment object and webhooks). payitgo.WEBHOOK_EVENTS lists the event types.

Errors

try:
    client.payments.create(order_id="ORD-1", amount_usd=1, token="BTC")
except payitgo.PayitgoError as e:
    print(e.status, e.message)   # 400 minimum payment in BTC is $10 right now (network fees)

PayitgoError.status is the HTTP status (0 for a network error). construct_event raises SignatureVerificationError for a bad signature, a stale timestamp, or when it has no secret (none passed and no webhook_secret).

Development

python3 -m unittest discover -s tests

Metadata

Release files for payitgo 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for payitgo 0.3.0
File Size Uploaded
payitgo-0.3.0.tar.gz 14.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for payitgo 0.3.0
File Interpreter ABI Platform
payitgo-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 25.3 kB

Release files / payitgo-0.3.0.tar.gz

Download URL payitgo-0.3.0.tar.gz
Size 14.8 kB
Tags Source
SHA-256 checksum
How to use checksums
176d0bbd35a79af58bdb4b8408d6c8cc433007c76f889488a6065240941f5607
BLAKE2b-256 checksum
How to use checksums
90feffcdc2f3353c4a820b17cb430409ea8fd074b33681fdba0762134b2fd1dc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / payitgo-0.3.0-py3-none-any.whl

Download URL payitgo-0.3.0-py3-none-any.whl
Size 10.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
447c1f8d6850e6db0bdde020cdcaadf0cdbd2c8fc3f2f5c50180ef5ea6d887da
BLAKE2b-256 checksum
How to use checksums
24379b1981a3927e72bb1b4c3fc8b84c21a52425fe9c5c67042e718966b92cbd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page