> For the complete documentation index, see [llms.txt](https://docs.loomgate.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.loomgate.io/integrations/node-server.md).

# Máy chủ Node.js

Gọi API và xác minh webhook từ máy chủ Node.js với @loompay/loomgate-js-sdk/server.

`@loompay/loomgate-js-sdk/server` (Node.js ≥ 18) gồm client gọi API bằng secret key và hàm xác minh webhook.

```bash
pnpm add @loompay/loomgate-js-sdk
```

```js
import { createLoomgateServerClient, LoomgateApiError } from '@loompay/loomgate-js-sdk/server';

const loomgate = createLoomgateServerClient(process.env.LOOMGATE_SECRET_KEY, {
  apiBaseUrl: 'https://api.loomgate.io',
  timeoutMs: 60_000, // mặc định
});
```

## Các hàm

Tham số và kết quả giữ nguyên JSON snake\_case của API.

| Nhóm                 | Hàm                                                                                                                                                                      |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `paymentIntents`     | `create(params, { idempotencyKey? })`, `retrieve(id)`, `update(id, params)`, `confirm(id, params)`, `release(id)`, `list({ limit?, starting_after?, order_reference? })` |
| `feeQuotes`          | `create({ amount, currency })`                                                                                                                                           |
| `refunds`            | `create({ payment_intent, amount?, reason? }, { idempotencyKey? })`, `retrieve(id)`                                                                                      |
| `balance`            | `retrieve()`                                                                                                                                                             |
| `webhookSigningKeys` | `list()`                                                                                                                                                                 |

```js
// Tạo payment cho đơn 1042
const intent = await loomgate.paymentIntents.create(
  {
    amount: 10000,
    currency: 'usd',
    items: [{ name: 'T-shirt', quantity: 2, unit_amount: 4500 }],
    shipping_amount: 1000,
    metadata: { order_id: '1042' },
  },
  { idempotencyKey: 'order-1042' },
);

// Trước khi người mua trả: bổ sung người mua, mã đơn, địa chỉ giao hàng
await loomgate.paymentIntents.update(intent.id, {
  order_reference: '1042',
  buyer: { name: 'Jane Doe' },
  shipping_details: {
    name: 'Jane Doe',
    address: { line1: '1 Market St', city: 'San Francisco', state: 'CA', postal_code: '94105', country: 'US' },
  },
});

// Hoàn một phần: một key cho mỗi lần hoàn, lưu trước khi gọi
const refund = await loomgate.refunds.create(
  { payment_intent: intent.id, amount: 2500, reason: 'requested_by_customer' },
  { idempotencyKey: 'refund-1042-1' },
);
```

## Lỗi

Lỗi của API được throw thành `LoomgateApiError` với `type`, `code`, `message`, `status` (HTTP; `0` khi không tới được máy chủ), `param?`, `declineCode?`. Secret key không bao giờ xuất hiện trong message.

```js
try {
  await loomgate.paymentIntents.confirm(id, { confirmation_token, billing_details });
} catch (err) {
  if (err instanceof LoomgateApiError && err.type === 'card_error') {
    await loomgate.paymentIntents.release(id); // cho người mua thử thẻ khác
  }
  throw err;
}
```

## Timeout

Mỗi lời gọi bị hủy sau `timeoutMs` (mặc định 60 giây; `confirm` chờ ít nhất 180 giây) và throw `api_error` / `request_timeout` với `status: 0`. Khi đó kết quả **chưa rõ**: đọc lại bằng `retrieve`, hoặc gửi lại với cùng `idempotencyKey`. Sau timeout của `confirm` thì không release.

## Idempotency-Key

Truyền `{ idempotencyKey }` cho `paymentIntents.create` và `refunds.create`. SDK chỉ nhận key 1–255 ký tự ASCII in được, không có khoảng trắng ở đầu/cuối; sai thì reject `validation_error` mà không gửi request. Xem mục Idempotency ở [Tham chiếu API](/api-reference.md).

## Xác minh webhook

```js
import { verifyWebhook } from '@loompay/loomgate-js-sdk/server';

const result = verifyWebhook(rawBody, signatureHeader, {
  endpointId: 'we_…',
  publicKeys: { [kid]: publicKeyPem }, // từ loomgate.webhookSigningKeys.list()
  toleranceSeconds: 300, // mặc định
});
// { ok: true } | { ok: false, code }
```

Xem ví dụ đầy đủ ở [Xác minh chữ ký](/webhooks/verify-signatures.md).

Mọi request của SDK gửi kèm `User-Agent: loomgate-js-sdk/<version> (node/<version>)`.

## Ví dụ máy chủ

[`examples/js/worker/index.ts`](https://github.com/loomgroup/loomgate-examples/blob/main/examples/js/worker/index.ts) trong [kho ví dụ loomgate-examples](https://github.com/loomgroup/loomgate-examples) là một máy chủ nhỏ viết bằng Hono: tính giá giỏ hàng, tạo payment bằng SDK máy chủ và trả `client_secret` cho trình duyệt, rồi đọc trạng thái đơn. Mã chạy như một Cloudflare Worker và cũng chạy được trên Node.js. Mỗi ví dụ trong kho đều có phần máy chủ tương tự ở `worker/index.ts`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.loomgate.io/integrations/node-server.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
