Node.js SDK
platico (Node.js SDK)
Zvanični Node.js SDK za Platico API. Stripe-oblikovan, nula runtime zavisnosti, Node 18+. Ovaj vodič vas vodi od nule do uspešne naplate za 5 minuta.
1. Instalacija i ključevi
npm 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).
<!-- 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 express from 'express'
import Platico from 'platico'
const platico = new Platico(process.env.PLATICO_API_KEY!) // sk_test_...
const app = express()
app.use(express.json())
app.post('/checkout', async (req, res) => {
const intent = await platico.paymentIntents.create({
amount: 4990, // minor units — 49,90 RSD
currency: 'rsd',
confirm: true, // odmah potvrdi
payment_method_token: req.body.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'
res.json(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
const updated = await platico.paymentIntents.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
const customer = await platico.customers.create({
email: 'jovan@example.rs',
name: 'Jovan Petrović',
})
const pm = await platico.paymentMethods.create({
type: 'card',
card: { token }, // token iz platico.js createPaymentToken()
customer: customer.id,
})
// 2) kasnije — naplati sačuvanu karticu; bez platico.js, bez unosa kartice
await platico.paymentIntents.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'.
const pi = await platico.paymentIntents.create({
amount: 4990,
currency: 'rsd',
capture_method: 'manual',
confirm: true,
payment_method_token: token,
}) // status: 'requires_capture'
await platico.paymentIntents.capture(pi.id) // naplati sredstva
await platico.paymentIntents.cancel(pi.id) // …ili otpusti rezervacijuPovraćaji
await platico.refunds.create({ payment_intent: pi.id }) // pun povraćaj
await platico.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 await (const pi of platico.paymentIntents.list({ status: 'succeeded' })) {
console.log(pi.id, pi.amount)
}
// ili sakupi ograničen broj (limit je obavezan — sprečava OOM)
const recent = await platico.customers.list({}).autoPagingToArray({ limit: 100 })Rukovanje greškama
import Platico, { CardError, RateLimitError, ApiError } from 'platico'
try {
await platico.paymentIntents.create({ /* … */ })
} catch (err) {
if (err instanceof CardError) {
// err.code / err.declineCode — npr. 'card_declined' / 'insufficient_funds'
} else if (err instanceof RateLimitError) {
// pokušaj ponovo posle err.retryAfter sekundi
} else if (err instanceof ApiError) {
// err.statusCode, err.requestId — navedi requestId u tiketu podrške
} else {
throw err
}
}Prosledi idempotency ključ na svakom mutirajućem pozivu da retry bude bezbedan:
await platico.paymentIntents.create(params, { idempotencyKey: `order_${orderId}` })Sporovi (chargeback-ovi)
for await (const d of platico.disputes.list({ status: 'needs_response' })) {
console.log(d.id, d.amount, d.reason)
}
const dispute = await platico.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 await (const ev of platico.events.list({ type: 'payment_intent.succeeded' })) {
console.log(ev.id, ev.type, ev.data.object)
}
const event = await platico.events.retrieve('evt_...')Zahteva platico ≥ 0.3.0.
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.
const endpoint = await platico.webhookEndpoints.create({
url: 'https://vas-sajt.rs/webhooks/platico',
enabled_events: ['payment_intent.succeeded', 'charge.refunded'],
})
console.log(endpoint.secret) // whsec_… — prikazano jednom
await platico.webhookEndpoints.list({})
await platico.webhookEndpoints.update(endpoint.id, { status: 'disabled' })
await platico.webhookEndpoints.roll(endpoint.id) // rotiraj potpisni ključ
await platico.webhookEndpoints.del(endpoint.id)Zahteva platico ≥ 0.3.0. Režim endpoint-a prati ključ (sk_test_ → test, sk_live_ → live).
Webhook verifikacija
import express from 'express'
import Platico from 'platico'
const platico = new Platico(process.env.PLATICO_API_KEY!)
const app = express()
// VAŽNO: webhook-ovi treba SIROVI body. express.json() ga
// re-serijalizuje i HMAC više ne odgovara.
app.post(
'/webhooks/platico',
express.raw({ type: 'application/json' }),
(req, res) => {
try {
const event = platico.webhooks.constructEvent(
req.body, // Buffer
req.header('Platico-Signature') ?? '',
process.env.PLATICO_WEBHOOK_SECRET!,
)
console.log(event.type, event.data.object)
res.sendStatus(200)
} catch (err) {
console.error('Webhook odbijen:', err)
res.sendStatus(400)
}
},
)Konfiguracija
new Platico(apiKey, {
apiBase: 'https://api.platico.rs', // podrazumevano
timeout: 30_000, // ms, podrazumevano
maxNetworkRetries: 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 odbija ne-HTTPS apiBase URL (osim loopback hostova).
- API ključ se NIKADA ne pojavljuje u serijalizovanim greškama.
- HMAC verifikacija koristi crypto.timingSafeEqual — 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 4xx-non-429.
Provera potpisa publikacije
npm install platico
npm audit signaturesVerzije od v0.2.1+ nose Sigstore atestaciju koja povezuje tarball sa konkretnim GitHub Actions workflow run-om.
