feat: add a read-only Mercado Pago account probe
All checks were successful
Build and deploy / Validate source (push) Successful in 4s
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 45s
All checks were successful
Build and deploy / Validate source (push) Successful in 4s
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 45s
Card payments with the test credentials are refused with 10111 and 10113, which depend on the account behind the token. python -m app.mercadopago_probe shows that account (and whether it is a test user), the card methods it accepts, and how Mercado Pago classifies a card's first digits: type, issuer and instalments. It creates and charges nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
68
app/mercadopago_probe.py
Normal file
68
app/mercadopago_probe.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
"""Read-only look at the Mercado Pago account behind MP_ACCESS_TOKEN.
|
||||||
|
|
||||||
|
python -m app.mercadopago_probe [--bin 503143] [--valor 50]
|
||||||
|
|
||||||
|
Run from the api container's console (Portainer > Containers > api >
|
||||||
|
Console). It creates nothing and charges nothing: it asks Mercado Pago which
|
||||||
|
account the token belongs to, which card methods the account accepts, and how
|
||||||
|
it classifies a card number's first digits (type, issuer, instalments). That
|
||||||
|
is what payment errors such as 10111 (issuer) and 10113 (method excluded by a
|
||||||
|
rule) depend on.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from .core.secrets import load as load_secret_files
|
||||||
|
|
||||||
|
API = 'https://api.mercadopago.com'
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
parser = argparse.ArgumentParser(prog='python -m app.mercadopago_probe')
|
||||||
|
parser.add_argument('--bin', default='503143', help="the card's first six digits (default: the Mastercard test card)")
|
||||||
|
parser.add_argument('--valor', default='50', help='amount in reais for the instalment lookup')
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
load_secret_files()
|
||||||
|
token = os.environ.get('MP_ACCESS_TOKEN', '')
|
||||||
|
if not token:
|
||||||
|
print('MP_ACCESS_TOKEN is not set in this container.')
|
||||||
|
return 1
|
||||||
|
http = httpx.Client(base_url=API, timeout=15, headers={'Authorization': f'Bearer {token}'})
|
||||||
|
|
||||||
|
print(f"Token: {'TEST' if token.startswith('TEST-') else 'produção'}")
|
||||||
|
me = http.get('/users/me')
|
||||||
|
if me.status_code == 200:
|
||||||
|
user = me.json()
|
||||||
|
tags = user.get('tags') or []
|
||||||
|
print(f"Conta: id {user.get('id')} · {user.get('nickname')} · site {user.get('site_id')} · "
|
||||||
|
f"usuário de teste: {'sim' if 'test_user' in tags else 'não'} · tags {tags}")
|
||||||
|
else:
|
||||||
|
print(f'Conta: HTTP {me.status_code} {me.text[:200]}')
|
||||||
|
|
||||||
|
methods = http.get('/v1/payment_methods')
|
||||||
|
if methods.status_code == 200:
|
||||||
|
cards = [m for m in methods.json() if m.get('payment_type_id') in ('credit_card', 'debit_card')]
|
||||||
|
print('Cartões aceitos pela conta:')
|
||||||
|
for m in cards:
|
||||||
|
print(f" {m.get('id'):<12} {m.get('payment_type_id'):<12} {m.get('status')}")
|
||||||
|
else:
|
||||||
|
print(f'Meios de pagamento: HTTP {methods.status_code} {methods.text[:200]}')
|
||||||
|
|
||||||
|
quote = http.get('/v1/payment_methods/installments', params={'bin': args.bin, 'amount': args.valor})
|
||||||
|
if quote.status_code == 200 and quote.json():
|
||||||
|
print(f'BIN {args.bin}:')
|
||||||
|
for option in quote.json():
|
||||||
|
issuer = option.get('issuer') or {}
|
||||||
|
counts = [c.get('installments') for c in option.get('payer_costs') or []]
|
||||||
|
print(f" meio {option.get('payment_method_id')} · tipo {option.get('payment_type_id')} · "
|
||||||
|
f"emissor {issuer.get('id')} ({issuer.get('name')}) · parcelas {counts}")
|
||||||
|
else:
|
||||||
|
print(f'BIN {args.bin}: HTTP {quote.status_code} {quote.text[:300]}')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user