Skip to main content

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.

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.js
  • kuti.payment_intents.create(...) — cobro directo
  • kuti.payment_intents.list(...) — filtros status, q, customer_id, source (single | link | recurring), payment_link_id
  • kuti.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)

Source distribution for kuti-pe 1.0.5
File Size Uploaded
kuti_pe-1.0.5.tar.gz 16.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kuti-pe 1.0.5
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

1.0.5 This release

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.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