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.
- Node.js
>= 18
npm install @simpay/typescriptimport { 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,
},
});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,
},
};Hasło API SimPay używane do autoryzacji Bearer.
Timeout requestów HTTP w milisekundach. Jeśli nie podasz własnej wartości, SDK użyje 10000 ms.
Domyślne ID usługi. Nadal możesz przekazywać serviceId jawnie do metod modułów.
Klucz do weryfikacji podpisów IPN.
Czy weryfikować adres źródłowy IPN przez allowlistę SimPay.
SimPayClient
SimPayErrorSimPayApiErrorSimPayValidationErrorSimPaySignatureErrorSimPayNetworkErrorSimPayIpnError
SDK eksportuje publiczne typy dla:
paymentssmsdirectbillingnotificationscommon
Przykład:
import type {
CreateTransactionRequest,
VerifyCodePayload,
DirectBillingCreateTransactionRequest,
PaymentIpnNotification,
} from "@simpay/typescript";Dostęp przez:
simpay.paymentssimpay.payments.servicessimpay.payments.channelssimpay.payments.transactionssimpay.payments.refundssimpay.payments.blikLevel0simpay.payments.blikRecurrent
const services = await simpay.payments.services.list();const service = await simpay.payments.services.get("service_id");const channels = await simpay.payments.channels.list("service_id");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://...",
};const transactions = await simpay.payments.transactions.list("service_id");const details = await simpay.payments.transactions.get(
"service_id",
"transaction_id",
);const refunds = await simpay.payments.refunds.list(
"service_id",
"transaction_id",
);const refund = await simpay.payments.refunds.get(
"service_id",
"transaction_id",
"refund_id",
);const createdRefund = await simpay.payments.refunds.create(
"service_id",
"transaction_id",
{
amount: 10,
},
);const result = await simpay.payments.blikLevel0.submitCode(
"service_id",
"transaction_id",
"123456",
);Odpowiedź:
{ accepted: true }const result = await simpay.payments.blikRecurrent.list("service_id", {
filter: {
status: "subscription_active",
mode: "BLIK",
},
page: 1,
perPage: 20,
sort: "-created_at",
});const created = await simpay.payments.blikRecurrent.create("service_id", {
transactionId: "tx_123",
ticket: { T6: "123456" },
alias: {
value: "AABBCC",
type: "PAYID",
label: "Subskrypcja premium",
},
options: {},
});const autopayment = await simpay.payments.blikRecurrent.autopayment(
"service_id",
"subscription_id",
{
transactionId: "tx_456",
},
);const aliases = await simpay.payments.blikRecurrent.listAliases("service_id", {
filter: {
status: "alias_active",
type: "PAYID",
},
page: 1,
perPage: 20,
sort: "-created_at",
});await simpay.payments.blikRecurrent.deleteAlias(
"service_id",
"alias_id",
{
reason: "Rezygnacja użytkownika",
},
);Dostęp przez:
simpay.smssimpay.sms.servicessimpay.sms.transactionssimpay.sms.verificationsimpay.sms.numbers
const services = await simpay.sms.services.list();const service = await simpay.sms.services.get("service_id");const transactions = await simpay.sms.transactions.list("service_id");const transaction = await simpay.sms.transactions.get("service_id", 123456);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,
};const numbers = await simpay.sms.numbers.list();const number = await simpay.sms.numbers.get(7055);const serviceNumbers = await simpay.sms.numbers.listByService("service_id");const serviceNumber = await simpay.sms.numbers.getByService("service_id", 7055);Dostęp przez:
simpay.directBillingsimpay.directBilling.servicessimpay.directBilling.calculationsimpay.directBilling.transactions
const services = await simpay.directBilling.services.list();const service = await simpay.directBilling.services.get("service_id");const calculation = await simpay.directBilling.calculation.calculate(
"service_id",
25,
);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",
},
},
);const transactions = await simpay.directBilling.transactions.list(
"service_id",
{
filter: {
status: "transaction_db_payed",
},
},
);const details = await simpay.directBilling.transactions.get(
"service_id",
"transaction_id",
);Dostęp przez:
simpay.notificationssimpay.notifications.paymentsimpay.notifications.directbilling
const result = await simpay.notifications.payment.verify({
payload: req.body,
sourceIp: req.ip,
});Po poprawnej walidacji metoda zwraca:
"OK"const isValid = simpay.notifications.payment.verifySignature(payload);
const isAllowedIp = await simpay.notifications.payment.validateSourceIp(ip);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");
}
});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.
const result = await simpay.notifications.directbilling.verify({
payload: req.body,
sourceIp: req.ip,
});Po poprawnej walidacji metoda zwraca:
"OK"const isValid = simpay.notifications.directbilling.verifySignature(payload);
const isAllowedIp = await simpay.notifications.directbilling.validateSourceIp(ip);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");
}
});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
Używaj, gdy:
- chcesz sprawdzić tylko podpis,
- payload został już wcześniej zwalidowany inną warstwą,
- testujesz lub debugujesz webhooki poza pełnym requestem HTTP.
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);
}
}npm testnpm run typechecknpm run lintnpm run formatnpm run verify