Comment implémenter un backend pour limiter le budget des agents IA basé sur Stripe
TuBrief 편집팀
2026년 7월 24일
0
Internet Technology원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Avec l'affinement du protocole HTTP 402 par la fondation x402 et le lancement de l'Agentic Commerce Suite par Stripe, nous sommes entrés dans l'ère où les agents IA gèrent les paiements de manière autonome. Pour les ingénieurs, c'est un véritable casse-tête, car il n'y a plus d'humain pour valider chaque clic.
Alors qu'un humain hésite sur la page de paiement, un agent pris dans une boucle LLM enchaîne les appels API des dizaines de fois par seconde. Si on le laisse faire, la carte de l'entreprise se retrouve rapidement bloquée pour dépassement de plafond. Ne pas réussir à contrôler en temps réel le portefeuille d'un agent dans une infrastructure distribuée mène tout droit à l'incident de paiement.
Lorsqu'un raisonnement LLM se retrouve bloqué en boucle ou qu'un service 402 externe gonfle artificiellement le montant des requêtes, le service doit être en mesure de bloquer ces actions. Si l'on tente de stocker les variables de montant dans la mémoire de l'application pour les valider, des problèmes de concurrence (Race Condition) surgissent inévitablement en environnement scale-out.
Il est plus sûr d'exécuter des scripts Lua dans Redis pour traiter les opérations de manière atomique. En moins de 50 ms, il est possible de vérifier simultanément et de manière isolée la limite par appel et le cumul journalier.
`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"}
`
Les paiements validés se voient attribuer un nombre à usage unique (Nonce). Celui-ci est stocké dans Redis avec un TTL de 30 secondes, puis supprimé dès la validation terminée. Cela permet de contrer les attaques par rejou (Replay Attack), où un tiers intercepte les paquets réseau pour renvoyer exactement la même requête. Pour respecter le protocole x402, on associe l'URL, le montant, le Nonce et l'horodatage pour signer le tout via HMAC-SHA256 ou au format EIP-712 dans l'en-tête PAYMENT-SIGNATURE.
La réponse renvoyée par le backend en cas de dépassement de plafond est également cruciale. Il faut faire en sorte que le LLM comprenne correctement la situation. On renvoie un code HTTP 429 en cas de dépassement de limite de débit, et HTTP 402 en cas de budget insuffisant ou de dépassement de plafond.
`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"
}
`
La procédure de mise en œuvre est simple :
PAYMENT-SIGNATURE.Si un délai d'attente (timeout) survient lors de l'utilisation de frameworks tels que LangChain ou CrewAI, l'agent rouvre machinalement une session pour appeler l'outil. Si l'on génère à chaque fois une clé d'idempotence (Idempotency Key) aléatoire sous forme de UUID, des paiements en double se produisent.
Il est nécessaire de hacher les arguments d'outils (Tool Arguments) transmis par l'agent afin d'extraire une clé d'idempotence déterministe. Ainsi, l'API Stripe réutilise d'elle-même le résultat du paiement précédent, évitant ainsi des prélèvements en double injustifiés.
Que faire si l'autorisation de paiement est accordée, mais que l'opération de scraping ou de sandbox devant s'exécuter juste après échoue ? Il faut alors déclencher une transaction compensatoire (Pattern Saga) pour restaurer les fonds. Ne pouvant pas modifier directement les registres de prélèvement du système externe, l'architecture consiste à exécuter un rollback logique en envoyant une API de remboursement immédiatement après l'approbation.
En cas de délai d'attente réseau (HTTP 500, 502, 503, 504), il convient de maîtriser la tempête de nouvelles tentatives à l'aide d'un backoff exponentiel avec gigue complète (Exponential Backoff with Full Jitter). En revanche, les erreurs de type refus de carte (card_declined) ou carte expirée (expired_card) doivent interrompre immédiatement le processus sans nouvelle tentative.
`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)
}
`
L'ordre d'intégration de ce code dans le backend est le suivant :
PaymentIntent de Stripe et exécuter les tâches suivantes au sein de execute_agent_payment_saga().stripe.Refund dans le bloc except pour récupérer immédiatement les fonds.Il est ainsi possible d'économiser les 4 heures par semaine perdues à corriger manuellement les paiements en double.
Lorsque le volume des paiements par agent augmente, l'équipe comptable se manifeste car l'historique des appels de prompts et les données de reçus ne concordent pas, empêchant l'émission des factures fiscales.
La table de la base de données d'audit doit lier de manière univoque l'ID d'inférence du prompt LLM (prompt_id), le contexte de paiement et l'ID de transaction Stripe. La structure s'articule autour des types BIGSERIAL et des index JSONB de 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);
`
L'API Stripe Tax permet d'automatiser la vérification du numéro d'identification fiscale (Tax ID) des entreprises et le calcul de l'autoliquidation de la TVA (Reverse Charge). Pour prévenir d'éventuelles contestations liées à des manipulations de la base de données par la suite, il est recommandé d'appliquer une signature Ed25519 et un chaînage de hachage (Hash-Chaining) SHA-256. Chaque journal conserve ainsi la référence au hachage de l'enregistrement précédent (prev_hash). Cette structure se conforme aux spécifications standard VOLT ou 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
}
`
La construction du pipeline s'effectue de la manière suivante :
agent_audit et ses index dans PostgreSQL conformément au DDL rédigé.Cela permet de réduire de plus de la moitié la charge de travail consacrée au règlement B2B trimestriel et aux justificatifs fiscaux.
| Domaine | Approche classique (Utilisation d'un SDK standard) | Après application des sécurités | Avantages concrets |
|---|---|---|---|
| Contrôle des fonds | Vidage du portefeuille en cas de dysfonctionnement de boucle | Blocage atomique en temps réel basé sur Redis Lua | Prévention des fuites de solde non autorisées |
| Erreurs de paiement | Paiements en double lors des nouvelles tentatives | Clé d'idempotence par arguments + Remboursement automatique Saga | Réduction du temps de correction des erreurs de paiement à 4h/semaine |
| Règlement B2B | Non-correspondance entre reçus et historique des appels | Journaux de chaîne de hachage Stripe Tax + Ed25519 | Réduction de plus de 50 % du temps de traitement comptable trimestriel |
Lorsque l'on confie un portefeuille à un agent, il faut concevoir les mécanismes de blocage et de rollback avant même la fonction de paiement. Dès l'implémentation du middleware de contrôle Redis, du pattern Saga et des journaux de chaînes de hachage, le système gagne en robustesse et permet de passer en production l'esprit tranquille.