# SDK для TypeScript

Один файл без зависимостей. Работает в Node.js 18+, Bun, Deno, Next.js (включая edge). Скачайте его в проект:

```bash
curl -o src/lib/egn-pay.ts https://pay.egnetwork.ru/sdk/egn-pay.ts
```

Типы в SDK совпадают с [OpenAPI](https://pay.egnetwork.ru/openapi.json). Обновить SDK можно той же командой.

## Создать платёж

```ts
import { EgnPay, EgnPayError } from "@/lib/egn-pay";

const pay = new EgnPay(process.env.EGN_PAY_KEY!);

try {
  const payment = await pay.payments.create({
    amount: 49900,
    order_id: `order-${order.id}`,
    description: "Подписка Pro на 1 месяц",
    return_url: "https://example.egnetwork.ru/billing/done",
    customer: { email: user.email },
    metadata: { user_id: String(user.id) },
  });
  redirect(payment.payment_url);
} catch (err) {
  if (err instanceof EgnPayError && err.code === "order_closed") {
    // прошлый платёж по заказу истёк — создайте новый order_id
  }
  throw err;
}
```

## Остальные методы

```ts
await pay.payments.retrieve("pay_…");
await pay.payments.findByOrderId("order-1042");
await pay.payments.list({ status: "succeeded", limit: 50 });
await pay.payments.cancel("pay_…");
await pay.payments.refund("pay_…");          // весь остаток
await pay.payments.refund("pay_…", 10000);   // 100 ₽
await pay.me();                               // проверить ключ
```

Сетевые ошибки и `5xx` у `GET` и `POST /payments` повторяются автоматически (до 2 раз). Это безопасно благодаря идемпотентности по `order_id`. Ошибки API бросаются как `EgnPayError` с полями `status`, `code`, `message` и `details`.

## Вебхук (Next.js App Router)

```ts
// app/api/egn-pay/webhook/route.ts
import { EgnPay } from "@/lib/egn-pay";

export async function POST(req: Request) {
  const raw = await req.text();
  let event;
  try {
    event = await EgnPay.webhooks.constructEvent(raw, req.headers.get("x-egn-signature"), process.env.EGN_PAY_WEBHOOK_SECRET!);
  } catch {
    return new Response("bad signature", { status: 400 });
  }

  const p = event.data.payment;
  switch (event.type) {
    case "payment.succeeded":
      await orders.markPaid(p.order_id, p.id); // идемпотентно
      break;
    case "payment.failed":
    case "payment.expired":
    case "payment.canceled":
      await orders.close(p.order_id);
      break;
    case "payment.refunded":
    case "payment.partially_refunded":
      await orders.syncRefund(p.order_id, p.refunded_amount);
      break;
  }
  return new Response("ok");
}
```

## Страница возврата

```ts
// app/billing/done/page.tsx
export default async function Done({ searchParams }: { searchParams: Promise<{ payment_id?: string }> }) {
  const { payment_id } = await searchParams;
  const payment = payment_id ? await pay.payments.retrieve(payment_id) : null; // не доверяем ?status=
  return payment?.status === "succeeded" ? <p>Спасибо!</p> : <p>Оплата не завершена</p>;
}
```
