Platico
Prijava

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 platico

Sa 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.

KarticaBrojRezultat
Visa4111 1111 1111 1111uspeh
Visa4242 4242 4242 4242neuspeh
Mastercard5555 5555 5555 4444uspeh
Maestro5000 0010 0000 0007uspeh

Bilo koji datum u budućnosti, bilo koji 3-cifreni CVV, bilo koje ime.

Očekivano: kartica 4111 ima obavezan 3DS, pa prvi odgovor uvek bude 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 prikaz

Autorizuj 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 rezervaciju

Povrać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.com

Sigurnost

  • 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 signatures

Verzije od v0.2.1+ nose Sigstore atestaciju koja povezuje tarball sa konkretnim GitHub Actions workflow run-om.

Linkovi