Cómo implementar límites de fondos en el backend para agentes de IA basados en Stripe
TuBrief 편집팀
2026년 7월 24일
0
Internet Technology원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Con la fundación x402 puliendo la especificación HTTP 402 y Stripe lanzando la Agentic Commerce Suite, hemos entrado en la era en la que los agentes de IA pagan de forma autónoma. Para los ingenieros, esto es un dolor de cabeza. La persona que presionaba el botón ha desaparecido.
Un ser humano duda en la pantalla de pago, pero un agente atrapado en un bucle de LLM realizará decenas de llamadas a la API por segundo. Si lo dejas pasar, la tarjeta corporativa quedará paralizada por superar el límite en cuestión de segundos. Si no puedes controlar la cartera del agente en tiempo real a través de una infraestructura distribuida, los incidentes de pago son un desastre anunciado.
Cuando la inferencia de un LLM se queda atascada en un bucle o un servicio 402 externo infla el monto solicitado, debes detenerlo a nivel de servicio. Si intentas validar el monto del pago manteniendo variables en la memoria de la aplicación, te enfrentarás a una condición de carrera (Race Condition) en un entorno con escalado horizontal (Scale-out).
Es más seguro ejecutar scripts de Lua en Redis para procesar operaciones atómicas. Valida simultáneamente el límite por transacción y el límite acumulado diario en un estado aislado en menos de 50 ms.
`lua
-- Redis Lua Script for Dual Spending Limits (Per-Call & Daily Cumulative)
-- KEYS[1]: 일일 누적 사용량 키 (spending:agent:{agent_id}:{YYYYMMDD})
-- KEYS[2]: 속도 제한 키 (rate:agent:{agent_id})
-- ARGV[1]: 요청 결제 금액 (float)
-- ARGV[2]: 회당 최대 결제 한도 (float)
-- ARGV[3]: 일일 최대 결제 한도 (float)
-- ARGV[4]: 일일 키 만료 시간 (seconds, e.g., 86400)
local daily_key = KEYS[1]
local rate_key = KEYS[2]
local amount = tonumber(ARGV[1])
local max_per_call = tonumber(ARGV[2])
local max_daily = tonumber(ARGV[3])
local ttl_seconds = tonumber(ARGV[4])
-- 1. 회당 결제 한도 검증
if amount > max_per_call then
return {0, "EXCEEDS_PER_CALL_LIMIT"}
end
-- 2. 일일 누적 결제 한도 검증
local current_daily = tonumber(redis.call("GET", daily_key) or "0")
if (current_daily + amount) > max_daily then
return {0, "EXCEEDS_DAILY_LIMIT"}
end
-- 3. 누적 금액 업데이트 및 TTL 설정
redis.call("INCRBYFLOAT", daily_key, amount)
if redis.call("TTL", daily_key) == -1 then
redis.call("EXPIRE", daily_key, ttl_seconds)
end
return {1, "APPROVED"}
`
A las transacciones de pago que pasan la validación se les asigna un número de un solo uso (Nonce). Se almacena en Redis con un TTL de 30 segundos y se elimina inmediatamente después de completar la verificación. Esto sirve para bloquear ataques de repetición (Replay Attack), donde alguien intercepta los paquetes de red para volver a enviar la misma solicitud. Al adaptarse al protocolo x402, se utiliza un encabezado PAYMENT-SIGNATURE firmado con la especificación HMAC-SHA256 o EIP-712, agrupando la URL, el monto, el Nonce y la marca de tiempo.
La respuesta que devuelve el backend cuando se excede el límite también es crucial. Debes asegurarte de que el LLM interprete la situación correctamente. Si superó el límite de velocidad, devuelve un HTTP 429; si el presupuesto es insuficiente o superó el límite de gasto, devuelve un HTTP 402.
`json
{
"x402Version": 2,
"error": "DAILY_SPENDING_LIMIT_EXCEEDED",
"message": "The requested transaction of $15.00 exceeds the remaining daily budget of $5.20.",
"policy": {
"max_per_call": 10.00,
"daily_limit": 50.00,
"current_daily_spend": 44.80,
"currency": "USD"
},
"action_required": "OPERATOR_APPROVAL_NEEDED"
}
`
El procedimiento de implementación es claro:
PAYMENT-SIGNATURE.Si ocurre un tiempo de espera (timeout) al ejecutar frameworks como LangChain o CrewAI, el agente reabrirá la sesión despreocupadamente para llamar a la herramienta de nuevo. En este punto, si generas y envías una clave de idempotencia (Idempotency Key) con un UUID aleatorio diferente cada vez, provocará un cobro doble.
Debes extraer una clave de idempotencia determinista calculando el hash de los argumentos de la herramienta (Tool Arguments) transferidos por el agente. De esta manera, la API de Stripe reutilizará automáticamente el resultado del pago anterior, evitando cobros duplicados injustificados.
¿Qué pasa si el pago es aprobado, pero la operación de scraping o de sandbox subsecuente falla? Debes ejecutar una transacción de compensación (Patrón Saga) para restaurar los fondos. Dado que no puedes modificar directamente los registros de deducción de sistemas externos, la estructura consiste en realizar una llamada a la API de reembolso inmediatamente después de la aprobación para llevar a cabo una reversión lógica.
Si aparecen tiempos de espera de red (HTTP 500, 502, 503, 504), debes controlar la tormenta de reintentos mediante un retroceso exponencial con variación completa (Exponential Backoff with Full Jitter). Por el contrario, los errores de tarjeta rechazada (card_declined) o expirada (expired_card) deben detenerse de inmediato sin reintentar.
`python
import hashlib
import json
import uuid
import stripe
def generate_agent_idempotency_key(agent_id: str, tool_name: str, tool_args: dict) -> str:
canonical_args = json.dumps(tool_args, sort_keys=True)
raw_hash = hashlib.sha256(f"{agent_id}:{tool_name}:{canonical_args}".encode('utf-8')).hexdigest()
return str(uuid.UUID(raw_hash[:32]))
def execute_agent_payment_saga(
agent_id: str,
customer_id: str,
amount_cents: int,
tool_args: dict,
service_executor_func
) -> dict:
idem_key = generate_agent_idempotency_key(agent_id, "code_execution_tool", tool_args)
# 1단계: PaymentIntent 생성 및 승인 (Phase 1: Prepare & Commit)
try:
intent = stripe.PaymentIntent.create(
amount=amount_cents,
currency="usd",
customer=customer_id,
payment_method_types=["card", "link"],
confirm=True,
off_session=True,
idempotency_key=f"pi_{idem_key}"
)
except stripe.error.StripeError as e:
return {"success": False, "stage": "PAYMENT_FAILED", "error": e.user_message}
# 2단계: 실제 다운스트림 서비스 실행 (Phase 2: Execution)
try:
service_result = service_executor_func(tool_args)
return {"success": True, "data": service_result, "payment_id": intent.id}
except Exception as service_exception:
# 서비스 실행 실패 시 보상 트랜잭션 실행 (Saga Rollback: Automatic Refund)
try:
refund = stripe.Refund.create(
payment_intent=intent.id,
reason="requested_by_customer",
metadata={"rollback_reason": str(service_exception), "agent_id": agent_id},
idempotency_key=f"ref_{idem_key}"
)
return {
"success": False,
"stage": "SERVICE_FAILED_REFUNDED",
"refund_id": refund.id,
"error": str(service_exception)
}
except stripe.error.StripeError as refund_error:
return {
"success": False,
"stage": "CRITICAL_REFUND_FAILURE",
"payment_id": intent.id,
"error": str(refund_error)
}
`
El orden para desplegar este código en el backend es el siguiente:
PaymentIntent de Stripe y ejecuta las tareas posteriores dentro de execute_agent_payment_saga().stripe.Refund en el bloque except para recuperar el dinero de inmediato.Puedes ahorrar las 4 horas semanales que solías perder reconciliando pagos dobles manualmente.
A medida que aumenta el volumen de pagos del agente, el equipo de contabilidad se pondrá en contacto. Te dirán que no pueden emitir facturas de impuestos porque el historial de llamadas de prompts y los datos de los recibos no coinciden.
En la tabla de auditoría de la base de datos, debes vincular el ID de inferencia del prompt del LLM (prompt_id), el contexto de pago y el ID de transacción de Stripe en una relación 1:1. Diseña la estructura base usando BIGSERIAL e índices JSONB en PostgreSQL.
`sql
CREATE SCHEMA IF NOT EXISTS agent_audit;
CREATE TABLE agent_audit.transaction_logs (
id BIGSERIAL PRIMARY KEY,
prompt_id UUID NOT NULL,
agent_id VARCHAR(64) NOT NULL,
customer_id VARCHAR(255) NOT NULL,
payment_intent_id VARCHAR(255) UNIQUE,
mpp_tx_hash VARCHAR(255),
resource_url TEXT NOT NULL,
amount_subtotal NUMERIC(12, 4) NOT NULL,
amount_tax NUMERIC(12, 4) NOT NULL DEFAULT 0.0000,
amount_total NUMERIC(12, 4) NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
tax_code VARCHAR(32) NOT NULL DEFAULT 'txcd_10103000',
customer_tax_id VARCHAR(64),
tax_country VARCHAR(2) NOT NULL,
taxability_override VARCHAR(32),
payload_digest VARCHAR(64) NOT NULL,
signature TEXT NOT NULL,
prev_hash VARCHAR(64) NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_agent_audit_prompt ON agent_audit.transaction_logs(agent_id, prompt_id);
CREATE INDEX idx_agent_audit_created ON agent_audit.transaction_logs(created_at);
CREATE INDEX idx_agent_audit_gin_meta ON agent_audit.transaction_logs USING gin (metadata);
`
Automatiza la validación del identificador fiscal de la empresa (Tax ID) y el cálculo de la inversión del sujeto pasivo (Reverse Charge) con la API de Stripe Tax. Para evitar futuras disputas sobre la manipulación de la base de datos, es aconsejable implementar firmas Ed25519 y encadenamiento de hashes (Hash-Chaining) con SHA-256. Esto hace que cada registro contenga el valor hash del registro anterior (prev_hash). Se convierte en una estructura acorde con las especificaciones estándar de VOLT o VeriLedger.
`python
from cryptography.hazmat.primitives.asymmetric import ed25519
import base64
import hashlib
private_key = ed25519.Ed25519PrivateKey.generate()
def create_tax_compliant_agent_invoice(
customer_id: str,
amount_cents: int,
currency: str,
tax_id_number: str = None,
country_code: str = "DE"
) -> dict:
if tax_id_number:
stripe.Customer.create_tax_id(customer_id, type="eu_vat", value=tax_id_number)
calculation = stripe.tax.Calculation.create(
currency=currency,
customer_details={
"address": {"country": country_code},
"address_source": "billing",
"tax_ids": [{"type": "eu_vat", "value": tax_id_number}] if tax_id_number else []
},
line_items=[{
"amount": amount_cents,
"reference": "AGENT_API_USAGE",
"tax_behavior": "exclusive",
"tax_code": "txcd_10103000"
}]
)
return {
"calculation_id": calculation.id,
"subtotal": amount_cents / 100.0,
"tax_amount": calculation.tax_amount_exclusive / 100.0,
"total": (amount_cents + calculation.tax_amount_exclusive) / 100.0,
"is_reverse_charge": calculation.customer_details.taxability_override == "reverse_charge"
}
def generate_non_repudiable_audit_proof(
prompt_id: str,
payment_intent_id: str,
amount: float,
prev_hash: str
) -> dict:
payload_raw = f"{prompt_id}:{payment_intent_id}:{amount:.4f}:{prev_hash}"
payload_hash = hashlib.sha256(payload_raw.encode('utf-8')).hexdigest()
signature_bytes = private_key.sign(payload_hash.encode('utf-8'))
signature_b64 = base64.b64encode(signature_bytes).decode('utf-8')
current_block_hash = hashlib.sha256(f"{payload_hash}:{signature_b64}".encode('utf-8')).hexdigest()
return {
"payload_hash": payload_hash,
"signature": signature_b64,
"block_hash": current_block_hash
}
`
El proceso de construcción del pipeline se realiza de la siguiente manera:
agent_audit y los índices en PostgreSQL según el DDL redactado.Puedes reducir a más de la mitad el esfuerzo dedicado a las liquidaciones B2B trimestrales y al procesamiento de respaldos fiscales.
| Área | Método tradicional (usando SDK estándar) | Tras aplicar salvaguardas | Beneficio práctico |
|---|---|---|---|
| Control de fondos | Agotamiento de la cartera si el bucle falla | Bloqueo atómico en tiempo real basado en Redis Lua | Prevención de fugas no autorizadas de saldo |
| Errores de pago | Cobros dobles al reintentar | Clave de idempotencia basada en argumentos + Reembolso automático Saga | Reducción de 4 horas semanales en la corrección de errores de pago |
| Liquidación B2B | Falta de mapeo entre recibos y llamadas | Stripe Tax + Registros con cadena de hash Ed25519 | Reducción de más del 50% en el tiempo de procesamiento contable trimestral |
Al darle una cartera a un agente, debes diseñar las funciones de bloqueo y reversión antes que la función de pago. En el momento en que integras el middleware de control de Redis, el patrón Saga y los registros con cadena de hash, construyes un sistema que te permite dormir tranquilo por la noche incluso después de desplegarlo en producción.