kuti-pe
SDK oficial de KUTI para Python. Crea sesiones de checkout, consulta el estado de un pago y verifica webhooks — sin reimplementar auth, manejo de errores ni firma HMAC a mano.
Solo servidor. Este paquete usa tu secret key (
kuti_live_.../kuti_test_...). Nunca lo importes en código que se sirva al navegador.
Instalación
pip install kuti-pe
Quickstart
import os
from kuti import KutiClient
kuti = KutiClient(os.environ["KUTI_SECRET_KEY"])
session = kuti.checkout_sessions.create(
amount={"amount": "249.90", "currency": "PEN"},
payment_method_types=["INTEROPERABLE_QR", "BANK_TRANSFER"],
description="Zapatillas running talla 42",
customer={"id": "cus_01ABC"},
# customer={"first_name": "María", "last_name": "López", "email": "maria@example.com"},
idempotency_key=f"order-{order_id}",
)
# window.Kuti.open({ checkoutUrl: session.checkout_url, onSuccess, onFailure })
Clientes y campos personalizados
El cliente tiene la misma forma en customers.create, en el customer de un cobro y en el de
una checkout session. custom_fields son los campos que el negocio definió en
Ajustes → Clientes → Campos (la key de cada campo):
from kuti import CustomerInput
customer = kuti.customers.create(
CustomerInput(
type="INDIVIDUAL",
first_name="María",
last_name="López",
document={"type": "DNI", "number": "45678912"}, # type opcional: se deduce del número
email="maria@example.com",
custom_fields={"grade": "quinto", "student_code": "2026-00781"},
)
)
# En un cobro: se reutiliza el cliente por id → external_id → documento, o se crea.
kuti.payment_intents.create(
amount={"amount": "250.00", "currency": "PEN"},
payment_method_types=["INTEROPERABLE_QR"],
description="Pensión marzo",
customer={"document": {"number": "45678912"}, "custom_fields": {"grade": "sexto"}},
idempotency_key="pension-2026-03-45678912",
)
# Editar: solo cambian las keys enviadas; None borra el valor.
kuti.customers.update(customer.id, custom_fields={"birth_date": None})
Confirmar un pago
intent = kuti.payment_intents.retrieve(payment_intent_id)
if intent.status == "SUCCEEDED":
# fulfill order
pass
Verificar un webhook
from flask import Flask, request
from kuti import verify_webhook_signature, KutiSignatureVerificationError
import os
app = Flask(__name__)
@app.post("/webhooks/kuti")
def kuti_webhook():
payload = request.get_data(as_text=True) # body CRUDO, sin json.loads antes
try:
verify_webhook_signature(
payload,
request.headers["X-Kuti-Signature"],
request.headers["X-Kuti-Timestamp"],
os.environ["KUTI_WEBHOOK_SECRET"],
)
except KutiSignatureVerificationError:
return "Invalid signature", 400
event = request.get_json(force=True)
# payment.succeeded, checkout.session.completed, …
return "", 200
Manejo de errores
Todas las excepciones de la API extienden KutiApiError (status, code, request_id, doc_url, details):
from kuti import KutiValidationError, KutiNotFoundError, KutiApiError
try:
kuti.checkout_sessions.create(...)
except KutiValidationError as err:
print(err.details) # [ErrorDetail(field="amount.amount", ...)]
except KutiNotFoundError:
pass
except KutiApiError as err:
print(err.code, err.request_id) # úsalo al reportar un bug a soporte
Los GET y los POST con idempotency_key se reintentan automáticamente en errores de red o 429/503. Un POST sin idempotency_key nunca se reintenta, para no duplicar un cobro.
Links de pago
Un enlace permanente que pagan muchas personas (curso, entrada, donación). Cada pago es un cobro
normal con payment_link_id.
link = kuti.payment_links.create(
title="Taller de Excel — sábado 10am",
template="COURSE",
pricing="FIXED",
amount="120.00",
payment_method_types=["INTEROPERABLE_QR", "BANK_TRANSFER"],
customer_field_ids=["cfd_…"], # [] = solo nombre, apellido y correo
button_label="Inscribirme",
success_message="¡Listo! Te esperamos el sábado.",
success_button_label="Unirme al grupo",
success_button_url="https://chat.whatsapp.com/…",
)
print(link.url) # https://pay.kuti.pe/l/taller-de-excel
# Quienes pagaron el link
paid = kuti.payment_intents.list(payment_link_id=link.id, status="SUCCEEDED", per_page="all")
Enviar el cobro al crearlo
kuti.payment_intents.create(
amount={"amount": "250.00", "currency": "PEN"},
payment_method_types=["INTEROPERABLE_QR"],
customer={"id": "cus_…"},
send_via=["EMAIL", "WHATSAPP"], # None = ["EMAIL"]; [] = no enviar
)
API
KutiClient(secret_key, base_url=None)kuti.customers.create(customer, metadata=None)/retrieve(id)/update(id, ...)/list(...)/delete(id)kuti.checkout_sessions.create(...)— Checkout.jskuti.payment_intents.create(...)— cobro directokuti.payment_intents.list(...)— filtrosstatus,q,customer_id,source(single | link | recurring),payment_link_idkuti.payment_intents.retrieve(id)kuti.payment_intents.cancel(id)kuti.payment_intents.send_whatsapp(id, ...)kuti.payment_links.create(...)/retrieve(id)/update(id, ...)/list(...)/activate(id)/deactivate(id)/check_slug(slug, except_id=None)verify_webhook_signature(payload, signature_header, timestamp_header, secret, tolerance_seconds=300)
Requisitos
Python 3.9+ · sin dependencias de runtime.
Release files for kuti-pe 1.0.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kuti_pe-1.0.5.tar.gz | 16.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kuti_pe-1.0.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.9 kB
Release files / kuti_pe-1.0.5.tar.gz
| Download URL | kuti_pe-1.0.5.tar.gz |
|---|---|
| Size | 16.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
df26cf526e71ef920169ae07bbec2230792dd4b43e58dad5bee4e15da1167410
|
|
BLAKE2b-256 checksum How to use checksums |
1db9b97253510c19496b25cd946b389acf4e2908aaa94db2deb3a727b3f7ed17
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / kuti_pe-1.0.5-py3-none-any.whl
| Download URL | kuti_pe-1.0.5-py3-none-any.whl |
|---|---|
| Size | 18.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ec3a9c11411bcc170097f1d96d679d94924b6cc35ccab865e1661f3371b3096a
|
|
BLAKE2b-256 checksum How to use checksums |
7b619d709fe392a15014cd92b7e4e2fa9a572e97eeabeb8807a3b2b082ebd0f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|