Piaskownica
Klucz testowy działa od założenia konta: pieniądze nie płyną, ekran płatności prowadzi do banku testowego, a webhooki wychodzą tak samo jak w produkcji. Tu są dane testowe, scenariusze, symulacja przez API i lista kontrolna przed przejściem na produkcję.
Klucz testowy #
Klucz plc_test_… wydajesz w panelu, w zakładce Integracja i klucze (przycisk Wydaj klucz przy pozycji Klucz testowy). Nie potrzebujesz karty, planu ani umowy z operatorem. Klucz pokazujemy raz; wymiana wycofuje poprzedni od razu.
Wszystko, co utworzysz tym kluczem, ma tryb: "test" i jest niewidoczne dla klucza produkcyjnego. W panelu transakcje testowe są oznaczone osobno.
Dane testowe #
Ekran płatności (adres_platnosci) w piaskownicy prowadzi do banku testowego. Wynik wybierasz kodem BLIK albo przyciskiem.
| Metoda | Dane | Wynik | Opis |
|---|---|---|---|
| BLIK | 777777 | oplacona | Bank testowy zatwierdza płatność. |
| BLIK | 111111 | odrzucona | Bank testowy odmawia. Płatność wraca do stanu odrzucona, klient może spróbować ponownie. |
| BLIK | inny sześciocyfrowy | błąd formularza | Ekran prosi o jeden z dwóch kodów powyżej, status bez zmian. |
| Karta, szybki przelew | przycisk „Zatwierdź płatność” | oplacona | Symulacja 3-D Secure 2 (karta) albo logowania do banku (przelew). Bez numeru karty. |
| Karta, szybki przelew | przycisk „Odrzuć w banku” | odrzucona |
Scenariusze #
- Płatność zatwierdzona
-
Jak: BLIK 777777 albo przycisk Zatwierdź płatność
Wynik: status oplacona, webhook platnosc.oplacona, oplacona z czasem księgowania, do_zwrotu równe kwocie
- Odmowa banku i druga próba
-
Jak: BLIK 111111, potem 777777 z tego samego adresu
Wynik: najpierw odrzucona i platnosc.odrzucona, potem oplacona i platnosc.oplacona dla tej samej płatności
- Zwrot częściowy
-
Jak: POST /v1/platnosci/{id}/zwroty z kwota
Wynik: status bez zmian, zwrocono rośnie, do_zwrotu maleje, zdarzenie platnosc.zwrot z polem zwrot
- Zwrot pełny
-
Jak: POST /v1/platnosci/{id}/zwroty bez ciała
Wynik: status zwrocona, do_zwrotu równe 0
- Zwrot ponad pulę
-
Jak: kwota większa niż do_zwrotu
Wynik: 422 zwrot_odrzucony z opisem kwoty, stan bez zmian
- Powtórzone zamówienie
-
Jak: drugi POST z tym samym numer_zamowienia, kwotą i walutą
Wynik: 200 z tą samą płatnością; inna kwota to 409 numer_zamowienia_zajety
- Ponowienie po przerwaniu
-
Jak: ten sam Idempotency-Key i ta sama treść
Wynik: zapisana odpowiedź z nagłówkiem Idempotent-Replayed: true, nic nie wykonuje się drugi raz
- Waluta spoza planu
-
Jak: waluta EUR bez planu Skala
Wynik: 422 waluta_poza_planem
- Koniec limitu demo
-
Jak: 31. płatność bez opłaconego planu
Wynik: 402 limit_demo
- Za dużo zapytań
-
Jak: ponad 120 zapytań w minucie jednym kluczem
Wynik: 429 limit_zapytan z nagłówkiem Retry-After
Symulacja wyniku przez API #
Do testów automatycznych służy POST /v1/platnosci/{id}/symulacja: ustawia wynik bez ekranu, tą samą drogą co bank testowy, więc powstaje zdarzenie i idzie webhook. Pole wynik to oplacona albo odrzucona, opcjonalne metoda (blik, card, transfer) zapisuje się przy płatności.
- Tylko klucz testowy. Kluczem produkcyjnym: 400 tylko_piaskownica.
- Tylko płatność w stanie
nowaalboodrzucona. Dlaoplaconaizwrocona: 409 stan_nieprawidlowy. - Po
odrzuconamożesz zasymulować drugą próbę z wynikiemoplacona, tak jak klient na ekranie.
curl -X POST "https://placenie.pl/api/v1/platnosci/tr_8h2k1j0f9e8d7c6b5a4z/symulacja" \
-H "Authorization: Bearer plc_test_..." \
-H "Content-Type: application/json" \
-d '{
"wynik": "oplacona",
"metoda": "blik"
}'
$ch = curl_init('https://placenie.pl/api/v1/platnosci/tr_8h2k1j0f9e8d7c6b5a4z/symulacja');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer plc_test_...',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'wynik' => 'oplacona',
'metoda' => 'blik',
]),
CURLOPT_RETURNTRANSFER => true,
]);
$platnosc = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
throw new RuntimeException($platnosc['blad']['kod'].': '.$platnosc['blad']['opis']);
}
const odpowiedz = await fetch('https://placenie.pl/api/v1/platnosci/tr_8h2k1j0f9e8d7c6b5a4z/symulacja', {
method: 'POST',
headers: {
Authorization: 'Bearer plc_test_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
wynik: 'oplacona',
metoda: 'blik',
}),
});
const platnosc = await odpowiedz.json();
if (!odpowiedz.ok) {
throw new Error(`${platnosc.blad.kod}: ${platnosc.blad.opis}`);
}
import requests
odpowiedz = requests.post(
"https://placenie.pl/api/v1/platnosci/tr_8h2k1j0f9e8d7c6b5a4z/symulacja",
headers={"Authorization": "Bearer plc_test_..."},
json={
"wynik": "oplacona",
"metoda": "blik",
},
)
platnosc = odpowiedz.json()
if not odpowiedz.ok:
raise RuntimeError(f"{platnosc['blad']['kod']}: {platnosc['blad']['opis']}")
{
"obiekt": "platnosc",
"id": "tr_8h2k1j0f9e8d7c6b5a4z",
"tryb": "test",
"status": "oplacona",
"kwota": 12900,
"waluta": "PLN",
"zwrocono": 0,
"do_zwrotu": 12900,
"numer_zamowienia": "ZAM-2026-001",
"opis": "Zamówienie ZAM-2026-001",
"email_klienta": "[email protected]",
"adres_powrotu": "https://twojsklep.pl/zamowienie/ZAM-2026-001/dziekujemy",
"metoda": "blik",
"metadane": {
"koszyk": "c_8812",
"klient_crm": "4471"
},
"adres_platnosci": "https://placenie.pl/zaplac/tr_8h2k1j0f9e8d7c6b5a4z",
"utworzona": "2026-10-03T10:00:00+00:00",
"oplacona": "2026-10-03T10:01:12+00:00",
"zwroty": []
}
Czym piaskownica różni się od produkcji #
| Element | Piaskownica (plc_test_) | Produkcja (plc_live_) |
|---|---|---|
| Dostęp | od założenia konta | opłacony plan i podłączone konto operatora |
| Ekran płatności | bank testowy (kody BLIK i przyciski) | formularz operatora płatności, prawdziwy bank |
| Status oczekuje | nie występuje | po przejściu klienta do operatora, do czasu wyniku; GET odświeża stan u operatora |
| Zwroty | od razu wykonany | mogą być zlecony u operatora, a potem wykonany albo odrzucony (platnosc.zwrot_odrzucony) |
| Symulacja | POST /v1/platnosci/{id}/symulacja | 400 tylko_piaskownica |
| Pieniądze i prowizja | nie płyną, prowizja 0 | rozlicza operator, prowizja według planu |
| Webhooki, podpis, ponowienia, API | identyczne | |
Limity #
| Limit | Wartość | Po przekroczeniu |
|---|---|---|
| Płatności testowe bez opłaconego planu | 30 na konto | 402 limit_demo; opłacony plan zdejmuje limit |
| Zapytania do API | 120 na minutę na klucz | 429 limit_zapytan, Retry-After w sekundach |
| Specyfikacja openapi.json | 60 na minutę na adres IP | 429 |
| Kwota płatności | 100 do 99 999 999 groszy | 422 dane_nieprawidlowe |
| Metadane | 50 kluczy, klucz 40, wartość 500 znaków | 422 dane_nieprawidlowe |
| Obiekty na stronie listy | 1 do 100 (limit) | 422 dane_nieprawidlowe |
| Klucz idempotencji | 1 do 255 znaków, ważny 24 h | 400 klucz_idempotencji_nieprawidlowy |
| Czas odpowiedzi odbiornika webhooka | 8 s | ponowienie według harmonogramu |
Przejście na produkcję #
Produkcja różni się od piaskownicy tylko kluczem i tym, że po drugiej stronie stoi operator płatności. Kod, który przeszedł scenariusze powyżej, przechodzi na produkcję bez zmian, jeśli obsługuje stany, których piaskownica nie pokazuje.
-
1
Opłacony plan
Klucz produkcyjny i podłączenie operatora działają w opłaconym planie. Plan uruchamiasz samodzielnie w panelu; ceny są na stronie cennika.
-
2
Konto operatora płatności
W panelu, w Integracja i klucze, podłącz konto operatora (dziś PayU: id punktu płatności, drugi klucz MD5, client_id i client_secret). Bez tego POST /v1/platnosci kluczem produkcyjnym zwraca 409 operator_niepodlaczony.
-
3
Klucz produkcyjny
Wydaj plc_live_… w panelu i trzymaj go w konfiguracji serwera, nigdy w repozytorium ani w kodzie przeglądarki. Klucz produkcyjny widzi tylko transakcje produkcyjne.
-
4
Webhook i sekret
Adres https na publicznym hoście, sekret podpisu w konfiguracji, weryfikacja podpisu z tolerancją 300 sekund, odpowiedź 2xx w mniej niż 8 sekund.
-
5
Stany, których nie ma w piaskownicy
Status oczekuje (klient u operatora), zwrot zlecony i zdarzenie platnosc.zwrot_odrzucony. Nie wydawaj towaru przy oczekuje.
-
6
Potwierdzenie zapłaty
Towar albo usługa wychodzi po webhooku platnosc.oplacona albo po GET ze statusem oplacona, nigdy po samym powrocie klienta na adres_powrotu.
-
7
Idempotencja
Idempotency-Key przy każdym POST, który tworzy płatność albo zwrot; numer_zamowienia przy każdej płatności.
-
8
Obsługa błędów
Kod obsługuje blad.typ (grupa) i loguje blad.zadanie_id (ten sam co X-Request-Id). 429 ponawia po Retry-After.
-
9
Zgodność w przód
Klient ignoruje nieznane pola w odpowiedziach; nowe pola dochodzą bez zmiany wersji.