# Bắt đầu nhanh

Nhận khoản thanh toán đầu tiên ở môi trường `development`: liên kết ngân hàng giả lập, tạo payment, giả lập người mua chuyển tiền, rồi nhận lời gọi có chữ ký trên server của bạn. Bạn không cần tài khoản ngân hàng thật.

## Trước khi bắt đầu

- Tài khoản Hạ Tầng đã bật module **Thanh toán** (Cài đặt doanh nghiệp → Module). Khi module tắt, mọi request Thanh toán trả về `403 product_disabled`.
- Một server nhận được `POST` HTTPS từ internet, dùng ở bước 6. Khi chạy trên máy cá nhân, dùng tunnel như ngrok hoặc Cloudflare Tunnel.

Mọi request trên trang này đều gửi `Hatang-Environment: development`. Không có header này, request sẽ đi vào production. Xem [Môi trường và sandbox](https://hatang.dev/vi/docs/guides/payments/environments.md).

## Bước 1: Tạo API key

Trên dashboard, mở **API Keys** và tạo key có scope `payments:read` và `payments:write`. Chỉ giữ key trên server.

```bash
export HATANG_API_KEY=ht_123...
```

Với Node.js, cài SDK:

```bash
npm install @hatang/sdk
```

```ts title="lib/hatang.ts"
import { Hatang } from "@hatang/sdk";

export const hatang = new Hatang(process.env.HATANG_API_KEY, {
  baseUrl: "https://api.hatang.dev",
  environment: "development", // gửi dưới dạng header Hatang-Environment
});
```

## Bước 2: Liên kết ngân hàng giả lập

Bắt đầu liên kết, rồi xác nhận bằng mã `000000`. Ngân hàng giả lập luôn dùng mã này.

**cURL**

```bash
curl https://api.hatang.dev/payments/bank-connections \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Hatang-Environment: development" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "simulator", "label": "Tài khoản thử" }'

curl https://api.hatang.dev/payments/bank-connections/bcn_123/confirm \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Hatang-Environment: development" \
  -H "Content-Type: application/json" \
  -d '{ "otp": "000000" }'
```

**Node.js**

```ts
import { hatang } from "./lib/hatang";

const { data: conn, error } = await hatang.payments.bankConnections.create({ provider: "simulator", label: "Tài khoản thử" });
if (error) throw new Error(`${error.name}: ${error.message}`);
await hatang.payments.bankConnections.confirm(conn.id, "000000");
```

**Python**

```python
import os, requests

API = "https://api.hatang.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['HATANG_API_KEY']}", "Hatang-Environment": "development"}

conn = requests.post(f"{API}/payments/bank-connections", headers=HEADERS,
                     json={"provider": "simulator", "label": "Tài khoản thử"}, timeout=10).json()
requests.post(f"{API}/payments/bank-connections/{conn['id']}/confirm", headers=HEADERS,
              json={"otp": "000000"}, timeout=10).raise_for_status()
```

**PHP**

```php
<?php
$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.hatang.dev',
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('HATANG_API_KEY'),
        'Hatang-Environment' => 'development',
    ],
]);
$conn = json_decode($client->post('/payments/bank-connections', [
    'json' => ['provider' => 'simulator', 'label' => 'Tài khoản thử'],
])->getBody(), true);
$client->post("/payments/bank-connections/{$conn['id']}/confirm", ['json' => ['otp' => '000000']]);
```

Lời gọi đầu trả về `201` với kết nối ở trạng thái `pending_otp` kèm `challenge`; sau khi xác nhận, kết nối chuyển sang `active`:

```json
{
  "object": "bank_connection",
  "id": "bcn_01a0b90428fd7c499145a12c133abe41",
  "environment": "development",
  "provider": "simulator",
  "bank": { "bin": "970422", "name": "MBBank" },
  "account_number": "SIM4821937506",
  "account_name": "TAI KHOAN GIA LAP",
  "account_type": "personal",
  "status": "active",
  "label": "Tài khoản thử",
  "link_mode": "otp",
  "activated_at": "2026-09-19T09:30:02.114Z",
  "created_at": "2026-09-19T09:29:51.402Z"
}
```

Bạn cũng làm được việc này trên dashboard: **Cài đặt thanh toán → Tài khoản ngân hàng → Thêm tài khoản ngân hàng**.

## Bước 3: Tạo payment

Gửi `Idempotency-Key` (mã đơn là lựa chọn tốt) để request thử lại trả về cùng một payment thay vì tạo cái thứ hai.

**cURL**

```bash
curl https://api.hatang.dev/payments \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Hatang-Environment: development" \
  -H "Idempotency-Key: order-1001" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 150000,
    "description": "Đơn hàng #1001",
    "ipn_url": "https://shop.example.vn/hatang/ipn",
    "metadata": { "order_id": "1001" }
  }'
```

**Node.js**

```ts
// createOrder dùng mã đơn làm Idempotency-Key và lưu vào metadata.order_id.
const { data: payment, error } = await hatang.payments.createOrder({
  order_id: "1001",
  amount: 150_000,
  description: "Đơn hàng #1001",
  ipn_url: "https://shop.example.vn/hatang/ipn",
});
if (error) throw new Error(`${error.name}: ${error.message}`);
console.log(payment.checkout_url);
```

**Python**

```python
from hatang import Hatang  # pip install hatang

hatang = Hatang(environment="development")  # gửi dưới dạng header Hatang-Environment
payment = hatang.payments.create({
    "amount": 150000,
    "description": "Đơn hàng #1001",
    "ipn_url": "https://shop.example.vn/hatang/ipn",
    "metadata": {"order_id": "1001"},
}, idempotency_key="order-1001")
print(payment["checkout_url"])
```

**PHP**

```php
$payment = json_decode($client->post('/payments', [
    'headers' => ['Idempotency-Key' => 'order-1001'],
    'json' => [
        'amount' => 150000,
        'description' => 'Đơn hàng #1001',
        'ipn_url' => 'https://shop.example.vn/hatang/ipn',
        'metadata' => ['order_id' => '1001'],
    ],
])->getBody(), true);
echo $payment['checkout_url'];
```

Phản hồi là `201` với payment ở trạng thái `pending`, kèm `code`, `qr` và `checkout_url`. Object đầy đủ được mô tả ở [Tạo thanh toán](https://hatang.dev/vi/docs/guides/payments/create-payment.md#response).

## Bước 4: Mở trang checkout

Mở `checkout_url` trên trình duyệt. Trang hiển thị VietQR, thông tin ngân hàng và đồng hồ đếm ngược. Ở development, trang có thêm nút giả lập chuyển khoản.

## Bước 5: Giả lập chuyển khoản

**cURL**

```bash
curl https://api.hatang.dev/payments/simulate/transfer \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Hatang-Environment: development" \
  -H "Content-Type: application/json" \
  -d '{ "payment_id": "pay_123" }'
```

**Node.js**

```ts
await hatang.payments.simulateTransfer({ payment_id: payment.id });
```

**Python**

```python
requests.post(f"{API}/payments/simulate/transfer", headers=HEADERS,
              json={"payment_id": payment["id"]}, timeout=10).raise_for_status()
```

**PHP**

```php
$client->post('/payments/simulate/transfer', ['json' => ['payment_id' => $payment['id']]]);
```

Lời gọi trả về `202`. Trong vài giây, payment chuyển sang `paid`, trang checkout chuyển sang trạng thái đã thanh toán, và `ipn_url` của bạn nhận lời gọi `payment.succeeded`.

## Bước 6: Xử lý IPN

Endpoint của bạn phải kiểm tra header `Hatang-Signature` với body gốc trước khi tin bất cứ điều gì, rồi trả `2xx` trong 10 giây. Secret key nằm ở **Cài đặt thanh toán → Khoá tích hợp** (key development bắt đầu bằng `sk_test_`).

```ts title="app/api/hatang/ipn/route.ts (Next.js)"
import { constructIpnEvent, HatangSignatureError } from "@hatang/sdk";

export async function POST(req: Request) {
  const raw = await req.text(); // body gốc, trước khi parse JSON
  try {
    const ipn = await constructIpnEvent(raw, req.headers, process.env.HATANG_SECRET_KEY!);
    if (ipn.data.status === "paid" || ipn.data.status === "overpaid") {
      await markOrderPaid(ipn.data.metadata.order_id as string, ipn.data.paid_amount);
    }
    return new Response(null, { status: 200 });
  } catch (err) {
    if (err instanceof HatangSignatureError) return new Response(null, { status: 401 });
    throw err; // trả 5xx để Hatang thử lại sau
  }
}
```

[Xác minh IPN](https://hatang.dev/vi/docs/guides/payments/verify-ipn.md) có handler tương tự bằng PHP, Python và C#.

## Bước 7: Xem những gì đã gửi

Mọi lời gọi IPN và kết quả của nó được liệt kê theo payment, mới nhất trước:

```bash
curl https://api.hatang.dev/payments/pay_123/ipn-deliveries \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Hatang-Environment: development"
```

```json
{
  "object": "list",
  "data": [
    {
      "object": "ipn_delivery",
      "id": "ipn_01a0b90429d87cf89e37fc2f66040ec5",
      "payment_id": "pay_01a0b904297873e8b3524a26fdca0ab5",
      "event_type": "payment.succeeded",
      "url": "https://shop.example.vn/hatang/ipn",
      "status": "success",
      "attempt": 1,
      "response_status": 200,
      "response_ms": 84,
      "last_error": null,
      "next_attempt_at": null,
      "delivered_at": "2026-09-19T09:34:13.041Z",
      "created_at": "2026-09-19T09:34:12.952Z"
    }
  ]
}
```

Trang chi tiết payment trên dashboard hiển thị cùng danh sách này.

## Lỗi thường gặp

| Lỗi | Nguyên nhân | Cách xử lý |
|---|---|---|
| `422 no_bank_connection` | Chưa có tài khoản ngân hàng nào đang hoạt động ở môi trường này | Làm bước 2, hoặc kiểm tra đã gửi `Hatang-Environment: development` chưa |
| `422 validation_error` kèm "Several bank accounts are linked; pass connection_id" | Có nhiều hơn một tài khoản đang hoạt động | Gửi `connection_id` khi tạo payment |
| `422 validation_error` ở `ipn_url` | URL là địa chỉ nội bộ (`localhost`, `10.x`, `192.168.x`…) hoặc không phải `https` | Dùng URL HTTPS công khai, ví dụ qua tunnel |
| `401 restricted_api_key` | Key thiếu scope Thanh toán | Tạo key có `payments:read` và `payments:write` |
| `403 environment_mismatch` | Giả lập chuyển khoản ngoài môi trường development | Gửi `Hatang-Environment: development` |
| Không nhận được IPN | Endpoint không trả `2xx` | Xem `last_error` ở bước 7; Hạ Tầng thử lại trong khoảng một ngày |

## Bước tiếp theo

- [Tạo thanh toán](https://hatang.dev/vi/docs/guides/payments/create-payment.md): mọi trường, trạng thái và tình huống đặc biệt.
- [Nhận kết quả](https://hatang.dev/vi/docs/guides/payments/payment-results.md): so sánh IPN, webhook và polling.
- [SDK JavaScript](https://hatang.dev/vi/docs/guides/payments/sdk-javascript.md): toàn bộ luồng trong vài lời gọi.
- [Lên production](https://hatang.dev/vi/docs/guides/payments/go-live.md) khi đã sẵn sàng nhận tiền thật.

Source: https://hatang.dev/vi/docs/guides/payments/quickstart
