feat: PIX codes expire after 30 minutes, with a countdown
All checks were successful
Build and deploy / Validate source (push) Successful in 11s
Build and deploy / Integration suite on a real stack (push) Successful in 2m50s
Build and deploy / Secret scan and release gate (push) Successful in 8s
Build and deploy / Publish images (push) Successful in 1m54s

A PIX is created with a 30-minute date_of_expiration, and the PIX page
counts down to it. When it runs out the page says the code expired and
offers a new one. The API keeps one open code per quote: the same code
until it expires, then exactly one new attempt (serialised per quote, with
its own idempotency key), the old one marked expired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-28 15:40:26 -03:00
parent 875a7ef9c7
commit 641aafc87d
7 changed files with 81 additions and 10 deletions

View File

@@ -8,6 +8,7 @@ import hashlib
import hmac
import json
import unittest
from datetime import datetime, timezone
import httpx
@@ -110,11 +111,20 @@ class MercadoPagoTests(unittest.TestCase):
request = self.requests[-1]
body = json.loads(request.content)
self.assertEqual(request.headers['x-idempotency-key'],
'dtf-quote-11111111-2222-3333-4444-555555555555-pix')
'dtf-quote-11111111-2222-3333-4444-555555555555-pix-1')
self.assertEqual((body['payment_method_id'], body['transaction_amount']), ('pix', 123.45))
self.assertEqual(body['external_reference'], '11111111-2222-3333-4444-555555555555')
self.assertEqual(body['notification_url'], 'https://dtf.example/api/payments/webhook')
self.assertEqual((created['pix_qr_code'], created['status']), ('000201PIX', 'pending'))
# The code expires in 30 minutes, in the format Mercado Pago documents.
expires = datetime.fromisoformat(body['date_of_expiration'])
self.assertRegex(body['date_of_expiration'], r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}-03:00$')
self.assertAlmostEqual((expires - datetime.now(timezone.utc)).total_seconds(), 1800, delta=60)
self.assertEqual(created['expires_at'], body['date_of_expiration'])
# A new code after the last expired is the next attempt, not the same payment.
self.mp.create('11111111-2222-3333-4444-555555555555', 12345,
{'mail': 'a@example.test', 'cnpj': '11222333000181'}, {'type': 'pix', 'attempt': 2})
self.assertTrue(self.requests[-1].headers['x-idempotency-key'].endswith('-pix-2'))
def test_card_payment_uses_the_browser_token_only(self):
self.mp.create('q', 1000, {'mail': 'a@example.test', 'cnpj': '11222333000181'},