<!-- Source: https://docs.infraio.xyz/ko/sdks/javascript -->
<!-- Last updated: 2026-10-04 -->

# JavaScript / 브라우저 SDK

`@lartech/infraio-checkout-js`는 현재 공개된 유일한 SDK입니다. 브라우저에서
실행되며 호스팅 결제 페이지를 엽니다. 아직 백엔드 SDK는
없습니다. API를 직접 호출하세요. [빠른 시작](https://docs.infraio.xyz/ko/get-started/quickstart)에
HMAC 서명 헬퍼가 포함되어 있습니다.

- 현재 버전: `0.1.1-beta.17` (pre-1.0; 마이너 변경 예상)
- 포맷: **ESM** (`index.js`), **CJS** (`index.cjs`), **IIFE** (`index.global.js`)
- 타입 번들됨 (`index.d.ts`)
- 런타임 피어 없음 — React, jQuery 또는 기타 종속성 없음

> **Note:**
>
> **서버 측 엔트리는 없습니다.** 웹훅 서명 검증 헬퍼는 번들되지
> 않습니다 — `crypto`로 직접 구현하세요([서명 검증
> 페이지](https://docs.infraio.xyz/ko/webhooks/signature-verification)에 4개 언어로 복사하여 붙여넣을
> 수 있는 코드가 있습니다).

## 설치

**npm**

```bash
npm install @lartech/infraio-checkout-js
```

**pnpm**

```bash
pnpm add @lartech/infraio-checkout-js
```

**yarn**

```bash
yarn add @lartech/infraio-checkout-js
```

**bun**

```bash
bun add @lartech/infraio-checkout-js
```

**CDN**

```html
<script src="https://unpkg.com/@lartech/infraio-checkout-js/dist/index.global.js"></script>
<script>
  const sdk = await InfraIo.loadInfraIo("pk_live_yourkeyhere");
  sdk.checkout({ sessionId, checkoutUrl });
</script>
```

## `loadInfraIo(publicKey, options?)`

`Promise<InfraIoInstance>`를 반환합니다.

```ts
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo("pk_live_yourkeyhere", {
  // 선택. 비프로덕션 환경을 가리킬 때만 오버라이드하세요.
  checkoutUrl: "https://checkout-dev.infraio.xyz",
});
```

| 매개변수 | 타입 | 필수 | 비고 |
| --- | --- | --- | --- |
| `publicKey` | `string` | ✓ | `pk_(live\|test)_…`와 일치해야 함 |
| `options.checkoutUrl` | `string` | — | 기본 체크아웃 URL을 오버라이드합니다. 기본값: `https://checkout.infraio.xyz`. 체크아웃 페이지는 일치하는 API에 자동으로 연결됩니다. |

## `sdk.checkout({ … })`

호스팅 결제 페이지를 엽니다. `void`를 반환합니다(상태는 콜백 사용).

| 필드 | 타입 | 필수 | 비고 |
| --- | --- | --- | --- |
| `sessionId` | `string` | ✓ | `POST /b2b/v1/checkout-sessions/quick`이 반환한 `session_key` |
| `checkoutUrl` | `string` | — | 동일한 엔드포인트가 반환한 전체 URL. 생략 시 SDK가 `loadInfraIo()`의 `checkoutUrl`(또는 기본값) + `sessionId`로 구성합니다 |
| `mode` | `"popup" \| "redirect" \| "embed"` | — | 기본값 `"popup"` |
| `container` | `string \| HTMLElement` | 임베드만 | iframe이 마운트될 CSS 선택자 또는 DOM 요소 |
| `width` | `number` | — | 팝업 전용. 기본값 `560`. `[320, 1280]` 범위로 클램프 |
| `height` | `number` | — | 팝업 전용. 기본값 `780`. `[400, 1000]` 범위로 클램프 |
| `timeoutMs` | `number` | — | 팝업 전용. iframe 로드 타임아웃. 기본값 `30000`. `0`을 전달하면 비활성화 |
| `locale` | `string` | — | 체크아웃 페이지에 `?locale=`로 전달되는 BCP-47 태그(`en`, `vi`, `ja`, `ko`, `es`, `pt-BR`, `ru`, `tr`, `zh-CN`, `zh-TW`) |
| `hideSummary` | `boolean` | — | 주문 요약 컬럼 숨기기. 기본값 `false` |
| `hideHeader` | `boolean` | — | InfraIO Pay 헤더 및 내장 지갑 연결 버튼 숨기기. 기본값 `false`. 완전한 화이트 라벨을 위해 `walletAddress`와 함께 사용 |
| `walletAddress` | `string` | — | 구매자 지갑 사전 연결. `onSignRequest`가 필요함 |
| `walletChainId` | `number` | — | 사전 연결된 지갑의 EVM 체인 ID |
| `onReady` | `() => void` | — | iframe이 상호작용 가능해질 때 발생. 팝업/임베드 전용 |
| `onSignRequest` | `(req: { method: string; params: unknown[] }) => Promise<string>` | `walletAddress` 설정 시 | SDK가 지갑 RPC를 가맹점 핸들러로 프록시 — 서명된 hex를 반환 |
| `onSuccess` | `({ sessionId }) => void` | — | 성공적인 결제 시 발생. **권위 있는 신호가 아님 — 웹훅이 권위 있음** |
| `onCancel` | `() => void` | — | 구매자가 결제하지 않고 팝업/임베드를 닫을 때 발생 |
| `onError` | `(err: InfraIoError) => void` | — | iframe 로드 실패 시 발생(팝업 + 임베드 모드). 잘못된 인수는 동기적으로 throw되며 여기서 전달되지 **않습니다**. 리디렉션 모드는 런타임 오류 표면이 없음 — 실패는 리디렉션된 페이지에서 관찰됨. |

> **Warning:**
>
> `onSuccess`는 권위 있는 신호가 **아닙니다**. 웹훅이 나중에 결제 실패를
> 결정하더라도 발생할 수 있습니다(테스트넷 reorg, 구매자 측 타이밍). UX에만
> 사용하세요(예: "감사합니다!" 표시, 리디렉션). 이행 처리 전에 항상 웹훅으로
> 확인하세요.

## `sdk.close()`

열려 있는 팝업이나 임베드를 프로그래밍 방식으로 해제합니다. 리디렉션 모드에서는
no-op입니다.

```ts
sdk.checkout({ sessionId, mode: "popup", /* … */ });
// 나중에, 예를 들어 사용자가 페이지를 떠날 때
sdk.close();
```

## `sdk.openRefundRequest({ … })`

백엔드가 `POST /b2b/v1/merchants/{merchant_id}/refund-requests`로 발급한 일회성
토큰에 대해 호스팅 **환불 요청 양식**을 엽니다. 구매자는 저희 페이지에서 환불
목적지 주소 + 사유 + (선택) 메타데이터를 입력합니다. 가맹점 페이지는 열기/닫기
라이프사이클만 처리합니다. **`close()` 함수**를 반환합니다 — 호출하여 팝업을
프로그래밍 방식으로 해제하거나 임베드 iframe을 분리합니다. 리디렉션 모드에서
반환된 함수는 no-op입니다.

> **Note:**
>
> 양식은 `https://checkout.infraio.xyz/refund-request/:token`에 있습니다. 이
> 메서드는 해당 URL을 팝업/리디렉션/임베드로 감싸서 구매자가 가맹점 도메인을
> 벗어나지 않거나(팝업/임베드) 자동으로 돌아오도록(리디렉션) 합니다. 백엔드가
> 토큰을 발급하기 위해 호출하는 엔드포인트는
> `POST /b2b/v1/merchants/{merchant_id}/refund-requests`입니다 — 시크릿 키로
> HMAC 서명하며, 나머지 B2B 표면과 동일한 인증입니다.
> [개념 → 환불](https://docs.infraio.xyz/ko/concepts/refunds#mint-via-b2b-api)을 참조하세요.

```ts
import { loadInfraIo } from "@lartech/infraio-checkout-js";

const sdk = await loadInfraIo("pk_live_yourkeyhere");

// 서버 측에서 토큰을 발급한 다음, 브라우저의 SDK에 전달합니다.
const { token } = await fetch("/api/mint-refund-token", { method: "POST" }).then(r => r.json());

const close = sdk.openRefundRequest({
  token,
  mode: "popup",
  onSuccess: ({ linkToken, refundId }) => {
    // 구매자가 양식을 제출했습니다.
    // linkToken → /r/:linkToken 상태 페이지(구매자와 공유).
    // refundId  → 승인/거부에 B2B API와 함께 사용.
    window.location.href = `/r/${linkToken}`;
  },
  onCancel: () => { /* 구매자가 제출 없이 닫음 */ },
});

// 나중에 필요하면 프로그래밍 방식으로 팝업 해제:
// close();
```

| 필드 | 타입 | 필수 | 비고 |
| --- | --- | --- | --- |
| `token` | `string` | ✓ | `POST /b2b/v1/merchants/{merchant_id}/refund-requests`가 반환한 `rfqt_…` 토큰 |
| `mode` | `"popup" \| "redirect" \| "embed"` | — | 기본값 `"popup"`. `sdk.checkout()`과 동일한 표면 시맨틱 — [모드 비고](#mode-notes) 참조 |
| `container` | `string \| HTMLElement` | 임베드만 | iframe이 마운트될 CSS 선택자 또는 DOM 요소 |
| `locale` | `string` | — | `?locale=`로 전달되는 BCP-47 태그(`en`, `vi`, `ja`, `ko`, `es`, `pt-BR`, `ru`, `tr`, `zh-CN`, `zh-TW`) |
| `hideHeader` | `boolean` | — | iframe 내부의 InfraIO Pay 헤더 숨기기. 팝업/임베드에서 SDK가 자체 모달 크롬을 그리므로 페이지 헤더는 보통 필요하지 않습니다. 기본값 `false` |
| `hideSummary` | `boolean` | — | 주문 요약 컬럼을 숨기고 환불 양식만 표시. 기본값 `false` |
| `walletAddress` | `string` | — | 목적지 지갑 필드(`?wallet_address=`)를 사전 채움. 구매자의 지갑을 이미 알고 있는 가맹점이 수동 재입력을 건너뛸 수 있게 함 |
| `onSuccess` | `(data: { linkToken: string; refundId: string }) => void` | — | 구매자가 양식을 제출한 후 발생. `linkToken` → 구매자와 공유할 `/r/:linkToken` 상태 페이지. `refundId` → 승인/거부에 B2B API와 함께 사용 |
| `onCancel` | `() => void` | — | 구매자가 제출 없이 팝업/임베드를 닫을 때 발생 |
| `onError` | `(err: InfraIoError) => void` | — | iframe 로드 실패 또는 잘못된 인수에 발생. 토큰 만료/취소는 호스팅 페이지에서 처리되며 `onError`로 처리되지 않습니다 |

**`onError`를 통해 노출되는 토큰 상태**

구매자가 만료된 토큰을 열면 페이지 자체가 표시를 처리하며("만료됨 — 새 링크
요청" 안내 등을 렌더링), SDK는 이러한 경우 `onError`를 **발생시키지** 않습니다 —
구매자는 양식 흐름 내에 있고 가맹점 코드는 반응할 필요가 없습니다. `onError`는
가맹점 코드가 조치할 수 있는 경우(잘못된 인수, iframe 로드 시 네트워크 실패)
에만 발생합니다.

> **Warning:**
>
> `openRefundRequest`는 환불 인텐트를 기록할 뿐 자금을 이동시키지
> **않습니다**. `onSuccess` 이후 환불 행은 `PENDING`(또는 가맹점 구성이
> 고객 환불을 자동 승인하는 경우 `APPROVED`)입니다. 가맹점이 직접 관리하는 지갑에서
> 온체인 송금을 서명하고 브로드캐스트한 다음, tx 해시를
> `POST /b2b/v1/refunds/:id/submit-tx`로 게시해야 합니다. 전체
> 라이프사이클은 [개념 → 환불](https://docs.infraio.xyz/ko/concepts/refunds)을 참조하세요.

## 오류 클래스

```ts
import { InfraIoError } from "@lartech/infraio-checkout-js";

sdk.checkout({
  sessionId,
  onError: (err: InfraIoError) => {
    switch (err.code) {
      case "invalid_request_error":   /* 잘못된 sessionId / 인수 */ break;
      case "iframe_load_error":       /* iframe 로드 실패 */ break;
      case "iframe_timeout_error":    /* timeoutMs 초과 */ break;
      case "already_open_error":      /* 다른 체크아웃이 이미 열려 있음 */ break;
      case "network_error":           /* 체크아웃 오리진과 통신 중 일시적인 네트워크 문제 */ break;
      case "api_error":               /* SDK 발급 호출에 대해 백엔드가 non-2xx 반환 */ break;
    }
  },
});
```

`sdk.openRefundRequest()`는 `invalid_request_error`(누락 또는 잘못된 토큰/인수)만
동기적으로 throw합니다. `iframe_load_error`(환불 요청 iframe 로드 실패)는
throw되지 않고 **`onError`를 통해 비동기적으로** 전달됩니다. `iframe_timeout_error`
또는 `already_open_error`는 발생시키지 **않습니다** — 환불 요청 팝업에는 로드
타임아웃이 없으며 여러 동시 팝업을 허용합니다.

## 모드 비고

### 팝업
- 어두운 반투명 배경의 중앙 정렬 오버레이
- `z-index: 2147483647` (max int32) — 다른 모든 것 위에 위치
- 열려 있는 동안 본문 스크롤 잠금. 닫히면 복원
- 닫기 버튼이 초기 포커스를 받음. Tab은 팝업 내에 트랩됨
- 닫기 방법: 닫기 버튼, Escape, 외부 클릭, `sdk.close()`. 이들 모두
  `onCancel`을 호출
- 체크아웃 페이지는 `postMessage`로 리사이즈를 요청할 수 있음 — SDK가
  `width`/`height` 제한 내로 클램프

### 리디렉션
- `window.location.href`를 통한 하드 내비게이션
- `?return_url=<current-page>`를 자동으로 추가하여 구매자가 출발점으로
  돌아오게 함. 세션의 `success_url` / `cancel_url`이 이미 이를 처리하면
  왕복은 `return_url`을 무시

### 임베드
- `allow="payment; clipboard-write"`가 있는 iframe (HTML5 Feature Policy
  지시문 — `sandbox` 속성이 아님). iframe은 체크아웃 오리진에서 제공되므로
  구매자 측 지갑 팝업과 클립보드 쓰기는 추가 옵트인 없이 동작
- 컨테이너 너비는 100%. 높이는 `INFRAIO_RESIZE` postMessage를 통해 자동
  조정, `[200, 2000]px` 범위로 클램프
- iframe 경계 외에 CSS 격리 없음 — 부모 페이지 스타일은 누출되지 않음
- 체크아웃이 상호작용 가능해질 때 자체 로딩 상태를 숨길 수 있도록 항상
  `onReady`를 연결

## TypeScript

모든 타입이 번들됩니다. 가장 유용한 익스포트:

```ts
import type {
  CheckoutOptions,
  RefundRequestOptions,
  LoadOptions,
  InfraIoInstance,
  InfraIoErrorCode,
} from "@lartech/infraio-checkout-js";
import { loadInfraIo, InfraIoError, VERSION } from "@lartech/infraio-checkout-js";
```

`VERSION`은 SDK 자체의 버전 문자열입니다 — 버그 리포트에 유용합니다.
