Python SDK
platico (Python SDK)
Zvanični Python SDK za Platico API. Stripe-oblikovan, nula runtime zavisnosti, Python 3.9+. Ovaj vodič vas vodi od nule do uspešne naplate za 5 minuta.
1. Instalacija i ključevi
pip install platicoSa stranice API ključevi uzmite dva ključa (oba se kreiraju automatski u test režimu):
- Publishable ključ (
pk_test_…) — ide u vaš frontend. Tajan nije; može samo da tokenizuje karticu, ne i da naplati. - Secret ključ (
sk_test_…) — ide na vaš server. Nikada ga ne izlažite u browseru.
2. Frontend — naplata kartice sa platico.js
platico.js ubacuje polja za broj kartice i CVV kao bezbedne iframe-ove i vraća jednokratni token. Podaci kartice nikada ne dodiruju vaš server (ostajete van PCI DSS SAQ D). Frontend je isti bez obzira na jezik servera.
<!-- 1. Učitaj platico.js -->
<script src="https://js.platico.rs/v1/platico.js"></script>
<!-- 2. Mesta gde se montiraju iframe-ovi -->
<div id="card-number"></div>
<div id="card-cvv"></div>
<button id="pay">Plati 49,90 RSD</button>const platico = Platico('pk_test_...', { apiBase: 'https://api.platico.rs' })
await platico.mountCard({ cardNumber: '#card-number', cvv: '#card-cvv' })
document.getElementById('pay').addEventListener('click', async () => {
// Tokenizuj karticu — vraća jednokratni payment token.
const { token } = await platico.createPaymentToken({
cardholder: 'Petar Petrović',
expMonth: '12',
expYear: '2030',
})
// Pošalji token SVOM serveru (nikada secret ključ u browser).
const res = await fetch('/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
})
const intent = await res.json()
// 3-D Secure: ako je potrebna akcija, otvori izazov.
if (intent.status === 'requires_action') {
await platico.handleNextAction(intent)
// Po povratku ponovo proveri status preko svog servera.
}
})3. Server — kreiraj i potvrdi naplatu
import os
from flask import Flask, request, jsonify
from platico import Platico
client = Platico(os.environ["PLATICO_API_KEY"]) # sk_test_...
app = Flask(__name__)
@app.post("/checkout")
def checkout():
data = request.get_json()
intent = client.payment_intents.create({
"amount": 4990, # minor units — 49,90 RSD
"currency": "rsd",
"confirm": True, # odmah potvrdi
"payment_method_token": data["token"],
# Stranica NA VAŠEM sajtu na koju se kupac vraća posle 3DS-a.
"return_url": "https://vas-sajt.rs/checkout/complete",
})
# status: "succeeded" | "requires_action" | "requires_payment_method"
return jsonify(intent)4. 3-D Secure
Ako kartica zahteva 3DS, status je requires_action, a intent["next_action"]["redirect_to_url"]["url"] nosi izazov. Frontend ga otvara sa platico.handleNextAction(intent) (popup). Postavite return_url (u koraku 3) na stranicu NA VAŠEM sajtu — handleNextAction se razrešava čim se popup vrati na nju. Zatim ponovo dohvatite PaymentIntent na serveru.
5. Dohvati konačni status
updated = client.payment_intents.retrieve(intent["id"])
if updated["status"] == "succeeded":
# Naplata uspela — ispuni narudžbinu.
...Pri dohvatanju, Platico lenjo usaglašava status sa procesorom — tako PI prelazi u succeeded i bez javnog webhook tunela na localhost-u.
6. Sandbox i test kartice
Test režim koristi sk_test_ / pk_test_ i AllSecure sandbox procesor. Bazni URL API-ja je https://api.platico.rs.
| Kartica | Broj | Rezultat |
|---|---|---|
| Visa | 4111 1111 1111 1111 | uspeh |
| Visa | 4242 4242 4242 4242 | neuspeh |
| Mastercard | 5555 5555 5555 4444 | uspeh |
| Maestro | 5000 0010 0000 0007 | uspeh |
Bilo koji datum u budućnosti, bilo koji 3-cifreni CVV, bilo koje ime.
requires_action — to nije greška. Na sandbox 3DS ekranu izaberite Authenticated (ECI 05) da simulirate uspešan izazov; zatim ponovo dohvatite PI i videćete succeeded.Sačuvaj karticu i naplati ponovo (povratni kupac)
# 1) jednom — napravi kupca i sačuvaj tokenizovanu karticu na njemu
customer = client.customers.create({
"email": "jovan@example.rs",
"name": "Jovan Petrović",
})
pm = client.payment_methods.create({
"type": "card",
"card": {"token": token}, # token iz platico.js createPaymentToken()
"customer": customer["id"],
})
# 2) kasnije — naplati sačuvanu karticu; bez platico.js, bez unosa kartice
client.payment_intents.create({
"amount": 1990,
"currency": "rsd",
"customer": customer["id"],
"payment_method": pm["id"],
"confirm": True,
})
# pm["card"] → {brand, last4, exp_month, exp_year} za prikazAutorizuj sada, naplati kasnije
Rezerviši iznos pri narudžbini i naplati kad pošalješ — prosledi capture_method="manual".
pi = client.payment_intents.create({
"amount": 4990,
"currency": "rsd",
"capture_method": "manual",
"confirm": True,
"payment_method_token": token,
}) # status: "requires_capture"
client.payment_intents.capture(pi["id"]) # naplati sredstva
client.payment_intents.cancel(pi["id"]) # …ili otpusti rezervacijuPovraćaji
client.refunds.create({"payment_intent": pi["id"]}) # pun povraćaj
client.refunds.create({ # delimičan (10,00 RSD)
"payment_intent": pi["id"],
"amount": 1000,
"reason": "requested_by_customer",
})Liste i auto-paginacija
# prođi kroz sve rezultate — jedna stranica u memoriji
for pi in client.payment_intents.list({"status": "succeeded"}):
print(pi["id"], pi["amount"])
# ili sakupi ograničen broj (limit je obavezan — sprečava OOM)
recent = client.customers.list().auto_paging_to_list(limit=100)Rukovanje greškama
from platico import CardError, RateLimitError, ApiError
try:
client.payment_intents.create({ ... })
except CardError as err:
# err.code / err.decline_code — npr. "card_declined" / "insufficient_funds"
...
except RateLimitError as err:
# pokušaj ponovo posle err.retry_after sekundi
...
except ApiError as err:
# err.status_code, err.request_id — navedi request_id u tiketu podrške
...Prosledi idempotency ključ na svakom mutirajućem pozivu da retry bude bezbedan:
client.payment_intents.create(params, idempotency_key=f"order_{order_id}")Sporovi (chargeback-ovi)
for d in client.disputes.list({"status": "needs_response"}):
print(d["id"], d["amount"], d["reason"])
dispute = client.disputes.retrieve("dp_...")Događaji (events)
Isti događaji koji pokreću webhook-ove, dostupni iz koda. Ograničeni na režim vašeg ključa.
# auto-paginacija; filtriraj po tipu
for ev in client.events.list({"type": "payment_intent.succeeded"}):
print(ev["id"], ev["type"], ev["data"]["object"])
event = client.events.retrieve("evt_...")Webhook endpoint-ovi
Upravljaj odredištima za isporuku iz koda. Potpisni secret se vraća JEDNOM — pri kreiranju i roll-u — pa ga tada sačuvaj; koristi ga za verifikaciju isporuka.
endpoint = client.webhook_endpoints.create({
"url": "https://vas-sajt.rs/webhooks/platico",
"enabled_events": ["payment_intent.succeeded", "charge.refunded"],
})
print(endpoint["secret"]) # whsec_… — prikazano jednom
client.webhook_endpoints.list()
client.webhook_endpoints.update(endpoint["id"], {"status": "disabled"})
client.webhook_endpoints.roll(endpoint["id"]) # rotiraj potpisni ključ
client.webhook_endpoints.delete(endpoint["id"])Režim endpoint-a prati ključ (sk_test_ → test, sk_live_ → live).
Webhook verifikacija
import os
from flask import Flask, request
from platico import (
construct_event,
WebhookSignatureInvalidError,
WebhookTimestampStaleError,
WebhookMalformedHeaderError,
)
app = Flask(__name__)
# VAŽNO: webhook-ovi treba SIROVI body (bajtovi). request.get_json()
# re-serijalizuje JSON i HMAC više ne odgovara — koristi request.get_data().
@app.post("/webhooks/platico")
def webhook():
try:
event = construct_event(
request.get_data(), # sirovi bajtovi
request.headers.get("Platico-Signature", ""),
os.environ["PLATICO_WEBHOOK_SECRET"],
)
except (
WebhookSignatureInvalidError,
WebhookTimestampStaleError,
WebhookMalformedHeaderError,
):
return "", 400
print(event["type"], event["data"]["object"])
return "", 200Konfiguracija
Platico(
api_key,
api_base="https://api.platico.rs", # podrazumevano
timeout=30.0, # sekunde, podrazumevano
max_network_retries=3, # 5xx / 429 / mreža
telemetry=False, # opt-in
)Content-Security-Policy
Ako vaša checkout stranica koristi CSP, dozvolite ove hostove:
script-src https://js.platico.rs https://asxgw.paymentsandbox.cloud https://asxgw.com
frame-src https://asxgw.paymentsandbox.cloud https://asxgw.com https://secure.asxgw.com
connect-src https://api.platico.rs https://asxgw.paymentsandbox.cloud https://asxgw.com
img-src https://asxgw.paymentsandbox.cloud https://asxgw.comSigurnost
- SDK uvek verifikuje TLS (lanac sertifikata + hostname) — bez opcije za isključivanje.
- API ključ se NIKADA ne pojavljuje u serijalizovanim greškama (ni u traceback-u/pickle-u).
- HMAC verifikacija webhook-a koristi hmac.compare_digest (konstantno vreme) — nikada ==.
- Auto Idempotency-Key na svakom mutirajućem pozivu; isti ključ na svim retry-jevima.
- Retry samo na 5xx / 429 / network greške. Nikada na ostale 4xx.
- Nula runtime zavisnosti — samo standardna biblioteka.
Provera onoga što ste instalirali
pip install platicoOd prvog CI izdanja, svaka verzija se objavljuje preko PyPI Trusted Publishing-a (OIDC) i nosi PEP 740 atestaciju koja povezuje artefakt sa konkretnim GitHub Actions workflow run-om.
