Skip to content

SimPaypl/simpay-typescript

Repository files navigation

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/typescript

Szybki 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

  • SimPayError
  • SimPayApiError
  • SimPayValidationError
  • SimPaySignatureError
  • SimPayNetworkError
  • SimPayIpnError

Typy

SDK eksportuje publiczne typy dla:

  • payments
  • sms
  • directbilling
  • notifications
  • common

Przykład:

import type {
  CreateTransactionRequest,
  VerifyCodePayload,
  DirectBillingCreateTransactionRequest,
  PaymentIpnNotification,
} from "@simpay/typescript";

Moduły

Payments

Dostęp przez:

simpay.payments

Dostępne podmoduły

  • simpay.payments.services
  • simpay.payments.channels
  • simpay.payments.transactions
  • simpay.payments.refunds
  • simpay.payments.blikLevel0
  • simpay.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: "john@example.com",
    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: "user@example.com",
    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.:

  • subscriptionId
  • aliasId

Ważne: samo subscriptionId nie 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

  • attempt wysyłasz w zakresie 0-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
  • Model M (kwota zmienna, z potwierdzaniem każdej płatności):
    • opcjonalnie: options.initiationDate, options.expiresAt, options.frequency

Najważniejsze rzeczy, o których łatwo zapomnieć

  • antifraud.systemId musi być stałe dla tego samego użytkownika.
  • amount: 0 jest dozwolone tylko przy rejestracji zgody (pierwsza transakcja).
  • Kolejnych obciążeń nie wykonujesz na 0 PLN.
  • BLIK recurrent działa tylko dla PLN i customer.countryCode = "PL".
  • Przy pierwszej transakcji wysyłasz customer.ip i antifraud.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.sms

Dostępne podmoduły

  • simpay.sms.services
  • simpay.sms.transactions
  • simpay.sms.verification
  • simpay.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.directBilling

Dostępne podmoduły

  • simpay.directBilling.services
  • simpay.directBilling.calculation
  • simpay.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.notifications

Dostępne podmoduły

  • simpay.notifications.payment
  • simpay.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 – baza
  • SimPayApiError – API zwróciło błąd
  • SimPayValidationError – niepoprawne dane wejściowe
  • SimPayNetworkError – błąd sieci / timeout
  • SimPaySignatureError – błędy podpisu
  • SimPayIpnError – 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: "john@example.com",
    },
  });
} 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 test

Typecheck

npm run typecheck

Lint

npm run lint

Format

npm run format

Pełna walidacja

npm run verify

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors