feat: offer credit card, debit card and PIX, with the bank's 3-D Secure step
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m3s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m31s
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m3s
Build and deploy / Secret scan and release gate (push) Successful in 5s
Build and deploy / Publish images (push) Successful in 1m31s
The payment page lists credit card (preselected), debit card and PIX. Each card option limits Mercado Pago's form to its kind; debit is paid at once. Card payments ask for 3-D Secure when the issuer requires it, and a challenge opens the bank's page in a frame, which needs PAYMENT_CHALLENGE_SOURCES=https: (frames and form posts only). A card left waiting for that confirmation stops blocking a new attempt after ten minutes, and a refusal reported by the notification returns the customer to the payment choice. Written from the documentation; not yet run with a real debit card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,8 @@ router = APIRouter()
|
||||
# Generous: a provider legitimately retries, and a signature check is cheap.
|
||||
# This exists so an unsigned flood cannot keep the database busy.
|
||||
WEBHOOK_LIMIT = 600
|
||||
# How long a card waiting for the bank's confirmation holds off a new attempt.
|
||||
CHALLENGE_MINUTES = 10
|
||||
|
||||
|
||||
@router.post('/api/payments/webhook')
|
||||
@@ -73,8 +75,14 @@ def intent(body: PaymentIntent, session_id=Depends(owner)):
|
||||
raise HTTPException(409, 'Quote is already paid')
|
||||
# Never a second charge: an approved payment is waiting for its
|
||||
# notification to become the order, and a card in review may still be.
|
||||
# A card waiting for the bank's confirmation (3-D Secure) blocks only
|
||||
# for CHALLENGE_MINUTES: a customer who gave up on it must still be able
|
||||
# to pay, and an unanswered challenge is not charged.
|
||||
if c.execute('''SELECT 1 FROM dtf_local.payment_intents WHERE quote_id=%s
|
||||
AND (status='approved' OR (method='card' AND status='pending'))''', (body.quote_id,)).fetchone():
|
||||
AND (status='approved' OR (method='card' AND status='pending'
|
||||
AND NOT (jsonb_typeof(response->'challenge')='object'
|
||||
AND created_at < now() - make_interval(mins => %s))))''',
|
||||
(body.quote_id, CHALLENGE_MINUTES)).fetchone():
|
||||
raise HTTPException(409, 'A payment for this quote is already approved or in review')
|
||||
if body.method.type == 'pix':
|
||||
existing = c.execute('''SELECT * FROM dtf_local.payment_intents WHERE quote_id=%s AND method='pix'
|
||||
|
||||
Reference in New Issue
Block a user