@simpay/typescript
v1.0.3
Published
Official Simpay payment gateway SDK for TypeScript
Downloads
42
Readme
Oficjalne SDK SimPay dla TypeScript i Node.js.
Biblioteka udostępnia klienta SimPayClient oraz moduły dla:
- płatności online (
payments) - SMS Premium (
sms) - Direct Billing (
directBilling) - notyfikacji IPN (
notifications)
SDK działa w środowisku Node.js i korzysta z natywnego fetch.
Wymagania
- Node.js
>= 18
Instalacja
npm install @simpay/typescriptSzybki start
import { SimPayClient } from "@simpay/typescript";
const simpay = new SimPayClient({
api: {
password: process.env.SIMPAY_API_PASSWORD!,
},
service: {
id: process.env.SIMPAY_SERVICE_ID!,
},
ipn: {
signatureKey: process.env.SIMPAY_IPN_SIGNATURE_KEY!,
validateSourceIp: true,
},
});Konfiguracja klienta
import type { SimPayClientConfig } from "@simpay/typescript";
const config: SimPayClientConfig = {
api: {
password: "YOUR_API_PASSWORD",
timeout: 10000,
},
service: {
id: "YOUR_DEFAULT_SERVICE_ID",
},
ipn: {
signatureKey: "YOUR_IPN_SIGNATURE_KEY",
validateSourceIp: false,
},
};Pola konfiguracji
api.password
Hasło API SimPay używane do autoryzacji Bearer.
api.timeout
Timeout requestów HTTP w milisekundach. Jeśli nie podasz własnej wartości, SDK użyje 10000 ms.
service.id
Domyślne ID usługi. Nadal możesz przekazywać serviceId jawnie do metod modułów.
ipn.signatureKey
Klucz do weryfikacji podpisów IPN.
ipn.validateSourceIp
Czy weryfikować adres źródłowy IPN przez allowlistę SimPay.
Publiczne API
Runtime
SimPayClient
Błędy
SimPayErrorSimPayApiErrorSimPayValidationErrorSimPaySignatureErrorSimPayNetworkErrorSimPayIpnError
Typy
SDK eksportuje publiczne typy dla:
paymentssmsdirectbillingnotificationscommon
Przykład:
import type {
CreateTransactionRequest,
VerifyCodePayload,
DirectBillingCreateTransactionRequest,
PaymentIpnNotification,
} from "@simpay/typescript";Moduły
Payments
Dostęp przez:
simpay.paymentsDostępne podmoduły
simpay.payments.servicessimpay.payments.channelssimpay.payments.transactionssimpay.payments.refundssimpay.payments.blikLevel0simpay.payments.blikRecurrent
Payments → Services
Lista usług płatności
const services = await simpay.payments.services.list();Szczegóły usługi
const service = await simpay.payments.services.get("service_id");Payments → Channels
Lista kanałów płatności
const channels = await simpay.payments.channels.list("service_id");Payments → Transactions
Utworzenie transakcji
const transaction = await simpay.payments.transactions.create("service_id", {
amount: 12.5,
currency: "PLN",
description: "Order #123",
control: "order_123",
customer: {
email: "[email protected]",
name: "John Doe",
},
returns: {
success: "https://twoja-strona.pl/payment/success",
failure: "https://twoja-strona.pl/payment/failure",
},
});Przykładowa odpowiedź:
const response = {
transactionId: "tx_123",
redirectUrl: "https://...",
};Lista transakcji
const transactions = await simpay.payments.transactions.list("service_id");Szczegóły transakcji
const details = await simpay.payments.transactions.get(
"service_id",
"transaction_id",
);Payments → Refunds
Lista refundów transakcji
const refunds = await simpay.payments.refunds.list(
"service_id",
"transaction_id",
);Szczegóły refundu
const refund = await simpay.payments.refunds.get(
"service_id",
"transaction_id",
"refund_id",
);Utworzenie refundu
const createdRefund = await simpay.payments.refunds.create(
"service_id",
"transaction_id",
{
amount: 10,
},
);Payments → BLIK Level 0
Wysłanie 6-cyfrowego kodu BLIK
const result = await simpay.payments.blikLevel0.submitCode(
"service_id",
"transaction_id",
"123456",
);Odpowiedź:
{ accepted: true }Payments → BLIK Recurrent
Pełna dokumentacja SimPay dla BLIK Recurrent: https://docs.simpay.pl/payment/blik-recurrent
Flow Płatności Powtarzalnej BLIK (krok po kroku)
1) Rejestracja subskrypcji (zgoda użytkownika)
Najpierw tworzysz pierwszą transakcję dla kanału blik-recurrent.
Ta transakcja zakłada zgodę użytkownika i pozwala potem utworzyć subskrypcję.
const firstTransaction = await simpay.payments.transactions.create("service_id", {
amount: 0, // może być 0 tylko przy rejestracji zgody
currency: "PLN", // BLIK recurrent działa w PLN
description: "Subskrypcja premium",
control: "SUB-123",
directChannel: "blik-recurrent",
customer: {
email: "[email protected]",
ip: "1.2.3.4",
countryCode: "PL",
},
antifraud: {
useragent: "Mozilla/5.0 ...",
systemId: "user-123", // stałe ID użytkownika w Twoim systemie
},
});Potem tworzysz subskrypcję BLIK (model A/O/M) i podajesz kod BLIK (ticket.T6):
const created = await simpay.payments.blikRecurrent.create("service_id", {
transactionId: firstTransaction.transactionId,
ticket: { T6: "123456" },
alias: {
value: "AABBCC",
type: "PAYID",
label: "Subskrypcja premium",
},
options: {
// przykład modelu A (AUTO)
model: "A",
expiresAt: "2030-12-31T23:59:59+01:00",
frequency: "1M",
amountLimitPerTransaction: 49.99,
initiationDate: "2026-05-01T10:00:00+02:00",
amountLimitTotal: 2000,
},
});Jeśli rejestracja się powiedzie, dostajesz m.in.:
subscriptionIdaliasId
Ważne: samo
subscriptionIdnie oznacza, że subskrypcja jest aktywna.
2) Czekasz na IPN aktywujący subskrypcję
Subskrypcja jest gotowa do obciążeń dopiero po notyfikacji IPN:
subscription:status_changed- ze statusem
subscription_active
3) Kolejne obciążenia aktywnej subskrypcji
Dopiero wtedy wywołujesz autopayment:
const autopayment = await simpay.payments.blikRecurrent.autopayment(
"service_id",
"subscription_id",
{
transactionId: "order-2026-001",
attempt: 0, // zakres 0-9
alias: {
noDelay: true,
},
},
);Technicznie odpowiada to endpointowi:
POST /payment/{serviceId}/blik/subscriptions/{subscriptionId}/autopayment
Zasady pracy z attempt
attemptwysyłasz w zakresie0-9.- Pierwsza próba to
attempt: 0, każda kolejna próba musi zwiększać numer o+1. - Dobrą praktyką jest iterowanie prób sekwencyjnie (
0 -> 1 -> 2 ...). - Nie wysyłaj kolejnej próby, dopóki poprzednia nie jest zakończona (np. przez finalny status transakcji/IPN). Równoległe próby dla tego samego obciążenia mogą powodować błędy walidacji lub niespójny stan.
Modele subskrypcji i options
- Model A (stała kwota, bez kolejnych potwierdzeń):
options.expiresAt(wymagane)options.frequency(wymagane)options.amountLimitPerTransaction(wymagane)options.initiationDate(wymagane)options.amountLimitTotal(wymagane)
- Model O (kwota zmienna, bez potwierdzania):
- zazwyczaj używane:
options.initiationDate,options.expiresAt
- zazwyczaj używane:
- Model M (kwota zmienna, z potwierdzaniem każdej płatności):
- opcjonalnie:
options.initiationDate,options.expiresAt,options.frequency
- opcjonalnie:
Najważniejsze rzeczy, o których łatwo zapomnieć
antifraud.systemIdmusi być stałe dla tego samego użytkownika.amount: 0jest dozwolone tylko przy rejestracji zgody (pierwsza transakcja).- Kolejnych obciążeń nie wykonujesz na
0 PLN. - BLIK recurrent działa tylko dla
PLNicustomer.countryCode = "PL". - Przy pierwszej transakcji wysyłasz
customer.ipiantifraud.useragent; przy kolejnych obciążeniach już nie.
Lista subskrypcji BLIK
const result = await simpay.payments.blikRecurrent.list("service_id", {
filter: {
status: "subscription_active",
mode: "BLIK",
},
page: 1,
perPage: 20,
sort: "-created_at",
});Utworzenie subskrypcji BLIK
const created = await simpay.payments.blikRecurrent.create("service_id", {
transactionId: "tx_123",
ticket: { T6: "123456" },
alias: {
value: "AABBCC",
type: "PAYID",
label: "Subskrypcja premium",
},
options: {
model: "O",
},
});Autopayment dla subskrypcji
const autopayment = await simpay.payments.blikRecurrent.autopayment(
"service_id",
"subscription_id",
{
transactionId: "tx_456",
attempt: 0,
},
);Lista aliasów BLIK
const aliases = await simpay.payments.blikRecurrent.listAliases("service_id", {
filter: {
status: "alias_active",
type: "PAYID",
},
page: 1,
perPage: 20,
sort: "-created_at",
});Wyrejestrowanie aliasu BLIK
await simpay.payments.blikRecurrent.deleteAlias(
"service_id",
"alias_id",
{
reason: "Rezygnacja użytkownika",
},
);SMS Premium
Dostęp przez:
simpay.smsDostępne podmoduły
simpay.sms.servicessimpay.sms.transactionssimpay.sms.verificationsimpay.sms.numbers
SMS → Services
Lista usług SMS
const services = await simpay.sms.services.list();Szczegóły usługi SMS
const service = await simpay.sms.services.get("service_id");SMS → Transactions
Lista transakcji SMS
const transactions = await simpay.sms.transactions.list("service_id");Szczegóły transakcji SMS
const transaction = await simpay.sms.transactions.get("service_id", 123456);SMS → Verification
Weryfikacja kodu SMS
const result = await simpay.sms.verification.verify("service_id", {
code: "ABC123",
number: 7055,
});Przykładowa odpowiedź:
const response = {
used: false,
code: "ABC123",
test: true,
from: "48500100200",
number: 7055,
value: 5,
};SMS → Numbers
Lista wszystkich numerów SMS
const numbers = await simpay.sms.numbers.list();Szczegóły numeru
const number = await simpay.sms.numbers.get(7055);Lista numerów dla usługi
const serviceNumbers = await simpay.sms.numbers.listByService("service_id");Szczegóły numeru dla usługi
const serviceNumber = await simpay.sms.numbers.getByService("service_id", 7055);Direct Billing
Dostęp przez:
simpay.directBillingDostępne podmoduły
simpay.directBilling.servicessimpay.directBilling.calculationsimpay.directBilling.transactions
Direct Billing → Services
Lista usług Direct Billing
const services = await simpay.directBilling.services.list();Szczegóły usługi Direct Billing
const service = await simpay.directBilling.services.get("service_id");Direct Billing → Calculation
Kalkulacja prowizji
const calculation = await simpay.directBilling.calculation.calculate(
"service_id",
25,
);Direct Billing → Transactions
Utworzenie transakcji Direct Billing
const transaction = await simpay.directBilling.transactions.create(
"service_id",
{
amount: 19.99,
amountType: "gross",
description: "Subscription renewal",
control: "order_123",
phoneNumber: "500600700",
returns: {
success: "https://twoja-strona.pl/db/success",
failure: "https://twoja-strona.pl/db/failure",
},
},
);Lista transakcji Direct Billing
const transactions = await simpay.directBilling.transactions.list(
"service_id",
{
filter: {
status: "transaction_db_payed",
},
},
);Szczegóły transakcji Direct Billing
const details = await simpay.directBilling.transactions.get(
"service_id",
"transaction_id",
);Notifications / IPN
Dostęp przez:
simpay.notificationsDostępne podmoduły
simpay.notifications.paymentsimpay.notifications.directbilling
Payment IPN
Weryfikacja IPN płatności
const result = await simpay.notifications.payment.verify({
payload: req.body,
sourceIp: req.ip,
});Po poprawnej walidacji metoda zwraca:
"OK"Dodatkowe metody
const isValid = simpay.notifications.payment.verifySignature(payload);
const isAllowedIp = await simpay.notifications.payment.validateSourceIp(ip);Przykład z Express.js
Poniższy przykład pokazuje pełny handler webhooka płatności:
- odbiór
req.body - weryfikację podpisu i adresu IP
- zawężanie typu notyfikacji przez
switch (payload.type) - zwrócenie wymaganej odpowiedzi
OK
import express from "express";
import {
SimPayClient,
SimPayIpnError,
type PaymentIpnNotification,
} from "@simpay/typescript";
const app = express();
app.use(express.json());
const simpay = new SimPayClient({
api: {
password: process.env.SIMPAY_API_PASSWORD!,
},
service: {
id: process.env.SIMPAY_SERVICE_ID!,
},
ipn: {
signatureKey: process.env.SIMPAY_IPN_SIGNATURE_KEY!,
validateSourceIp: true,
},
});
app.post("/webhooks/simpay/payment", async (req, res) => {
try {
await simpay.notifications.payment.verify({
payload: req.body,
sourceIp: req.ip,
});
const payload = req.body as PaymentIpnNotification;
switch (payload.type) {
case "transaction:status_changed": {
const transactionId = payload.data.id;
const status = payload.data.status;
const payerTransactionId = payload.data.payer_transaction_id;
console.log("payment transaction status changed", {
transactionId,
payerTransactionId,
status,
});
break;
}
case "transaction_refund:status_changed": {
const refundId = payload.data.id;
const refundStatus = payload.data.status;
const transactionId = payload.data.transaction.id;
console.log("refund status changed", {
refundId,
transactionId,
refundStatus,
});
break;
}
case "transaction_blik_level0:code_status_changed": {
const ticketStatus = payload.data.ticket_status;
const transactionId = payload.data.transaction.id;
const transactionStatus = payload.data.transaction.status;
console.log("blik level0 status changed", {
transactionId,
ticketStatus,
transactionStatus,
});
break;
}
case "blik:alias_status_changed": {
const aliasId = payload.data.id;
const aliasStatus = payload.data.status;
const aliasValue = payload.data.value;
console.log("blik alias status changed", {
aliasId,
aliasStatus,
aliasValue,
});
break;
}
case "subscription:status_changed": {
const subscriptionId = payload.data.id;
const subscriptionStatus = payload.data.status;
const mode = payload.data.mode;
console.log("subscription status changed", {
subscriptionId,
subscriptionStatus,
mode,
});
break;
}
case "ipn:test": {
console.log("received payment ipn test", {
serviceId: payload.data.service_id,
nonce: payload.data.nonce,
});
break;
}
default: {
const neverPayload: never = payload;
throw new Error(`Unhandled payment notification: ${String(neverPayload)}`);
}
}
res.status(200).send("OK");
} catch (error) {
if (error instanceof SimPayIpnError) {
res.status(400).send(error.code);
return;
}
res.status(500).send("ERROR");
}
});Dlaczego switch działa dobrze z typami?
Typ PaymentIpnNotification jest unią typów rozróżnianą po polu type.
To znaczy, że po wejściu do konkretnego case TypeScript zawęża payload.data do właściwego kształtu.
Przykład:
function handlePaymentNotification(payload: PaymentIpnNotification): void {
switch (payload.type) {
case "transaction:status_changed":
payload.data.id;
payload.data.status;
payload.data.payer_transaction_id;
break;
case "transaction_refund:status_changed":
payload.data.transaction.id;
payload.data.amount.value;
break;
}
}To daje bardzo wygodny i bezpieczny model obsługi webhooków bez ręcznego rzutowania każdej gałęzi.
Direct Billing IPN
Weryfikacja IPN Direct Billing
const result = await simpay.notifications.directbilling.verify({
payload: req.body,
sourceIp: req.ip,
});Po poprawnej walidacji metoda zwraca:
"OK"Dodatkowe metody
const isValid = simpay.notifications.directbilling.verifySignature(payload);
const isAllowedIp = await simpay.notifications.directbilling.validateSourceIp(ip);Przykład z Express.js
import express from "express";
import {
SimPayClient,
SimPayIpnError,
type DirectBillingTransactionNotification,
} from "@simpay/typescript";
const app = express();
app.use(express.json());
const simpay = new SimPayClient({
api: {
password: process.env.SIMPAY_API_PASSWORD!,
},
service: {
id: process.env.SIMPAY_SERVICE_ID!,
},
ipn: {
signatureKey: process.env.SIMPAY_IPN_SIGNATURE_KEY!,
validateSourceIp: true,
},
});
app.post("/webhooks/simpay/directbilling", async (req, res) => {
try {
await simpay.notifications.directbilling.verify({
payload: req.body,
sourceIp: req.ip,
});
const payload = req.body as DirectBillingTransactionNotification;
switch (payload.status) {
case "transaction_db_new":
case "transaction_db_confirmed":
case "transaction_db_payed":
case "transaction_db_rejected": {
console.log("direct billing notification", {
id: payload.id,
serviceId: payload.serviceId,
status: payload.status,
numberFrom: payload.number_from,
provider: payload.provider,
net: payload.values.net,
gross: payload.values.gross,
partner: payload.values.partner,
control: payload.control ?? null,
});
break;
}
default: {
const neverStatus: never = payload.status;
throw new Error(`Unhandled direct billing status: ${neverStatus}`);
}
}
res.status(200).send("OK");
} catch (error) {
if (error instanceof SimPayIpnError) {
res.status(400).send(error.code);
return;
}
res.status(500).send("ERROR");
}
});Kiedy używać verify(), a kiedy verifySignature()?
verify()
Używaj w normalnym webhook handlerze HTTP. Metoda sprawdza:
- adres IP źródła (jeśli
validateSourceIp: true) - obecność klucza podpisu
- kształt payloadu
- podpis notyfikacji
verifySignature()
Używaj, gdy:
- chcesz sprawdzić tylko podpis,
- payload został już wcześniej zwalidowany inną warstwą,
- testujesz lub debugujesz webhooki poza pełnym requestem HTTP.
Obsługa błędów
SDK rozróżnia kilka klas błędów:
SimPayError– bazaSimPayApiError– API zwróciło błądSimPayValidationError– niepoprawne dane wejścioweSimPayNetworkError– błąd sieci / timeoutSimPaySignatureError– błędy podpisuSimPayIpnError– błędy IPN / webhooków
Przykład:
import {
SimPayApiError,
SimPayNetworkError,
SimPayValidationError,
} from "@simpay/typescript";
try {
const result = await simpay.payments.transactions.create("service_id", {
amount: 10,
customer: {
email: "[email protected]",
},
});
} catch (error) {
if (error instanceof SimPayValidationError) {
console.error("Validation:", error.message);
} else if (error instanceof SimPayApiError) {
console.error("API:", error.statusCode, error.errorCode);
} else if (error instanceof SimPayNetworkError) {
console.error("Network:", error.message);
} else {
console.error("Unknown error:", error);
}
}Development
Uruchamianie testów
npm testTypecheck
npm run typecheckLint
npm run lintFormat
npm run formatPełna walidacja
npm run verify