{
  "slug": "toss-payments",
  "name": "@gj-kit/toss-payments",
  "version": "0.6.1",
  "description": "Type-safe Toss Payments API v2, widget, billing, cancellation, and webhook flows for TypeScript.",
  "homepage": "https://gj-kit.github.io/gj-kit/packages/toss-payments/",
  "repository": "git+https://github.com/gj-kit/gj-kit.git",
  "license": "MIT",
  "engines": {
    "node": ">=20"
  },
  "peerDependencies": {
    "@tosspayments/tosspayments-sdk": "^2"
  },
  "peerDependenciesMeta": {
    "@tosspayments/tosspayments-sdk": {
      "optional": true
    }
  },
  "entries": [
    {
      "subpath": ".",
      "id": "root",
      "declarationTarget": "./dist/index.d.cts",
      "symbols": [
        {
          "name": "andThen",
          "slug": "and-then",
          "kind": "function",
          "declaration": "/** 성공 시 다음 Result 연산으로 연결 — 에러 타입은 합집합으로 누적된다. */\ndeclare function andThen<T, U, E, F>(r: Result<T, E>, f: (value: T) => Result<U, F>): Result<U, E | F>;",
          "sourceDocumentation": "성공 시 다음 Result 연산으로 연결 — 에러 타입은 합집합으로 누적된다."
        },
        {
          "name": "ApiClientKey",
          "slug": "api-client-key",
          "kind": "type",
          "declaration": "/**\n * 형식(템플릿 리터럴)과 명목성(브랜드)을 동시에 강제 —\n * `'test_ck_oops'` 리터럴도 parse 없이는 대입 불가.\n */\ntype ApiClientKey<E extends Env = Env> = (E extends 'test' ? `test_ck_${string}` : `live_ck_${string}`) & Brand<'ApiClientKey'> & EnvTag<E>;",
          "sourceDocumentation": "형식(템플릿 리터럴)과 명목성(브랜드)을 동시에 강제 —\n`'test_ck_oops'` 리터럴도 parse 없이는 대입 불가."
        },
        {
          "name": "ApiSecretKey",
          "slug": "api-secret-key",
          "kind": "type",
          "declaration": "type ApiSecretKey<E extends Env = Env> = (E extends 'test' ? `test_sk_${string}` : `live_sk_${string}`) & Brand<'ApiSecretKey'> & EnvTag<E>;"
        },
        {
          "name": "AUDIT_REDACTED_KEYS",
          "slug": "audit-redacted-keys",
          "kind": "constant",
          "declaration": "AUDIT_REDACTED_KEYS: readonly string[]",
          "sourceDocumentation": "redaction 대상 키 목록 — 단일 상수 export로 감사 가능하게 (버전 관리 대상, 설계 §3.2 확정 표).\n\n매칭은 **대소문자 무시**이며 req/res body를 재귀 순회해 값이 `'[REDACTED]'`로 치환된다.\n이 목록 외 추가 규칙 1건: `card`/`refundAccount` 컨텍스트(부모 키) 하위의 `number`도 치환\n(카드번호 마스킹본·환불 계좌번호 — 실측 응답 필드).\n\n잔존 리스크: denylist는 토스가 새 민감 필드를 추가하면 누락될 수 있다 — 실측 응답 픽스처\n전수 redaction 스냅샷 테스트 + 마이너 업데이트 시 필드 감사로 완화한다."
        },
        {
          "name": "AuditEntry",
          "slug": "audit-entry",
          "kind": "interface",
          "declaration": "/**\n * audit — 아웃바운드 토스 API req/res 증거 기록 (설계 §3.2, must 3/3 수렴).\n *\n * 타입뿐인 계약 + 순수 redaction 순회기 — 환경 중립(core)이며 런타임 의존성 0.\n * 부착 지점은 server/client.ts의 내부 request() 단일 관문이다(흩어짐 없음).\n *\n * 협상 불가 계약:\n * - `record()`는 await되지 않는다(fire-and-forget) — audit 오류가 결제 요청의\n *   지연·실패에 영향을 주는 경로가 없다(기록 실패 < 결제 실패).\n * - redaction은 비설정화 — 끄는 옵션·설정 파라미터를 제공하지 않는다.\n * - Authorization 헤더는 AuditEntry에 **필드 자체가 없다** — 마스킹이 아니라 구조적 부재.\n */\n/**\n * 시도 1건 = 엔트리 1건 (outcome 유니언) — request/response 분리 kind안은\n * 상관(join) 비용 때문에 기각(설계 §7-7).\n *\n * ⚠ responseBody에는 redaction 후에도 고객 이름·이메일 등 PII가 잔존할 수 있다 —\n * 보관 주체·기간·접근 통제는 sink 소유자(사용자) 책임이다.\n */\ninterface AuditEntry {\n    /** crypto.randomUUID — 시도 1건당 1엔트리. */\n    readonly id: string;\n    /** ISO 8601 요청(시도) 시작 시각. */\n    readonly at: string;\n    readonly env: Env;\n    readonly method: 'GET' | 'POST' | 'DELETE';\n    /** '/v1/payments/confirm' 등 pathname만 — 쿼리 미포함. */\n    readonly path: string;\n    /** 1부터 — retry(§3.4) 결합 시 시도마다 엔트리 1건. */\n    readonly attempt: number;\n    readonly idempotencyKey: string | null;\n    /** redaction 통과본. ⚠ 헤더 필드가 타입에 없다 — Authorization은 구조적으로 기록 불가. body 없는 요청은 null. */\n    readonly requestBody: unknown;\n    readonly durationMs: number;\n    /** x-tosspayments-trace-id — 고객센터 문의 키. */\n    readonly traceId: string | null;\n    readonly outcome: {\n        readonly kind: 'ok';\n        readonly httpStatus: number;\n        /** redaction 통과본. */\n        readonly responseBody: unknown;\n    } | {\n        readonly kind: 'toss-error';\n        readonly httpStatus: number;\n        readonly code: string;\n        readonly message: string;\n    } | {\n        readonly kind: 'transport';\n        readonly code: 'NETWORK_ERROR' | 'TIMEOUT';\n    };\n}",
          "sourceDocumentation": "시도 1건 = 엔트리 1건 (outcome 유니언) — request/response 분리 kind안은\n상관(join) 비용 때문에 기각(설계 §7-7).\n\n⚠ responseBody에는 redaction 후에도 고객 이름·이메일 등 PII가 잔존할 수 있다 —\n보관 주체·기간·접근 통제는 sink 소유자(사용자) 책임이다."
        },
        {
          "name": "AuditOptions",
          "slug": "audit-options",
          "kind": "interface",
          "declaration": "interface AuditOptions {\n    readonly sink: AuditSink;\n    /** sink 실패 통지. 기본 무시 — 이 콜백의 throw도 삼켜진다. */\n    readonly onSinkError?: (cause: unknown, entry: AuditEntry) => void;\n}"
        },
        {
          "name": "AuditSink",
          "slug": "audit-sink",
          "kind": "interface",
          "declaration": "interface AuditSink {\n    /**\n     * 시도 1건당 1회 호출된다. 반환 Promise는 클라이언트가 await하지 않는다 —\n     * sync throw·async rejection 모두 삼켜지고 `AuditOptions.onSinkError`로만 통지된다.\n     */\n    record(entry: AuditEntry): void | Promise<void>;\n}"
        },
        {
          "name": "BillingErrorCode",
          "slug": "billing-error-code",
          "kind": "type",
          "declaration": "type BillingErrorCode = 'NOT_MATCHES_CUSTOMER_KEY' | 'ALREADY_REMOVED_BILLING_KEY' | 'NOT_SUPPORTED_METHOD' | 'NOT_SUPPORTED_CARD_TYPE' | 'INVALID_BILL_KEY_REQUEST' | 'INVALID_BILLING_AUTH' | 'INVALID_CARD_NUMBER' | 'FAILED_BILL_KEY_AUTH_CREATION' | 'FAILED_BILLING_AUTO_CANCEL' | (string & {});"
        },
        {
          "name": "BuiltInRefundPolicyConfig",
          "slug": "built-in-refund-policy-config",
          "kind": "type",
          "declaration": "type BuiltInRefundPolicyConfig = FullRefundPolicyConfig | PercentageRefundPolicyConfig | ElapsedTimeRefundPolicyConfig | RemainingUnitsRefundPolicyConfig;"
        },
        {
          "name": "CancelErrorCode",
          "slug": "cancel-error-code",
          "kind": "type",
          "declaration": "/** 취소 API 공식 표 30개 + 실측 보강 — `(string & {})`로 열린 확장(미등록 코드도 수용). */\ntype CancelErrorCode = 'ALREADY_CANCELED_PAYMENT' | 'ALREADY_REFUND_PAYMENT' | 'NOT_CANCELABLE_PAYMENT' | 'NOT_CANCELABLE_PAYMENT_FOR_DORMANT_USER' | 'NOT_CANCELABLE_AMOUNT' | 'EXCEED_CANCEL_AMOUNT_DISCOUNT_AMOUNT' | 'EXCEED_CANCEL_LIMIT' | 'EXCEED_MAX_REFUND_DUE' | 'NOT_ALLOWED_PARTIAL_REFUND' | 'NOT_ALLOWED_PARTIAL_REFUND_WAITING_DEPOSIT' | 'INVALID_REFUND_ACCOUNT_INFO' | 'INVALID_REFUND_ACCOUNT_NUMBER' | 'INVALID_BANK' | 'NOT_AVAILABLE_BANK' | 'FORBIDDEN_BANK_REFUND_REQUEST' | 'NOT_MATCHES_REFUNDABLE_AMOUNT' | 'FORBIDDEN_CONSECUTIVE_REQUEST' | 'IDEMPOTENT_REQUEST_PROCESSING' | 'INVALID_IDEMPOTENCY_KEY' | 'PROVIDER_ERROR' | 'FAILED_INTERNAL_SYSTEM_PROCESSING' | 'FAILED_REFUND_PROCESS' | 'FAILED_METHOD_HANDLING_CANCEL' | 'FAILED_PARTIAL_REFUND' | 'COMMON_ERROR' | 'FAILED_PAYMENT_INTERNAL_SYSTEM_PROCESSING' | 'REFUND_REJECTED' | 'UNAUTHORIZED_KEY' | 'INCORRECT_BASIC_AUTH_FORMAT' | 'FORBIDDEN_REQUEST' | 'INVALID_REQUEST' | 'NOT_FOUND_PAYMENT' | (string & {});",
          "sourceDocumentation": "취소 API 공식 표 30개 + 실측 보강 — `(string & {})`로 열린 확장(미등록 코드도 수용)."
        },
        {
          "name": "cancelReason",
          "slug": "cancel-reason",
          "kind": "function",
          "declaration": "declare function cancelReason(raw: string): Result<CancelReason, InvalidInput<'cancelReason'>>;"
        },
        {
          "name": "CancelReason",
          "slug": "cancel-reason--type",
          "kind": "type",
          "declaration": "/** 취소 사유 — 1–200자. */\ntype CancelReason = string & Brand<'CancelReason'>;",
          "sourceDocumentation": "취소 사유 — 1–200자."
        },
        {
          "name": "cancelRequestId",
          "slug": "cancel-request-id",
          "kind": "function",
          "declaration": "declare function cancelRequestId(raw: string): Result<CancelRequestId, InvalidInput<'cancelRequestId'>>;"
        },
        {
          "name": "CancelRequestId",
          "slug": "cancel-request-id--type",
          "kind": "type",
          "declaration": "/**\n * 취소 요청 ID — 6–64자, `^[A-Za-z0-9\\-_=]+$` (상점 발급 고유값).\n * **중국·동남아 비동기(Alipay 등) 결제 취소에만 필수**다 — 공식 V2 '해외 간편결제\n * 연동하기'(문서 ID 53)의 취소 Request Body 규격. 국내/일반 취소에는 불필요.\n */\ntype CancelRequestId = string & Brand<'CancelRequestId'>;",
          "sourceDocumentation": "취소 요청 ID — 6–64자, `^[A-Za-z0-9\\-_=]+$` (상점 발급 고유값).\n**중국·동남아 비동기(Alipay 등) 결제 취소에만 필수**다 — 공식 V2 '해외 간편결제\n연동하기'(문서 ID 53)의 취소 Request Body 규격. 국내/일반 취소에는 불필요."
        },
        {
          "name": "CancelTransaction",
          "slug": "cancel-transaction",
          "kind": "interface",
          "declaration": "interface CancelTransaction {\n    readonly transactionKey: string;\n    readonly cancelAmount: number;\n    readonly cancelReason: string;\n    readonly taxFreeAmount: number;\n    readonly taxExemptionAmount: number;\n    /** (응답) 이 취소 후 남은 환불 가능액 — 취소 요청 파라미터의 refundableAmount와 이름만 같다. */\n    readonly refundableAmount: number;\n    readonly transferDiscountAmount: number;\n    readonly easyPayDiscountAmount: number;\n    readonly canceledAt: string;\n    readonly receiptKey: string | null;\n    /** 해외 간편결제(PayPal)는 IN_PROGRESS로 시작하는 비동기 취소 — CANCEL_STATUS_CHANGED 웹훅으로 완결. */\n    readonly cancelStatus: 'DONE' | 'IN_PROGRESS' | 'ABORTED';\n    /** 비동기 취소 전용. */\n    readonly cancelRequestId: string | null;\n}"
        },
        {
          "name": "CARD_ISSUER_NAMES_KO",
          "slug": "card-issuer-names-ko",
          "kind": "constant",
          "declaration": "CARD_ISSUER_NAMES_KO: Readonly<Record<KnownCardIssuerCode, string>>",
          "sourceDocumentation": "Korean display names for every code in {@link KnownCardIssuerCode}, keyed by the two-character\ncode Toss returns in `card.issuerCode` / `card.acquirerCode`.\n\nNames are the \"카드사\" column of the official table verbatim, except that the acquirer\nqualifiers on the two 우리 rows are dropped for display: `33` → `우리BC카드` (acquired by BC),\n`W1` → `우리카드` (acquired by 우리; response-only code). The object is frozen; treat it as a\nlookup table, not as product copy — override names in your own layer if your UI needs shorter\nlabels."
        },
        {
          "name": "CardDetails",
          "slug": "card-details",
          "kind": "interface",
          "declaration": "interface CardDetails {\n    readonly amount: number;\n    readonly issuerCode: string;\n    readonly acquirerCode: string | null;\n    /** 마스킹된 카드번호. */\n    readonly number: string;\n    readonly installmentPlanMonths: number;\n    readonly approveNo: string;\n    readonly useCardPoint: boolean;\n    readonly cardType: '신용' | '체크' | '기프트' | '미확인';\n    readonly ownerType: '개인' | '법인' | '미확인';\n    readonly acquireStatus: string;\n    readonly isInterestFree: boolean;\n    readonly interestPayer: string | null;\n}"
        },
        {
          "name": "cardIssuerName",
          "slug": "card-issuer-name",
          "kind": "function",
          "declaration": "/**\n * Display name for a Toss card issuer/acquirer code, or `undefined` when the code is not in the\n * documented table (Toss may add institutions; render a neutral fallback such as \"카드\" yourself).\n *\n * Matching is exact — Toss returns codes exactly as listed (uppercase letter, no whitespace), so\n * no normalisation is applied. Only `'ko'` is supported today; the `locale` parameter exists so\n * other languages can be added without changing the signature.\n */\ndeclare function cardIssuerName(code: string, locale?: 'ko'): string | undefined;",
          "sourceDocumentation": "Display name for a Toss card issuer/acquirer code, or `undefined` when the code is not in the\ndocumented table (Toss may add institutions; render a neutral fallback such as \"카드\" yourself).\n\nMatching is exact — Toss returns codes exactly as listed (uppercase letter, no whitespace), so\nno normalisation is applied. Only `'ko'` is supported today; the `locale` parameter exists so\nother languages can be added without changing the signature."
        },
        {
          "name": "CardPayment",
          "slug": "card-payment",
          "kind": "interface",
          "declaration": "interface CardPayment extends PaymentBase {\n    readonly method: '카드';\n    readonly card: CardDetails;\n    readonly virtualAccount: null;\n}"
        },
        {
          "name": "categorizeCancelError",
          "slug": "categorize-cancel-error",
          "kind": "function",
          "declaration": "declare function categorizeCancelError(code: string): ErrorCategory;"
        },
        {
          "name": "CLASSIFIED_TOSS_ERROR_CODES",
          "slug": "classified-toss-error-codes",
          "kind": "constant",
          "declaration": "CLASSIFIED_TOSS_ERROR_CODES: readonly string[]",
          "sourceDocumentation": "Every Toss error code the library has a classification for — the keys of the internal\ncode table, frozen, in table order. `classifyTossErrorCode(code)` returns a non-`UNKNOWN`\ncategory exactly for these codes.\n\nExposed so that derived tables (e.g. `OUTCOME_QUERY_FIRST_ERROR_CODES`) and consumer audits\ncan be checked against the single source instead of a hand-copied list — adding a code to\nthe table is then a visible, testable event."
        },
        {
          "name": "classifyTossErrorCode",
          "slug": "classify-toss-error-code",
          "kind": "function",
          "declaration": "/** 미등록 코드 → UNKNOWN + 비재시도(보수 판정). 원문 code/message/httpStatus는 호출부가 무손실 보존한다. */\ndeclare function classifyTossErrorCode(code: string): ErrorCodeClassification;",
          "sourceDocumentation": "미등록 코드 → UNKNOWN + 비재시도(보수 판정). 원문 code/message/httpStatus는 호출부가 무손실 보존한다."
        },
        {
          "name": "compareLedgerRefund",
          "slug": "compare-ledger-refund",
          "kind": "function",
          "declaration": "/**\n * Compares a provider payment-state snapshot against the app ledger's cumulative refund\n * target — \"has the provider confirmed the refunds my ledger claims?\".\n *\n * Expressed purely in provider-snapshot terms: `snapshot.canceledAmount`\n * (`totalAmount - balanceAmount` at summarize time) is the provider's current cumulative\n * canceled amount, and `pendingCancelAmount` is the sum of `cancelAmount` over\n * `cancelStatus: 'IN_PROGRESS'` transactions. Per the kit's Phase-0 field measurements\n * (and the cancel path's own 2xx validation), an accepted async cancel already shows the\n * reduced balance while `IN_PROGRESS` — so pending amounts are *inside* `canceledAmount`,\n * and an aborted cancel takes the balance back up. `'settled'` therefore additionally\n * requires that nothing is in flight; see {@link LedgerRefundComparison} for the exact\n * three-way semantics. The ledger target stays app-owned — see {@link LedgerRefundTarget}.\n * Both the branded and the serialized snapshot forms are accepted; the ids play no part in\n * the verdict, so no re-branding is required. Comparing a snapshot of the *wrong payment*\n * against a ledger target is a caller-side identity error this helper cannot detect.\n *\n * Mapping to an app-side `SUCCEEDED / UNCONFIRMED / MISMATCH` three-way: `'settled'` maps\n * to succeeded, but the kit's `'unconfirmed'` is strictly the in-flight-cancel case — an\n * app-style \"the cancel request likely never reached the provider, replay the sealed\n * request\" state surfaces here as `'mismatch'` / `'provider-below-ledger'`. Pass\n * {@link LedgerRefundTarget.requestedAmount} to have that case labelled\n * `shortfall: 'at-prior-state'` (vs `'unexplained'`); do not map the three kit names 1:1\n * onto an app's replay policy without it.\n *\n * Honesty rule for broken inputs: when the snapshot's amounts are untrustworthy\n * (`invalid-amount`/`balance-exceeds-total` issues, or a `canceledAmount` that is not a\n * non-negative safe integer), the verdict is `'mismatch'` with\n * `direction: 'indeterminate'` and the gating issues attached. Deliberately *not*\n * reproduced: the \"status CANCELED with only totalAmount valid ⇒ assume fully refunded\"\n * fallback some reconciliation paths use — that is a guess, and settling a ledger on it\n * belongs to the app's explicit policy, not a library default.\n */\ndeclare function compareLedgerRefund(snapshot: PaymentStateSnapshot | SerializedPaymentStateSnapshot, ledger: LedgerRefundTarget): LedgerRefundComparison;",
          "sourceDocumentation": "Compares a provider payment-state snapshot against the app ledger's cumulative refund\ntarget — \"has the provider confirmed the refunds my ledger claims?\".\n\nExpressed purely in provider-snapshot terms: `snapshot.canceledAmount`\n(`totalAmount - balanceAmount` at summarize time) is the provider's current cumulative\ncanceled amount, and `pendingCancelAmount` is the sum of `cancelAmount` over\n`cancelStatus: 'IN_PROGRESS'` transactions. Per the kit's Phase-0 field measurements\n(and the cancel path's own 2xx validation), an accepted async cancel already shows the\nreduced balance while `IN_PROGRESS` — so pending amounts are *inside* `canceledAmount`,\nand an aborted cancel takes the balance back up. `'settled'` therefore additionally\nrequires that nothing is in flight; see {@link LedgerRefundComparison} for the exact\nthree-way semantics. The ledger target stays app-owned — see {@link LedgerRefundTarget}.\nBoth the branded and the serialized snapshot forms are accepted; the ids play no part in\nthe verdict, so no re-branding is required. Comparing a snapshot of the *wrong payment*\nagainst a ledger target is a caller-side identity error this helper cannot detect.\n\nMapping to an app-side `SUCCEEDED / UNCONFIRMED / MISMATCH` three-way: `'settled'` maps\nto succeeded, but the kit's `'unconfirmed'` is strictly the in-flight-cancel case — an\napp-style \"the cancel request likely never reached the provider, replay the sealed\nrequest\" state surfaces here as `'mismatch'` / `'provider-below-ledger'`. Pass\n{@link LedgerRefundTarget.requestedAmount} to have that case labelled\n`shortfall: 'at-prior-state'` (vs `'unexplained'`); do not map the three kit names 1:1\nonto an app's replay policy without it.\n\nHonesty rule for broken inputs: when the snapshot's amounts are untrustworthy\n(`invalid-amount`/`balance-exceeds-total` issues, or a `canceledAmount` that is not a\nnon-negative safe integer), the verdict is `'mismatch'` with\n`direction: 'indeterminate'` and the gating issues attached. Deliberately *not*\nreproduced: the \"status CANCELED with only totalAmount valid ⇒ assume fully refunded\"\nfallback some reconciliation paths use — that is a guess, and settling a ledger on it\nbelongs to the app's explicit policy, not a library default."
        },
        {
          "name": "ConfirmErrorCode",
          "slug": "confirm-error-code",
          "kind": "type",
          "declaration": "type ConfirmErrorCode = 'ALREADY_PROCESSED_PAYMENT'\n/** 인증 후 10분 초과 404 — 재시도 불가한 최종 실패(결제 재요청 필요). */\n | 'NOT_FOUND_PAYMENT_SESSION' | 'PAY_PROCESS_ABORTED' | 'INVALID_REQUEST' | 'INVALID_PAYMENT_KEY' | 'REJECT_CARD_PAYMENT' | 'PROVIDER_ERROR' | 'UNAUTHORIZED_KEY' | 'INVALID_API_KEY' | 'FORBIDDEN_REQUEST' | 'NOT_FOUND_PAYMENT' | (string & {});"
        },
        {
          "name": "createCustomRefundPolicy",
          "slug": "create-custom-refund-policy",
          "kind": "function",
          "declaration": "/** 프로젝트 고유 규칙을 같은 검증·반올림·quote 계약에 연결하는 escape hatch. */\ndeclare function createCustomRefundPolicy<Context>(config: CustomRefundPolicyConfig<Context>): Result<RefundPolicy<CustomRefundQuoteInput<Context>>, RefundPolicyConfigError>;",
          "sourceDocumentation": "프로젝트 고유 규칙을 같은 검증·반올림·quote 계약에 연결하는 escape hatch."
        },
        {
          "name": "createRefundPolicy",
          "slug": "create-refund-policy",
          "kind": "function",
          "declaration": "/** 내장 정책 생성. 설정 오류는 부팅 시 orThrow로 처리할 수 있도록 Result로 반환한다. */\ndeclare function createRefundPolicy<const Config extends BuiltInRefundPolicyConfig>(config: Config): Result<RefundPolicy<QuoteInputFor<Config>>, RefundPolicyConfigError>;",
          "sourceDocumentation": "내장 정책 생성. 설정 오류는 부팅 시 orThrow로 처리할 수 있도록 Result로 반환한다."
        },
        {
          "name": "customerKey",
          "slug": "customer-key",
          "kind": "function",
          "declaration": "declare function customerKey(raw: string): Result<CustomerKey, InvalidInput<'customerKey'>>;"
        },
        {
          "name": "CustomerKey",
          "slug": "customer-key--type",
          "kind": "type",
          "declaration": "/**\n * 고객 키 — 2–300자, `^[A-Za-z0-9\\-_=.@]+$`.\n *\n * Phase 0 실측(2026-08-09): 토스 서버는 사실상 검증하지 않는다 —\n * 301자는 400이 아닌 **500 FAILED_DB_PROCESSING**, `\"bad key!\"`(공백+허용 외 문자)도 200.\n * 따라서 이 생성자가 실질 방어선이다. \"특수문자 최소 1개\" 문구는 허용 집합\n * 나열로 확인됐으므로(순수 영숫자 200 통과) 특수문자 필수 검증은 하지 않는다.\n */\ntype CustomerKey = string & Brand<'CustomerKey'>;",
          "sourceDocumentation": "고객 키 — 2–300자, `^[A-Za-z0-9\\-_=.@]+$`.\n\nPhase 0 실측(2026-08-09): 토스 서버는 사실상 검증하지 않는다 —\n301자는 400이 아닌 **500 FAILED_DB_PROCESSING**, `\"bad key!\"`(공백+허용 외 문자)도 200.\n따라서 이 생성자가 실질 방어선이다. \"특수문자 최소 1개\" 문구는 허용 집합\n나열로 확인됐으므로(순수 영숫자 200 통과) 특수문자 필수 검증은 하지 않는다."
        },
        {
          "name": "CustomRefundPolicyConfig",
          "slug": "custom-refund-policy-config",
          "kind": "interface",
          "declaration": "interface CustomRefundPolicyConfig<Context> extends RefundPolicyIdentity {\n    readonly kind: \"custom\";\n    readonly rounding: RefundRoundingMode;\n    /** throw 대신 Result 실패를 사용한다. throw도 라이브러리가 포착해 quote 오류로 바꾼다. */\n    readonly calculate: (input: CustomRefundQuoteInput<Context>) => Result<RefundEntitlement, unknown>;\n}"
        },
        {
          "name": "CustomRefundQuoteInput",
          "slug": "custom-refund-quote-input",
          "kind": "interface",
          "declaration": "interface CustomRefundQuoteInput<Context> extends RefundQuoteInput {\n    readonly context: Context;\n}"
        },
        {
          "name": "DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS",
          "slug": "default-idempotency-replay-window-ms",
          "kind": "constant",
          "declaration": "DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS: number",
          "sourceDocumentation": "Conservative replay window used by {@link isWithinIdempotencyReplayWindow} when no explicit\nwindow is given: **14 days** — one full day of margin below {@link TOSS_IDEMPOTENCY_KEY_TTL_MS}.\n\nTwo regimes split at this boundary:\n\n- **Replay within the window** — resending the *same* key with the *same* body is safe: if the\n  first request reached Toss, the original response is replayed byte-for-byte and nothing is\n  executed twice; if it never arrived, it runs once now.\n- **New attempt after the window** — the same key may be executed as a brand-new request.\n  Do **not** resubmit. The only safe automatic action is to look the outcome up\n  (`getPaymentByOrderId` / `getPayment`) and decide from the durable state; a genuinely new\n  charge needs a new key (a new `attempt`) and an explicit decision.\n\nWhy a day of margin: the provider states the window at day granularity (\"15 days from first\nuse\") without specifying the boundary or time zone, so the real cutoff may land earlier than\n15 × 24 h after the first request; and the caller's clock and the provider's clock drift. A\nwhole day absorbs both without guessing. The precondition this relies on is that `issuedAt`\nwas recorded **no later than** the first network attempt — then it is a lower bound on the\nprovider's first-use time and a window measured from it can only be conservative. The\nlibrary's own `CancelRetryTicket` expires on this same 14-day window."
        },
        {
          "name": "DEFAULT_REFUND_QUOTE_TTL_MS",
          "slug": "default-refund-quote-ttl-ms",
          "kind": "constant",
          "declaration": "DEFAULT_REFUND_QUOTE_TTL_MS: number",
          "sourceDocumentation": "별도 경계가 없는 견적도 무기한 실행되지 않도록 하는 기본 수명(5분)."
        },
        {
          "name": "deriveIdempotencyKey",
          "slug": "derive-idempotency-key",
          "kind": "function",
          "declaration": "/**\n * Deterministically derives an `Idempotency-Key` from a logical operation identity.\n *\n * Format: `<operation>:<part>:<part>…` (segments joined by `:`; with no `parts` the key is just\n * `<operation>`), plus `#<attempt>` when `attempt` is given. Example:\n * `subscription_renewal:sub_01:1756652400000` and, for a new attempt,\n * `subscription_renewal:sub_01:1756652400000#7c9e…`. The same input always yields the same key,\n * so a crash-recovered worker reproduces the key it submitted before and gets Toss's replay\n * instead of a second execution.\n *\n * **The encoding is injective — distinct inputs never derive the same key.** Every segment\n * (`operation`, each element of `parts`, `attempt`) must be non-empty (`reason: 'empty'`) and\n * must consist of visible ASCII **excluding** the two delimiters `:` and `#`\n * (`reason: 'bad-charset'`). Because no segment can contain a delimiter, a key contains `#`\n * exactly once iff an attempt was given, and the prefix splits on `:` back into exactly\n * `operation` + `parts`. Underscores, dots, `@`, `=`, `-` and the like are fine, so the ids the\n * library already validates (`orderId`, `customerKey`, `cancelRequestId`, UUIDs, epoch strings)\n * all pass unchanged; ISO timestamps with `:` do not — use an epoch or a date-only marker.\n *\n * The assembled key then runs through the public {@link idempotencyKey} parser so the provider\n * length limit (1–300 chars, otherwise 400 `INVALID_IDEMPOTENCY_KEY` — `reason: 'too-long'`) and\n * the header-safe charset are enforced in exactly one place: an `Ok` result is always sendable.\n *\n * This is an **explicit** helper: the library never derives keys behind your back, because a\n * deterministic key combined with 4xx replay is a trap the caller must consciously manage with\n * the `attempt` field.\n */\ndeclare function deriveIdempotencyKey(input: DeriveIdempotencyKeyInput): Result<IdempotencyKey, InvalidInput<'idempotencyKey'>>;",
          "sourceDocumentation": "Deterministically derives an `Idempotency-Key` from a logical operation identity.\n\nFormat: `<operation>:<part>:<part>…` (segments joined by `:`; with no `parts` the key is just\n`<operation>`), plus `#<attempt>` when `attempt` is given. Example:\n`subscription_renewal:sub_01:1756652400000` and, for a new attempt,\n`subscription_renewal:sub_01:1756652400000#7c9e…`. The same input always yields the same key,\nso a crash-recovered worker reproduces the key it submitted before and gets Toss's replay\ninstead of a second execution.\n\n**The encoding is injective — distinct inputs never derive the same key.** Every segment\n(`operation`, each element of `parts`, `attempt`) must be non-empty (`reason: 'empty'`) and\nmust consist of visible ASCII **excluding** the two delimiters `:` and `#`\n(`reason: 'bad-charset'`). Because no segment can contain a delimiter, a key contains `#`\nexactly once iff an attempt was given, and the prefix splits on `:` back into exactly\n`operation` + `parts`. Underscores, dots, `@`, `=`, `-` and the like are fine, so the ids the\nlibrary already validates (`orderId`, `customerKey`, `cancelRequestId`, UUIDs, epoch strings)\nall pass unchanged; ISO timestamps with `:` do not — use an epoch or a date-only marker.\n\nThe assembled key then runs through the public {@link idempotencyKey } parser so the provider\nlength limit (1–300 chars, otherwise 400 `INVALID_IDEMPOTENCY_KEY` — `reason: 'too-long'`) and\nthe header-safe charset are enforced in exactly one place: an `Ok` result is always sendable.\n\nThis is an **explicit** helper: the library never derives keys behind your back, because a\ndeterministic key combined with 4xx replay is a trap the caller must consciously manage with\nthe `attempt` field."
        },
        {
          "name": "DeriveIdempotencyKeyInput",
          "slug": "derive-idempotency-key-input",
          "kind": "interface",
          "declaration": "/** Input for {@link deriveIdempotencyKey}. */\ninterface DeriveIdempotencyKeyInput {\n    /** Logical operation name, e.g. `'billing_initial_charge'` or `'subscription_renewal'`. */\n    readonly operation: string;\n    /**\n     * Identity of the logical business event — ids and period markers that make the key\n     * deterministic (subscription id, period start epoch, quote id, …). Never put raw\n     * billing/auth keys, card or account numbers here: the key travels in request headers and\n     * audit logs.\n     */\n    readonly parts: readonly string[];\n    /**\n     * Optional attempt discriminator. Omit it for the first submission; supply a fresh value\n     * (e.g. a UUID) for a *new* attempt after a definitive 4xx, because Toss replays the original\n     * 4xx for the same key for 15 days. Keep it **absent** when you intend a replay\n     * (transport failure, `IDEMPOTENT_REQUEST_PROCESSING`).\n     */\n    readonly attempt?: string | undefined;\n}",
          "sourceDocumentation": "Input for {@link deriveIdempotencyKey}."
        },
        {
          "name": "diffPaymentState",
          "slug": "diff-payment-state",
          "kind": "function",
          "declaration": "/**\n * 동일 결제의 두 상태 스냅샷을 비교한다.\n *\n * 어떤 status 전이도 거부하지 않는다. 식별자가 다를 때만 Err이며, 잔액 증가와 취소\n * transaction 제거는 성공 결과의 warnings로 전달한다.\n */\ndeclare function diffPaymentState(previous: PaymentStateSnapshot, next: PaymentStateSnapshot): Result<PaymentStateDiff, PaymentStateIdentityError>;",
          "sourceDocumentation": "동일 결제의 두 상태 스냅샷을 비교한다.\n\n어떤 status 전이도 거부하지 않는다. 식별자가 다를 때만 Err이며, 잔액 증가와 취소\ntransaction 제거는 성공 결과의 warnings로 전달한다."
        },
        {
          "name": "EasyPayDetails",
          "slug": "easy-pay-details",
          "kind": "interface",
          "declaration": "interface EasyPayDetails {\n    readonly provider: string;\n    readonly amount: number;\n    readonly discountAmount: number;\n}"
        },
        {
          "name": "EasyPayPayment",
          "slug": "easy-pay-payment",
          "kind": "interface",
          "declaration": "interface EasyPayPayment extends PaymentBase {\n    readonly method: '간편결제';\n    readonly easyPay: EasyPayDetails;\n}"
        },
        {
          "name": "ElapsedTimeRefundBracket",
          "slug": "elapsed-time-refund-bracket",
          "kind": "interface",
          "declaration": "interface ElapsedTimeRefundBracket {\n    /** anchorAt부터 이 값 미만인 반열린 구간에 적용된다. 양수 밀리초. */\n    readonly untilMs: number;\n    /** 0..10,000 정수. */\n    readonly rateBps: number;\n    readonly reason?: string;\n}"
        },
        {
          "name": "ElapsedTimeRefundPolicyConfig",
          "slug": "elapsed-time-refund-policy-config",
          "kind": "interface",
          "declaration": "interface ElapsedTimeRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"elapsed-time-rate\";\n    /** untilMs가 엄격한 오름차순이어야 한다. 경계 시각은 다음 구간으로 넘어간다. */\n    readonly brackets: readonly ElapsedTimeRefundBracket[];\n    readonly fallbackRateBps: number;\n    readonly fallbackReason?: string;\n    readonly rounding: RefundRoundingMode;\n}"
        },
        {
          "name": "ElapsedTimeRefundQuoteInput",
          "slug": "elapsed-time-refund-quote-input",
          "kind": "interface",
          "declaration": "interface ElapsedTimeRefundQuoteInput extends RefundQuoteInput {\n    /** 경과시간 0의 기준 시각. evaluatedAt이 더 이르면 경과시간은 0으로 clamp한다. */\n    readonly anchorAt: Date;\n}"
        },
        {
          "name": "Env",
          "slug": "env",
          "kind": "type",
          "declaration": "/**\n * 키 4종 — 템플릿 리터럴(형식) × 브랜드(명목성) × EnvTag(test/live phantom).\n *\n * 이 모듈(\".\"에서 도달)은 **client key 파서만** export한다.\n * secret key 파서(parseApiSecretKey/parseWidgetSecretKey)는 server/keys.ts 전용 —\n * 브라우저 번들에서 시크릿 키 타입의 값을 제조할 방법 자체를 없애는 격리 규칙.\n */\ntype Env = 'test' | 'live';",
          "sourceDocumentation": "키 4종 — 템플릿 리터럴(형식) × 브랜드(명목성) × EnvTag(test/live phantom).\n\n이 모듈(\".\"에서 도달)은 **client key 파서만** export한다.\nsecret key 파서(parseApiSecretKey/parseWidgetSecretKey)는 server/keys.ts 전용 —\n브라우저 번들에서 시크릿 키 타입의 값을 제조할 방법 자체를 없애는 격리 규칙."
        },
        {
          "name": "EnvTag",
          "slug": "env-tag",
          "kind": "type",
          "declaration": "/**\n * test/live phantom 태그 — 런타임 표현 없음. `isTestKey`/`isLiveKey`로만 내로잉한다.\n * 상호 배타 축(EnvAxis)이라 `EnvTag<'test'> & EnvTag<'live'>`는 never로 붕괴한다 —\n * 술어 내로잉이 유니언에서 반대 env 멤버를 정확히 걸러내기 위한 구조 (brand.ts 참조).\n */\ntype EnvTag<E extends Env> = EnvAxis<E>;",
          "sourceDocumentation": "test/live phantom 태그 — 런타임 표현 없음. `isTestKey`/`isLiveKey`로만 내로잉한다.\n상호 배타 축(EnvAxis)이라 `EnvTag<'test'> & EnvTag<'live'>`는 never로 붕괴한다 —\n술어 내로잉이 유니언에서 반대 env 멤버를 정확히 걸러내기 위한 구조 (brand.ts 참조)."
        },
        {
          "name": "err",
          "slug": "err",
          "kind": "function",
          "declaration": "declare function err<E>(error: E): Err<E>;"
        },
        {
          "name": "Err",
          "slug": "err--interface",
          "kind": "interface",
          "declaration": "interface Err<out E> {\n    readonly ok: false;\n    readonly error: E;\n}"
        },
        {
          "name": "ErrorCategory",
          "slug": "error-category",
          "kind": "type",
          "declaration": "type ErrorCategory = 'STATE' | 'AMOUNT' | 'PARTIAL_NOT_ALLOWED' | 'DEADLINE' | 'ACCOUNT' | 'CONCURRENCY' | 'TRANSIENT' | 'AUTH' | 'NOT_FOUND' | 'REJECTED' | 'REQUEST' | 'UNKNOWN';"
        },
        {
          "name": "ErrorCodeClassification",
          "slug": "error-code-classification",
          "kind": "interface",
          "declaration": "interface ErrorCodeClassification {\n    readonly category: ErrorCategory;\n    readonly retryable: boolean;\n}"
        },
        {
          "name": "FullRefundPolicyConfig",
          "slug": "full-refund-policy-config",
          "kind": "interface",
          "declaration": "interface FullRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"full\";\n}"
        },
        {
          "name": "generateCustomerKey",
          "slug": "generate-customer-key",
          "kind": "function",
          "declaration": "/** `crypto.randomUUID()` — 36자 `[0-9a-f-]`로 위젯(≤50)·서버(≤300) 두 규격을 모두 만족한다. */\ndeclare function generateCustomerKey(): WidgetCustomerKey;",
          "sourceDocumentation": "`crypto.randomUUID()` — 36자 `[0-9a-f-]`로 위젯(≤50)·서버(≤300) 두 규격을 모두 만족한다."
        },
        {
          "name": "generateIdempotencyKey",
          "slug": "generate-idempotency-key",
          "kind": "function",
          "declaration": "/** `crypto.randomUUID()` — 36자로 300자 한도 내 항상 유효. */\ndeclare function generateIdempotencyKey(): IdempotencyKey;",
          "sourceDocumentation": "`crypto.randomUUID()` — 36자로 300자 한도 내 항상 유효."
        },
        {
          "name": "generateOrderId",
          "slug": "generate-order-id",
          "kind": "function",
          "declaration": "/**\n * 항상 유효한 OrderId 생성 — `${prefix}${epoch36}${rand}`.\n * 6–64자 보장: 코어(epoch36 8자 + 난수 10자 = 18자)가 하한을 채우고,\n * prefix는 허용 외 문자 제거 후 총 64자를 넘지 않게 절단한다.\n */\ndeclare function generateOrderId(prefix?: string): OrderId;",
          "sourceDocumentation": "항상 유효한 OrderId 생성 — `${prefix}${epoch36}${rand}`.\n6–64자 보장: 코어(epoch36 8자 + 난수 10자 = 18자)가 하한을 채우고,\nprefix는 허용 외 문자 제거 후 총 64자를 넘지 않게 절단한다."
        },
        {
          "name": "GiftCertificateDetails",
          "slug": "gift-certificate-details",
          "kind": "interface",
          "declaration": "interface GiftCertificateDetails {\n    readonly approveNo: string;\n    readonly settlementStatus: string;\n}"
        },
        {
          "name": "GiftCertificatePayment",
          "slug": "gift-certificate-payment",
          "kind": "interface",
          "declaration": "interface GiftCertificatePayment extends PaymentBase {\n    readonly method: '문화상품권' | '도서문화상품권' | '게임문화상품권';\n    readonly giftCertificate: GiftCertificateDetails;\n}"
        },
        {
          "name": "idempotencyKey",
          "slug": "idempotency-key",
          "kind": "function",
          "declaration": "/**\n * 멱등키 스마트 생성자 — 1–300자 + 헤더 안전 문자셋(`^[\\x21-\\x7E]+$`).\n * 한글·공백·CR/LF 등은 `reason: 'bad-charset'`. Ok이면 그 값은 어떤 fetch 구현에서도\n * `Idempotency-Key` 헤더로 바이트 동일하게 전송된다.\n */\ndeclare function idempotencyKey(raw: string): Result<IdempotencyKey, InvalidInput<'idempotencyKey'>>;",
          "sourceDocumentation": "멱등키 스마트 생성자 — 1–300자 + 헤더 안전 문자셋(`^[\\x21-\\x7E]+$`).\n한글·공백·CR/LF 등은 `reason: 'bad-charset'`. Ok이면 그 값은 어떤 fetch 구현에서도\n`Idempotency-Key` 헤더로 바이트 동일하게 전송된다."
        },
        {
          "name": "IdempotencyKey",
          "slug": "idempotency-key--type",
          "kind": "type",
          "declaration": "/**\n * 멱등키 — 1–300자(초과 시 400 INVALID_IDEMPOTENCY_KEY), 문자셋 `^[\\x21-\\x7E]+$`\n * (공백 없는 출력 가능 ASCII — 헤더 안전 집합).\n *\n * 문자셋 근거: 토스 문서는 길이만 규정하지만 값은 `Idempotency-Key` **요청 헤더**로 전송된다.\n * 비 Latin-1 문자·CR/LF는 fetch `Headers`가 TypeError로 거부해 소켓에 닿기도 전에\n * 실패하고(그 TypeError는 transport 계층에서 NETWORK_ERROR로 오분류됨), 공백·탭·Latin-1\n * 확장 문자는 중간 프록시가 trim/재인코딩할 수 있어 같은 키의 재전송이 다른 바이트로 도착할\n * 위험이 있다. 생성 시점에 거부하는 쪽이 \"Ok면 전송 가능\"을 보장하는 유일한 길이다.\n *\n * 처음 사용일부터 15일 유효 — TTL 초과 뒤 같은 키는 새 요청으로 실행될 수 있다(문서는 기간만\n * 명시하며 만료 뒤 동작은 서술하지 않음 — 안전하지 않은 것으로 취급).\n * 멱등 판정 조합은 \"키 + API 키 + 주소 + 메서드\"이며 **body는 포함되지 않는다**(문서 명시).\n */\ntype IdempotencyKey = string & Brand<'IdempotencyKey'>;",
          "sourceDocumentation": "멱등키 — 1–300자(초과 시 400 INVALID_IDEMPOTENCY_KEY), 문자셋 `^[\\x21-\\x7E]+$`\n(공백 없는 출력 가능 ASCII — 헤더 안전 집합).\n\n문자셋 근거: 토스 문서는 길이만 규정하지만 값은 `Idempotency-Key` **요청 헤더**로 전송된다.\n비 Latin-1 문자·CR/LF는 fetch `Headers`가 TypeError로 거부해 소켓에 닿기도 전에\n실패하고(그 TypeError는 transport 계층에서 NETWORK_ERROR로 오분류됨), 공백·탭·Latin-1\n확장 문자는 중간 프록시가 trim/재인코딩할 수 있어 같은 키의 재전송이 다른 바이트로 도착할\n위험이 있다. 생성 시점에 거부하는 쪽이 \"Ok면 전송 가능\"을 보장하는 유일한 길이다.\n\n처음 사용일부터 15일 유효 — TTL 초과 뒤 같은 키는 새 요청으로 실행될 수 있다(문서는 기간만\n명시하며 만료 뒤 동작은 서술하지 않음 — 안전하지 않은 것으로 취급).\n멱등 판정 조합은 \"키 + API 키 + 주소 + 메서드\"이며 **body는 포함되지 않는다**(문서 명시)."
        },
        {
          "name": "InvalidInput",
          "slug": "invalid-input",
          "kind": "interface",
          "declaration": "/**\n * Validation failure of a library-owned input.\n *\n * `Reason` defaults to the string-constraint reasons every id/key parser uses, so all\n * existing `InvalidInput<'orderId'>`-style references keep their exact shape. Structured\n * inputs (e.g. `parsePaymentStateSnapshot`) instantiate it with their own reason union.\n */\ninterface InvalidInput<Field extends string, Reason extends string = 'too-short' | 'too-long' | 'bad-charset' | 'empty'> {\n    readonly source: 'library';\n    readonly kind: 'invalid-input';\n    readonly field: Field;\n    readonly reason: Reason;\n}",
          "sourceDocumentation": "Validation failure of a library-owned input.\n\n`Reason` defaults to the string-constraint reasons every id/key parser uses, so all\nexisting `InvalidInput<'orderId'>`-style references keep their exact shape. Structured\ninputs (e.g. `parsePaymentStateSnapshot`) instantiate it with their own reason union."
        },
        {
          "name": "InvalidPaymentStateSnapshot",
          "slug": "invalid-payment-state-snapshot",
          "kind": "interface",
          "declaration": "/**\n * Parse failure for {@link parsePaymentStateSnapshot} — an\n * `InvalidInput<'paymentStateSnapshot'>` extended with the snapshot-specific reason union\n * and the `path` of the offending value (`'$'` for the root, otherwise a dotted path such as\n * `'cancels[2].cancelAmount'`).\n */\ninterface InvalidPaymentStateSnapshot extends InvalidInput<\"paymentStateSnapshot\", PaymentStateSnapshotParseReason> {\n    readonly path: string;\n}",
          "sourceDocumentation": "Parse failure for {@link parsePaymentStateSnapshot} — an\n`InvalidInput<'paymentStateSnapshot'>` extended with the snapshot-specific reason union\nand the `path` of the offending value (`'$'` for the root, otherwise a dotted path such as\n`'cancels[2].cancelAmount'`)."
        },
        {
          "name": "isAlreadyFullyCanceledError",
          "slug": "is-already-fully-canceled-error",
          "kind": "function",
          "declaration": "/**\n * \"이미 완전 취소됨\" 재취소 이중 매핑 헬퍼.\n *\n * Phase 0 실측(2026-08-09): 단일 전액 취소 후 재취소는 400 ALREADY_CANCELED_PAYMENT,\n * **부분취소 이력이 있는 결제의 잔액 0 재취소는 403 NOT_CANCELABLE_AMOUNT**로 온다.\n * 두 코드를 모두 수용해야 한다. (라이브러리 사전검증이 잔액 초과 부분취소를 API 호출 전에\n * 차단하므로, 이 헬퍼에 도달하는 NOT_CANCELABLE_AMOUNT는 사실상 재취소 케이스다.)\n */\ndeclare function isAlreadyFullyCanceledError(e: TossApiFailure): boolean;",
          "sourceDocumentation": "\"이미 완전 취소됨\" 재취소 이중 매핑 헬퍼.\n\nPhase 0 실측(2026-08-09): 단일 전액 취소 후 재취소는 400 ALREADY_CANCELED_PAYMENT,\n**부분취소 이력이 있는 결제의 잔액 0 재취소는 403 NOT_CANCELABLE_AMOUNT**로 온다.\n두 코드를 모두 수용해야 한다. (라이브러리 사전검증이 잔액 초과 부분취소를 API 호출 전에\n차단하므로, 이 헬퍼에 도달하는 NOT_CANCELABLE_AMOUNT는 사실상 재취소 케이스다.)"
        },
        {
          "name": "isDone",
          "slug": "is-done",
          "kind": "function",
          "declaration": "/** DONE이면 approvedAt은 non-null — 런타임에서도 함께 확인해 거짓 내로잉을 막는다. */\ndeclare function isDone(p: Payment): p is Payment & {\n    status: 'DONE';\n    approvedAt: string;\n};",
          "sourceDocumentation": "DONE이면 approvedAt은 non-null — 런타임에서도 함께 확인해 거짓 내로잉을 막는다."
        },
        {
          "name": "isErr",
          "slug": "is-err",
          "kind": "function",
          "declaration": "declare function isErr<T, E>(r: Result<T, E>): r is Err<E>;"
        },
        {
          "name": "isExecutableRefundQuote",
          "slug": "is-executable-refund-quote",
          "kind": "function",
          "declaration": "/** policy.quote/restoreQuote가 만든 실행 가능한 in-memory quote인지 확인한다. */\ndeclare function isExecutableRefundQuote(value: unknown): value is RefundQuote;",
          "sourceDocumentation": "policy.quote/restoreQuote가 만든 실행 가능한 in-memory quote인지 확인한다."
        },
        {
          "name": "isFullyCanceled",
          "slug": "is-fully-canceled",
          "kind": "function",
          "declaration": "/**\n * 완전 취소 판정 — ⚠ `status === 'CANCELED'` 검사가 아니다.\n *\n * Phase 0 실측(2026-08-09): 부분취소 이력이 있으면 잔액 전액 취소 후에도\n * status가 `PARTIAL_CANCELED`로 남는다(balanceAmount 0). 따라서 CANCELED 문자열만\n * 검사하지 않고, `balanceAmount === 0`과 취소 상태/이력 신호를 함께 본다. 취소 신호가\n * 없는 READY의 잔액 0은 완전 취소가 아니다.\n *\n * The parameter is the structural subset this predicate actually reads (`status`,\n * `balanceAmount`, `cancels`), so callers that only hold a reduced payment snapshot\n * (see `PaymentStateInput`) can use it too. A full `Payment` is always assignable —\n * including a fresh inline object literal: the explicit `| Payment` union member exists\n * solely so the excess-property check accepts literals spelling out non-Pick `Payment`\n * fields. Existing call sites compile unchanged.\n */\ndeclare function isFullyCanceled(p: Pick<Payment, 'status' | 'balanceAmount' | 'cancels'> | Payment): boolean;",
          "sourceDocumentation": "완전 취소 판정 — ⚠ `status === 'CANCELED'` 검사가 아니다.\n\nPhase 0 실측(2026-08-09): 부분취소 이력이 있으면 잔액 전액 취소 후에도\nstatus가 `PARTIAL_CANCELED`로 남는다(balanceAmount 0). 따라서 CANCELED 문자열만\n검사하지 않고, `balanceAmount === 0`과 취소 상태/이력 신호를 함께 본다. 취소 신호가\n없는 READY의 잔액 0은 완전 취소가 아니다.\n\nThe parameter is the structural subset this predicate actually reads (`status`,\n`balanceAmount`, `cancels`), so callers that only hold a reduced payment snapshot\n(see `PaymentStateInput`) can use it too. A full `Payment` is always assignable —\nincluding a fresh inline object literal: the explicit `| Payment` union member exists\nsolely so the excess-property check accepts literals spelling out non-Pick `Payment`\nfields. Existing call sites compile unchanged."
        },
        {
          "name": "isLiveKey",
          "slug": "is-live-key",
          "kind": "function",
          "declaration": "/** env 내로잉 가드 — {@link isTestKey}의 live 대응. */\ndeclare function isLiveKey<K extends string>(key: K): key is K & EnvTag<'live'>;",
          "sourceDocumentation": "env 내로잉 가드 — {@link isTestKey}의 live 대응."
        },
        {
          "name": "isOk",
          "slug": "is-ok",
          "kind": "function",
          "declaration": "declare function isOk<T, E>(r: Result<T, E>): r is Ok<T>;"
        },
        {
          "name": "isRetryable",
          "slug": "is-retryable",
          "kind": "function",
          "declaration": "/** retryable은 생성 시 코드 테이블로 각인된 값 — TransportFailure는 항상 true. */\ndeclare function isRetryable(e: TossApiFailure | TransportFailure): boolean;",
          "sourceDocumentation": "retryable은 생성 시 코드 테이블로 각인된 값 — TransportFailure는 항상 true."
        },
        {
          "name": "isTestKey",
          "slug": "is-test-key",
          "kind": "function",
          "declaration": "/** env 내로잉 가드 — EnvTag는 phantom이라 프로퍼티 판별이 불가능해 접두사로 판정한다. */\ndeclare function isTestKey<K extends string>(key: K): key is K & EnvTag<'test'>;",
          "sourceDocumentation": "env 내로잉 가드 — EnvTag는 phantom이라 프로퍼티 판별이 불가능해 접두사로 판정한다."
        },
        {
          "name": "isWithinIdempotencyReplayWindow",
          "slug": "is-within-idempotency-replay-window",
          "kind": "function",
          "declaration": "/**\n * Whether a key first used at `issuedAt` may still be **replayed** (same key, same body) at `now`.\n *\n * Exact semantics: returns `true` when `now - issuedAt < windowMs` — elapsed time strictly less\n * than the window. At exactly `windowMs` the window has closed and the result is `false`.\n * A negative elapsed time (`issuedAt` after `now`, e.g. clock skew) counts as within the window;\n * reject implausible future timestamps separately if your flow needs to. Any non-finite operand\n * (invalid `Date`, `NaN`, `±Infinity` — in `issuedAt`, `now`, or `windowMs`) yields `false`, the\n * side that never resubmits.\n *\n * `windowMs` defaults to {@link DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS}; pass\n * {@link TOSS_IDEMPOTENCY_KEY_TTL_MS} only if you deliberately want the provider's full window\n * with no safety margin.\n */\ndeclare function isWithinIdempotencyReplayWindow(issuedAt: Date | number, now: Date | number, windowMs?: number): boolean;",
          "sourceDocumentation": "Whether a key first used at `issuedAt` may still be **replayed** (same key, same body) at `now`.\n\nExact semantics: returns `true` when `now - issuedAt < windowMs` — elapsed time strictly less\nthan the window. At exactly `windowMs` the window has closed and the result is `false`.\nA negative elapsed time (`issuedAt` after `now`, e.g. clock skew) counts as within the window;\nreject implausible future timestamps separately if your flow needs to. Any non-finite operand\n(invalid `Date`, `NaN`, `±Infinity` — in `issuedAt`, `now`, or `windowMs`) yields `false`, the\nside that never resubmits.\n\n`windowMs` defaults to {@link DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS}; pass\n{@link TOSS_IDEMPOTENCY_KEY_TTL_MS} only if you deliberately want the provider's full window\nwith no safety margin."
        },
        {
          "name": "KeyParseError",
          "slug": "key-parse-error",
          "kind": "interface",
          "declaration": "interface KeyParseError {\n    readonly source: 'library';\n    readonly kind: 'invalid-key';\n    /** 기대한 접두사 형식 — 예: \"test_ck_ | live_ck_\" */\n    readonly expected: string;\n    readonly reason: 'bad-prefix' | 'empty-body' | 'bad-length';\n    /** 접두사 인식 진단 — 다른 종류의 키를 넣었으면 어떤 키인지 알려준다. */\n    readonly message: string;\n}"
        },
        {
          "name": "KnownCardIssuerCode",
          "slug": "known-card-issuer-code",
          "kind": "type",
          "declaration": "/**\n * 카드사 두 자리 코드 → 한글 표시명 — 공식 \"기관 코드\" 표(docs.tosspayments.com/codes/org-codes,\n * 문서 ID 118, \"카드사 코드\" 국내·해외) 전사. 응답 `card.issuerCode` / `card.acquirerCode`는\n * 항상 이 두 자리 코드다(한글·영문 코드는 요청 전용).\n *\n * 표시명은 문서의 \"카드사\" 열 그대로이되, 우리 계열 두 행의 매입사 괄호(\"(BC 매입)\"/\n * \"(우리 매입)\")만 뗐다 — 화면 표기용이기 때문. 뜻은 문서 참고 문구와 같다:\n * `33` 우리BC카드는 BC 매입, `W1` 우리카드는 우리 매입(응답 전용 코드).\n */\n/**\n * Card issuer/acquirer codes that Toss documents on its \"기관 코드\" page (`/codes/org-codes`,\n * \"카드사 코드\" — domestic and overseas). Responses (`card.issuerCode`, `card.acquirerCode`)\n * always carry one of these two-character codes; the Korean/English aliases are request-only.\n *\n * This union is a documentation aid for exhaustive tables; {@link cardIssuerName} accepts any\n * string so a code Toss adds later degrades to `undefined`, not to a compile error.\n */\ntype KnownCardIssuerCode = '3K' | '46' | '71' | '30' | '31' | '51' | '38' | '41' | '62' | '36' | '33' | 'W1' | '37' | '39' | '35' | '42' | '15' | '3A' | '24' | '21' | '61' | '11' | '91' | '34' | '6D' | '4M' | '3C' | '7A' | '4J' | '4V';",
          "sourceDocumentation": "Card issuer/acquirer codes that Toss documents on its \"기관 코드\" page (`/codes/org-codes`,\n\"카드사 코드\" — domestic and overseas). Responses (`card.issuerCode`, `card.acquirerCode`)\nalways carry one of these two-character codes; the Korean/English aliases are request-only.\n\nThis union is a documentation aid for exhaustive tables; {@link cardIssuerName} accepts any\nstring so a code Toss adds later degrades to `undefined`, not to a compile error."
        },
        {
          "name": "LedgerRefundComparison",
          "slug": "ledger-refund-comparison",
          "kind": "type",
          "declaration": "/**\n * Verdict of {@link compareLedgerRefund} — a three-way discriminated union.\n *\n * Balance model (Phase-0 field measurements, enforced by the cancel path's response\n * validation): an accepted async cancel *already* reduces `balanceAmount` while its\n * `cancelStatus` is `IN_PROGRESS`; completion (`DONE`) keeps the reduction, abortion\n * (`ABORTED`) restores the balance. `snapshot.canceledAmount` therefore *includes*\n * in-flight amounts, and the final confirmed amount lies in\n * `[canceledAmount - pendingCancelAmount, canceledAmount]`.\n *\n * - `'settled'` — `snapshot.canceledAmount` equals the ledger target **and no cancel is in\n *   flight** (`pendingCancelAmount` is always `0` here). Only then is recording the refund\n *   as final safe: an `IN_PROGRESS` cancel could still resolve `ABORTED` and take the\n *   balance back up (money that never moved).\n * - `'unconfirmed'` — at least one `IN_PROGRESS` cancel keeps the verdict provisional, and\n *   the target lies within the possible final range above, so it may still settle without\n *   any new provider action. Do not settle the ledger yet; re-fetch the payment (a\n *   `CANCEL_STATUS_CHANGED` webhook is `unverified`) and compare again. This mirrors\n *   `lifecycle: 'cancellation-pending'` taking priority over amount-based `'full'`.\n * - `'mismatch'` — the target is outside every possible outcome (`direction` says which\n *   way; with `requestedAmount` supplied, `shortfall` splits `'provider-below-ledger'` into\n *   `'at-prior-state'` / `'unexplained'`), or the comparison is impossible\n *   (`direction: 'indeterminate'`): the snapshot's amounts carry consistency issues\n *   (attached in `consistencyIssues`), or the ledger target itself was invalid\n *   (`invalidLedgerTarget: true`).\n */\ntype LedgerRefundComparison = {\n    readonly kind: \"settled\";\n    /** Provider-confirmed cumulative canceled amount (`snapshot.canceledAmount`). */\n    readonly canceledAmount: number;\n    /** Always `0` in this verdict — any in-flight cancel forces `'unconfirmed'`. */\n    readonly pendingCancelAmount: number;\n    readonly expectedRefundedAmount: number;\n} | {\n    readonly kind: \"unconfirmed\";\n    readonly canceledAmount: number;\n    readonly pendingCancelAmount: number;\n    readonly expectedRefundedAmount: number;\n} | {\n    readonly kind: \"mismatch\";\n    readonly direction: LedgerRefundMismatchDirection;\n    readonly canceledAmount: number;\n    readonly pendingCancelAmount: number;\n    readonly expectedRefundedAmount: number;\n    /**\n     * `true` when `expectedRefundedAmount` (or a supplied `requestedAmount`) was not a\n     * valid ledger amount.\n     */\n    readonly invalidLedgerTarget: boolean;\n    /**\n     * Present only when `direction: 'provider-below-ledger'` and the ledger supplied\n     * `requestedAmount` — see {@link LedgerRefundShortfall}.\n     */\n    readonly shortfall?: LedgerRefundShortfall;\n    /**\n     * The amount-integrity issues that blocked the comparison (`invalid-amount`,\n     * `balance-exceeds-total`) — empty for a plain amount mismatch. The snapshot keeps\n     * the full issue list.\n     */\n    readonly consistencyIssues: readonly PaymentStateConsistencyIssue[];\n};",
          "sourceDocumentation": "Verdict of {@link compareLedgerRefund} — a three-way discriminated union.\n\nBalance model (Phase-0 field measurements, enforced by the cancel path's response\nvalidation): an accepted async cancel *already* reduces `balanceAmount` while its\n`cancelStatus` is `IN_PROGRESS`; completion (`DONE`) keeps the reduction, abortion\n(`ABORTED`) restores the balance. `snapshot.canceledAmount` therefore *includes*\nin-flight amounts, and the final confirmed amount lies in\n`[canceledAmount - pendingCancelAmount, canceledAmount]`.\n\n- `'settled'` — `snapshot.canceledAmount` equals the ledger target **and no cancel is in\n  flight** (`pendingCancelAmount` is always `0` here). Only then is recording the refund\n  as final safe: an `IN_PROGRESS` cancel could still resolve `ABORTED` and take the\n  balance back up (money that never moved).\n- `'unconfirmed'` — at least one `IN_PROGRESS` cancel keeps the verdict provisional, and\n  the target lies within the possible final range above, so it may still settle without\n  any new provider action. Do not settle the ledger yet; re-fetch the payment (a\n  `CANCEL_STATUS_CHANGED` webhook is `unverified`) and compare again. This mirrors\n  `lifecycle: 'cancellation-pending'` taking priority over amount-based `'full'`.\n- `'mismatch'` — the target is outside every possible outcome (`direction` says which\n  way; with `requestedAmount` supplied, `shortfall` splits `'provider-below-ledger'` into\n  `'at-prior-state'` / `'unexplained'`), or the comparison is impossible\n  (`direction: 'indeterminate'`): the snapshot's amounts carry consistency issues\n  (attached in `consistencyIssues`), or the ledger target itself was invalid\n  (`invalidLedgerTarget: true`)."
        },
        {
          "name": "LedgerRefundMismatchDirection",
          "slug": "ledger-refund-mismatch-direction",
          "kind": "type",
          "declaration": "/** Which side is ahead in a `'mismatch'` verdict. */\ntype LedgerRefundMismatchDirection = \n/** Provider-confirmed refunds exceed the ledger target — the ledger is missing refunds. */\n\"provider-exceeds-ledger\"\n/**\n * Provider refunds fall short of the target — even if every in-flight cancel completes,\n * the confirmed amount cannot reach it.\n */\n | \"provider-below-ledger\"\n/** The amounts cannot be compared (inconsistent snapshot or invalid ledger target). */\n | \"indeterminate\";",
          "sourceDocumentation": "Which side is ahead in a `'mismatch'` verdict."
        },
        {
          "name": "LedgerRefundShortfall",
          "slug": "ledger-refund-shortfall",
          "kind": "type",
          "declaration": "/**\n * Sub-classification of a `'provider-below-ledger'` mismatch, present only when the ledger\n * supplied {@link LedgerRefundTarget.requestedAmount}.\n *\n * - `'at-prior-state'` — the provider's confirmed amount equals\n *   `expectedRefundedAmount - requestedAmount` and no cancel is in flight: the provider is\n *   exactly where it was before the reconciling refund request, so that request most likely\n *   never reached it. Replaying the persisted (sealed, idempotent) cancel request is safe.\n * - `'unexplained'` — any other shortfall (or one with cancels still in flight). Do not\n *   auto-replay; escalate.\n */\ntype LedgerRefundShortfall = \"at-prior-state\" | \"unexplained\";",
          "sourceDocumentation": "Sub-classification of a `'provider-below-ledger'` mismatch, present only when the ledger\nsupplied {@link LedgerRefundTarget.requestedAmount}.\n\n- `'at-prior-state'` — the provider's confirmed amount equals\n  `expectedRefundedAmount - requestedAmount` and no cancel is in flight: the provider is\n  exactly where it was before the reconciling refund request, so that request most likely\n  never reached it. Replaying the persisted (sealed, idempotent) cancel request is safe.\n- `'unexplained'` — any other shortfall (or one with cancels still in flight). Do not\n  auto-replay; escalate."
        },
        {
          "name": "LedgerRefundTarget",
          "slug": "ledger-refund-target",
          "kind": "interface",
          "declaration": "/**\n * The app-owned reconciliation target for {@link compareLedgerRefund}.\n *\n * The library never derives or stores this number — how much *should* have been refunded is\n * ledger state the consuming app owns, validates and persists. The helper only answers\n * whether the provider snapshot confirms it.\n */\ninterface LedgerRefundTarget {\n    /**\n     * Cumulative amount the app's ledger expects the provider to have refunded for this\n     * payment. Must be a non-negative safe integer; anything else yields\n     * `kind: 'mismatch'` with `invalidLedgerTarget: true` instead of a guessed verdict.\n     */\n    readonly expectedRefundedAmount: number;\n    /**\n     * Optional: the amount of the *single refund request currently being reconciled* —\n     * i.e. `expectedRefundedAmount` = previously-confirmed refunds + `requestedAmount`.\n     *\n     * When provided, a `'mismatch'` / `'provider-below-ledger'` verdict carries\n     * {@link LedgerRefundShortfall} in `shortfall`, distinguishing `'at-prior-state'` (the\n     * provider sits exactly at the pre-request amount with nothing in flight — the request\n     * most likely never reached the provider, so replaying a sealed idempotent cancel request\n     * is the natural recovery) from `'unexplained'` (any other shortfall — hold for a human).\n     * Without it the helper cannot tell those two apart. Must, when present, be a safe\n     * integer with `0 <= requestedAmount <= expectedRefundedAmount`; anything else yields\n     * `kind: 'mismatch'` with `invalidLedgerTarget: true`.\n     */\n    readonly requestedAmount?: number;\n}",
          "sourceDocumentation": "The app-owned reconciliation target for {@link compareLedgerRefund}.\n\nThe library never derives or stores this number — how much *should* have been refunded is\nledger state the consuming app owns, validates and persists. The helper only answers\nwhether the provider snapshot confirms it."
        },
        {
          "name": "map",
          "slug": "map",
          "kind": "function",
          "declaration": "/** 성공 값만 변환 — 실패는 그대로 통과한다. */\ndeclare function map<T, U, E>(r: Result<T, E>, f: (value: T) => U): Result<U, E>;",
          "sourceDocumentation": "성공 값만 변환 — 실패는 그대로 통과한다."
        },
        {
          "name": "mapErr",
          "slug": "map-err",
          "kind": "function",
          "declaration": "/** 실패 값만 변환 — 성공은 그대로 통과한다. */\ndeclare function mapErr<T, E, F>(r: Result<T, E>, f: (error: E) => F): Result<T, F>;",
          "sourceDocumentation": "실패 값만 변환 — 성공은 그대로 통과한다."
        },
        {
          "name": "matchesRefundObservedPaymentState",
          "slug": "matches-refund-observed-payment-state",
          "kind": "function",
          "declaration": "/** 실행 직전 재조회 Payment가 quote 생성 시점과 같은 상태인지 확인한다. */\ndeclare function matchesRefundObservedPaymentState(observed: RefundObservedPaymentState, payment: Payment): boolean;",
          "sourceDocumentation": "실행 직전 재조회 Payment가 quote 생성 시점과 같은 상태인지 확인한다."
        },
        {
          "name": "MobilePhoneDetails",
          "slug": "mobile-phone-details",
          "kind": "interface",
          "declaration": "interface MobilePhoneDetails {\n    readonly customerMobilePhone: string;\n    readonly settlementStatus: string;\n    readonly receiptUrl: string;\n}"
        },
        {
          "name": "MobilePhonePayment",
          "slug": "mobile-phone-payment",
          "kind": "interface",
          "declaration": "interface MobilePhonePayment extends PaymentBase {\n    readonly method: '휴대폰';\n    readonly mobilePhone: MobilePhoneDetails;\n}"
        },
        {
          "name": "mustQueryOutcomeBeforeRetry",
          "slug": "must-query-outcome-before-retry",
          "kind": "function",
          "declaration": "/**\n * `true` when the caller must look the payment/billing outcome up before retrying or failing\n * the operation, because the provider may have completed it:\n *\n * - every `TransportFailure` (`NETWORK_ERROR` / `TIMEOUT`) — the request may have reached Toss\n *   and the response was lost;\n * - every `TossApiFailure` whose `code` is in {@link OUTCOME_QUERY_FIRST_ERROR_CODES}.\n *\n * Decided by `source` and `code` only — never by HTTP status (`PROVIDER_ERROR` is a 400 that\n * belongs here; `REFUND_REJECTED` is a 400 that does not). `false` means the error is a\n * definitive refusal as far as the library can tell; unregistered codes return `false`.\n *\n * The lookup itself stays with the caller: for confirm use `resolveConfirmFailure`, for cancel\n * and billing approve re-fetch the payment by orderId and compare against your ledger. Note the\n * two CONCURRENCY codes are special among the `true` cases: the *original* request may still be\n * running, so a lookup that finds nothing (`NOT_FOUND_PAYMENT`) does **not** prove it never\n * happened — replay the same key after a delay instead of minting a new attempt.\n */\ndeclare function mustQueryOutcomeBeforeRetry(failure: TossApiFailure | TransportFailure): boolean;",
          "sourceDocumentation": "`true` when the caller must look the payment/billing outcome up before retrying or failing\nthe operation, because the provider may have completed it:\n\n- every `TransportFailure` (`NETWORK_ERROR` / `TIMEOUT`) — the request may have reached Toss\n  and the response was lost;\n- every `TossApiFailure` whose `code` is in {@link OUTCOME_QUERY_FIRST_ERROR_CODES}.\n\nDecided by `source` and `code` only — never by HTTP status (`PROVIDER_ERROR` is a 400 that\nbelongs here; `REFUND_REJECTED` is a 400 that does not). `false` means the error is a\ndefinitive refusal as far as the library can tell; unregistered codes return `false`.\n\nThe lookup itself stays with the caller: for confirm use `resolveConfirmFailure`, for cancel\nand billing approve re-fetch the payment by orderId and compare against your ledger. Note the\ntwo CONCURRENCY codes are special among the `true` cases: the *original* request may still be\nrunning, so a lookup that finds nothing (`NOT_FOUND_PAYMENT`) does **not** prove it never\nhappened — replay the same key after a delay instead of minting a new attempt."
        },
        {
          "name": "observeRefundPaymentState",
          "slug": "observe-refund-payment-state",
          "kind": "function",
          "declaration": "/** 민감한 취소 사유·환불계좌 없이 quote 결속에 필요한 Payment 관측값을 만든다. */\ndeclare function observeRefundPaymentState(payment: Payment): RefundObservedPaymentState;",
          "sourceDocumentation": "민감한 취소 사유·환불계좌 없이 quote 결속에 필요한 Payment 관측값을 만든다."
        },
        {
          "name": "ok",
          "slug": "ok",
          "kind": "function",
          "declaration": "declare function ok<T>(value: T): Ok<T>;"
        },
        {
          "name": "Ok",
          "slug": "ok--interface",
          "kind": "interface",
          "declaration": "interface Ok<out T> {\n    readonly ok: true;\n    readonly value: T;\n}"
        },
        {
          "name": "orderId",
          "slug": "order-id",
          "kind": "function",
          "declaration": "declare function orderId(raw: string): Result<OrderId, InvalidInput<'orderId'>>;"
        },
        {
          "name": "OrderId",
          "slug": "order-id--type",
          "kind": "type",
          "declaration": "/**\n * 문자열 도메인 타입 — 스마트 생성자.\n * 검증 통과가 브랜드 획득의 유일한 경로다 (`as` 없이는 제조 불가).\n */\n/**\n * 주문 ID — 6–64자, `^[A-Za-z0-9_-]+$`.\n *\n * `'='`를 거부하는 근거: SDK 문서는 `=`를 포함한 집합을 허용하지만\n * 레퍼런스/빌링 승인의 orderId 규격은 영숫자와 `-`,`_`만 허용한다.\n * 같은 orderId가 일반 결제와 빌링 승인 양쪽에 쓰일 수 있으므로\n * 보수적 교집합을 채택해 빌링 승인 규격과의 충돌을 회피한다.\n */\ntype OrderId = string & Brand<'OrderId'>;",
          "sourceDocumentation": "주문 ID — 6–64자, `^[A-Za-z0-9_-]+$`.\n\n`'='`를 거부하는 근거: SDK 문서는 `=`를 포함한 집합을 허용하지만\n레퍼런스/빌링 승인의 orderId 규격은 영숫자와 `-`,`_`만 허용한다.\n같은 orderId가 일반 결제와 빌링 승인 양쪽에 쓰일 수 있으므로\n보수적 교집합을 채택해 빌링 승인 규격과의 충돌을 회피한다."
        },
        {
          "name": "orderName",
          "slug": "order-name",
          "kind": "function",
          "declaration": "declare function orderName(raw: string): Result<OrderName, InvalidInput<'orderName'>>;"
        },
        {
          "name": "OrderName",
          "slug": "order-name--type",
          "kind": "type",
          "declaration": "/** 주문명 — 1–100자. */\ntype OrderName = string & Brand<'OrderName'>;",
          "sourceDocumentation": "주문명 — 1–100자."
        },
        {
          "name": "orThrow",
          "slug": "or-throw",
          "kind": "function",
          "declaration": "/**\n * 유일한 throw 탈출구 — **부팅 시 설정 파싱(키 로드) 전용**.\n *\n * 요청 처리 경로에서는 사용하지 말 것: 이 라이브러리의 모든 공개 작업은 Result를\n * 반환하며, 요청 경로의 실패는 판별자 내로잉(`if (!r.ok)`)으로 다뤄야 한다.\n * 메서드가 아닌 자유 함수인 이유: Result 값은 어디서든 plain 객체로 직렬화 안전해야 한다.\n *\n * @param context - 던지는 Error 메시지 앞에 붙는 식별 문맥 (예: 'TOSS_SECRET_KEY')\n * @throws Error - 실패 변형일 때. `cause`에 원본 에러 값을 보존한다.\n */\ndeclare function orThrow<T, E>(r: Result<T, E>, context?: string): T;",
          "sourceDocumentation": "유일한 throw 탈출구 — **부팅 시 설정 파싱(키 로드) 전용**.\n\n요청 처리 경로에서는 사용하지 말 것: 이 라이브러리의 모든 공개 작업은 Result를\n반환하며, 요청 경로의 실패는 판별자 내로잉(`if (!r.ok)`)으로 다뤄야 한다.\n메서드가 아닌 자유 함수인 이유: Result 값은 어디서든 plain 객체로 직렬화 안전해야 한다."
        },
        {
          "name": "OUTCOME_QUERY_FIRST_ERROR_CODES",
          "slug": "outcome-query-first-error-codes",
          "kind": "constant",
          "declaration": "OUTCOME_QUERY_FIRST_ERROR_CODES: readonly string[]",
          "sourceDocumentation": "Toss error codes after which the caller **must look the outcome up** (`getPaymentByOrderId`\n/ `getPayment`) before retrying with a new key or marking the operation failed — the provider\nmay have completed (or be completing) the operation even though the response is an error.\nMarking such an operation FAILED without a lookup is how \"money left, user told it failed\"\nincidents happen.\n\nMembership reasons (classification from `classifyTossErrorCode`):\n\n- `ALREADY_PROCESSED_PAYMENT` (400, STATE, not retryable) — a confirm for this paymentKey was\n  already completed, typically by a refreshed page or a duplicate worker. The outcome\n  *exists*; fetch it and treat it as success rather than failure.\n- `IDEMPOTENT_REQUEST_PROCESSING` (409, CONCURRENCY) — the original request with this key is\n  still in flight. Documented instruction: request again and read the result.\n- `FORBIDDEN_CONSECUTIVE_REQUEST` (403, CONCURRENCY) — a back-to-back request on the same\n  resource was refused; the earlier one may have succeeded.\n- `PROVIDER_ERROR` (400, TRANSIENT) — the upstream institution (card company/bank) failed\n  mid-flight; Toss may hold a partially recorded state. 400 but retryable, which is why the\n  HTTP status must never drive this decision.\n- `FAILED_INTERNAL_SYSTEM_PROCESSING`, `FAILED_PAYMENT_INTERNAL_SYSTEM_PROCESSING`,\n  `COMMON_ERROR` (500, TRANSIENT) — Toss-side processing failed after the request was accepted;\n  whether the ledger moved is unknown.\n- `FAILED_REFUND_PROCESS`, `FAILED_METHOD_HANDLING_CANCEL`, `FAILED_PARTIAL_REFUND`\n  (500, TRANSIENT) — cancel/refund failed on bank latency or method handling; the bank may have\n  executed the refund. Re-fetch the payment and compare `balanceAmount`/`cancels`.\n- `FAILED_BILLING_AUTO_CANCEL` (500, TRANSIENT) — the automatic reversal of a billing charge\n  failed transiently; the charge and/or its reversal may exist.\n- `FAILED_BILL_KEY_AUTH_CREATION` (500, TRANSIENT) — billing-key issuance failed mid-way.\n  Toss has no billing-key lookup API, so the \"lookup\" here is your own `BillingKeyStore`:\n  check whether a key was already persisted for the customer before issuing again.\n\nInvariant kept by this table: every code the library marks `retryable: true` is in this set,\nbecause `retryable` means \"worth retrying with a **new** key after judgment\" (README §5) and\nthat judgment is exactly an outcome lookup. The unit suite checks it against\n`CLASSIFIED_TOSS_ERROR_CODES` (the code table's own keys), so adding a retryable code to the\ntable without adding it here fails CI. Deliberately **excluded**:\n`NOT_MATCHES_REFUNDABLE_AMOUNT` (measured: the cancel was not executed — re-fetch to recompute\nthe amount, but there is no outcome uncertainty), every REJECTED/AUTH/REQUEST/AMOUNT/DEADLINE\ncode (definitive refusals), and unregistered codes (the library cannot vouch for them; apply\nyour own policy for unknown 5xx responses)."
        },
        {
          "name": "parseApiClientKey",
          "slug": "parse-api-client-key",
          "kind": "function",
          "declaration": "declare function parseApiClientKey(raw: string): Result<ApiClientKey<'test'> | ApiClientKey<'live'>, KeyParseError>;"
        },
        {
          "name": "ParsedRefundQuote",
          "slug": "parsed-refund-quote",
          "kind": "type",
          "declaration": "/** JSON 구조·산술 검증은 통과했지만 활성 policy로 재계산되기 전인 비실행 데이터. */\ntype ParsedRefundQuote = Omit<RefundQuote, keyof Brand<\"RefundQuote\">>;",
          "sourceDocumentation": "JSON 구조·산술 검증은 통과했지만 활성 policy로 재계산되기 전인 비실행 데이터."
        },
        {
          "name": "parsePaymentStateSnapshot",
          "slug": "parse-payment-state-snapshot",
          "kind": "function",
          "declaration": "/**\n * Validates an untrusted value (a stored/transported\n * {@link SerializedPaymentStateSnapshot}) back into a branded {@link PaymentStateSnapshot}.\n *\n * Structure is checked exhaustively — `schemaVersion: 1`, every field's type, every literal\n * against its closed union (status, lifecycle, amountState, cancelStatus, issue kinds and\n * their per-kind fields) — and `paymentKey`/`orderId` are re-branded through the existing\n * {@link paymentKey}/{@link orderId} smart constructors, keeping validation-as-the-only-path\n * to a brand intact. The first failing location is reported in `error.path`.\n *\n * Two hardening rules beyond the per-field checks:\n *\n * - **Single read.** Every own enumerable property of the untrusted value is read exactly\n *   once (a one-shot shallow copy per level) before validation, so the value that was\n *   type-checked is the value placed in the branded result — an accessor property cannot\n *   return a valid value to the check and a different one to the constructor. Inherited\n *   (prototype-supplied) properties are ignored.\n * - **Pinned arithmetic.** `canceledAmount` must equal `totalAmount - balanceAmount`\n *   whenever both amounts are safe integers — the one derivation `schemaVersion: 1` pins\n *   that {@link compareLedgerRefund}'s verdict hangs on. Snapshots whose amounts already\n *   carry `invalid-amount` issues are left to the comparison's indeterminate gate instead.\n *\n * Otherwise this is a *shape* gate, not a re-summarization: the remaining derived fields\n * (`lifecycle`, `amountState`, `isCancelable`, `consistencyIssues`, …) are trusted as data\n * produced by an earlier {@link summarizePaymentState} and are not re-derived here.\n */\ndeclare function parsePaymentStateSnapshot(value: unknown): Result<PaymentStateSnapshot, InvalidPaymentStateSnapshot>;",
          "sourceDocumentation": "Validates an untrusted value (a stored/transported\n{@link SerializedPaymentStateSnapshot}) back into a branded {@link PaymentStateSnapshot}.\n\nStructure is checked exhaustively — `schemaVersion: 1`, every field's type, every literal\nagainst its closed union (status, lifecycle, amountState, cancelStatus, issue kinds and\ntheir per-kind fields) — and `paymentKey`/`orderId` are re-branded through the existing\n{@link paymentKey }/{@link orderId } smart constructors, keeping validation-as-the-only-path\nto a brand intact. The first failing location is reported in `error.path`.\n\nTwo hardening rules beyond the per-field checks:\n\n- **Single read.** Every own enumerable property of the untrusted value is read exactly\n  once (a one-shot shallow copy per level) before validation, so the value that was\n  type-checked is the value placed in the branded result — an accessor property cannot\n  return a valid value to the check and a different one to the constructor. Inherited\n  (prototype-supplied) properties are ignored.\n- **Pinned arithmetic.** `canceledAmount` must equal `totalAmount - balanceAmount`\n  whenever both amounts are safe integers — the one derivation `schemaVersion: 1` pins\n  that {@link compareLedgerRefund}'s verdict hangs on. Snapshots whose amounts already\n  carry `invalid-amount` issues are left to the comparison's indeterminate gate instead.\n\nOtherwise this is a *shape* gate, not a re-summarization: the remaining derived fields\n(`lifecycle`, `amountState`, `isCancelable`, `consistencyIssues`, …) are trusted as data\nproduced by an earlier {@link summarizePaymentState} and are not re-derived here."
        },
        {
          "name": "parseRefundQuote",
          "slug": "parse-refund-quote",
          "kind": "function",
          "declaration": "/**\n * DB/메시지의 JSON quote를 구조·공통 산술 기준으로 파싱한다.\n * 반환값은 실행할 수 없다. 반드시 활성 policy.restoreQuote(stored, input)로 재계산해야 한다.\n */\ndeclare function parseRefundQuote(input: unknown): Result<ParsedRefundQuote, RefundQuoteParseError>;",
          "sourceDocumentation": "DB/메시지의 JSON quote를 구조·공통 산술 기준으로 파싱한다.\n반환값은 실행할 수 없다. 반드시 활성 policy.restoreQuote(stored, input)로 재계산해야 한다."
        },
        {
          "name": "parseWidgetClientKey",
          "slug": "parse-widget-client-key",
          "kind": "function",
          "declaration": "declare function parseWidgetClientKey(raw: string): Result<WidgetClientKey<'test'> | WidgetClientKey<'live'>, KeyParseError>;"
        },
        {
          "name": "Payment",
          "slug": "payment",
          "kind": "type",
          "declaration": "type Payment = CardPayment | VirtualAccountPayment | EasyPayPayment | TransferPayment | MobilePhonePayment | GiftCertificatePayment | PendingMethodPayment;"
        },
        {
          "name": "PaymentAmountState",
          "slug": "payment-amount-state",
          "kind": "type",
          "declaration": "/** 현재 금액만으로 본 취소 정도. full 판정은 기존 {@link isFullyCanceled} 계약을 따른다. */\ntype PaymentAmountState = \"none\" | \"partial\" | \"full\";",
          "sourceDocumentation": "현재 금액만으로 본 취소 정도. full 판정은 기존 {@link isFullyCanceled} 계약을 따른다."
        },
        {
          "name": "PaymentBase",
          "slug": "payment-base",
          "kind": "interface",
          "declaration": "interface PaymentBase {\n    /** API 버전 — CalVer 날짜 문자열. */\n    readonly version: string;\n    readonly paymentKey: PaymentKey;\n    readonly type: 'NORMAL' | 'BILLING' | 'BRANDPAY';\n    readonly orderId: OrderId;\n    readonly orderName: string;\n    readonly mId: string;\n    readonly currency: 'KRW' | 'USD' | 'JPY';\n    readonly totalAmount: number;\n    /** '취소할 수 있는 금액(잔고)' — 완전 취소 판정의 유일한 근거 (status 아님 — Phase 0 실측). */\n    readonly balanceAmount: number;\n    readonly status: PaymentStatus;\n    readonly requestedAt: string;\n    readonly approvedAt: string | null;\n    readonly useEscrow: boolean;\n    readonly lastTransactionKey: string | null;\n    readonly suppliedAmount: number;\n    readonly vat: number;\n    /** 문화비(도서·공연비 등) 지출 여부 — 리서치 문서 Payment 필드 목록에 포함 확인. */\n    readonly cultureExpense: boolean;\n    readonly taxFreeAmount: number;\n    readonly taxExemptionAmount: number;\n    readonly cancels: readonly CancelTransaction[] | null;\n    readonly isPartialCancelable: boolean;\n    /** 가상계좌 웹훅(DEPOSIT_CALLBACK) 검증용 — 승인 시 저장 필수. */\n    readonly secret: string | null;\n    readonly metadata: Readonly<Record<string, string>> | null;\n    readonly receipt: {\n        readonly url: string;\n    } | null;\n    readonly checkout: {\n        readonly url: string;\n    } | null;\n    readonly country: string;\n    readonly failure: {\n        readonly code: string;\n        readonly message: string;\n    } | null;\n    /** 응답 원문 — 타입에 없는 필드(cashReceipt/cashReceipts/discount 등)의 탈출구. */\n    readonly raw: unknown;\n}"
        },
        {
          "name": "PaymentCancelChanges",
          "slug": "payment-cancel-changes",
          "kind": "interface",
          "declaration": "interface PaymentCancelChanges {\n    readonly added: readonly PaymentCancelTransactionSnapshot[];\n    readonly updated: readonly PaymentCancelTransactionUpdate[];\n    readonly removed: readonly PaymentCancelTransactionSnapshot[];\n}"
        },
        {
          "name": "PaymentCancelTransactionSnapshot",
          "slug": "payment-cancel-transaction-snapshot",
          "kind": "interface",
          "declaration": "/** 상태 관리에 필요한 최소 취소 트랜잭션. 사유·영수증 및 Payment.raw는 의도적으로 제외한다. */\ninterface PaymentCancelTransactionSnapshot {\n    readonly transactionKey: string;\n    readonly cancelAmount: number;\n    readonly refundableAmount: number;\n    readonly canceledAt: string;\n    readonly cancelStatus: CancelTransaction[\"cancelStatus\"];\n    readonly cancelRequestId: string | null;\n}",
          "sourceDocumentation": "상태 관리에 필요한 최소 취소 트랜잭션. 사유·영수증 및 Payment.raw는 의도적으로 제외한다."
        },
        {
          "name": "PaymentCancelTransactionUpdate",
          "slug": "payment-cancel-transaction-update",
          "kind": "interface",
          "declaration": "interface PaymentCancelTransactionUpdate {\n    readonly previous: PaymentCancelTransactionSnapshot;\n    readonly next: PaymentCancelTransactionSnapshot;\n}"
        },
        {
          "name": "paymentKey",
          "slug": "payment-key",
          "kind": "function",
          "declaration": "declare function paymentKey(raw: string): Result<PaymentKey, InvalidInput<'paymentKey'>>;"
        },
        {
          "name": "PaymentKey",
          "slug": "payment-key--type",
          "kind": "type",
          "declaration": "/** 결제 키 — 1–200자 (문서: 최대 200자). */\ntype PaymentKey = string & Brand<'PaymentKey'>;",
          "sourceDocumentation": "결제 키 — 1–200자 (문서: 최대 200자)."
        },
        {
          "name": "PaymentLifecycle",
          "slug": "payment-lifecycle",
          "kind": "type",
          "declaration": "/**\n * 결제 상태 스냅샷과 변경 요약.\n *\n * Payment 상태는 단방향 상태 머신이 아니다. 특히 입금 오류로\n * DONE -> WAITING_FOR_DEPOSIT 역전이가 가능하므로, 이 모듈은 전이를 허용/거부하지 않고\n * 두 관측값의 차이만 기술한다. 영속화 순서와 동시성 제어는 호출자의 저장소가 맡는다.\n */\ntype PaymentLifecycle = \"pending\" | \"awaiting-deposit\" | \"paid\" | \"cancellation-pending\" | \"partially-canceled\" | \"fully-canceled\" | \"failed\" | \"expired\" | \"inconsistent\";",
          "sourceDocumentation": "결제 상태 스냅샷과 변경 요약.\n\nPayment 상태는 단방향 상태 머신이 아니다. 특히 입금 오류로\nDONE -> WAITING_FOR_DEPOSIT 역전이가 가능하므로, 이 모듈은 전이를 허용/거부하지 않고\n두 관측값의 차이만 기술한다. 영속화 순서와 동시성 제어는 호출자의 저장소가 맡는다."
        },
        {
          "name": "PaymentMethod",
          "slug": "payment-method",
          "kind": "type",
          "declaration": "/** 응답 원문 그대로의 한글 리터럴 — 영문 enum을 지어내면 런타임 전부 불일치한다. */\ntype PaymentMethod = '카드' | '가상계좌' | '간편결제' | '휴대폰' | '계좌이체' | '문화상품권' | '도서문화상품권' | '게임문화상품권';",
          "sourceDocumentation": "응답 원문 그대로의 한글 리터럴 — 영문 enum을 지어내면 런타임 전부 불일치한다."
        },
        {
          "name": "PaymentStateBalanceChange",
          "slug": "payment-state-balance-change",
          "kind": "interface",
          "declaration": "interface PaymentStateBalanceChange extends PaymentStateValueChange<number> {\n    /** next - previous. 취소가 진행되면 보통 음수다. */\n    readonly delta: number;\n}"
        },
        {
          "name": "PaymentStateConsistencyIssue",
          "slug": "payment-state-consistency-issue",
          "kind": "type",
          "declaration": "type PaymentStateConsistencyIssue = {\n    readonly kind: \"invalid-amount\";\n    readonly field: \"totalAmount\" | \"balanceAmount\";\n    readonly value: number;\n    readonly reason: \"not-safe-integer\" | \"negative\";\n} | {\n    readonly kind: \"balance-exceeds-total\";\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"zero-balance-with-non-canceled-status\";\n    readonly status: Exclude<PaymentStatus, \"CANCELED\" | \"PARTIAL_CANCELED\">;\n} | {\n    readonly kind: \"cancellation-status-without-history\";\n    readonly status: \"CANCELED\" | \"PARTIAL_CANCELED\";\n} | {\n    readonly kind: \"canceled-status-with-balance\";\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"partial-status-without-canceled-amount\";\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"partial-status-without-effective-cancellation\";\n} | {\n    readonly kind: \"paid-status-with-canceled-amount\";\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"full-cancellation-status-mismatch\";\n    readonly status: Exclude<PaymentStatus, \"CANCELED\" | \"PARTIAL_CANCELED\">;\n} | {\n    readonly kind: \"completed-cancel-status-mismatch\";\n    readonly status: Exclude<PaymentStatus, \"CANCELED\" | \"PARTIAL_CANCELED\">;\n} | {\n    readonly kind: \"latest-cancel-balance-mismatch\";\n    readonly transactionKey: string;\n    readonly cancelRefundableAmount: number;\n    readonly paymentBalanceAmount: number;\n} | {\n    readonly kind: \"duplicate-cancel-transaction-key\";\n    readonly transactionKey: string;\n};"
        },
        {
          "name": "PaymentStateDiff",
          "slug": "payment-state-diff",
          "kind": "interface",
          "declaration": "interface PaymentStateDiff {\n    readonly previous: PaymentStateSnapshot;\n    readonly next: PaymentStateSnapshot;\n    readonly changed: boolean;\n    readonly statusChange: PaymentStateValueChange<PaymentStatus> | null;\n    readonly lifecycleChange: PaymentStateValueChange<PaymentLifecycle> | null;\n    readonly balanceAmountChange: PaymentStateBalanceChange | null;\n    readonly lastTransactionKeyChange: PaymentStateValueChange<string | null> | null;\n    readonly amountStateChange: PaymentStateValueChange<PaymentAmountState> | null;\n    readonly pendingCancellationChange: PaymentStateValueChange<boolean> | null;\n    readonly abortedCancellationChange: PaymentStateValueChange<boolean> | null;\n    readonly cancelableChange: PaymentStateValueChange<boolean> | null;\n    readonly partiallyCancelableChange: PaymentStateValueChange<boolean> | null;\n    readonly cancelChanges: PaymentCancelChanges;\n    readonly warnings: readonly PaymentStateDiffWarning[];\n}"
        },
        {
          "name": "PaymentStateDiffWarning",
          "slug": "payment-state-diff-warning",
          "kind": "type",
          "declaration": "type PaymentStateDiffWarning = {\n    /** 환불 취소·입금 오류 등 정상적인 역전이일 수도 있으므로 오류로 차단하지 않는다. */\n    readonly kind: \"balance-increased\";\n    readonly previousBalanceAmount: number;\n    readonly nextBalanceAmount: number;\n    readonly delta: number;\n} | {\n    /** 제공자 응답의 취소 배열에서 이전 transactionKey가 사라졌다. */\n    readonly kind: \"cancel-removed\";\n    readonly transactionKey: string;\n};"
        },
        {
          "name": "PaymentStateIdentityError",
          "slug": "payment-state-identity-error",
          "kind": "interface",
          "declaration": "/** 서로 다른 결제의 스냅샷을 비교하려 한 경우에만 반환되는 오류. */\ninterface PaymentStateIdentityError {\n    readonly source: \"library\";\n    readonly kind: \"payment-state-identity-mismatch\";\n    readonly mismatches: readonly PaymentStateIdentityMismatch[];\n}",
          "sourceDocumentation": "서로 다른 결제의 스냅샷을 비교하려 한 경우에만 반환되는 오류."
        },
        {
          "name": "PaymentStateIdentityMismatch",
          "slug": "payment-state-identity-mismatch",
          "kind": "interface",
          "declaration": "interface PaymentStateIdentityMismatch {\n    readonly field: \"paymentKey\" | \"orderId\";\n    readonly previous: string;\n    readonly next: string;\n}"
        },
        {
          "name": "PaymentStateInput",
          "slug": "payment-state-input",
          "kind": "type",
          "declaration": "/**\n * The minimal structural input {@link summarizePaymentState} actually reads — exactly these\n * eight fields, nothing else (verified against the implementation: the lifecycle, amount and\n * consistency judgments consume `status`/`totalAmount`/`balanceAmount`/`lastTransactionKey`/\n * `isPartialCancelable`/`cancels`, and the snapshot carries `paymentKey`/`orderId`).\n *\n * A full `Payment` is always assignable — including a fresh inline object literal:\n * {@link summarizePaymentState} is typed `PaymentStateInput | Payment`, and the `Payment`\n * union member exists solely so TypeScript's excess-property check accepts literals that\n * spell out non-Pick `Payment` fields (`version`, `requestedAt`, …). Existing call sites\n * compile unchanged. The point of the reduced shape is the opposite direction: an app-owned\n * payment view that stripped `raw`/`secret`/card details can still produce a snapshot,\n * **provided its eight fields are faithful copies of a real Payment response**. Do not fabricate `lastTransactionKey`,\n * `isPartialCancelable` or `cancels` to satisfy the type — the consistency and\n * cancelability judgments would then describe your fabrication, not the provider state.\n */\ntype PaymentStateInput = Pick<Payment, \"paymentKey\" | \"orderId\" | \"status\" | \"totalAmount\" | \"balanceAmount\" | \"lastTransactionKey\" | \"isPartialCancelable\" | \"cancels\">;",
          "sourceDocumentation": "The minimal structural input {@link summarizePaymentState} actually reads — exactly these\neight fields, nothing else (verified against the implementation: the lifecycle, amount and\nconsistency judgments consume `status`/`totalAmount`/`balanceAmount`/`lastTransactionKey`/\n`isPartialCancelable`/`cancels`, and the snapshot carries `paymentKey`/`orderId`).\n\nA full `Payment` is always assignable — including a fresh inline object literal:\n{@link summarizePaymentState} is typed `PaymentStateInput | Payment`, and the `Payment`\nunion member exists solely so TypeScript's excess-property check accepts literals that\nspell out non-Pick `Payment` fields (`version`, `requestedAt`, …). Existing call sites\ncompile unchanged. The point of the reduced shape is the opposite direction: an app-owned\npayment view that stripped `raw`/`secret`/card details can still produce a snapshot,\n**provided its eight fields are faithful copies of a real Payment response**. Do not fabricate `lastTransactionKey`,\n`isPartialCancelable` or `cancels` to satisfy the type — the consistency and\ncancelability judgments would then describe your fabrication, not the provider state."
        },
        {
          "name": "PaymentStateSnapshot",
          "slug": "payment-state-snapshot",
          "kind": "interface",
          "declaration": "/**\n * 저장·로그하기 안전한 결제 상태 요약.\n *\n * Payment.secret, Payment.raw, 카드/계좌 상세 및 취소 사유는 포함하지 않는다.\n */\ninterface PaymentStateSnapshot {\n    /** 영속 스키마 진화를 위한 고정 버전. */\n    readonly schemaVersion: 1;\n    readonly paymentKey: PaymentKey;\n    readonly orderId: OrderId;\n    readonly status: PaymentStatus;\n    readonly lifecycle: PaymentLifecycle;\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n    readonly lastTransactionKey: string | null;\n    /** totalAmount - balanceAmount. 비정상 응답에서는 음수일 수 있으며 consistencyIssues에 남는다. */\n    readonly canceledAmount: number;\n    readonly amountState: PaymentAmountState;\n    readonly hasPendingCancellation: boolean;\n    readonly hasAbortedCancellation: boolean;\n    /** 현재 스냅샷에서 새 취소 요청을 시도할 수 있는지에 대한 보수적 힌트. */\n    readonly isCancelable: boolean;\n    /** 입금 전·비동기 취소 진행 중을 제외하고 부분취소가 가능한지에 대한 보수적 힌트. */\n    readonly isPartiallyCancelable: boolean;\n    readonly cancels: readonly PaymentCancelTransactionSnapshot[];\n    readonly consistencyIssues: readonly PaymentStateConsistencyIssue[];\n}",
          "sourceDocumentation": "저장·로그하기 안전한 결제 상태 요약.\n\nPayment.secret, Payment.raw, 카드/계좌 상세 및 취소 사유는 포함하지 않는다."
        },
        {
          "name": "PaymentStateSnapshotParseReason",
          "slug": "payment-state-snapshot-parse-reason",
          "kind": "type",
          "declaration": "/**\n * Why {@link parsePaymentStateSnapshot} rejected a value: the four string-constraint reasons\n * come from re-branding `paymentKey`/`orderId` through the existing id parsers;\n * `'malformed'` covers every structural failure (wrong type, missing field, unknown literal,\n * unsupported `schemaVersion`). The offending location is in\n * {@link InvalidPaymentStateSnapshot.path}.\n */\ntype PaymentStateSnapshotParseReason = InvalidInput<\"paymentStateSnapshot\">[\"reason\"] | \"malformed\";",
          "sourceDocumentation": "Why {@link parsePaymentStateSnapshot} rejected a value: the four string-constraint reasons\ncome from re-branding `paymentKey`/`orderId` through the existing id parsers;\n`'malformed'` covers every structural failure (wrong type, missing field, unknown literal,\nunsupported `schemaVersion`). The offending location is in\n{@link InvalidPaymentStateSnapshot.path}."
        },
        {
          "name": "PaymentStateValueChange",
          "slug": "payment-state-value-change",
          "kind": "interface",
          "declaration": "interface PaymentStateValueChange<T> {\n    readonly previous: T;\n    readonly next: T;\n}"
        },
        {
          "name": "PaymentStatus",
          "slug": "payment-status",
          "kind": "type",
          "declaration": "/**\n * Payment 객체 — method 한글 리터럴 판별 유니언 + `raw: unknown` 탈출구.\n * 필드 목록의 근거: docs/research/toss-payments-v2.md \"Payment 객체 주요 필드\".\n */\ntype PaymentStatus = 'READY' | 'IN_PROGRESS' | 'WAITING_FOR_DEPOSIT' | 'DONE' | 'CANCELED' | 'PARTIAL_CANCELED' | 'ABORTED' | 'EXPIRED';",
          "sourceDocumentation": "Payment 객체 — method 한글 리터럴 판별 유니언 + `raw: unknown` 탈출구.\n필드 목록의 근거: docs/research/toss-payments-v2.md \"Payment 객체 주요 필드\"."
        },
        {
          "name": "PendingMethodPayment",
          "slug": "pending-method-payment",
          "kind": "interface",
          "declaration": "/** 승인 전 결제 — method nullable. status는 전체 유니언 유지(협착은 미검증 불변식). */\ninterface PendingMethodPayment extends PaymentBase {\n    readonly method: null;\n}",
          "sourceDocumentation": "승인 전 결제 — method nullable. status는 전체 유니언 유지(협착은 미검증 불변식)."
        },
        {
          "name": "PercentageRefundPolicyConfig",
          "slug": "percentage-refund-policy-config",
          "kind": "interface",
          "declaration": "interface PercentageRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"percentage\";\n    /** 0..10,000 정수. */\n    readonly rateBps: number;\n    readonly rounding: RefundRoundingMode;\n    readonly reason?: string;\n}"
        },
        {
          "name": "REFUND_RATE_SCALE",
          "slug": "refund-rate-scale",
          "kind": "constant",
          "declaration": "REFUND_RATE_SCALE: 10000",
          "sourceDocumentation": "100% = 10,000 basis points. 부동소수 퍼센트를 공개 계약으로 쓰지 않는다."
        },
        {
          "name": "REFUND_TIME",
          "slug": "refund-time",
          "kind": "constant",
          "declaration": "REFUND_TIME: Readonly<{\n    minute: 60000;\n    hour: number;\n    day: number;\n}>",
          "sourceDocumentation": "경과시간 정책을 읽기 좋게 정의하기 위한 고정 길이 상수. 달력 일수 계산에는 쓰지 않는다."
        },
        {
          "name": "RefundCalculation",
          "slug": "refund-calculation",
          "kind": "type",
          "declaration": "type RefundCalculation = {\n    readonly kind: \"full\";\n} | {\n    readonly kind: \"percentage\";\n    readonly rateBps: number;\n} | {\n    readonly kind: \"elapsed-time-rate\";\n    readonly elapsedMs: number;\n    /** fallback이면 null. */\n    readonly bracketIndex: number | null;\n    readonly rateBps: number;\n} | {\n    readonly kind: \"remaining-units\";\n    readonly totalUnits: number;\n    readonly remainingUnits: number;\n    readonly rateBps: number;\n} | {\n    readonly kind: \"custom\";\n    readonly entitlementKind: \"rate\" | \"amount\";\n    readonly details: Readonly<Record<string, RefundCalculationDetail>>;\n};"
        },
        {
          "name": "RefundCalculationDetail",
          "slug": "refund-calculation-detail",
          "kind": "type",
          "declaration": "type RefundCalculationDetail = string | number | boolean | null;"
        },
        {
          "name": "RefundCalendarError",
          "slug": "refund-calendar-error",
          "kind": "type",
          "declaration": "type RefundCalendarError = {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-calendar\";\n    readonly field: string;\n    readonly reason: string;\n};"
        },
        {
          "name": "RefundEntitlement",
          "slug": "refund-entitlement",
          "kind": "type",
          "declaration": "/** custom 정책이 반환하는 누적 환불 entitlement. 현재 실행액은 기존 환불을 차감해 계산한다. */\ntype RefundEntitlement = {\n    readonly kind: \"rate\";\n    readonly rateBps: number;\n    readonly reason?: string;\n    readonly details?: Readonly<Record<string, RefundCalculationDetail>>;\n} | {\n    readonly kind: \"amount\";\n    /** 이번 환불액이 아니라 정책상 누적 환불 가능 총액. */\n    readonly amount: number;\n    readonly reason?: string;\n    readonly details?: Readonly<Record<string, RefundCalculationDetail>>;\n};",
          "sourceDocumentation": "custom 정책이 반환하는 누적 환불 entitlement. 현재 실행액은 기존 환불을 차감해 계산한다."
        },
        {
          "name": "RefundObservedCancelState",
          "slug": "refund-observed-cancel-state",
          "kind": "interface",
          "declaration": "interface RefundObservedCancelState {\n    readonly transactionKey: string;\n    readonly cancelAmount: number;\n    readonly refundableAmount: number;\n    readonly canceledAt: string;\n    readonly cancelStatus: CancelTransaction[\"cancelStatus\"];\n}"
        },
        {
          "name": "RefundObservedPaymentState",
          "slug": "refund-observed-payment-state",
          "kind": "interface",
          "declaration": "/** quote를 만든 Payment와 실행 직전 재조회 Payment가 같은 관측 상태인지 대조하는 값. */\ninterface RefundObservedPaymentState {\n    readonly status: PaymentStatus;\n    readonly method: Payment[\"method\"];\n    readonly lastTransactionKey: string | null;\n    readonly isPartialCancelable: boolean;\n    readonly cancels: readonly RefundObservedCancelState[];\n}",
          "sourceDocumentation": "quote를 만든 Payment와 실행 직전 재조회 Payment가 같은 관측 상태인지 대조하는 값."
        },
        {
          "name": "RefundPolicy",
          "slug": "refund-policy",
          "kind": "interface",
          "declaration": "interface RefundPolicy<Input extends RefundQuoteInput = RefundQuoteInput> extends Brand<\"RefundPolicy\"> {\n    readonly id: string;\n    readonly version: string;\n    readonly kind: RefundPolicyKind;\n    quote(input: Input): Result<RefundQuote, RefundQuoteError>;\n    /** 저장 JSON을 활성 policy와 동일 입력으로 재계산해 실행 가능한 quote로 복원한다. */\n    restoreQuote(stored: unknown, input: Input): Result<RefundQuote, RefundQuoteRestoreError>;\n}"
        },
        {
          "name": "RefundPolicyConfigError",
          "slug": "refund-policy-config-error",
          "kind": "type",
          "declaration": "type RefundPolicyConfigError = {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-policy\";\n    readonly policyId: string;\n    readonly field: string;\n    readonly reason: string;\n};"
        },
        {
          "name": "RefundPolicyIdentity",
          "slug": "refund-policy-identity",
          "kind": "interface",
          "declaration": "interface RefundPolicyIdentity {\n    /** CS·원장에 남길 안정적인 정책 ID. */\n    readonly id: string;\n    /** 정책 변경 뒤에도 과거 계산을 재현하기 위한 버전. */\n    readonly version: string;\n    /** quote의 최대 수명. 생략하면 {@link DEFAULT_REFUND_QUOTE_TTL_MS}. */\n    readonly quoteTtlMs?: number;\n}"
        },
        {
          "name": "RefundPolicyKind",
          "slug": "refund-policy-kind",
          "kind": "type",
          "declaration": "type RefundPolicyKind = BuiltInRefundPolicyConfig[\"kind\"] | \"custom\";"
        },
        {
          "name": "RefundQuote",
          "slug": "refund-quote",
          "kind": "interface",
          "declaration": "/**\n * 정책 계산 결과. plain serializable 값이며 실제 cancel 실행 전 server의 prepareRefund를\n * 통과해야 한다. quote.amount는 항상 이번에 추가로 실행할 금액이다.\n */\ninterface RefundQuote extends Brand<\"RefundQuote\"> {\n    readonly kind: \"none\" | \"full\" | \"partial\";\n    readonly policy: {\n        readonly id: string;\n        readonly version: string;\n        readonly kind: RefundPolicyKind;\n    };\n    readonly paymentKey: PaymentKey;\n    readonly orderId: OrderId;\n    readonly currency: Payment[\"currency\"];\n    readonly evaluatedAt: string;\n    /** exclusive. 실행 시각이 이 값 이상이면 반드시 재조회·재견적한다. */\n    readonly validUntil: string;\n    readonly observedPaymentState: RefundObservedPaymentState;\n    /** quote 생성 때 관찰한 Toss Payment.balanceAmount. */\n    readonly observedBalanceAmount: number;\n    /** 프로젝트 장부가 기대한 잔액. 생성 성공 시 observed와 같다. */\n    readonly expectedBalanceAmount: number;\n    readonly basisAmount: number;\n    readonly alreadyRefundedAmount: number;\n    /** 정책상 누적 환불 가능 총액(반올림 후). */\n    readonly entitlementAmount: number;\n    /** entitlement에서 기존 확정 환불을 뺀 이번 실행액. */\n    readonly amount: number;\n    readonly balanceAfterRefund: number;\n    /** 과거 확정 환불이 현재 정책 entitlement를 이미 넘은 금액. */\n    readonly overRefundedAmount: number;\n    /** 금액 직접 산출 정책이면 null. */\n    readonly rateBps: number | null;\n    readonly rounding: RefundRoundingMode;\n    readonly calculation: RefundCalculation;\n    readonly reason: string | null;\n}",
          "sourceDocumentation": "정책 계산 결과. plain serializable 값이며 실제 cancel 실행 전 server의 prepareRefund를\n통과해야 한다. quote.amount는 항상 이번에 추가로 실행할 금액이다."
        },
        {
          "name": "RefundQuoteError",
          "slug": "refund-quote-error",
          "kind": "type",
          "declaration": "type RefundQuoteError = {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-input\";\n    readonly policyId: string;\n    readonly field: string;\n    readonly reason: string;\n} | {\n    readonly source: \"library\";\n    readonly kind: \"expected-refund-balance-mismatch\";\n    readonly policyId: string;\n    readonly expected: number;\n    readonly actual: number;\n} | {\n    readonly source: \"library\";\n    readonly kind: \"calculated-refund-exceeds-balance\";\n    readonly policyId: string;\n    readonly calculatedAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly source: \"library\";\n    readonly kind: \"custom-refund-calculation-failed\";\n    readonly policyId: string;\n    readonly cause: unknown;\n};"
        },
        {
          "name": "RefundQuoteInput",
          "slug": "refund-quote-input",
          "kind": "interface",
          "declaration": "/**\n * 모든 정책 quote의 공통 입력.\n *\n * basisAmount/alreadyRefundedAmount/expectedBalanceAmount는 프로젝트 장부가 제공한다.\n * 라이브러리는 expectedBalanceAmount를 최신 Payment.balanceAmount와 대조하므로 외부\n * 부분취소나 장부 drift가 있으면 계산 전에 멈춘다.\n */\ninterface RefundQuoteInput {\n    readonly payment: Payment;\n    /** 정책 비율을 곱할 프로젝트 장부 기준 금액. */\n    readonly basisAmount: number;\n    /** 프로젝트 장부에 provider 완료로 확정된 누적 환불액. */\n    readonly alreadyRefundedAmount: number;\n    /** 프로젝트 장부가 기대하는 현재 Toss 환불 가능 잔액. */\n    readonly expectedBalanceAmount: number;\n    /** 정책을 평가한 시각. 테스트 가능한 명시 입력이며 quote에 ISO로 남는다. */\n    readonly evaluatedAt: Date;\n    /**\n     * 프로젝트 상태가 반드시 다시 평가되어야 하는 더 이른 경계(선택).\n     * 예: 잔여 달력 일수는 다음 현지 자정. 정책 TTL/시간 구간 경계와의 최솟값이 적용된다.\n     */\n    readonly validUntil?: Date;\n}",
          "sourceDocumentation": "모든 정책 quote의 공통 입력.\n\nbasisAmount/alreadyRefundedAmount/expectedBalanceAmount는 프로젝트 장부가 제공한다.\n라이브러리는 expectedBalanceAmount를 최신 Payment.balanceAmount와 대조하므로 외부\n부분취소나 장부 drift가 있으면 계산 전에 멈춘다."
        },
        {
          "name": "RefundQuoteParseError",
          "slug": "refund-quote-parse-error",
          "kind": "interface",
          "declaration": "interface RefundQuoteParseError {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-quote\";\n    readonly field: string;\n    readonly reason: string;\n}"
        },
        {
          "name": "RefundQuotePolicyMismatchError",
          "slug": "refund-quote-policy-mismatch-error",
          "kind": "interface",
          "declaration": "interface RefundQuotePolicyMismatchError {\n    readonly source: \"library\";\n    readonly kind: \"refund-quote-policy-mismatch\";\n    readonly policyId: string;\n    readonly reason: \"stored-quote-does-not-match-recalculation\";\n}"
        },
        {
          "name": "RefundQuoteRestoreError",
          "slug": "refund-quote-restore-error",
          "kind": "type",
          "declaration": "type RefundQuoteRestoreError = RefundQuoteParseError | RefundQuoteError | RefundQuotePolicyMismatchError;"
        },
        {
          "name": "RefundRoundingMode",
          "slug": "refund-rounding-mode",
          "kind": "type",
          "declaration": "type RefundRoundingMode = \"floor\" | \"ceil\" | \"half-up\";"
        },
        {
          "name": "remainingCalendarDays",
          "slug": "remaining-calendar-days",
          "kind": "function",
          "declaration": "/**\n * IANA 시간대의 달력 일수로 totalUnits/remainingUnits를 만든다.\n * 반환값은 remaining-units 정책 quote 입력에 그대로 펼칠 수 있다.\n */\ndeclare function remainingCalendarDays(input: RemainingCalendarDaysInput): Result<RemainingCalendarDays, RefundCalendarError>;",
          "sourceDocumentation": "IANA 시간대의 달력 일수로 totalUnits/remainingUnits를 만든다.\n반환값은 remaining-units 정책 quote 입력에 그대로 펼칠 수 있다."
        },
        {
          "name": "RemainingCalendarDays",
          "slug": "remaining-calendar-days--interface",
          "kind": "interface",
          "declaration": "interface RemainingCalendarDays {\n    readonly startsOn: string;\n    readonly endsOnExclusive: string;\n    readonly evaluatedOn: string;\n    readonly timeZone: string;\n    readonly requestDay: \"refundable\" | \"consumed\";\n    /** 다음 현지 달력 날짜가 시작되는 exclusive 재평가 시각(ISO instant). */\n    readonly validUntil: string;\n    readonly totalUnits: number;\n    readonly remainingUnits: number;\n}"
        },
        {
          "name": "RemainingCalendarDaysInput",
          "slug": "remaining-calendar-days-input",
          "kind": "interface",
          "declaration": "interface RemainingCalendarDaysInput {\n    /** YYYY-MM-DD, 서비스 시작일 포함. */\n    readonly startsOn: string;\n    /** YYYY-MM-DD, 서비스 종료일 미포함 — 기간은 [startsOn, endsOnExclusive). */\n    readonly endsOnExclusive: string;\n    readonly evaluatedAt: Date;\n    /** IANA time zone (예: Asia/Seoul). */\n    readonly timeZone: string;\n    /** 요청 당일을 환불 대상에 포함할지 명시한다. */\n    readonly requestDay: \"refundable\" | \"consumed\";\n}"
        },
        {
          "name": "RemainingUnitsRefundPolicyConfig",
          "slug": "remaining-units-refund-policy-config",
          "kind": "interface",
          "declaration": "interface RemainingUnitsRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"remaining-units\";\n    /** 잔여 비율에 추가로 곱할 비율. 기본 10,000(100%). */\n    readonly rateBps?: number;\n    readonly rounding: RefundRoundingMode;\n    readonly reason?: string;\n}"
        },
        {
          "name": "RemainingUnitsRefundQuoteInput",
          "slug": "remaining-units-refund-quote-input",
          "kind": "interface",
          "declaration": "interface RemainingUnitsRefundQuoteInput extends RefundQuoteInput {\n    /** 전체 일수·회차·사용량 단위. 양수 안전한 정수. */\n    readonly totalUnits: number;\n    /** 0..totalUnits 안전한 정수. */\n    readonly remainingUnits: number;\n}"
        },
        {
          "name": "Result",
          "slug": "result",
          "kind": "type",
          "declaration": "/**\n * Result — plain 판별 유니언 + 자유 함수 콤비네이터.\n * 메서드 클래스 금지(직렬화 안전) — 값은 어디서든 plain 객체다.\n */\ntype Result<T, E> = Ok<T> | Err<E>;",
          "sourceDocumentation": "Result — plain 판별 유니언 + 자유 함수 콤비네이터.\n메서드 클래스 금지(직렬화 안전) — 값은 어디서든 plain 객체다."
        },
        {
          "name": "SerializedPaymentStateSnapshot",
          "slug": "serialized-payment-state-snapshot",
          "kind": "interface",
          "declaration": "/**\n * Brand-free, JSON-ready form of {@link PaymentStateSnapshot}.\n *\n * Identical field-for-field, except `paymentKey`/`orderId` are plain `string`s — safe to put\n * in a response DTO, a queue message or a jsonb column without exporting the branded id types\n * across your provider boundary. Every other field is already a JSON primitive, a plain\n * object array, or `null`. A `PaymentStateSnapshot` is assignable to this type (brands are\n * strings underneath); the reverse direction must go through\n * {@link parsePaymentStateSnapshot}.\n *\n * JSON caveat: a snapshot whose `consistencyIssues` include `invalid-amount` with\n * `reason: 'not-safe-integer'` may carry non-finite numbers (`NaN`/`Infinity`), which\n * `JSON.stringify` silently turns into `null` — the later parse then rejects the value\n * honestly instead of resurrecting a fake amount. Check `consistencyIssues` before\n * persisting a snapshot as JSON.\n */\ninterface SerializedPaymentStateSnapshot {\n    readonly schemaVersion: 1;\n    readonly paymentKey: string;\n    readonly orderId: string;\n    readonly status: PaymentStatus;\n    readonly lifecycle: PaymentLifecycle;\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n    readonly lastTransactionKey: string | null;\n    readonly canceledAmount: number;\n    readonly amountState: PaymentAmountState;\n    readonly hasPendingCancellation: boolean;\n    readonly hasAbortedCancellation: boolean;\n    readonly isCancelable: boolean;\n    readonly isPartiallyCancelable: boolean;\n    readonly cancels: readonly PaymentCancelTransactionSnapshot[];\n    readonly consistencyIssues: readonly PaymentStateConsistencyIssue[];\n}",
          "sourceDocumentation": "Brand-free, JSON-ready form of {@link PaymentStateSnapshot}.\n\nIdentical field-for-field, except `paymentKey`/`orderId` are plain `string`s — safe to put\nin a response DTO, a queue message or a jsonb column without exporting the branded id types\nacross your provider boundary. Every other field is already a JSON primitive, a plain\nobject array, or `null`. A `PaymentStateSnapshot` is assignable to this type (brands are\nstrings underneath); the reverse direction must go through\n{@link parsePaymentStateSnapshot}.\n\nJSON caveat: a snapshot whose `consistencyIssues` include `invalid-amount` with\n`reason: 'not-safe-integer'` may carry non-finite numbers (`NaN`/`Infinity`), which\n`JSON.stringify` silently turns into `null` — the later parse then rejects the value\nhonestly instead of resurrecting a fake amount. Check `consistencyIssues` before\npersisting a snapshot as JSON."
        },
        {
          "name": "serializePaymentStateSnapshot",
          "slug": "serialize-payment-state-snapshot",
          "kind": "function",
          "declaration": "/**\n * Strips the id brands off a snapshot for transport/persistence.\n *\n * Pure structural copy — no field is renamed, derived or dropped, so\n * `parsePaymentStateSnapshot(JSON.parse(JSON.stringify(serialized)))` round-trips back to a\n * deep-equal branded snapshot (for JSON-safe snapshots; see the type's JSON caveat). The\n * result shares no object references with the input: mutating it cannot corrupt the\n * original snapshot.\n */\ndeclare function serializePaymentStateSnapshot(snapshot: PaymentStateSnapshot): SerializedPaymentStateSnapshot;",
          "sourceDocumentation": "Strips the id brands off a snapshot for transport/persistence.\n\nPure structural copy — no field is renamed, derived or dropped, so\n`parsePaymentStateSnapshot(JSON.parse(JSON.stringify(serialized)))` round-trips back to a\ndeep-equal branded snapshot (for JSON-safe snapshots; see the type's JSON caveat). The\nresult shares no object references with the input: mutating it cannot corrupt the\noriginal snapshot."
        },
        {
          "name": "summarizePaymentState",
          "slug": "summarize-payment-state",
          "kind": "function",
          "declaration": "/**\n * Payment에서 민감 필드를 제거한 현재 상태 스냅샷을 만든다.\n *\n * Accepts the structural {@link PaymentStateInput} (the eight fields the summary actually\n * reads) rather than a full `Payment`, so app-owned reduced payment views can produce\n * snapshots too. Full `Payment` values remain assignable unchanged — the explicit\n * `| Payment` union member keeps fresh inline full-`Payment` literals free of\n * excess-property errors.\n */\ndeclare function summarizePaymentState(payment: PaymentStateInput | Payment): PaymentStateSnapshot;",
          "sourceDocumentation": "Payment에서 민감 필드를 제거한 현재 상태 스냅샷을 만든다.\n\nAccepts the structural {@link PaymentStateInput} (the eight fields the summary actually\nreads) rather than a full `Payment`, so app-owned reduced payment views can produce\nsnapshots too. Full `Payment` values remain assignable unchanged — the explicit\n`| Payment` union member keeps fresh inline full-`Payment` literals free of\nexcess-property errors."
        },
        {
          "name": "TOSS_IDEMPOTENCY_KEY_TTL_MS",
          "slug": "toss-idempotency-key-ttl-ms",
          "kind": "constant",
          "declaration": "TOSS_IDEMPOTENCY_KEY_TTL_MS: number",
          "sourceDocumentation": "How long Toss binds an `Idempotency-Key` to its first response: **15 days from first use**,\nin milliseconds.\n\nSource: Toss Payments API reference, \"Using the API › Authorization\", `Idempotency-Key`\nsection (docs.tosspayments.com/reference/using-api/authorization): max 300 characters, valid\nfor 15 days from the first use, applies to every POST. What happens to a key reused *after*\nthe window is not explicitly documented; treat it as unsafe — the same key **may be executed\nas a brand-new request** — so a long-lived retry queue must stop resubmitting with the same\nkey once the window has passed and fall back to a lookup.\n\nMeasured against the test environment: 4xx error responses are bound to the key for the same\nwindow, so a key that received a definitive 4xx cannot be \"fixed\" by resending — derive a new\nattempt instead (see {@link deriveIdempotencyKey})."
        },
        {
          "name": "TossApiFailure",
          "slug": "toss-api-failure",
          "kind": "interface",
          "declaration": "/**\n * 에러 모델 — 최상위 판별자 `source`: 'library'(API 미도달 보장) / 'toss'(서버 응답) / 'network'(전송).\n *\n * retryable 판정은 **코드 테이블**로만 한다 — HTTP status 판정 금지.\n * 근거: PROVIDER_ERROR는 400이지만 재시도 가능, REFUND_REJECTED는 400이지만 비재시도.\n */\ninterface TossApiFailure<Code extends string = string> {\n    readonly source: 'toss';\n    /** 토스 응답 {code, message} 원문 무손실 보존. */\n    readonly code: Code;\n    readonly message: string;\n    /** 보존하되 판정에 쓰지 않는다. */\n    readonly httpStatus: number;\n    readonly category: ErrorCategory;\n    /** ⚠ 코드 테이블 판정 — HTTP status 아님. */\n    readonly retryable: boolean;\n    /** x-tosspayments-trace-id */\n    readonly traceId: string | null;\n}",
          "sourceDocumentation": "에러 모델 — 최상위 판별자 `source`: 'library'(API 미도달 보장) / 'toss'(서버 응답) / 'network'(전송).\n\nretryable 판정은 **코드 테이블**로만 한다 — HTTP status 판정 금지.\n근거: PROVIDER_ERROR는 400이지만 재시도 가능, REFUND_REJECTED는 400이지만 비재시도."
        },
        {
          "name": "TransferDetails",
          "slug": "transfer-details",
          "kind": "interface",
          "declaration": "interface TransferDetails {\n    readonly bankCode: string;\n    readonly settlementStatus: string;\n}"
        },
        {
          "name": "TransferPayment",
          "slug": "transfer-payment",
          "kind": "interface",
          "declaration": "interface TransferPayment extends PaymentBase {\n    readonly method: '계좌이체';\n    readonly transfer: TransferDetails;\n}"
        },
        {
          "name": "TransportFailure",
          "slug": "transport-failure",
          "kind": "interface",
          "declaration": "interface TransportFailure {\n    readonly source: 'network';\n    readonly code: 'NETWORK_ERROR' | 'TIMEOUT';\n    readonly retryable: true;\n    readonly cause: unknown;\n}"
        },
        {
          "name": "unwrapOr",
          "slug": "unwrap-or",
          "kind": "function",
          "declaration": "/** 실패 시 대체 값을 반환한다. */\ndeclare function unwrapOr<T, E>(r: Result<T, E>, fallback: T): T;",
          "sourceDocumentation": "실패 시 대체 값을 반환한다."
        },
        {
          "name": "VirtualAccountDetails",
          "slug": "virtual-account-details",
          "kind": "interface",
          "declaration": "interface VirtualAccountDetails {\n    readonly accountNumber: string;\n    readonly accountType: string;\n    readonly bankCode: string;\n    readonly customerName: string;\n    readonly dueDate: string;\n    readonly expired: boolean;\n    readonly settlementStatus: string;\n    readonly refundStatus: string;\n    readonly refundReceiveAccount: unknown | null;\n}"
        },
        {
          "name": "VirtualAccountPayment",
          "slug": "virtual-account-payment",
          "kind": "interface",
          "declaration": "interface VirtualAccountPayment extends PaymentBase {\n    readonly method: '가상계좌';\n    readonly virtualAccount: VirtualAccountDetails;\n    /**\n     * 가상계좌 승인 응답에서만 내려오는 DEPOSIT_CALLBACK 대조값.\n     *\n     * 결제 조회 API는 같은 가상계좌 결제라도 `null`을 반환할 수 있다. 따라서 일반\n     * `Payment` 조회 결과에서 이 값을 복구할 수 있다고 가정하면 안 된다. confirm 응답에서\n     * secret을 보장해야 하는 코드는 server의 `ConfirmedPayment`를 사용한다.\n     */\n    readonly secret: string | null;\n    readonly card: null;\n}"
        },
        {
          "name": "WidgetClientKey",
          "slug": "widget-client-key",
          "kind": "type",
          "declaration": "type WidgetClientKey<E extends Env = Env> = (E extends 'test' ? `test_gck_${string}` : `live_gck_${string}`) & Brand<'WidgetClientKey'> & EnvTag<E>;"
        },
        {
          "name": "widgetCustomerKey",
          "slug": "widget-customer-key",
          "kind": "function",
          "declaration": "declare function widgetCustomerKey(raw: string): Result<WidgetCustomerKey, InvalidInput<'customerKey'>>;"
        },
        {
          "name": "WidgetCustomerKey",
          "slug": "widget-customer-key--type",
          "kind": "type",
          "declaration": "/**\n * CustomerKey ∧ 길이 ≤50 (SDK 문서 한도) — 브라우저 API는 이것만 받는다.\n * 50자(SDK) vs 300자(서버 실측) 문서 모순을 서브타입 분리로 해소한다.\n */\ntype WidgetCustomerKey = CustomerKey & Brand<'WidgetCustomerKey'>;",
          "sourceDocumentation": "CustomerKey ∧ 길이 ≤50 (SDK 문서 한도) — 브라우저 API는 이것만 받는다.\n50자(SDK) vs 300자(서버 실측) 문서 모순을 서브타입 분리로 해소한다."
        },
        {
          "name": "WidgetSecretKey",
          "slug": "widget-secret-key",
          "kind": "type",
          "declaration": "type WidgetSecretKey<E extends Env = Env> = (E extends 'test' ? `test_gsk_${string}` : `live_gsk_${string}`) & Brand<'WidgetSecretKey'> & EnvTag<E>;"
        }
      ]
    },
    {
      "subpath": "./browser",
      "id": "browser",
      "declarationTarget": "./dist/browser.d.cts",
      "symbols": [
        {
          "name": "AgreementWidget",
          "slug": "agreement-widget",
          "kind": "interface",
          "declaration": "interface AgreementWidget {\n    /** {@link RenderedTossWidgets.on}과 동일한 래퍼 수준 구독 해제. */\n    on(event: 'agreementStatusChange', handler: (s: {\n        agreedRequiredTerms: boolean;\n    }) => void): () => void;\n    destroy(): Promise<void>;\n}"
        },
        {
          "name": "Anonymous",
          "slug": "anonymous--type",
          "kind": "type",
          "declaration": "/**\n * 결제위젯 — loadWidgets 3단계 typestate.\n *\n * SDK 문서의 순서 제약(setAmount가 렌더링·결제요청보다 반드시 선행)을\n * \"메서드 부재\"로 강제한다: 각 상태 타입에는 그 시점에 허용된 메서드만 존재한다.\n *   상태 0 TossWidgets            — setAmount만\n *   상태 1 TossWidgetsWithAmount  — render 2종 + setAmount(금액 변경)\n *   상태 2 RenderedTossWidgets    — 여기서만 requestPayment\n */\n/**\n * 비회원 결제 표식 — SDK ANONYMOUS의 브랜딩 재노출.\n * 빌링 인증(requestBillingAuth)의 customer 파라미터에는 대입 불가한 타입이다\n * (빌링은 고유 customerKey 전제 — 연구문서 §빌링).\n */\ntype Anonymous = Brand<'Anonymous'>;",
          "sourceDocumentation": "비회원 결제 표식 — SDK ANONYMOUS의 브랜딩 재노출.\n빌링 인증(requestBillingAuth)의 customer 파라미터에는 대입 불가한 타입이다\n(빌링은 고유 customerKey 전제 — 연구문서 §빌링)."
        },
        {
          "name": "ANONYMOUS",
          "slug": "anonymous",
          "kind": "constant",
          "declaration": "ANONYMOUS: Anonymous",
          "sourceDocumentation": "SDK v2.7.1 `declare const ANONYMOUS = \"@@ANONYMOUS\"`와 동일 값의 재선언 —\nSDK는 optional peer라 정적 import로 값을 재export할 수 없다."
        },
        {
          "name": "BillingAuthRequest",
          "slug": "billing-auth-request",
          "kind": "type",
          "declaration": "/**\n * 빌링 등록 인증창 — SDK payment({customerKey}).requestBillingAuth 래퍼.\n *\n * 빌링은 API 개별 키(ck) 전용 — 위젯 키(gck)는 컴파일 에러.\n * customer는 WidgetCustomerKey만 — ANONYMOUS는 파라미터 타입에서 배제된다\n * (빌링은 고유 customerKey 전제, SDK 문서에 ANONYMOUS 빌링 언급 없음 — 연구문서 §빌링).\n */\n/**\n * SDK v2.7.1 타입 정의로 확정(Phase 0): method 'CARD' | 'TRANSFER' 판별 유니언.\n * selectableCardTypes는 CARD 전용 — TRANSFER 쪽은 `?: never`로 변수 경유 전달까지 차단한다.\n */\ntype BillingAuthRequest = {\n    readonly method: 'CARD';\n    /** origin을 포함한 완전 URL — 성공 시 쿼리로 authKey, customerKey가 붙는다. */\n    readonly successUrl: string;\n    readonly failUrl: string;\n    readonly customerName?: string;\n    readonly customerEmail?: string;\n    readonly windowTarget?: 'self' | 'iframe';\n    /** 결제화면에 노출할 카드 타입 — 입력 순서대로 노출, 첫 항목이 기본 선택. */\n    readonly selectableCardTypes?: readonly ('PERSONAL' | 'CORPORATE')[];\n} | {\n    readonly method: 'TRANSFER';\n    readonly successUrl: string;\n    readonly failUrl: string;\n    readonly customerName?: string;\n    readonly customerEmail?: string;\n    readonly windowTarget?: 'self' | 'iframe';\n    /** 카드 전용 파라미터 (SDK v2.7.1 타입 정합) — TRANSFER에서는 존재 자체가 컴파일 에러. */\n    readonly selectableCardTypes?: never;\n};",
          "sourceDocumentation": "SDK v2.7.1 타입 정의로 확정(Phase 0): method 'CARD' | 'TRANSFER' 판별 유니언.\nselectableCardTypes는 CARD 전용 — TRANSFER 쪽은 `?: never`로 변수 경유 전달까지 차단한다."
        },
        {
          "name": "loadWidgets",
          "slug": "load-widgets",
          "kind": "function",
          "declaration": "/**\n * 위젯 로드 — WidgetClientKey(gck) 전용. ApiSecretKey는 물론 ApiClientKey(ck)도 컴파일 에러다.\n * customer는 2–50자 서브타입(WidgetCustomerKey) 또는 {@link ANONYMOUS}만 —\n * 300자 허용 서버용 CustomerKey는 컴파일 에러.\n */\ndeclare function loadWidgets(clientKey: WidgetClientKey, customer: WidgetCustomerKey | Anonymous): Promise<Result<TossWidgets, WidgetError>>;",
          "sourceDocumentation": "위젯 로드 — WidgetClientKey(gck) 전용. ApiSecretKey는 물론 ApiClientKey(ck)도 컴파일 에러다.\ncustomer는 2–50자 서브타입(WidgetCustomerKey) 또는 {@link ANONYMOUS}만 —\n300자 허용 서버용 CustomerKey는 컴파일 에러."
        },
        {
          "name": "PaymentRequestOutcome",
          "slug": "payment-request-outcome",
          "kind": "type",
          "declaration": "/**\n * 결제 요청 결과 — 사용자 취소는 에러가 아니다.\n * 리다이렉트 모드 고정: 프로미스 모드는 모바일 미지원(문서 근거)이라 제공하지 않는다.\n */\ntype PaymentRequestOutcome = {\n    readonly kind: 'redirecting';\n} | {\n    readonly kind: 'user-canceled';\n    readonly code: 'USER_CANCEL' | 'PAY_PROCESS_CANCELED';\n    readonly message: string;\n};",
          "sourceDocumentation": "결제 요청 결과 — 사용자 취소는 에러가 아니다.\n리다이렉트 모드 고정: 프로미스 모드는 모바일 미지원(문서 근거)이라 제공하지 않는다."
        },
        {
          "name": "RenderedTossWidgets",
          "slug": "rendered-toss-widgets",
          "kind": "interface",
          "declaration": "/** 상태 2: 렌더 완료 — 여기서만 결제 요청이 가능하다. */\ninterface RenderedTossWidgets {\n    requestPayment(request: WidgetPaymentRequest): Promise<Result<PaymentRequestOutcome, SdkError>>;\n    setAmount(amount: WidgetAmount): Promise<Result<RenderedTossWidgets, WidgetError>>;\n    getSelectedPaymentMethod(): Promise<Result<{\n        readonly code: string;\n    }, WidgetError>>;\n    /**\n     * 결제수단 선택 이벤트 구독. 반환 함수로 구독을 해제한다 —\n     * SDK on()은 해제를 제공하지 않으므로 래퍼가 활성 플래그로 전달을 중단한다.\n     */\n    on(event: 'paymentMethodSelect', handler: (m: {\n        code: string;\n    }) => void): () => void;\n    destroy(): Promise<void>;\n}",
          "sourceDocumentation": "상태 2: 렌더 완료 — 여기서만 결제 요청이 가능하다."
        },
        {
          "name": "requestBillingAuth",
          "slug": "request-billing-auth",
          "kind": "function",
          "declaration": "/**\n * 자동결제(빌링) 등록 인증창을 연다. 성공 시 successUrl로 리다이렉트되며\n * 서버에서 parseBillingAuthCallback → confirmPendingAuth → issue로 이어진다.\n * 사용자 취소(USER_CANCEL / PAY_PROCESS_CANCELED)는 에러가 아닌 user-canceled variant다.\n */\ndeclare function requestBillingAuth(clientKey: ApiClientKey, customer: WidgetCustomerKey, request: BillingAuthRequest): Promise<Result<PaymentRequestOutcome, SdkError>>;",
          "sourceDocumentation": "자동결제(빌링) 등록 인증창을 연다. 성공 시 successUrl로 리다이렉트되며\n서버에서 parseBillingAuthCallback → confirmPendingAuth → issue로 이어진다.\n사용자 취소(USER_CANCEL / PAY_PROCESS_CANCELED)는 에러가 아닌 user-canceled variant다."
        },
        {
          "name": "SdkError",
          "slug": "sdk-error",
          "kind": "interface",
          "declaration": "/** SDK 호출이 던진 예외의 {code, message} 회수 형태. */\ninterface SdkError {\n    readonly kind: 'sdk';\n    readonly code: string;\n    readonly message: string;\n}",
          "sourceDocumentation": "SDK 호출이 던진 예외의 {code, message} 회수 형태."
        },
        {
          "name": "TossWidgets",
          "slug": "toss-widgets",
          "kind": "interface",
          "declaration": "/** 상태 0: 금액 미설정 — render/requestPayment 메서드 자체가 없다. */\ninterface TossWidgets {\n    setAmount(amount: WidgetAmount): Promise<Result<TossWidgetsWithAmount, WidgetError>>;\n}",
          "sourceDocumentation": "상태 0: 금액 미설정 — render/requestPayment 메서드 자체가 없다."
        },
        {
          "name": "TossWidgetsWithAmount",
          "slug": "toss-widgets-with-amount",
          "kind": "interface",
          "declaration": "/** 상태 1: 금액 설정됨 — 렌더링 가능. setAmount는 쿠폰 등 금액 변경용으로 남는다. */\ninterface TossWidgetsWithAmount {\n    renderPaymentMethods(options: {\n        readonly selector: string;\n        readonly variantKey?: string;\n    }): Promise<Result<RenderedTossWidgets, WidgetError>>;\n    renderAgreement(options: {\n        readonly selector: string;\n        readonly variantKey?: string;\n    }): Promise<Result<AgreementWidget, WidgetError>>;\n    setAmount(amount: WidgetAmount): Promise<Result<TossWidgetsWithAmount, WidgetError>>;\n}",
          "sourceDocumentation": "상태 1: 금액 설정됨 — 렌더링 가능. setAmount는 쿠폰 등 금액 변경용으로 남는다."
        },
        {
          "name": "WidgetAmount",
          "slug": "widget-amount",
          "kind": "interface",
          "declaration": "interface WidgetAmount {\n    readonly currency: 'KRW' | 'USD' | 'JPY';\n    readonly value: number;\n}"
        },
        {
          "name": "WidgetError",
          "slug": "widget-error",
          "kind": "type",
          "declaration": "type WidgetError = SdkError | {\n    readonly kind: 'load-failed';\n    readonly cause: unknown;\n};"
        },
        {
          "name": "WidgetPaymentRequest",
          "slug": "widget-payment-request",
          "kind": "interface",
          "declaration": "interface WidgetPaymentRequest {\n    /** 스마트 생성자 산출물만 — 서버 createOrder가 발급한 값은 JSON 경계에서 orderId(raw)로 재파싱한다. */\n    readonly orderId: OrderId;\n    readonly orderName: OrderName;\n    /** origin을 포함한 완전 URL (SDK 문서 요구 — 런타임 검증). */\n    readonly successUrl: string;\n    readonly failUrl: string;\n    readonly customerEmail?: string;\n    readonly customerName?: string;\n    readonly customerMobilePhone?: string;\n    readonly taxFreeAmount?: number;\n    /** 최대 5쌍 (문서: metadata 최대 5개 key-value — 런타임 검증). */\n    readonly metadata?: Readonly<Record<string, string>>;\n}"
        }
      ]
    },
    {
      "subpath": "./server",
      "id": "server",
      "declarationTarget": "./dist/server.d.cts",
      "symbols": [
        {
          "name": "andThen",
          "slug": "and-then",
          "kind": "function",
          "declaration": "/** 성공 시 다음 Result 연산으로 연결 — 에러 타입은 합집합으로 누적된다. */\ndeclare function andThen<T, U, E, F>(r: Result<T, E>, f: (value: T) => Result<U, F>): Result<U, E | F>;",
          "sourceDocumentation": "성공 시 다음 Result 연산으로 연결 — 에러 타입은 합집합으로 누적된다."
        },
        {
          "name": "ApiClientKey",
          "slug": "api-client-key",
          "kind": "type",
          "declaration": "/**\n * 형식(템플릿 리터럴)과 명목성(브랜드)을 동시에 강제 —\n * `'test_ck_oops'` 리터럴도 parse 없이는 대입 불가.\n */\ntype ApiClientKey<E extends Env = Env> = (E extends 'test' ? `test_ck_${string}` : `live_ck_${string}`) & Brand<'ApiClientKey'> & EnvTag<E>;",
          "sourceDocumentation": "형식(템플릿 리터럴)과 명목성(브랜드)을 동시에 강제 —\n`'test_ck_oops'` 리터럴도 parse 없이는 대입 불가."
        },
        {
          "name": "ApiSecretKey",
          "slug": "api-secret-key",
          "kind": "type",
          "declaration": "type ApiSecretKey<E extends Env = Env> = (E extends 'test' ? `test_sk_${string}` : `live_sk_${string}`) & Brand<'ApiSecretKey'> & EnvTag<E>;"
        },
        {
          "name": "asCancelable",
          "slug": "as-cancelable",
          "kind": "function",
          "declaration": "declare function asCancelable(payment: Payment): Result<CancelablePayment, NotCancelableError>;"
        },
        {
          "name": "AUDIT_REDACTED_KEYS",
          "slug": "audit-redacted-keys",
          "kind": "constant",
          "declaration": "AUDIT_REDACTED_KEYS: readonly string[]",
          "sourceDocumentation": "redaction 대상 키 목록 — 단일 상수 export로 감사 가능하게 (버전 관리 대상, 설계 §3.2 확정 표).\n\n매칭은 **대소문자 무시**이며 req/res body를 재귀 순회해 값이 `'[REDACTED]'`로 치환된다.\n이 목록 외 추가 규칙 1건: `card`/`refundAccount` 컨텍스트(부모 키) 하위의 `number`도 치환\n(카드번호 마스킹본·환불 계좌번호 — 실측 응답 필드).\n\n잔존 리스크: denylist는 토스가 새 민감 필드를 추가하면 누락될 수 있다 — 실측 응답 픽스처\n전수 redaction 스냅샷 테스트 + 마이너 업데이트 시 필드 감사로 완화한다."
        },
        {
          "name": "AuditEntry",
          "slug": "audit-entry",
          "kind": "interface",
          "declaration": "/**\n * audit — 아웃바운드 토스 API req/res 증거 기록 (설계 §3.2, must 3/3 수렴).\n *\n * 타입뿐인 계약 + 순수 redaction 순회기 — 환경 중립(core)이며 런타임 의존성 0.\n * 부착 지점은 server/client.ts의 내부 request() 단일 관문이다(흩어짐 없음).\n *\n * 협상 불가 계약:\n * - `record()`는 await되지 않는다(fire-and-forget) — audit 오류가 결제 요청의\n *   지연·실패에 영향을 주는 경로가 없다(기록 실패 < 결제 실패).\n * - redaction은 비설정화 — 끄는 옵션·설정 파라미터를 제공하지 않는다.\n * - Authorization 헤더는 AuditEntry에 **필드 자체가 없다** — 마스킹이 아니라 구조적 부재.\n */\n/**\n * 시도 1건 = 엔트리 1건 (outcome 유니언) — request/response 분리 kind안은\n * 상관(join) 비용 때문에 기각(설계 §7-7).\n *\n * ⚠ responseBody에는 redaction 후에도 고객 이름·이메일 등 PII가 잔존할 수 있다 —\n * 보관 주체·기간·접근 통제는 sink 소유자(사용자) 책임이다.\n */\ninterface AuditEntry {\n    /** crypto.randomUUID — 시도 1건당 1엔트리. */\n    readonly id: string;\n    /** ISO 8601 요청(시도) 시작 시각. */\n    readonly at: string;\n    readonly env: Env;\n    readonly method: 'GET' | 'POST' | 'DELETE';\n    /** '/v1/payments/confirm' 등 pathname만 — 쿼리 미포함. */\n    readonly path: string;\n    /** 1부터 — retry(§3.4) 결합 시 시도마다 엔트리 1건. */\n    readonly attempt: number;\n    readonly idempotencyKey: string | null;\n    /** redaction 통과본. ⚠ 헤더 필드가 타입에 없다 — Authorization은 구조적으로 기록 불가. body 없는 요청은 null. */\n    readonly requestBody: unknown;\n    readonly durationMs: number;\n    /** x-tosspayments-trace-id — 고객센터 문의 키. */\n    readonly traceId: string | null;\n    readonly outcome: {\n        readonly kind: 'ok';\n        readonly httpStatus: number;\n        /** redaction 통과본. */\n        readonly responseBody: unknown;\n    } | {\n        readonly kind: 'toss-error';\n        readonly httpStatus: number;\n        readonly code: string;\n        readonly message: string;\n    } | {\n        readonly kind: 'transport';\n        readonly code: 'NETWORK_ERROR' | 'TIMEOUT';\n    };\n}",
          "sourceDocumentation": "시도 1건 = 엔트리 1건 (outcome 유니언) — request/response 분리 kind안은\n상관(join) 비용 때문에 기각(설계 §7-7).\n\n⚠ responseBody에는 redaction 후에도 고객 이름·이메일 등 PII가 잔존할 수 있다 —\n보관 주체·기간·접근 통제는 sink 소유자(사용자) 책임이다."
        },
        {
          "name": "AuditOptions",
          "slug": "audit-options",
          "kind": "interface",
          "declaration": "interface AuditOptions {\n    readonly sink: AuditSink;\n    /** sink 실패 통지. 기본 무시 — 이 콜백의 throw도 삼켜진다. */\n    readonly onSinkError?: (cause: unknown, entry: AuditEntry) => void;\n}"
        },
        {
          "name": "AuditSink",
          "slug": "audit-sink",
          "kind": "interface",
          "declaration": "interface AuditSink {\n    /**\n     * 시도 1건당 1회 호출된다. 반환 Promise는 클라이언트가 await하지 않는다 —\n     * sync throw·async rejection 모두 삼켜지고 `AuditOptions.onSinkError`로만 통지된다.\n     */\n    record(entry: AuditEntry): void | Promise<void>;\n}"
        },
        {
          "name": "AuthKeyReceived",
          "slug": "auth-key-received",
          "kind": "interface",
          "declaration": "interface AuthKeyReceived extends Brand<'AuthKeyReceived'> {\n    /** 대조 완료된 **세션 유래** 값 — 콜백 값이 아니다. authKey는 계속 봉인 상태. */\n    readonly customerKey: CustomerKey;\n}"
        },
        {
          "name": "AwaitingDepositCancelable",
          "slug": "awaiting-deposit-cancelable",
          "kind": "interface",
          "declaration": "interface AwaitingDepositCancelable extends Brand<'Cancelable'> {\n    /** WAITING_FOR_DEPOSIT → 전액만 + refundAccount 금지(환불할 금액이 없다). */\n    readonly kind: 'awaiting-deposit';\n    readonly payment: Payment & {\n        readonly status: 'WAITING_FOR_DEPOSIT';\n    };\n    readonly balanceAmount: number;\n}"
        },
        {
          "name": "AwaitingDepositRefundRequest",
          "slug": "awaiting-deposit-refund-request",
          "kind": "interface",
          "declaration": "interface AwaitingDepositRefundRequest {\n    readonly reason: CancelReason;\n    readonly refundAccount?: never;\n    readonly taxFreeAmount?: never;\n    readonly cancelRequestId?: CancelRequestId;\n    readonly currency?: never;\n}"
        },
        {
          "name": "BillingApproveError",
          "slug": "billing-approve-error",
          "kind": "type",
          "declaration": "type BillingApproveError = TossApiFailure<BillingErrorCode> | TransportFailure | {\n    readonly source: 'library';\n    readonly kind: 'missing-idempotency-key';\n} | {\n    readonly source: 'library';\n    readonly kind: 'invalid-input';\n    readonly field: string;\n    readonly reason: string;\n}\n/** 봉인 소실 복제본 — billing.load()로 재수화하라. */\n | {\n    readonly source: 'library';\n    readonly kind: 'profile-detached';\n    readonly customerKey: CustomerKey;\n};"
        },
        {
          "name": "BillingAuthCallback",
          "slug": "billing-auth-callback",
          "kind": "type",
          "declaration": "type BillingAuthCallback = {\n    readonly status: 'authorized';\n    readonly pending: PendingBillingAuth;\n} | {\n    readonly status: 'user-canceled';\n    readonly code: string;\n} | {\n    readonly status: 'failed';\n    readonly code: string;\n    readonly message: string;\n};"
        },
        {
          "name": "BillingCapabilities",
          "slug": "billing-capabilities",
          "kind": "interface",
          "declaration": "interface BillingCapabilities {\n    /** 카드 정보 직접 전달 발급(/v1/billing/authorizations/card) — 추가 계약 필요. */\n    readonly directCardIssue?: true;\n    /** @deprecated approve는 이제 모든 구성에서 멱등키가 필수다. */\n    readonly requireApproveIdempotencyKey?: true;\n}"
        },
        {
          "name": "BillingErrorCode",
          "slug": "billing-error-code",
          "kind": "type",
          "declaration": "type BillingErrorCode = 'NOT_MATCHES_CUSTOMER_KEY' | 'ALREADY_REMOVED_BILLING_KEY' | 'NOT_SUPPORTED_METHOD' | 'NOT_SUPPORTED_CARD_TYPE' | 'INVALID_BILL_KEY_REQUEST' | 'INVALID_BILLING_AUTH' | 'INVALID_CARD_NUMBER' | 'FAILED_BILL_KEY_AUTH_CREATION' | 'FAILED_BILLING_AUTO_CANCEL' | (string & {});"
        },
        {
          "name": "BillingFlow",
          "slug": "billing-flow",
          "kind": "type",
          "declaration": "type BillingFlow<E extends Env, C extends BillingCapabilities = {}> = BillingFlowBase<E> & (C extends {\n    directCardIssue: true;\n} ? {\n    /**\n     * 추가 계약 필요(NOT_SUPPORTED_METHOD). 테스트 표준 카드 9410001234567890\n     * (Phase 0 실측 — BIN 6자리 단독은 400 INVALID_CARD_NUMBER).\n     */\n    issueWithCard(input: DirectCardIssueInput, options?: CallOptions<E>): Promise<Result<BillingProfile, IssueBillingKeyError>>;\n} : {});"
        },
        {
          "name": "BillingFlowBase",
          "slug": "billing-flow-base",
          "kind": "interface",
          "declaration": "interface BillingFlowBase<E extends Env> {\n    /**\n     * POST /v1/billing/authorizations/issue → store.save 성공 후에만 Ok.\n     * 저장 실패면 Err에 발급된 record 동봉 — 조회 API가 없으므로 저장 실패 = 키 유실이다.\n     */\n    issue(auth: AuthKeyReceived, options?: CallOptions<E>): Promise<Result<BillingProfile, IssueBillingKeyError>>;\n    /** 스토어에서 재수화 — BillingProfile을 얻는 유일한 다른 경로(봉인 소실 복구 API). */\n    load(customerKey: CustomerKey): Promise<Result<BillingProfile | null, StoreFailure>>;\n    /**\n     * ⚠ 마이그레이션 전용 이관 경로(§7 확정 6) — 기존 시스템이 보유한 billingKey를\n     * 형식 검증 후 store.save 하고 BillingProfile로 승격한다.\n     *\n     * 토스에는 빌링키 조회 API가 없어 **record 값의 진위를 서버에서 재검증할 수 없다** —\n     * 오염된 record면 '타입은 맞고 값은 틀린' 프로필이 만들어져 approve에서\n     * INVALID_BILL_KEY_REQUEST 류로 실패한다. 신뢰할 수 있는 원본에서만 이관할 것.\n     */\n    import(record: BillingKeyRecord): Promise<Result<BillingProfile, ImportBillingKeyError>>;\n    /**\n     * BillingOrder에는 customerKey 필드가 없다 — 봉인 쌍으로만 승인 →\n     * NOT_MATCHES_CUSTOMER_KEY 구조적 방지. 봉인이 소실된 profile(스프레드 복제본 등)은\n     * 런타임 Err('profile-detached') — billing.load로 재수화 안내.\n     *\n     * options와 idempotencyKey는 모든 구성에서 필수다. TypeScript 우회를 거친 런타임 호출도\n     * missing-idempotency-key로 API 전송 전에 거부한다.\n     */\n    readonly approve: (profile: BillingProfile, order: BillingOrder, options: CallOptions<E> & {\n        readonly idempotencyKey: IdempotencyKey;\n    }) => Promise<Result<BillingPayment, BillingApproveError>>;\n    /**\n     * DELETE /v1/billing/{billingKey} 뒤\n     * `store.delete({ customerKey, expectedBillingKey: billingKey })`.\n     *\n     * 반환의 `currentStoredKeyDeleted`가 false면 profile은 이미 오래됐거나 행이 없어\n     * 현재 저장 credential을 지우지 않았다. 갱신 API는 존재하지 않는다 — refresh류\n     * 메서드 없음. 재발급 = 새 인증부터.\n     */\n    revoke(profile: BillingProfile, options?: CallOptions<E>): Promise<Result<RevokeBillingKeyOutcome, RevokeBillingKeyError>>;\n}"
        },
        {
          "name": "BillingKeyDeleteRequest",
          "slug": "billing-key-delete-request",
          "kind": "interface",
          "declaration": "/**\n * 현재 billing key를 조건부로 제거하는 요청.\n *\n * 두 raw 문자열을 별도 인자로 받지 않고 한 객체로 묶어, 예전의\n * `delete(customerKey)` 구현이 TypeScript 구조 타이핑에서 우연히 호환되는 일을 막는다.\n * 이 객체는 billing key 영속화 경계에서만 만들고 로그/telemetry에 통째로 남기지 않는다.\n */\ninterface BillingKeyDeleteRequest {\n    readonly customerKey: BillingKeyRecord['customerKey'];\n    readonly expectedBillingKey: BillingKeyRecord['billingKey'];\n}",
          "sourceDocumentation": "현재 billing key를 조건부로 제거하는 요청.\n\n두 raw 문자열을 별도 인자로 받지 않고 한 객체로 묶어, 예전의\n`delete(customerKey)` 구현이 TypeScript 구조 타이핑에서 우연히 호환되는 일을 막는다.\n이 객체는 billing key 영속화 경계에서만 만들고 로그/telemetry에 통째로 남기지 않는다."
        },
        {
          "name": "BillingKeyRecord",
          "slug": "billing-key-record",
          "kind": "interface",
          "declaration": "/**\n * 영속화 경계 — 여기서만 raw 쌍(customerKey + billingKey)이 보인다.\n *\n * ⚠ 토스의 빌링 보안 모델은 이 쌍의 분리에 의존한다(\"빌링키가 노출되어도 매핑된\n * customerKey를 모른다면 결제가 불가능합니다\" — 빌링 가이드). billingKey와\n * customerKey를 같은 로그에 함께 남기지 말 것.\n */\ninterface BillingKeyRecord {\n    readonly customerKey: string;\n    readonly billingKey: string;\n    /** 응답 원문 한글 리터럴 — 요청 enum(CARD/TRANSFER)과 비대칭. */\n    readonly method: '카드' | '계좌이체';\n    /** 발급 응답의 authenticatedAt. */\n    readonly issuedAt: string;\n    readonly card: {\n        readonly issuerCode: string;\n        readonly number: string;\n        readonly cardType: '신용' | '체크' | '기프트' | '미확인';\n        readonly ownerType: '개인' | '법인' | '미확인';\n    } | null;\n    /** 퀵계좌이체 발급 — 배열이다(응답 원문 구조). */\n    readonly transfers: readonly {\n        readonly bankName: string;\n        readonly bankAccountNumber: string;\n    }[] | null;\n}",
          "sourceDocumentation": "영속화 경계 — 여기서만 raw 쌍(customerKey + billingKey)이 보인다.\n\n⚠ 토스의 빌링 보안 모델은 이 쌍의 분리에 의존한다(\"빌링키가 노출되어도 매핑된\ncustomerKey를 모른다면 결제가 불가능합니다\" — 빌링 가이드). billingKey와\ncustomerKey를 같은 로그에 함께 남기지 말 것."
        },
        {
          "name": "BillingKeySaveOptions",
          "slug": "billing-key-save-options",
          "kind": "interface",
          "declaration": "/**\n * billing key 저장의 비밀 아닌 lifecycle 상관관계 값.\n *\n * `operationId`는 동일 customerKey의 서로 다른 발급 시도마다 달라야 한다. core\n * `billing.issue`는 `CallOptions.idempotencyKey`가 있으면 이 값으로 자동 전달한다.\n * raw billing key, auth key, 카드/계좌 정보는 절대 넣지 않는다.\n */\ninterface BillingKeySaveOptions {\n    readonly operationId?: string;\n}",
          "sourceDocumentation": "billing key 저장의 비밀 아닌 lifecycle 상관관계 값.\n\n`operationId`는 동일 customerKey의 서로 다른 발급 시도마다 달라야 한다. core\n`billing.issue`는 `CallOptions.idempotencyKey`가 있으면 이 값으로 자동 전달한다.\nraw billing key, auth key, 카드/계좌 정보는 절대 넣지 않는다."
        },
        {
          "name": "BillingKeyStore",
          "slug": "billing-key-store",
          "kind": "interface",
          "declaration": "/** 저장소 필수 주입 — 토스에 빌링키 조회 API가 없다: 저장이 유일한 보관 수단. */\ninterface BillingKeyStore {\n    /**\n     * record를 upsert한다. `operationId`는 발급 뒤 별도 projection을 같은 customerKey\n     * fence 안에서 마무리해야 하는 소비자를 위한 비밀이 아닌 상관관계 식별자다.\n     *\n     * 구현은 값을 로그에 남기지 말고, 지원한다면 현재 row와 원자적으로 대조할 수 있는\n     * fingerprint/receipt만 보관해야 한다. 일반 save만 필요한 소비자는 생략할 수 있다.\n     */\n    save(record: BillingKeyRecord, options?: BillingKeySaveOptions): Promise<void>;\n    find(customerKey: CustomerKey): Promise<BillingKeyRecord | null>;\n    /**\n     * 현재 저장된 billing key가 `expectedBillingKey`일 때만 원자적으로 삭제한다.\n     *\n     * `false`는 행이 없거나 더 새 키로 교체되어 아무 것도 지우지 않았다는 안전한 결과다.\n     * `find()` 후 일반 delete로 흉내 내면 재발급과 경합해 새 키를 지울 수 있으므로,\n     * 프로덕션 구현은 하나의 DB 조건문/CAS 또는 잠금 transaction으로 비교와 삭제를\n     * 함께 수행해야 한다. 저장소 실패만 throw한다.\n     */\n    delete(request: BillingKeyDeleteRequest): Promise<boolean>;\n}",
          "sourceDocumentation": "저장소 필수 주입 — 토스에 빌링키 조회 API가 없다: 저장이 유일한 보관 수단."
        },
        {
          "name": "BillingOrder",
          "slug": "billing-order",
          "kind": "interface",
          "declaration": "/** customerKey 필드가 없음이 핵심 — 봉인 쌍(profile)의 값만 전송된다. */\ninterface BillingOrder {\n    readonly orderId: OrderId;\n    readonly orderName: OrderName;\n    readonly amount: number;\n    readonly customerEmail?: string;\n    readonly customerName?: string;\n    /** FDS 부정거래 탐지용. */\n    readonly customerIp?: string;\n    readonly taxFreeAmount?: number;\n    readonly taxExemptionAmount?: number;\n}",
          "sourceDocumentation": "customerKey 필드가 없음이 핵심 — 봉인 쌍(profile)의 값만 전송된다."
        },
        {
          "name": "BillingPayment",
          "slug": "billing-payment",
          "kind": "type",
          "declaration": "type BillingPayment = Payment & {\n    readonly type: 'BILLING';\n    readonly status: 'DONE';\n};"
        },
        {
          "name": "BillingProfile",
          "slug": "billing-profile",
          "kind": "interface",
          "declaration": "/**\n * billingKey는 공개 필드·JSON 직렬화·열거 어디에도 노출되지 않는다(비공개 심볼, 비열거).\n * WeakMap이 아니므로 일반 전달은 안전하지만, 스프레드/직렬화 복제본은 approve에서\n * 명시적 Err('profile-detached') — billing.load()로 재수화하라.\n */\ninterface BillingProfile extends Brand<'BillingProfile'> {\n    readonly customerKey: CustomerKey;\n    readonly method: '카드' | '계좌이체';\n    /** \"433012******890\" — 표시용. */\n    readonly maskedSource: string;\n    readonly issuedAt: string;\n}",
          "sourceDocumentation": "billingKey는 공개 필드·JSON 직렬화·열거 어디에도 노출되지 않는다(비공개 심볼, 비열거).\nWeakMap이 아니므로 일반 전달은 안전하지만, 스프레드/직렬화 복제본은 approve에서\n명시적 Err('profile-detached') — billing.load()로 재수화하라."
        },
        {
          "name": "BuiltInRefundPolicyConfig",
          "slug": "built-in-refund-policy-config",
          "kind": "type",
          "declaration": "type BuiltInRefundPolicyConfig = FullRefundPolicyConfig | PercentageRefundPolicyConfig | ElapsedTimeRefundPolicyConfig | RemainingUnitsRefundPolicyConfig;"
        },
        {
          "name": "CallbackParseError",
          "slug": "callback-parse-error",
          "kind": "interface",
          "declaration": "interface CallbackParseError {\n    readonly source: 'library';\n    readonly kind: 'callback-parse';\n    /** 문제가 된 파라미터 이름 목록 (missing-param 외 reason에서도 대상 파라미터를 담는다). */\n    readonly missing: readonly string[];\n    readonly reason: 'missing-param' | 'bad-amount' | 'bad-order-id';\n}"
        },
        {
          "name": "CallbackQueryInput",
          "slug": "callback-query-input",
          "kind": "type",
          "declaration": "/**\n * confirm 플로우 — parse → verify(OrderStore) → confirm 3단계 + confirmCallback 원스톱.\n *\n * 금액 대조(\"쿼리 파라미터의 amount 값과 setAmount()의 amount 값이 같은지 반드시\n * 확인하세요\" — 문서 의무)와 10분 승인 시한을 타입으로 강제한다: confirm은\n * VerifiedCheckout만 받고, VerifiedCheckout은 verify 통과로만 얻는다.\n */\n/** 프레임워크 무관 콜백 입력 — Next.js req.url, Express req.query, Hono c.req.url, URL 전부 수용. */\ntype CallbackQueryInput = string | URL | URLSearchParams | Readonly<Record<string, string | readonly string[] | undefined>>;",
          "sourceDocumentation": "프레임워크 무관 콜백 입력 — Next.js req.url, Express req.query, Hono c.req.url, URL 전부 수용."
        },
        {
          "name": "CallOptions",
          "slug": "call-options",
          "kind": "interface",
          "declaration": "interface CallOptions<E extends Env> {\n    /**\n     * ≤300자, POST 전용. 처음 사용일부터 15일 유효 — TTL 초과 후 재사용하면 새 요청으로\n     * 실행될 수 있다(문서는 기간만 명시 — 안전하지 않은 것으로 취급). 멱등 판정 조합은 \"키 + API 키 + 주소 + 메서드\"이며 body는 포함되지\n     * 않는다(문서 명시) — 키 재사용 시 body 동일성은 호출자(또는 재시도 티켓)가 보장해야 한다.\n     */\n    readonly idempotencyKey?: IdempotencyKey;\n    /**\n     * TossPayments-Test-Code 헤더 — 에러 시나리오 시뮬레이션.\n     * 라이브 키에선 서버가 조용히 무시하는 함정 → 타입으로 차단.\n     * ⚠ 비분배 조건부 — 미내로잉 union 키(E = Env)도 never다.\n     */\n    readonly testCode?: [\n        E\n    ] extends [\n        'test'\n    ] ? string : never;\n    readonly signal?: AbortSignal;\n}"
        },
        {
          "name": "CancelablePayment",
          "slug": "cancelable-payment",
          "kind": "type",
          "declaration": "/**\n * 결제 취소 — 조회 → asCancelable → 실행 3단계 강제.\n *\n * paymentKey 문자열이나 Payment로 바로 취소하는 API는 존재하지 않는다 — asCancelable\n * 검증 통과가 브랜드 획득의 유일한 경로이고, 실행은 kind 내로잉 없이는 컴파일 에러다.\n */\n/**\n * asCancelable을 통과해야만 얻는 3-변형 판별 유니언.\n *\n * 취소 가능 상태 집합 DONE|PARTIAL_CANCELED|WAITING_FOR_DEPOSIT는 **비공식 유도**다 —\n * 문서는 집합을 명시적으로 열거하지 않으며, DONE 취소 가능 명시 + \"가상계좌 입금 전에는\n * 일반 결제와 똑같이 취소\" 서술 + 부분취소 잔액 흐름에서 유도했다. 서버 정책 변경 시\n * 과잉/과소 차단 가능성이 있다 (설계 문서 부록 A).\n */\ntype CancelablePayment = SettledCancelable | DepositedVaCancelable | AwaitingDepositCancelable;",
          "sourceDocumentation": "asCancelable을 통과해야만 얻는 3-변형 판별 유니언.\n\n취소 가능 상태 집합 DONE|PARTIAL_CANCELED|WAITING_FOR_DEPOSIT는 **비공식 유도**다 —\n문서는 집합을 명시적으로 열거하지 않으며, DONE 취소 가능 명시 + \"가상계좌 입금 전에는\n일반 결제와 똑같이 취소\" 서술 + 부분취소 잔액 흐름에서 유도했다. 서버 정책 변경 시\n과잉/과소 차단 가능성이 있다 (설계 문서 부록 A)."
        },
        {
          "name": "CancelError",
          "slug": "cancel-error",
          "kind": "type",
          "declaration": "type CancelError = TossApiFailure<CancelErrorCode>\n/** 응답 유실 — retry(ticket)로 동일 멱등키+동일 body 재실행. */\n | (TransportFailure & {\n    readonly retry: CancelRetryTicket;\n}) | CancelPreflightError | {\n    /** provider가 취소 트랜잭션을 최종 거부했다. 같은 멱등키 재시도로 성공시킬 수 없다. */\n    readonly source: 'library';\n    readonly kind: 'cancel-aborted';\n    readonly paymentKey: PaymentKey;\n    readonly transactionKey: string;\n    readonly cancelRequestId: string | null;\n};"
        },
        {
          "name": "CancelErrorCode",
          "slug": "cancel-error-code",
          "kind": "type",
          "declaration": "/** 취소 API 공식 표 30개 + 실측 보강 — `(string & {})`로 열린 확장(미등록 코드도 수용). */\ntype CancelErrorCode = 'ALREADY_CANCELED_PAYMENT' | 'ALREADY_REFUND_PAYMENT' | 'NOT_CANCELABLE_PAYMENT' | 'NOT_CANCELABLE_PAYMENT_FOR_DORMANT_USER' | 'NOT_CANCELABLE_AMOUNT' | 'EXCEED_CANCEL_AMOUNT_DISCOUNT_AMOUNT' | 'EXCEED_CANCEL_LIMIT' | 'EXCEED_MAX_REFUND_DUE' | 'NOT_ALLOWED_PARTIAL_REFUND' | 'NOT_ALLOWED_PARTIAL_REFUND_WAITING_DEPOSIT' | 'INVALID_REFUND_ACCOUNT_INFO' | 'INVALID_REFUND_ACCOUNT_NUMBER' | 'INVALID_BANK' | 'NOT_AVAILABLE_BANK' | 'FORBIDDEN_BANK_REFUND_REQUEST' | 'NOT_MATCHES_REFUNDABLE_AMOUNT' | 'FORBIDDEN_CONSECUTIVE_REQUEST' | 'IDEMPOTENT_REQUEST_PROCESSING' | 'INVALID_IDEMPOTENCY_KEY' | 'PROVIDER_ERROR' | 'FAILED_INTERNAL_SYSTEM_PROCESSING' | 'FAILED_REFUND_PROCESS' | 'FAILED_METHOD_HANDLING_CANCEL' | 'FAILED_PARTIAL_REFUND' | 'COMMON_ERROR' | 'FAILED_PAYMENT_INTERNAL_SYSTEM_PROCESSING' | 'REFUND_REJECTED' | 'UNAUTHORIZED_KEY' | 'INCORRECT_BASIC_AUTH_FORMAT' | 'FORBIDDEN_REQUEST' | 'INVALID_REQUEST' | 'NOT_FOUND_PAYMENT' | (string & {});",
          "sourceDocumentation": "취소 API 공식 표 30개 + 실측 보강 — `(string & {})`로 열린 확장(미등록 코드도 수용)."
        },
        {
          "name": "CancelOutcome",
          "slug": "cancel-outcome",
          "kind": "interface",
          "declaration": "interface CancelOutcome {\n    /** 전액 취소여도 status 'CANCELED' 단정 금지 — 부분취소 이력이 있으면 PARTIAL_CANCELED 유지(실측). */\n    readonly payment: Payment & {\n        readonly status: 'CANCELED' | 'PARTIAL_CANCELED';\n    };\n    /** 이번 취소 건. ABORTED는 CancelError로 분리되므로 성공 outcome에는 들어오지 않는다. */\n    readonly cancel: CancelTransaction & {\n        readonly cancelStatus: 'DONE' | 'IN_PROGRESS';\n    };\n    /** 완전 취소 판정의 유일한 기준: balanceAmount === 0. status로 판정하지 않는다. */\n    readonly fullyCanceled: boolean;\n    /** cancelStatus === 'IN_PROGRESS' (PayPal 등 해외 비동기) → CANCEL_STATUS_CHANGED 웹훅 대기. */\n    readonly pending: boolean;\n    /** 실제 사용된 키 (자동 생성분 포함). */\n    readonly idempotencyKey: IdempotencyKey;\n}"
        },
        {
          "name": "CancelPreflightError",
          "slug": "cancel-preflight-error",
          "kind": "type",
          "declaration": "type CancelPreflightError = {\n    /** 우회해서 서버로 보내면 403 NOT_CANCELABLE_AMOUNT (실측) — API 호출 전에 차단한다. */\n    readonly source: 'library';\n    readonly kind: 'amount-exceeds-balance';\n    readonly cancelAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly source: 'library';\n    readonly kind: 'expected-amount-mismatch';\n    readonly expected: number;\n    readonly actual: number;\n} | {\n    readonly source: 'library';\n    readonly kind: 'invalid-input';\n    readonly field: string;\n    readonly reason: string;\n} | {\n    readonly source: 'library';\n    readonly kind: 'partial-cancel-not-allowed';\n    readonly paymentKey: PaymentKey;\n} | {\n    readonly source: 'library';\n    readonly kind: 'retry-ticket-expired';\n    readonly issuedAt: string;\n} | {\n    readonly source: 'library';\n    readonly kind: 'retry-store-failure';\n    readonly operation: 'save' | 'load' | 'delete';\n    readonly cause: unknown;\n};"
        },
        {
          "name": "cancelReason",
          "slug": "cancel-reason",
          "kind": "function",
          "declaration": "declare function cancelReason(raw: string): Result<CancelReason, InvalidInput<'cancelReason'>>;"
        },
        {
          "name": "CancelReason",
          "slug": "cancel-reason--type",
          "kind": "type",
          "declaration": "/** 취소 사유 — 1–200자. */\ntype CancelReason = string & Brand<'CancelReason'>;",
          "sourceDocumentation": "취소 사유 — 1–200자."
        },
        {
          "name": "cancelRequestId",
          "slug": "cancel-request-id",
          "kind": "function",
          "declaration": "declare function cancelRequestId(raw: string): Result<CancelRequestId, InvalidInput<'cancelRequestId'>>;"
        },
        {
          "name": "CancelRequestId",
          "slug": "cancel-request-id--type",
          "kind": "type",
          "declaration": "/**\n * 취소 요청 ID — 6–64자, `^[A-Za-z0-9\\-_=]+$` (상점 발급 고유값).\n * **중국·동남아 비동기(Alipay 등) 결제 취소에만 필수**다 — 공식 V2 '해외 간편결제\n * 연동하기'(문서 ID 53)의 취소 Request Body 규격. 국내/일반 취소에는 불필요.\n */\ntype CancelRequestId = string & Brand<'CancelRequestId'>;",
          "sourceDocumentation": "취소 요청 ID — 6–64자, `^[A-Za-z0-9\\-_=]+$` (상점 발급 고유값).\n**중국·동남아 비동기(Alipay 등) 결제 취소에만 필수**다 — 공식 V2 '해외 간편결제\n연동하기'(문서 ID 53)의 취소 Request Body 규격. 국내/일반 취소에는 불필요."
        },
        {
          "name": "CancelRetryRecord",
          "slug": "cancel-retry-record",
          "kind": "interface",
          "declaration": "/** 환불계좌 등 민감 요청을 포함할 수 있으므로 반드시 암호화 at-rest 저장할 것. */\ninterface CancelRetryRecord {\n    readonly ticketId: string;\n    readonly paymentKey: string;\n    readonly idempotencyKey: string;\n    readonly issuedAt: string;\n    readonly path: string;\n    readonly bodyJson: string;\n    readonly testCode: string | undefined;\n    readonly expectedCancelAmount: number;\n    readonly previousBalanceAmount: number;\n}",
          "sourceDocumentation": "환불계좌 등 민감 요청을 포함할 수 있으므로 반드시 암호화 at-rest 저장할 것."
        },
        {
          "name": "CancelRetryStore",
          "slug": "cancel-retry-store",
          "kind": "interface",
          "declaration": "interface CancelRetryStore {\n    save(record: CancelRetryRecord): Promise<void>;\n    load(ticketId: string): Promise<CancelRetryRecord | null>;\n    delete(ticketId: string): Promise<void>;\n}"
        },
        {
          "name": "CancelRetryTicket",
          "slug": "cancel-retry-ticket",
          "kind": "interface",
          "declaration": "/**\n * transport 실패 시 발급되는 불투명 재시도 티켓. 같은 프로세스에서는 비열거 봉인을,\n * 재시작 뒤에는 CancelRetryStore의 암호화 record를 사용해 동일 멱등키+body를 복원한다.\n * issuedAt 기준 `DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS`(14일 — provider TTL 15일에서 하루 여유)가\n * 지난 티켓은 새 요청으로 실행될 위험이 있어 로컬에서 `retry-ticket-expired`로 거부한다.\n */\ninterface CancelRetryTicket extends Brand<'CancelRetryTicket'> {\n    readonly ticketId: string;\n    readonly paymentKey: PaymentKey;\n    readonly idempotencyKey: IdempotencyKey;\n    readonly issuedAt: string;\n    /** true면 네트워크 요청 전에 주입된 CancelRetryStore에 요청 바이트가 저장된 상태. */\n    readonly durable: boolean;\n}",
          "sourceDocumentation": "transport 실패 시 발급되는 불투명 재시도 티켓. 같은 프로세스에서는 비열거 봉인을,\n재시작 뒤에는 CancelRetryStore의 암호화 record를 사용해 동일 멱등키+body를 복원한다.\nissuedAt 기준 `DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS`(14일 — provider TTL 15일에서 하루 여유)가\n지난 티켓은 새 요청으로 실행될 위험이 있어 로컬에서 `retry-ticket-expired`로 거부한다."
        },
        {
          "name": "CancelTransaction",
          "slug": "cancel-transaction",
          "kind": "interface",
          "declaration": "interface CancelTransaction {\n    readonly transactionKey: string;\n    readonly cancelAmount: number;\n    readonly cancelReason: string;\n    readonly taxFreeAmount: number;\n    readonly taxExemptionAmount: number;\n    /** (응답) 이 취소 후 남은 환불 가능액 — 취소 요청 파라미터의 refundableAmount와 이름만 같다. */\n    readonly refundableAmount: number;\n    readonly transferDiscountAmount: number;\n    readonly easyPayDiscountAmount: number;\n    readonly canceledAt: string;\n    readonly receiptKey: string | null;\n    /** 해외 간편결제(PayPal)는 IN_PROGRESS로 시작하는 비동기 취소 — CANCEL_STATUS_CHANGED 웹훅으로 완결. */\n    readonly cancelStatus: 'DONE' | 'IN_PROGRESS' | 'ABORTED';\n    /** 비동기 취소 전용. */\n    readonly cancelRequestId: string | null;\n}"
        },
        {
          "name": "CARD_ISSUER_NAMES_KO",
          "slug": "card-issuer-names-ko",
          "kind": "constant",
          "declaration": "CARD_ISSUER_NAMES_KO: Readonly<Record<KnownCardIssuerCode, string>>",
          "sourceDocumentation": "Korean display names for every code in {@link KnownCardIssuerCode}, keyed by the two-character\ncode Toss returns in `card.issuerCode` / `card.acquirerCode`.\n\nNames are the \"카드사\" column of the official table verbatim, except that the acquirer\nqualifiers on the two 우리 rows are dropped for display: `33` → `우리BC카드` (acquired by BC),\n`W1` → `우리카드` (acquired by 우리; response-only code). The object is frozen; treat it as a\nlookup table, not as product copy — override names in your own layer if your UI needs shorter\nlabels."
        },
        {
          "name": "CardDetails",
          "slug": "card-details",
          "kind": "interface",
          "declaration": "interface CardDetails {\n    readonly amount: number;\n    readonly issuerCode: string;\n    readonly acquirerCode: string | null;\n    /** 마스킹된 카드번호. */\n    readonly number: string;\n    readonly installmentPlanMonths: number;\n    readonly approveNo: string;\n    readonly useCardPoint: boolean;\n    readonly cardType: '신용' | '체크' | '기프트' | '미확인';\n    readonly ownerType: '개인' | '법인' | '미확인';\n    readonly acquireStatus: string;\n    readonly isInterestFree: boolean;\n    readonly interestPayer: string | null;\n}"
        },
        {
          "name": "cardIssuerName",
          "slug": "card-issuer-name",
          "kind": "function",
          "declaration": "/**\n * Display name for a Toss card issuer/acquirer code, or `undefined` when the code is not in the\n * documented table (Toss may add institutions; render a neutral fallback such as \"카드\" yourself).\n *\n * Matching is exact — Toss returns codes exactly as listed (uppercase letter, no whitespace), so\n * no normalisation is applied. Only `'ko'` is supported today; the `locale` parameter exists so\n * other languages can be added without changing the signature.\n */\ndeclare function cardIssuerName(code: string, locale?: 'ko'): string | undefined;",
          "sourceDocumentation": "Display name for a Toss card issuer/acquirer code, or `undefined` when the code is not in the\ndocumented table (Toss may add institutions; render a neutral fallback such as \"카드\" yourself).\n\nMatching is exact — Toss returns codes exactly as listed (uppercase letter, no whitespace), so\nno normalisation is applied. Only `'ko'` is supported today; the `locale` parameter exists so\nother languages can be added without changing the signature."
        },
        {
          "name": "CardPayment",
          "slug": "card-payment",
          "kind": "interface",
          "declaration": "interface CardPayment extends PaymentBase {\n    readonly method: '카드';\n    readonly card: CardDetails;\n    readonly virtualAccount: null;\n}"
        },
        {
          "name": "categorizeCancelError",
          "slug": "categorize-cancel-error",
          "kind": "function",
          "declaration": "declare function categorizeCancelError(code: string): ErrorCategory;"
        },
        {
          "name": "CLASSIFIED_TOSS_ERROR_CODES",
          "slug": "classified-toss-error-codes",
          "kind": "constant",
          "declaration": "CLASSIFIED_TOSS_ERROR_CODES: readonly string[]",
          "sourceDocumentation": "Every Toss error code the library has a classification for — the keys of the internal\ncode table, frozen, in table order. `classifyTossErrorCode(code)` returns a non-`UNKNOWN`\ncategory exactly for these codes.\n\nExposed so that derived tables (e.g. `OUTCOME_QUERY_FIRST_ERROR_CODES`) and consumer audits\ncan be checked against the single source instead of a hand-copied list — adding a code to\nthe table is then a visible, testable event."
        },
        {
          "name": "classifyTossErrorCode",
          "slug": "classify-toss-error-code",
          "kind": "function",
          "declaration": "/** 미등록 코드 → UNKNOWN + 비재시도(보수 판정). 원문 code/message/httpStatus는 호출부가 무손실 보존한다. */\ndeclare function classifyTossErrorCode(code: string): ErrorCodeClassification;",
          "sourceDocumentation": "미등록 코드 → UNKNOWN + 비재시도(보수 판정). 원문 code/message/httpStatus는 호출부가 무손실 보존한다."
        },
        {
          "name": "compareLedgerRefund",
          "slug": "compare-ledger-refund",
          "kind": "function",
          "declaration": "/**\n * Compares a provider payment-state snapshot against the app ledger's cumulative refund\n * target — \"has the provider confirmed the refunds my ledger claims?\".\n *\n * Expressed purely in provider-snapshot terms: `snapshot.canceledAmount`\n * (`totalAmount - balanceAmount` at summarize time) is the provider's current cumulative\n * canceled amount, and `pendingCancelAmount` is the sum of `cancelAmount` over\n * `cancelStatus: 'IN_PROGRESS'` transactions. Per the kit's Phase-0 field measurements\n * (and the cancel path's own 2xx validation), an accepted async cancel already shows the\n * reduced balance while `IN_PROGRESS` — so pending amounts are *inside* `canceledAmount`,\n * and an aborted cancel takes the balance back up. `'settled'` therefore additionally\n * requires that nothing is in flight; see {@link LedgerRefundComparison} for the exact\n * three-way semantics. The ledger target stays app-owned — see {@link LedgerRefundTarget}.\n * Both the branded and the serialized snapshot forms are accepted; the ids play no part in\n * the verdict, so no re-branding is required. Comparing a snapshot of the *wrong payment*\n * against a ledger target is a caller-side identity error this helper cannot detect.\n *\n * Mapping to an app-side `SUCCEEDED / UNCONFIRMED / MISMATCH` three-way: `'settled'` maps\n * to succeeded, but the kit's `'unconfirmed'` is strictly the in-flight-cancel case — an\n * app-style \"the cancel request likely never reached the provider, replay the sealed\n * request\" state surfaces here as `'mismatch'` / `'provider-below-ledger'`. Pass\n * {@link LedgerRefundTarget.requestedAmount} to have that case labelled\n * `shortfall: 'at-prior-state'` (vs `'unexplained'`); do not map the three kit names 1:1\n * onto an app's replay policy without it.\n *\n * Honesty rule for broken inputs: when the snapshot's amounts are untrustworthy\n * (`invalid-amount`/`balance-exceeds-total` issues, or a `canceledAmount` that is not a\n * non-negative safe integer), the verdict is `'mismatch'` with\n * `direction: 'indeterminate'` and the gating issues attached. Deliberately *not*\n * reproduced: the \"status CANCELED with only totalAmount valid ⇒ assume fully refunded\"\n * fallback some reconciliation paths use — that is a guess, and settling a ledger on it\n * belongs to the app's explicit policy, not a library default.\n */\ndeclare function compareLedgerRefund(snapshot: PaymentStateSnapshot | SerializedPaymentStateSnapshot, ledger: LedgerRefundTarget): LedgerRefundComparison;",
          "sourceDocumentation": "Compares a provider payment-state snapshot against the app ledger's cumulative refund\ntarget — \"has the provider confirmed the refunds my ledger claims?\".\n\nExpressed purely in provider-snapshot terms: `snapshot.canceledAmount`\n(`totalAmount - balanceAmount` at summarize time) is the provider's current cumulative\ncanceled amount, and `pendingCancelAmount` is the sum of `cancelAmount` over\n`cancelStatus: 'IN_PROGRESS'` transactions. Per the kit's Phase-0 field measurements\n(and the cancel path's own 2xx validation), an accepted async cancel already shows the\nreduced balance while `IN_PROGRESS` — so pending amounts are *inside* `canceledAmount`,\nand an aborted cancel takes the balance back up. `'settled'` therefore additionally\nrequires that nothing is in flight; see {@link LedgerRefundComparison} for the exact\nthree-way semantics. The ledger target stays app-owned — see {@link LedgerRefundTarget}.\nBoth the branded and the serialized snapshot forms are accepted; the ids play no part in\nthe verdict, so no re-branding is required. Comparing a snapshot of the *wrong payment*\nagainst a ledger target is a caller-side identity error this helper cannot detect.\n\nMapping to an app-side `SUCCEEDED / UNCONFIRMED / MISMATCH` three-way: `'settled'` maps\nto succeeded, but the kit's `'unconfirmed'` is strictly the in-flight-cancel case — an\napp-style \"the cancel request likely never reached the provider, replay the sealed\nrequest\" state surfaces here as `'mismatch'` / `'provider-below-ledger'`. Pass\n{@link LedgerRefundTarget.requestedAmount} to have that case labelled\n`shortfall: 'at-prior-state'` (vs `'unexplained'`); do not map the three kit names 1:1\nonto an app's replay policy without it.\n\nHonesty rule for broken inputs: when the snapshot's amounts are untrustworthy\n(`invalid-amount`/`balance-exceeds-total` issues, or a `canceledAmount` that is not a\nnon-negative safe integer), the verdict is `'mismatch'` with\n`direction: 'indeterminate'` and the gating issues attached. Deliberately *not*\nreproduced: the \"status CANCELED with only totalAmount valid ⇒ assume fully refunded\"\nfallback some reconciliation paths use — that is a guess, and settling a ledger on it\nbelongs to the app's explicit policy, not a library default."
        },
        {
          "name": "ConfirmedPayment",
          "slug": "confirmed-payment",
          "kind": "type",
          "declaration": "/**\n * 승인 API가 직접 반환한 결제.\n *\n * 일반 조회의 가상계좌 `secret`은 `null`일 수 있지만, confirm 성공으로 반환하는 가상계좌는\n * DEPOSIT_CALLBACK을 검증할 non-empty secret을 반드시 포함한다. 이 좁힘은 2xx 응답의\n * runtime 검사 뒤에만 부여된다.\n */\ntype ConfirmedPayment = (Exclude<Payment, VirtualAccountPayment> & {\n    readonly status: ConfirmedStatus;\n}) | (VirtualAccountPayment & {\n    readonly status: ConfirmedStatus;\n    readonly secret: string;\n});",
          "sourceDocumentation": "승인 API가 직접 반환한 결제.\n\n일반 조회의 가상계좌 `secret`은 `null`일 수 있지만, confirm 성공으로 반환하는 가상계좌는\nDEPOSIT_CALLBACK을 검증할 non-empty secret을 반드시 포함한다. 이 좁힘은 2xx 응답의\nruntime 검사 뒤에만 부여된다."
        },
        {
          "name": "ConfirmedWithoutDepositSecret",
          "slug": "confirmed-without-deposit-secret",
          "kind": "type",
          "declaration": "/**\n * confirm 응답에서 secret을 받지 못했고, 조회가 결제 자체는 확인한 가상계좌.\n *\n * 결제를 실패로 처리하거나 재confirm하면 안 되지만, DEPOSIT_CALLBACK을 안전하게 검증할\n * 비밀값도 복구할 수 없다. 호출자는 주문을 보류하고 운영 복구 경로로 보내야 한다.\n */\ntype ConfirmedWithoutDepositSecret = VirtualAccountPayment & {\n    readonly status: ConfirmedStatus;\n    readonly secret: null;\n};",
          "sourceDocumentation": "confirm 응답에서 secret을 받지 못했고, 조회가 결제 자체는 확인한 가상계좌.\n\n결제를 실패로 처리하거나 재confirm하면 안 되지만, DEPOSIT_CALLBACK을 안전하게 검증할\n비밀값도 복구할 수 없다. 호출자는 주문을 보류하고 운영 복구 경로로 보내야 한다."
        },
        {
          "name": "ConfirmError",
          "slug": "confirm-error",
          "kind": "type",
          "declaration": "type ConfirmError = TossApiFailure<ConfirmErrorCode> | TransportFailure | {\n    readonly source: 'library';\n    readonly kind: 'approval-window-exceeded';\n    readonly deadline: Date;\n    readonly now: Date;\n};"
        },
        {
          "name": "ConfirmErrorCode",
          "slug": "confirm-error-code",
          "kind": "type",
          "declaration": "type ConfirmErrorCode = 'ALREADY_PROCESSED_PAYMENT'\n/** 인증 후 10분 초과 404 — 재시도 불가한 최종 실패(결제 재요청 필요). */\n | 'NOT_FOUND_PAYMENT_SESSION' | 'PAY_PROCESS_ABORTED' | 'INVALID_REQUEST' | 'INVALID_PAYMENT_KEY' | 'REJECT_CARD_PAYMENT' | 'PROVIDER_ERROR' | 'UNAUTHORIZED_KEY' | 'INVALID_API_KEY' | 'FORBIDDEN_REQUEST' | 'NOT_FOUND_PAYMENT' | (string & {});"
        },
        {
          "name": "ConfirmFlow",
          "slug": "confirm-flow",
          "kind": "interface",
          "declaration": "interface ConfirmFlow<E extends Env> {\n    /** 검증 + store.saveOrder까지 완료된 뒤에만 Ok — 금액을 저장 시점에 고정. */\n    createOrder(input: {\n        amount: number;\n        /** ≤100자 precheck. */\n        orderName: string;\n        /** 생략 시 generateOrderId(). */\n        orderId?: OrderId;\n        /** 기본 'KRW'. */\n        currency?: 'KRW' | 'USD' | 'JPY';\n    }): Promise<Result<PendingOrder, CreateOrderError>>;\n    /** 저장 주문 로드 → amount 일치 → 시한 검증. 통과해야만 VerifiedCheckout. */\n    verify(callback: UnverifiedCallback): Promise<Result<VerifiedCheckout, VerifyCheckoutError>>;\n    /**\n     * VerifiedCheckout만 받는다 — UnverifiedCallback은 컴파일 에러.\n     * 멱등키는 일급 옵션이며 **기본 미부착**(§7 확정 5 — 에러 응답 멱등 재생 여부 미실측이라 보수적).\n     */\n    confirm(checkout: VerifiedCheckout, options?: CallOptions<E>): Promise<Result<ConfirmedPayment, ConfirmError>>;\n    /** 원스톱: parse → verify → confirm. 검증을 생략이 아니라 내장 — 단계별 에러가 union으로 구분된다. */\n    confirmCallback(input: CallbackQueryInput, options?: CallOptions<E>): Promise<Result<ConfirmedPayment, CallbackParseError | VerifyCheckoutError | ConfirmError>>;\n    /**\n     * §3.7 {@link resolveConfirmFailure}의 플로우 결합판 — 플로우의 client를 재사용한다.\n     * 일반적인 가상계좌 조회는 secret을 반환하지 않으므로\n     * `confirmed-without-deposit-secret`을 주문 보류/운영 복구로 처리해야 한다. provider가\n     * 예외적으로 secret을 보존한 `actually-confirmed`일 때만 §3.1 저장 경로를 재사용한다.\n     *\n     * ⚠ 조회 Err = 진실 미확정 — 성공/실패 어느 쪽으로도 사용자에게 단정 안내하지 말 것.\n     */\n    resolveFailure(orderId: OrderId, error: ConfirmError): Promise<Result<ConfirmResolution, LookupError$1>>;\n}"
        },
        {
          "name": "ConfirmFlowOptions",
          "slug": "confirm-flow-options",
          "kind": "interface",
          "declaration": "interface ConfirmFlowOptions {\n    /**\n     * 기본 10분(600_000ms). Phase 0 확정: 인증 완료(successUrl 리다이렉트) 후 10분,\n     * 초과 시 상태 EXPIRED → confirm은 404 NOT_FOUND_PAYMENT_SESSION(재시도 불가 최종 실패).\n     * 30분은 결제창 실행(READY)→구매자 인증 구간의 별개 시한 — 라이브러리 통제 밖.\n     */\n    /** 1~600_000ms의 안전한 정수. provider 승인 시한(10분)을 넘겨 local 검증을 느슨하게 할 수 없다. */\n    readonly approvalWindowMs?: number;\n    readonly clock?: () => Date;\n    /**\n     * §3.1 가상계좌 secret 자동 저장 — confirm/confirmCallback이 Ok이고\n     * **`payment.method === '가상계좌'`일 때만** saveSecret을 await 호출한다.\n     *\n     * method 가드 근거(Phase 5 실측): BILLING 카드 결제 응답에도 secret이 non-null로\n     * 내려온다 — secret 존재로 판정하면 빌링 결제마다 무의미한 저장이 발생한다.\n     *\n     * 저장 실패여도 confirm은 **Ok 유지**(협상 불가) — 승인은 토스 측에서 이미 완결이라\n     * Err로 뒤집으면 \"승인됐는데 실패 처리 + 사용자 재confirm\"이라는 더 큰 사고가 된다.\n     * 가상계좌 secret은 승인 응답에서만 얻을 수 있고 조회로 복구할 수 없으므로, 저장 실패는\n     * 반드시 운영 알림·재처리 대상으로 남겨야 한다. 실패 통지:\n     * {@link onDepositSecretSaveFailed} + 'deposit.secret-save-failed' 이벤트.\n     */\n    readonly depositSecrets?: DepositSecretStore;\n    /**\n     * saveSecret 실패 통지 — payload에 **secret 원문 미포함**(로그 유출 방지).\n     * 가상계좌 secret은 조회로 복구할 수 없으므로, 호출자는 결제를 보류하고 승인 응답 원문을\n     * 노출하지 않는 운영 복구 절차를 통해 처리해야 한다.\n     * 미지정 시 실패 1건당 console.warn 1회(라이브러리에서 유일하게 시끄러운 기본값 —\n     * 침묵 유실 방지: 저장 누락 = 해당 주문의 DEPOSIT_CALLBACK 전부 unknown-order 거부).\n     * 이 콜백의 throw는 삼켜진다(Ok 확정 결과 무간섭).\n     */\n    readonly onDepositSecretSaveFailed?: (info: {\n        readonly orderId: OrderId;\n        readonly paymentKey: PaymentKey;\n        readonly cause: unknown;\n    }) => void;\n    /**\n     * §3.3 이벤트 버스 — 'payment.confirmed' / 'payment.confirm-failed' /\n     * 'deposit.secret-saved' / 'deposit.secret-save-failed' 발행 지점.\n     * createTossEvents 산출물만 발행이 흐른다(구조적 모조 객체는 no-op).\n     */\n    readonly events?: TossEvents;\n}"
        },
        {
          "name": "confirmPendingAuth",
          "slug": "confirm-pending-auth",
          "kind": "function",
          "declaration": "/**\n * 세션에 저장된 customerKey와 대조 — 통과해야만 AuthKeyReceived.\n * 이 단계를 건너뛰고 issue를 호출할 방법이 없다 (쿼리 값은 위변조 가능 — 문서도\n * \"검증 후 발급 API 호출\"을 요구).\n */\ndeclare function confirmPendingAuth(pending: PendingBillingAuth, expectedCustomerKey: CustomerKey): Result<AuthKeyReceived, {\n    readonly source: 'library';\n    readonly kind: 'customer-key-mismatch';\n    readonly expected: string;\n    readonly returned: string;\n}>;",
          "sourceDocumentation": "세션에 저장된 customerKey와 대조 — 통과해야만 AuthKeyReceived.\n이 단계를 건너뛰고 issue를 호출할 방법이 없다 (쿼리 값은 위변조 가능 — 문서도\n\"검증 후 발급 API 호출\"을 요구)."
        },
        {
          "name": "ConfirmResolution",
          "slug": "confirm-resolution",
          "kind": "type",
          "declaration": "/**\n * §3.7 confirm 실패 복구·안내 3분기 — \"confirm Err ≠ 결제 실패\"(G8).\n *\n * transport 실패(승인됐는데 응답 유실)·ALREADY_PROCESSED_PAYMENT(새로고침 이중 confirm)를\n * 일괄 실패 처리하면 \"돈은 나갔는데 실패 안내\"라는 최악의 CS 사고가 난다 — 조회로 진실을\n * 확정한 뒤 분기하라.\n */\ntype ConfirmResolution = \n/** 조회로 DONE|WAITING_FOR_DEPOSIT 및 (가상계좌라면) secret까지 확인됨. */\n{\n    readonly resolution: 'actually-confirmed';\n    readonly payment: ConfirmedPayment;\n}\n/** 결제는 확인됐지만 가상계좌 secret은 lookup으로 복구할 수 없다 — 주문 보류/운영 복구. */\n | {\n    readonly resolution: 'confirmed-without-deposit-secret';\n    readonly payment: ConfirmedWithoutDepositSecret;\n}\n/** NOT_FOUND_PAYMENT_SESSION(10분 초과) 등 — 결제 재요청 유도. */\n | {\n    readonly resolution: 'retry-payment';\n}\n/** 조회로도 미승인 확정. */\n | {\n    readonly resolution: 'definitively-failed';\n    readonly error: ConfirmError;\n};",
          "sourceDocumentation": "§3.7 confirm 실패 복구·안내 3분기 — \"confirm Err ≠ 결제 실패\"(G8).\n\ntransport 실패(승인됐는데 응답 유실)·ALREADY_PROCESSED_PAYMENT(새로고침 이중 confirm)를\n일괄 실패 처리하면 \"돈은 나갔는데 실패 안내\"라는 최악의 CS 사고가 난다 — 조회로 진실을\n확정한 뒤 분기하라."
        },
        {
          "name": "createBillingFlow",
          "slug": "create-billing-flow",
          "kind": "function",
          "declaration": "/**\n * 빌링 플로우 팩토리 — client는 'api' KeyKind 전용(위젯 키 클라이언트는 컴파일 에러),\n * store는 필수(조회 API가 없어 저장이 유일한 보관 수단).\n */\ndeclare function createBillingFlow<E extends Env, C extends BillingCapabilities = {}>(client: TossServerClient<E, 'api'>, store: BillingKeyStore, options?: {\n    readonly capabilities?: C;\n    /**\n     * §3.3 이벤트 버스 — billing.issued/approved/approve-failed/revoked 발행 지점.\n     * payload에 billingKey는 원천 부재(봉인 원칙 유지). createTossEvents 산출물만 발행이 흐른다.\n     */\n    readonly events?: TossEvents;\n}): BillingFlow<E, C>;",
          "sourceDocumentation": "빌링 플로우 팩토리 — client는 'api' KeyKind 전용(위젯 키 클라이언트는 컴파일 에러),\nstore는 필수(조회 API가 없어 저장이 유일한 보관 수단)."
        },
        {
          "name": "createConfirmFlow",
          "slug": "create-confirm-flow",
          "kind": "function",
          "declaration": "declare function createConfirmFlow<E extends Env>(client: TossServerClient<E, KeyKind>, store: OrderStore, options?: ConfirmFlowOptions): ConfirmFlow<E>;"
        },
        {
          "name": "createCustomRefundPolicy",
          "slug": "create-custom-refund-policy",
          "kind": "function",
          "declaration": "/** 프로젝트 고유 규칙을 같은 검증·반올림·quote 계약에 연결하는 escape hatch. */\ndeclare function createCustomRefundPolicy<Context>(config: CustomRefundPolicyConfig<Context>): Result<RefundPolicy<CustomRefundQuoteInput<Context>>, RefundPolicyConfigError>;",
          "sourceDocumentation": "프로젝트 고유 규칙을 같은 검증·반올림·quote 계약에 연결하는 escape hatch."
        },
        {
          "name": "createFileAuditSink",
          "slug": "create-file-audit-sink",
          "kind": "function",
          "declaration": "/**\n * createFileAuditSink — JSONL append 파일 싱크 참조 구현 (설계 §3.2).\n *\n * 코어 \"런타임 의존성 0·플랫폼 중립\" 원칙과의 공존:\n * - `node:fs/promises`는 **최초 record 시 지연 동적 import** — 정적 `node:` import가 없어\n *   번들에 node 의존이 각인되지 않는다(tsup `external: [/^node:/]` + platform neutral 유지).\n * - Edge에서 createFileAuditSink를 호출하지 않는 한 \"./server\"의 Edge 호환은 불변이다.\n *\n * 운영 계약:\n * - Promise 체이닝 직렬화 큐 — record 호출 순서 = 파일 append 순서(엔트리 교차 없음).\n *   한 append의 실패는 그 record의 반환 Promise로만 전파되고 큐는 계속 살아있다.\n * - fire-and-forget(클라이언트가 await하지 않음)이라 프로세스 즉사 시 마지막 엔트리가\n *   유실될 수 있다 — `flush()`/`close()`를 graceful shutdown 훅에 연결하라.\n * - ⚠ 다중 프로세스 병행 쓰기 무방비 — 단일 인스턴스(프로세스당 파일 1개) 전제.\n */\ndeclare function createFileAuditSink(filePath: string, options?: {\n    /** 엔트리 → 1행 문자열(개행 미포함). 기본 JSONL 1행(JSON.stringify). */\n    readonly formatter?: (entry: AuditEntry) => string;\n}): AuditSink & {\n    flush(): Promise<void>;\n    close(): Promise<void>;\n};",
          "sourceDocumentation": "createFileAuditSink — JSONL append 파일 싱크 참조 구현 (설계 §3.2).\n\n코어 \"런타임 의존성 0·플랫폼 중립\" 원칙과의 공존:\n- `node:fs/promises`는 **최초 record 시 지연 동적 import** — 정적 `node:` import가 없어\n  번들에 node 의존이 각인되지 않는다(tsup `external: [/^node:/]` + platform neutral 유지).\n- Edge에서 createFileAuditSink를 호출하지 않는 한 \"./server\"의 Edge 호환은 불변이다.\n\n운영 계약:\n- Promise 체이닝 직렬화 큐 — record 호출 순서 = 파일 append 순서(엔트리 교차 없음).\n  한 append의 실패는 그 record의 반환 Promise로만 전파되고 큐는 계속 살아있다.\n- fire-and-forget(클라이언트가 await하지 않음)이라 프로세스 즉사 시 마지막 엔트리가\n  유실될 수 있다 — `flush()`/`close()`를 graceful shutdown 훅에 연결하라.\n- ⚠ 다중 프로세스 병행 쓰기 무방비 — 단일 인스턴스(프로세스당 파일 1개) 전제."
        },
        {
          "name": "CreateOrderError",
          "slug": "create-order-error",
          "kind": "type",
          "declaration": "type CreateOrderError = {\n    readonly source: 'library';\n    readonly kind: 'invalid-input';\n    readonly field: string;\n    readonly reason: string;\n} | {\n    readonly source: 'library';\n    readonly kind: 'store-failure';\n    readonly operation: 'save';\n    readonly cause: unknown;\n};"
        },
        {
          "name": "createRefundPolicy",
          "slug": "create-refund-policy",
          "kind": "function",
          "declaration": "/** 내장 정책 생성. 설정 오류는 부팅 시 orThrow로 처리할 수 있도록 Result로 반환한다. */\ndeclare function createRefundPolicy<const Config extends BuiltInRefundPolicyConfig>(config: Config): Result<RefundPolicy<QuoteInputFor<Config>>, RefundPolicyConfigError>;",
          "sourceDocumentation": "내장 정책 생성. 설정 오류는 부팅 시 orThrow로 처리할 수 있도록 Result로 반환한다."
        },
        {
          "name": "createTossClient",
          "slug": "create-toss-client",
          "kind": "function",
          "declaration": "/**\n * 오버로드로 키 종류가 각인된다 — 위젯 상점의 confirm은 gsk 필수(키 쌍 규칙, 불일치 시\n * INVALID_API_KEY — 400인 점 주의). 빌링 플로우는 'api' KeyKind만 받는다.\n */\ndeclare function createTossClient<E extends Env>(key: ApiSecretKey<E>, options?: TossClientOptions): TossServerClient<E, 'api'>;\n\ndeclare function createTossClient<E extends Env>(key: WidgetSecretKey<E>, options?: TossClientOptions): TossServerClient<E, 'widget'>;",
          "sourceDocumentation": "오버로드로 키 종류가 각인된다 — 위젯 상점의 confirm은 gsk 필수(키 쌍 규칙, 불일치 시\nINVALID_API_KEY — 400인 점 주의). 빌링 플로우는 'api' KeyKind만 받는다."
        },
        {
          "name": "createTossEvents",
          "slug": "create-toss-events",
          "kind": "function",
          "declaration": "/**\n * 이벤트 버스 생성 — 각 배선 지점(TossClientOptions.events 등)에 주입한다.\n *\n * ⚠ 발행은 createTossEvents 산출물에만 흐른다 — 구조적으로 흉내 낸 사용자 객체를 주입하면\n * 구독 표면으로는 동작하지 않고 발행 지점이 조용히 no-op이 된다(내부 emit 계층 부재).\n */\ndeclare function createTossEvents(options?: {\n    /** 핸들러 예외 통지. 기본 무시 — 이 콜백의 throw도 삼켜진다. */\n    readonly onHandlerError?: (info: {\n        readonly type: TossEventName;\n        readonly cause: unknown;\n    }) => void;\n}): TossEvents;",
          "sourceDocumentation": "이벤트 버스 생성 — 각 배선 지점(TossClientOptions.events 등)에 주입한다.\n\n⚠ 발행은 createTossEvents 산출물에만 흐른다 — 구조적으로 흉내 낸 사용자 객체를 주입하면\n구독 표면으로는 동작하지 않고 발행 지점이 조용히 no-op이 된다(내부 emit 계층 부재)."
        },
        {
          "name": "createTossPayments",
          "slug": "create-toss-payments",
          "kind": "function",
          "declaration": "/**\n * 파사드 팩토리 — 오버로드 2종(API 키 / 위젯 키). 위젯 키 + `billingKeys`는 어느\n * 오버로드도 충족하지 못해 컴파일 에러다(키 쌍 규칙 선차단).\n *\n * ⚠ config를 스프레드로 동적 구성하면 `const` 추론이 풀려 조건부 프로퍼티 판정이\n * 무너질 수 있다 — 동적 구성이 필요하면 기존 개별 팩토리 4종을 직접 사용하라.\n * forRootAsync 등 간접 전달에는 {@link defineTossPaymentsConfig}로 추론을 고정하라.\n */\ndeclare function createTossPayments<E extends Env, const C extends TossPaymentsApiConfig<E>>(config: C & {\n    readonly secretKey: ApiSecretKey<E>;\n}): TossPaymentsKit<E, 'api', C>;\n\ndeclare function createTossPayments<E extends Env, const C extends TossPaymentsWidgetConfig<E>>(config: C & {\n    readonly secretKey: WidgetSecretKey<E>;\n}): TossPaymentsKit<E, 'widget', C>;",
          "sourceDocumentation": "파사드 팩토리 — 오버로드 2종(API 키 / 위젯 키). 위젯 키 + `billingKeys`는 어느\n오버로드도 충족하지 못해 컴파일 에러다(키 쌍 규칙 선차단).\n\n⚠ config를 스프레드로 동적 구성하면 `const` 추론이 풀려 조건부 프로퍼티 판정이\n무너질 수 있다 — 동적 구성이 필요하면 기존 개별 팩토리 4종을 직접 사용하라.\nforRootAsync 등 간접 전달에는 {@link defineTossPaymentsConfig}로 추론을 고정하라."
        },
        {
          "name": "customerKey",
          "slug": "customer-key",
          "kind": "function",
          "declaration": "declare function customerKey(raw: string): Result<CustomerKey, InvalidInput<'customerKey'>>;"
        },
        {
          "name": "CustomerKey",
          "slug": "customer-key--type",
          "kind": "type",
          "declaration": "/**\n * 고객 키 — 2–300자, `^[A-Za-z0-9\\-_=.@]+$`.\n *\n * Phase 0 실측(2026-08-09): 토스 서버는 사실상 검증하지 않는다 —\n * 301자는 400이 아닌 **500 FAILED_DB_PROCESSING**, `\"bad key!\"`(공백+허용 외 문자)도 200.\n * 따라서 이 생성자가 실질 방어선이다. \"특수문자 최소 1개\" 문구는 허용 집합\n * 나열로 확인됐으므로(순수 영숫자 200 통과) 특수문자 필수 검증은 하지 않는다.\n */\ntype CustomerKey = string & Brand<'CustomerKey'>;",
          "sourceDocumentation": "고객 키 — 2–300자, `^[A-Za-z0-9\\-_=.@]+$`.\n\nPhase 0 실측(2026-08-09): 토스 서버는 사실상 검증하지 않는다 —\n301자는 400이 아닌 **500 FAILED_DB_PROCESSING**, `\"bad key!\"`(공백+허용 외 문자)도 200.\n따라서 이 생성자가 실질 방어선이다. \"특수문자 최소 1개\" 문구는 허용 집합\n나열로 확인됐으므로(순수 영숫자 200 통과) 특수문자 필수 검증은 하지 않는다."
        },
        {
          "name": "CustomRefundPolicyConfig",
          "slug": "custom-refund-policy-config",
          "kind": "interface",
          "declaration": "interface CustomRefundPolicyConfig<Context> extends RefundPolicyIdentity {\n    readonly kind: \"custom\";\n    readonly rounding: RefundRoundingMode;\n    /** throw 대신 Result 실패를 사용한다. throw도 라이브러리가 포착해 quote 오류로 바꾼다. */\n    readonly calculate: (input: CustomRefundQuoteInput<Context>) => Result<RefundEntitlement, unknown>;\n}"
        },
        {
          "name": "CustomRefundQuoteInput",
          "slug": "custom-refund-quote-input",
          "kind": "interface",
          "declaration": "interface CustomRefundQuoteInput<Context> extends RefundQuoteInput {\n    readonly context: Context;\n}"
        },
        {
          "name": "DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS",
          "slug": "default-idempotency-replay-window-ms",
          "kind": "constant",
          "declaration": "DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS: number",
          "sourceDocumentation": "Conservative replay window used by {@link isWithinIdempotencyReplayWindow} when no explicit\nwindow is given: **14 days** — one full day of margin below {@link TOSS_IDEMPOTENCY_KEY_TTL_MS}.\n\nTwo regimes split at this boundary:\n\n- **Replay within the window** — resending the *same* key with the *same* body is safe: if the\n  first request reached Toss, the original response is replayed byte-for-byte and nothing is\n  executed twice; if it never arrived, it runs once now.\n- **New attempt after the window** — the same key may be executed as a brand-new request.\n  Do **not** resubmit. The only safe automatic action is to look the outcome up\n  (`getPaymentByOrderId` / `getPayment`) and decide from the durable state; a genuinely new\n  charge needs a new key (a new `attempt`) and an explicit decision.\n\nWhy a day of margin: the provider states the window at day granularity (\"15 days from first\nuse\") without specifying the boundary or time zone, so the real cutoff may land earlier than\n15 × 24 h after the first request; and the caller's clock and the provider's clock drift. A\nwhole day absorbs both without guessing. The precondition this relies on is that `issuedAt`\nwas recorded **no later than** the first network attempt — then it is a lower bound on the\nprovider's first-use time and a window measured from it can only be conservative. The\nlibrary's own `CancelRetryTicket` expires on this same 14-day window."
        },
        {
          "name": "DEFAULT_REFUND_QUOTE_TTL_MS",
          "slug": "default-refund-quote-ttl-ms",
          "kind": "constant",
          "declaration": "DEFAULT_REFUND_QUOTE_TTL_MS: number",
          "sourceDocumentation": "별도 경계가 없는 견적도 무기한 실행되지 않도록 하는 기본 수명(5분)."
        },
        {
          "name": "defineTossPaymentsConfig",
          "slug": "define-toss-payments-config",
          "kind": "function",
          "declaration": "/**\n * forRootAsync 등 간접 전달에서 `const` 추론을 고정하는 identity — 타입 보존용 (설계 §4 필수 사용).\n *\n * 팩토리 함수가 config를 반환하는 경로(NestJS useFactory 등)에서는 리터럴 추론이 풀려\n * 조건부 프로퍼티 판정이 무너질 수 있다 — 이 함수로 감싸 정의 시점에 타입을 고정하고,\n * `typeof` 로 배선 판정이 보존된 config 타입을 재사용하라.\n */\ndeclare function defineTossPaymentsConfig<E extends Env, const C extends TossPaymentsApiConfig<E> | TossPaymentsWidgetConfig<E>>(config: C): C;",
          "sourceDocumentation": "forRootAsync 등 간접 전달에서 `const` 추론을 고정하는 identity — 타입 보존용 (설계 §4 필수 사용).\n\n팩토리 함수가 config를 반환하는 경로(NestJS useFactory 등)에서는 리터럴 추론이 풀려\n조건부 프로퍼티 판정이 무너질 수 있다 — 이 함수로 감싸 정의 시점에 타입을 고정하고,\n`typeof` 로 배선 판정이 보존된 config 타입을 재사용하라."
        },
        {
          "name": "DepositedVaCancelable",
          "slug": "deposited-va-cancelable",
          "kind": "type",
          "declaration": "type DepositedVaCancelable = (DepositedVaCancelableBase & {\n    readonly partialAllowed: true;\n    readonly payment: DepositedVaCancelableBase['payment'] & {\n        readonly isPartialCancelable: true;\n    };\n}) | (DepositedVaCancelableBase & {\n    readonly partialAllowed: false;\n    readonly payment: DepositedVaCancelableBase['payment'] & {\n        readonly isPartialCancelable: false;\n    };\n});"
        },
        {
          "name": "DepositedVirtualAccountRefundRequest",
          "slug": "deposited-virtual-account-refund-request",
          "kind": "interface",
          "declaration": "interface DepositedVirtualAccountRefundRequest {\n    readonly reason: CancelReason;\n    readonly refundAccount: RefundAccount;\n    readonly taxFreeAmount?: number;\n    readonly cancelRequestId?: CancelRequestId;\n    readonly currency?: never;\n}"
        },
        {
          "name": "DepositSecretStore",
          "slug": "deposit-secret-store",
          "kind": "interface",
          "declaration": "/**\n * 가상계좌 secret 저장소 — 웹훅 `DepositSecretSource`(getSecret)의 상위 타입 (설계 §3.1, G1).\n *\n * 한 객체로 confirm측 자동 저장(`ConfirmFlowOptions.depositSecrets`) + 웹훅측 대조\n * (`WebhookVerifierConfig.depositSecrets`) 양쪽을 1회 배선한다 — 저장(README 수동 한 줄)과\n * 조회(웹훅 config)가 다른 파일에 흩어져 저장 누락 → DEPOSIT_CALLBACK 전부 unknown-order\n * 거부가 되는 사고를 구조로 막는다.\n */\ninterface DepositSecretStore extends DepositSecretSource {\n    /**\n     * upsert 시맨틱 계약 — 기존 수동 저장과 병용해도 이중 저장이 무해해야 한다.\n     * (getSecret(orderId): Promise<string | null>은 DepositSecretSource에서 상속 —\n     * 기존 WebhookVerifierConfig.depositSecrets에 그대로 전달 가능, 파괴 없음.)\n     */\n    saveSecret(orderId: OrderId, secret: string): Promise<void>;\n}",
          "sourceDocumentation": "가상계좌 secret 저장소 — 웹훅 `DepositSecretSource`(getSecret)의 상위 타입 (설계 §3.1, G1).\n\n한 객체로 confirm측 자동 저장(`ConfirmFlowOptions.depositSecrets`) + 웹훅측 대조\n(`WebhookVerifierConfig.depositSecrets`) 양쪽을 1회 배선한다 — 저장(README 수동 한 줄)과\n조회(웹훅 config)가 다른 파일에 흩어져 저장 누락 → DEPOSIT_CALLBACK 전부 unknown-order\n거부가 되는 사고를 구조로 막는다."
        },
        {
          "name": "deriveIdempotencyKey",
          "slug": "derive-idempotency-key",
          "kind": "function",
          "declaration": "/**\n * Deterministically derives an `Idempotency-Key` from a logical operation identity.\n *\n * Format: `<operation>:<part>:<part>…` (segments joined by `:`; with no `parts` the key is just\n * `<operation>`), plus `#<attempt>` when `attempt` is given. Example:\n * `subscription_renewal:sub_01:1756652400000` and, for a new attempt,\n * `subscription_renewal:sub_01:1756652400000#7c9e…`. The same input always yields the same key,\n * so a crash-recovered worker reproduces the key it submitted before and gets Toss's replay\n * instead of a second execution.\n *\n * **The encoding is injective — distinct inputs never derive the same key.** Every segment\n * (`operation`, each element of `parts`, `attempt`) must be non-empty (`reason: 'empty'`) and\n * must consist of visible ASCII **excluding** the two delimiters `:` and `#`\n * (`reason: 'bad-charset'`). Because no segment can contain a delimiter, a key contains `#`\n * exactly once iff an attempt was given, and the prefix splits on `:` back into exactly\n * `operation` + `parts`. Underscores, dots, `@`, `=`, `-` and the like are fine, so the ids the\n * library already validates (`orderId`, `customerKey`, `cancelRequestId`, UUIDs, epoch strings)\n * all pass unchanged; ISO timestamps with `:` do not — use an epoch or a date-only marker.\n *\n * The assembled key then runs through the public {@link idempotencyKey} parser so the provider\n * length limit (1–300 chars, otherwise 400 `INVALID_IDEMPOTENCY_KEY` — `reason: 'too-long'`) and\n * the header-safe charset are enforced in exactly one place: an `Ok` result is always sendable.\n *\n * This is an **explicit** helper: the library never derives keys behind your back, because a\n * deterministic key combined with 4xx replay is a trap the caller must consciously manage with\n * the `attempt` field.\n */\ndeclare function deriveIdempotencyKey(input: DeriveIdempotencyKeyInput): Result<IdempotencyKey, InvalidInput<'idempotencyKey'>>;",
          "sourceDocumentation": "Deterministically derives an `Idempotency-Key` from a logical operation identity.\n\nFormat: `<operation>:<part>:<part>…` (segments joined by `:`; with no `parts` the key is just\n`<operation>`), plus `#<attempt>` when `attempt` is given. Example:\n`subscription_renewal:sub_01:1756652400000` and, for a new attempt,\n`subscription_renewal:sub_01:1756652400000#7c9e…`. The same input always yields the same key,\nso a crash-recovered worker reproduces the key it submitted before and gets Toss's replay\ninstead of a second execution.\n\n**The encoding is injective — distinct inputs never derive the same key.** Every segment\n(`operation`, each element of `parts`, `attempt`) must be non-empty (`reason: 'empty'`) and\nmust consist of visible ASCII **excluding** the two delimiters `:` and `#`\n(`reason: 'bad-charset'`). Because no segment can contain a delimiter, a key contains `#`\nexactly once iff an attempt was given, and the prefix splits on `:` back into exactly\n`operation` + `parts`. Underscores, dots, `@`, `=`, `-` and the like are fine, so the ids the\nlibrary already validates (`orderId`, `customerKey`, `cancelRequestId`, UUIDs, epoch strings)\nall pass unchanged; ISO timestamps with `:` do not — use an epoch or a date-only marker.\n\nThe assembled key then runs through the public {@link idempotencyKey } parser so the provider\nlength limit (1–300 chars, otherwise 400 `INVALID_IDEMPOTENCY_KEY` — `reason: 'too-long'`) and\nthe header-safe charset are enforced in exactly one place: an `Ok` result is always sendable.\n\nThis is an **explicit** helper: the library never derives keys behind your back, because a\ndeterministic key combined with 4xx replay is a trap the caller must consciously manage with\nthe `attempt` field."
        },
        {
          "name": "DeriveIdempotencyKeyInput",
          "slug": "derive-idempotency-key-input",
          "kind": "interface",
          "declaration": "/** Input for {@link deriveIdempotencyKey}. */\ninterface DeriveIdempotencyKeyInput {\n    /** Logical operation name, e.g. `'billing_initial_charge'` or `'subscription_renewal'`. */\n    readonly operation: string;\n    /**\n     * Identity of the logical business event — ids and period markers that make the key\n     * deterministic (subscription id, period start epoch, quote id, …). Never put raw\n     * billing/auth keys, card or account numbers here: the key travels in request headers and\n     * audit logs.\n     */\n    readonly parts: readonly string[];\n    /**\n     * Optional attempt discriminator. Omit it for the first submission; supply a fresh value\n     * (e.g. a UUID) for a *new* attempt after a definitive 4xx, because Toss replays the original\n     * 4xx for the same key for 15 days. Keep it **absent** when you intend a replay\n     * (transport failure, `IDEMPOTENT_REQUEST_PROCESSING`).\n     */\n    readonly attempt?: string | undefined;\n}",
          "sourceDocumentation": "Input for {@link deriveIdempotencyKey}."
        },
        {
          "name": "diffPaymentState",
          "slug": "diff-payment-state",
          "kind": "function",
          "declaration": "/**\n * 동일 결제의 두 상태 스냅샷을 비교한다.\n *\n * 어떤 status 전이도 거부하지 않는다. 식별자가 다를 때만 Err이며, 잔액 증가와 취소\n * transaction 제거는 성공 결과의 warnings로 전달한다.\n */\ndeclare function diffPaymentState(previous: PaymentStateSnapshot, next: PaymentStateSnapshot): Result<PaymentStateDiff, PaymentStateIdentityError>;",
          "sourceDocumentation": "동일 결제의 두 상태 스냅샷을 비교한다.\n\n어떤 status 전이도 거부하지 않는다. 식별자가 다를 때만 Err이며, 잔액 증가와 취소\ntransaction 제거는 성공 결과의 warnings로 전달한다."
        },
        {
          "name": "DirectCardIssueInput",
          "slug": "direct-card-issue-input",
          "kind": "interface",
          "declaration": "interface DirectCardIssueInput {\n    readonly customerKey: CustomerKey;\n    readonly cardNumber: string;\n    readonly cardExpirationYear: string;\n    readonly cardExpirationMonth: string;\n    /** 생년월일 YYMMDD 6자리 또는 사업자등록번호 10자리. */\n    readonly customerIdentityNumber: string;\n    /** 카드 비밀번호 앞 2자리 — ⚠ 절대 로그에 남기지 말 것. */\n    readonly cardPassword: string;\n    readonly customerName?: string;\n    readonly customerEmail?: string;\n}"
        },
        {
          "name": "EasyPayDetails",
          "slug": "easy-pay-details",
          "kind": "interface",
          "declaration": "interface EasyPayDetails {\n    readonly provider: string;\n    readonly amount: number;\n    readonly discountAmount: number;\n}"
        },
        {
          "name": "EasyPayPayment",
          "slug": "easy-pay-payment",
          "kind": "interface",
          "declaration": "interface EasyPayPayment extends PaymentBase {\n    readonly method: '간편결제';\n    readonly easyPay: EasyPayDetails;\n}"
        },
        {
          "name": "ElapsedTimeRefundBracket",
          "slug": "elapsed-time-refund-bracket",
          "kind": "interface",
          "declaration": "interface ElapsedTimeRefundBracket {\n    /** anchorAt부터 이 값 미만인 반열린 구간에 적용된다. 양수 밀리초. */\n    readonly untilMs: number;\n    /** 0..10,000 정수. */\n    readonly rateBps: number;\n    readonly reason?: string;\n}"
        },
        {
          "name": "ElapsedTimeRefundPolicyConfig",
          "slug": "elapsed-time-refund-policy-config",
          "kind": "interface",
          "declaration": "interface ElapsedTimeRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"elapsed-time-rate\";\n    /** untilMs가 엄격한 오름차순이어야 한다. 경계 시각은 다음 구간으로 넘어간다. */\n    readonly brackets: readonly ElapsedTimeRefundBracket[];\n    readonly fallbackRateBps: number;\n    readonly fallbackReason?: string;\n    readonly rounding: RefundRoundingMode;\n}"
        },
        {
          "name": "ElapsedTimeRefundQuoteInput",
          "slug": "elapsed-time-refund-quote-input",
          "kind": "interface",
          "declaration": "interface ElapsedTimeRefundQuoteInput extends RefundQuoteInput {\n    /** 경과시간 0의 기준 시각. evaluatedAt이 더 이르면 경과시간은 0으로 clamp한다. */\n    readonly anchorAt: Date;\n}"
        },
        {
          "name": "Env",
          "slug": "env",
          "kind": "type",
          "declaration": "/**\n * 키 4종 — 템플릿 리터럴(형식) × 브랜드(명목성) × EnvTag(test/live phantom).\n *\n * 이 모듈(\".\"에서 도달)은 **client key 파서만** export한다.\n * secret key 파서(parseApiSecretKey/parseWidgetSecretKey)는 server/keys.ts 전용 —\n * 브라우저 번들에서 시크릿 키 타입의 값을 제조할 방법 자체를 없애는 격리 규칙.\n */\ntype Env = 'test' | 'live';",
          "sourceDocumentation": "키 4종 — 템플릿 리터럴(형식) × 브랜드(명목성) × EnvTag(test/live phantom).\n\n이 모듈(\".\"에서 도달)은 **client key 파서만** export한다.\nsecret key 파서(parseApiSecretKey/parseWidgetSecretKey)는 server/keys.ts 전용 —\n브라우저 번들에서 시크릿 키 타입의 값을 제조할 방법 자체를 없애는 격리 규칙."
        },
        {
          "name": "EnvTag",
          "slug": "env-tag",
          "kind": "type",
          "declaration": "/**\n * test/live phantom 태그 — 런타임 표현 없음. `isTestKey`/`isLiveKey`로만 내로잉한다.\n * 상호 배타 축(EnvAxis)이라 `EnvTag<'test'> & EnvTag<'live'>`는 never로 붕괴한다 —\n * 술어 내로잉이 유니언에서 반대 env 멤버를 정확히 걸러내기 위한 구조 (brand.ts 참조).\n */\ntype EnvTag<E extends Env> = EnvAxis<E>;",
          "sourceDocumentation": "test/live phantom 태그 — 런타임 표현 없음. `isTestKey`/`isLiveKey`로만 내로잉한다.\n상호 배타 축(EnvAxis)이라 `EnvTag<'test'> & EnvTag<'live'>`는 never로 붕괴한다 —\n술어 내로잉이 유니언에서 반대 env 멤버를 정확히 걸러내기 위한 구조 (brand.ts 참조)."
        },
        {
          "name": "err",
          "slug": "err",
          "kind": "function",
          "declaration": "declare function err<E>(error: E): Err<E>;"
        },
        {
          "name": "Err",
          "slug": "err--interface",
          "kind": "interface",
          "declaration": "interface Err<out E> {\n    readonly ok: false;\n    readonly error: E;\n}"
        },
        {
          "name": "ErrorCategory",
          "slug": "error-category",
          "kind": "type",
          "declaration": "type ErrorCategory = 'STATE' | 'AMOUNT' | 'PARTIAL_NOT_ALLOWED' | 'DEADLINE' | 'ACCOUNT' | 'CONCURRENCY' | 'TRANSIENT' | 'AUTH' | 'NOT_FOUND' | 'REJECTED' | 'REQUEST' | 'UNKNOWN';"
        },
        {
          "name": "ErrorCodeClassification",
          "slug": "error-code-classification",
          "kind": "interface",
          "declaration": "interface ErrorCodeClassification {\n    readonly category: ErrorCategory;\n    readonly retryable: boolean;\n}"
        },
        {
          "name": "executeRefund",
          "slug": "execute-refund",
          "kind": "function",
          "declaration": "declare function executeRefund<E extends Env, K extends KeyKind>(client: TossServerClient<E, K>, attemptValue: RefundExecutionAttempt, runtimeOptionsValue?: RefundRuntimeOptions<E>): Promise<Result<CancelOutcome, RefundExecutionError>>;"
        },
        {
          "name": "FailCallbackResult",
          "slug": "fail-callback-result",
          "kind": "type",
          "declaration": "/** failUrl 파싱 — 사용자 취소는 에러가 아닌 별도 variant. */\ntype FailCallbackResult = {\n    readonly kind: 'user-canceled';\n    readonly code: 'PAY_PROCESS_CANCELED' | 'USER_CANCEL';\n    readonly orderId: OrderId | null;\n} | {\n    readonly kind: 'failed';\n    readonly code: string;\n    readonly message: string;\n    readonly orderId: OrderId | null;\n};",
          "sourceDocumentation": "failUrl 파싱 — 사용자 취소는 에러가 아닌 별도 variant."
        },
        {
          "name": "FullRefundPolicyConfig",
          "slug": "full-refund-policy-config",
          "kind": "interface",
          "declaration": "interface FullRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"full\";\n}"
        },
        {
          "name": "generateCustomerKey",
          "slug": "generate-customer-key",
          "kind": "function",
          "declaration": "/** `crypto.randomUUID()` — 36자 `[0-9a-f-]`로 위젯(≤50)·서버(≤300) 두 규격을 모두 만족한다. */\ndeclare function generateCustomerKey(): WidgetCustomerKey;",
          "sourceDocumentation": "`crypto.randomUUID()` — 36자 `[0-9a-f-]`로 위젯(≤50)·서버(≤300) 두 규격을 모두 만족한다."
        },
        {
          "name": "generateIdempotencyKey",
          "slug": "generate-idempotency-key",
          "kind": "function",
          "declaration": "/** `crypto.randomUUID()` — 36자로 300자 한도 내 항상 유효. */\ndeclare function generateIdempotencyKey(): IdempotencyKey;",
          "sourceDocumentation": "`crypto.randomUUID()` — 36자로 300자 한도 내 항상 유효."
        },
        {
          "name": "generateOrderId",
          "slug": "generate-order-id",
          "kind": "function",
          "declaration": "/**\n * 항상 유효한 OrderId 생성 — `${prefix}${epoch36}${rand}`.\n * 6–64자 보장: 코어(epoch36 8자 + 난수 10자 = 18자)가 하한을 채우고,\n * prefix는 허용 외 문자 제거 후 총 64자를 넘지 않게 절단한다.\n */\ndeclare function generateOrderId(prefix?: string): OrderId;",
          "sourceDocumentation": "항상 유효한 OrderId 생성 — `${prefix}${epoch36}${rand}`.\n6–64자 보장: 코어(epoch36 8자 + 난수 10자 = 18자)가 하한을 채우고,\nprefix는 허용 외 문자 제거 후 총 64자를 넘지 않게 절단한다."
        },
        {
          "name": "GiftCertificateDetails",
          "slug": "gift-certificate-details",
          "kind": "interface",
          "declaration": "interface GiftCertificateDetails {\n    readonly approveNo: string;\n    readonly settlementStatus: string;\n}"
        },
        {
          "name": "GiftCertificatePayment",
          "slug": "gift-certificate-payment",
          "kind": "interface",
          "declaration": "interface GiftCertificatePayment extends PaymentBase {\n    readonly method: '문화상품권' | '도서문화상품권' | '게임문화상품권';\n    readonly giftCertificate: GiftCertificateDetails;\n}"
        },
        {
          "name": "idempotencyKey",
          "slug": "idempotency-key",
          "kind": "function",
          "declaration": "/**\n * 멱등키 스마트 생성자 — 1–300자 + 헤더 안전 문자셋(`^[\\x21-\\x7E]+$`).\n * 한글·공백·CR/LF 등은 `reason: 'bad-charset'`. Ok이면 그 값은 어떤 fetch 구현에서도\n * `Idempotency-Key` 헤더로 바이트 동일하게 전송된다.\n */\ndeclare function idempotencyKey(raw: string): Result<IdempotencyKey, InvalidInput<'idempotencyKey'>>;",
          "sourceDocumentation": "멱등키 스마트 생성자 — 1–300자 + 헤더 안전 문자셋(`^[\\x21-\\x7E]+$`).\n한글·공백·CR/LF 등은 `reason: 'bad-charset'`. Ok이면 그 값은 어떤 fetch 구현에서도\n`Idempotency-Key` 헤더로 바이트 동일하게 전송된다."
        },
        {
          "name": "IdempotencyKey",
          "slug": "idempotency-key--type",
          "kind": "type",
          "declaration": "/**\n * 멱등키 — 1–300자(초과 시 400 INVALID_IDEMPOTENCY_KEY), 문자셋 `^[\\x21-\\x7E]+$`\n * (공백 없는 출력 가능 ASCII — 헤더 안전 집합).\n *\n * 문자셋 근거: 토스 문서는 길이만 규정하지만 값은 `Idempotency-Key` **요청 헤더**로 전송된다.\n * 비 Latin-1 문자·CR/LF는 fetch `Headers`가 TypeError로 거부해 소켓에 닿기도 전에\n * 실패하고(그 TypeError는 transport 계층에서 NETWORK_ERROR로 오분류됨), 공백·탭·Latin-1\n * 확장 문자는 중간 프록시가 trim/재인코딩할 수 있어 같은 키의 재전송이 다른 바이트로 도착할\n * 위험이 있다. 생성 시점에 거부하는 쪽이 \"Ok면 전송 가능\"을 보장하는 유일한 길이다.\n *\n * 처음 사용일부터 15일 유효 — TTL 초과 뒤 같은 키는 새 요청으로 실행될 수 있다(문서는 기간만\n * 명시하며 만료 뒤 동작은 서술하지 않음 — 안전하지 않은 것으로 취급).\n * 멱등 판정 조합은 \"키 + API 키 + 주소 + 메서드\"이며 **body는 포함되지 않는다**(문서 명시).\n */\ntype IdempotencyKey = string & Brand<'IdempotencyKey'>;",
          "sourceDocumentation": "멱등키 — 1–300자(초과 시 400 INVALID_IDEMPOTENCY_KEY), 문자셋 `^[\\x21-\\x7E]+$`\n(공백 없는 출력 가능 ASCII — 헤더 안전 집합).\n\n문자셋 근거: 토스 문서는 길이만 규정하지만 값은 `Idempotency-Key` **요청 헤더**로 전송된다.\n비 Latin-1 문자·CR/LF는 fetch `Headers`가 TypeError로 거부해 소켓에 닿기도 전에\n실패하고(그 TypeError는 transport 계층에서 NETWORK_ERROR로 오분류됨), 공백·탭·Latin-1\n확장 문자는 중간 프록시가 trim/재인코딩할 수 있어 같은 키의 재전송이 다른 바이트로 도착할\n위험이 있다. 생성 시점에 거부하는 쪽이 \"Ok면 전송 가능\"을 보장하는 유일한 길이다.\n\n처음 사용일부터 15일 유효 — TTL 초과 뒤 같은 키는 새 요청으로 실행될 수 있다(문서는 기간만\n명시하며 만료 뒤 동작은 서술하지 않음 — 안전하지 않은 것으로 취급).\n멱등 판정 조합은 \"키 + API 키 + 주소 + 메서드\"이며 **body는 포함되지 않는다**(문서 명시)."
        },
        {
          "name": "ImportBillingKeyError",
          "slug": "import-billing-key-error",
          "kind": "type",
          "declaration": "type ImportBillingKeyError = {\n    readonly source: 'library';\n    readonly kind: 'invalid-input';\n    readonly field: string;\n    readonly reason: string;\n} | StoreFailure;"
        },
        {
          "name": "InvalidInput",
          "slug": "invalid-input",
          "kind": "interface",
          "declaration": "/**\n * Validation failure of a library-owned input.\n *\n * `Reason` defaults to the string-constraint reasons every id/key parser uses, so all\n * existing `InvalidInput<'orderId'>`-style references keep their exact shape. Structured\n * inputs (e.g. `parsePaymentStateSnapshot`) instantiate it with their own reason union.\n */\ninterface InvalidInput<Field extends string, Reason extends string = 'too-short' | 'too-long' | 'bad-charset' | 'empty'> {\n    readonly source: 'library';\n    readonly kind: 'invalid-input';\n    readonly field: Field;\n    readonly reason: Reason;\n}",
          "sourceDocumentation": "Validation failure of a library-owned input.\n\n`Reason` defaults to the string-constraint reasons every id/key parser uses, so all\nexisting `InvalidInput<'orderId'>`-style references keep their exact shape. Structured\ninputs (e.g. `parsePaymentStateSnapshot`) instantiate it with their own reason union."
        },
        {
          "name": "InvalidPaymentStateSnapshot",
          "slug": "invalid-payment-state-snapshot",
          "kind": "interface",
          "declaration": "/**\n * Parse failure for {@link parsePaymentStateSnapshot} — an\n * `InvalidInput<'paymentStateSnapshot'>` extended with the snapshot-specific reason union\n * and the `path` of the offending value (`'$'` for the root, otherwise a dotted path such as\n * `'cancels[2].cancelAmount'`).\n */\ninterface InvalidPaymentStateSnapshot extends InvalidInput<\"paymentStateSnapshot\", PaymentStateSnapshotParseReason> {\n    readonly path: string;\n}",
          "sourceDocumentation": "Parse failure for {@link parsePaymentStateSnapshot} — an\n`InvalidInput<'paymentStateSnapshot'>` extended with the snapshot-specific reason union\nand the `path` of the offending value (`'$'` for the root, otherwise a dotted path such as\n`'cancels[2].cancelAmount'`)."
        },
        {
          "name": "isAlreadyFullyCanceledError",
          "slug": "is-already-fully-canceled-error",
          "kind": "function",
          "declaration": "/**\n * \"이미 완전 취소됨\" 재취소 이중 매핑 헬퍼.\n *\n * Phase 0 실측(2026-08-09): 단일 전액 취소 후 재취소는 400 ALREADY_CANCELED_PAYMENT,\n * **부분취소 이력이 있는 결제의 잔액 0 재취소는 403 NOT_CANCELABLE_AMOUNT**로 온다.\n * 두 코드를 모두 수용해야 한다. (라이브러리 사전검증이 잔액 초과 부분취소를 API 호출 전에\n * 차단하므로, 이 헬퍼에 도달하는 NOT_CANCELABLE_AMOUNT는 사실상 재취소 케이스다.)\n */\ndeclare function isAlreadyFullyCanceledError(e: TossApiFailure): boolean;",
          "sourceDocumentation": "\"이미 완전 취소됨\" 재취소 이중 매핑 헬퍼.\n\nPhase 0 실측(2026-08-09): 단일 전액 취소 후 재취소는 400 ALREADY_CANCELED_PAYMENT,\n**부분취소 이력이 있는 결제의 잔액 0 재취소는 403 NOT_CANCELABLE_AMOUNT**로 온다.\n두 코드를 모두 수용해야 한다. (라이브러리 사전검증이 잔액 초과 부분취소를 API 호출 전에\n차단하므로, 이 헬퍼에 도달하는 NOT_CANCELABLE_AMOUNT는 사실상 재취소 케이스다.)"
        },
        {
          "name": "isDone",
          "slug": "is-done",
          "kind": "function",
          "declaration": "/** DONE이면 approvedAt은 non-null — 런타임에서도 함께 확인해 거짓 내로잉을 막는다. */\ndeclare function isDone(p: Payment): p is Payment & {\n    status: 'DONE';\n    approvedAt: string;\n};",
          "sourceDocumentation": "DONE이면 approvedAt은 non-null — 런타임에서도 함께 확인해 거짓 내로잉을 막는다."
        },
        {
          "name": "isErr",
          "slug": "is-err",
          "kind": "function",
          "declaration": "declare function isErr<T, E>(r: Result<T, E>): r is Err<E>;"
        },
        {
          "name": "isExecutableRefundQuote",
          "slug": "is-executable-refund-quote",
          "kind": "function",
          "declaration": "/** policy.quote/restoreQuote가 만든 실행 가능한 in-memory quote인지 확인한다. */\ndeclare function isExecutableRefundQuote(value: unknown): value is RefundQuote;",
          "sourceDocumentation": "policy.quote/restoreQuote가 만든 실행 가능한 in-memory quote인지 확인한다."
        },
        {
          "name": "isFullyCanceled",
          "slug": "is-fully-canceled",
          "kind": "function",
          "declaration": "/**\n * 완전 취소 판정 — ⚠ `status === 'CANCELED'` 검사가 아니다.\n *\n * Phase 0 실측(2026-08-09): 부분취소 이력이 있으면 잔액 전액 취소 후에도\n * status가 `PARTIAL_CANCELED`로 남는다(balanceAmount 0). 따라서 CANCELED 문자열만\n * 검사하지 않고, `balanceAmount === 0`과 취소 상태/이력 신호를 함께 본다. 취소 신호가\n * 없는 READY의 잔액 0은 완전 취소가 아니다.\n *\n * The parameter is the structural subset this predicate actually reads (`status`,\n * `balanceAmount`, `cancels`), so callers that only hold a reduced payment snapshot\n * (see `PaymentStateInput`) can use it too. A full `Payment` is always assignable —\n * including a fresh inline object literal: the explicit `| Payment` union member exists\n * solely so the excess-property check accepts literals spelling out non-Pick `Payment`\n * fields. Existing call sites compile unchanged.\n */\ndeclare function isFullyCanceled(p: Pick<Payment, 'status' | 'balanceAmount' | 'cancels'> | Payment): boolean;",
          "sourceDocumentation": "완전 취소 판정 — ⚠ `status === 'CANCELED'` 검사가 아니다.\n\nPhase 0 실측(2026-08-09): 부분취소 이력이 있으면 잔액 전액 취소 후에도\nstatus가 `PARTIAL_CANCELED`로 남는다(balanceAmount 0). 따라서 CANCELED 문자열만\n검사하지 않고, `balanceAmount === 0`과 취소 상태/이력 신호를 함께 본다. 취소 신호가\n없는 READY의 잔액 0은 완전 취소가 아니다.\n\nThe parameter is the structural subset this predicate actually reads (`status`,\n`balanceAmount`, `cancels`), so callers that only hold a reduced payment snapshot\n(see `PaymentStateInput`) can use it too. A full `Payment` is always assignable —\nincluding a fresh inline object literal: the explicit `| Payment` union member exists\nsolely so the excess-property check accepts literals spelling out non-Pick `Payment`\nfields. Existing call sites compile unchanged."
        },
        {
          "name": "isLiveKey",
          "slug": "is-live-key",
          "kind": "function",
          "declaration": "/** env 내로잉 가드 — {@link isTestKey}의 live 대응. */\ndeclare function isLiveKey<K extends string>(key: K): key is K & EnvTag<'live'>;",
          "sourceDocumentation": "env 내로잉 가드 — {@link isTestKey}의 live 대응."
        },
        {
          "name": "isOk",
          "slug": "is-ok",
          "kind": "function",
          "declaration": "declare function isOk<T, E>(r: Result<T, E>): r is Ok<T>;"
        },
        {
          "name": "isRetryable",
          "slug": "is-retryable",
          "kind": "function",
          "declaration": "/** retryable은 생성 시 코드 테이블로 각인된 값 — TransportFailure는 항상 true. */\ndeclare function isRetryable(e: TossApiFailure | TransportFailure): boolean;",
          "sourceDocumentation": "retryable은 생성 시 코드 테이블로 각인된 값 — TransportFailure는 항상 true."
        },
        {
          "name": "IssueBillingKeyError",
          "slug": "issue-billing-key-error",
          "kind": "type",
          "declaration": "type IssueBillingKeyError = TossApiFailure<BillingErrorCode> | TransportFailure\n/**\n * 키는 발급됐다 — 유실 방지를 위해 발급 record를 동봉한다(수동 복구용).\n * issuedRecord의 billingKey는 봉인되어 있어 에러 객체를 통째로 로깅해도 유출되지 않는다 —\n * 회수는 {@link recoverBillingKeyRecord}로만 가능하다.\n */\n | {\n    readonly source: 'library';\n    readonly kind: 'store-save-failed';\n    readonly cause: unknown;\n    readonly issuedRecord: SealedBillingKeyRecord;\n}\n/** 봉인 소실 복제본(스프레드/직렬화) — 인증 플로우를 다시 시작해야 한다. */\n | {\n    readonly source: 'library';\n    readonly kind: 'auth-detached';\n    readonly customerKey: CustomerKey;\n};"
        },
        {
          "name": "isTestKey",
          "slug": "is-test-key",
          "kind": "function",
          "declaration": "/** env 내로잉 가드 — EnvTag는 phantom이라 프로퍼티 판별이 불가능해 접두사로 판정한다. */\ndeclare function isTestKey<K extends string>(key: K): key is K & EnvTag<'test'>;",
          "sourceDocumentation": "env 내로잉 가드 — EnvTag는 phantom이라 프로퍼티 판별이 불가능해 접두사로 판정한다."
        },
        {
          "name": "isWithinIdempotencyReplayWindow",
          "slug": "is-within-idempotency-replay-window",
          "kind": "function",
          "declaration": "/**\n * Whether a key first used at `issuedAt` may still be **replayed** (same key, same body) at `now`.\n *\n * Exact semantics: returns `true` when `now - issuedAt < windowMs` — elapsed time strictly less\n * than the window. At exactly `windowMs` the window has closed and the result is `false`.\n * A negative elapsed time (`issuedAt` after `now`, e.g. clock skew) counts as within the window;\n * reject implausible future timestamps separately if your flow needs to. Any non-finite operand\n * (invalid `Date`, `NaN`, `±Infinity` — in `issuedAt`, `now`, or `windowMs`) yields `false`, the\n * side that never resubmits.\n *\n * `windowMs` defaults to {@link DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS}; pass\n * {@link TOSS_IDEMPOTENCY_KEY_TTL_MS} only if you deliberately want the provider's full window\n * with no safety margin.\n */\ndeclare function isWithinIdempotencyReplayWindow(issuedAt: Date | number, now: Date | number, windowMs?: number): boolean;",
          "sourceDocumentation": "Whether a key first used at `issuedAt` may still be **replayed** (same key, same body) at `now`.\n\nExact semantics: returns `true` when `now - issuedAt < windowMs` — elapsed time strictly less\nthan the window. At exactly `windowMs` the window has closed and the result is `false`.\nA negative elapsed time (`issuedAt` after `now`, e.g. clock skew) counts as within the window;\nreject implausible future timestamps separately if your flow needs to. Any non-finite operand\n(invalid `Date`, `NaN`, `±Infinity` — in `issuedAt`, `now`, or `windowMs`) yields `false`, the\nside that never resubmits.\n\n`windowMs` defaults to {@link DEFAULT_IDEMPOTENCY_REPLAY_WINDOW_MS}; pass\n{@link TOSS_IDEMPOTENCY_KEY_TTL_MS} only if you deliberately want the provider's full window\nwith no safety margin."
        },
        {
          "name": "KeyKind",
          "slug": "key-kind",
          "kind": "type",
          "declaration": "type KeyKind = 'api' | 'widget';"
        },
        {
          "name": "KeyParseError",
          "slug": "key-parse-error",
          "kind": "interface",
          "declaration": "interface KeyParseError {\n    readonly source: 'library';\n    readonly kind: 'invalid-key';\n    /** 기대한 접두사 형식 — 예: \"test_ck_ | live_ck_\" */\n    readonly expected: string;\n    readonly reason: 'bad-prefix' | 'empty-body' | 'bad-length';\n    /** 접두사 인식 진단 — 다른 종류의 키를 넣었으면 어떤 키인지 알려준다. */\n    readonly message: string;\n}"
        },
        {
          "name": "KnownCardIssuerCode",
          "slug": "known-card-issuer-code",
          "kind": "type",
          "declaration": "/**\n * 카드사 두 자리 코드 → 한글 표시명 — 공식 \"기관 코드\" 표(docs.tosspayments.com/codes/org-codes,\n * 문서 ID 118, \"카드사 코드\" 국내·해외) 전사. 응답 `card.issuerCode` / `card.acquirerCode`는\n * 항상 이 두 자리 코드다(한글·영문 코드는 요청 전용).\n *\n * 표시명은 문서의 \"카드사\" 열 그대로이되, 우리 계열 두 행의 매입사 괄호(\"(BC 매입)\"/\n * \"(우리 매입)\")만 뗐다 — 화면 표기용이기 때문. 뜻은 문서 참고 문구와 같다:\n * `33` 우리BC카드는 BC 매입, `W1` 우리카드는 우리 매입(응답 전용 코드).\n */\n/**\n * Card issuer/acquirer codes that Toss documents on its \"기관 코드\" page (`/codes/org-codes`,\n * \"카드사 코드\" — domestic and overseas). Responses (`card.issuerCode`, `card.acquirerCode`)\n * always carry one of these two-character codes; the Korean/English aliases are request-only.\n *\n * This union is a documentation aid for exhaustive tables; {@link cardIssuerName} accepts any\n * string so a code Toss adds later degrades to `undefined`, not to a compile error.\n */\ntype KnownCardIssuerCode = '3K' | '46' | '71' | '30' | '31' | '51' | '38' | '41' | '62' | '36' | '33' | 'W1' | '37' | '39' | '35' | '42' | '15' | '3A' | '24' | '21' | '61' | '11' | '91' | '34' | '6D' | '4M' | '3C' | '7A' | '4J' | '4V';",
          "sourceDocumentation": "Card issuer/acquirer codes that Toss documents on its \"기관 코드\" page (`/codes/org-codes`,\n\"카드사 코드\" — domestic and overseas). Responses (`card.issuerCode`, `card.acquirerCode`)\nalways carry one of these two-character codes; the Korean/English aliases are request-only.\n\nThis union is a documentation aid for exhaustive tables; {@link cardIssuerName} accepts any\nstring so a code Toss adds later degrades to `undefined`, not to a compile error."
        },
        {
          "name": "LedgerRefundComparison",
          "slug": "ledger-refund-comparison",
          "kind": "type",
          "declaration": "/**\n * Verdict of {@link compareLedgerRefund} — a three-way discriminated union.\n *\n * Balance model (Phase-0 field measurements, enforced by the cancel path's response\n * validation): an accepted async cancel *already* reduces `balanceAmount` while its\n * `cancelStatus` is `IN_PROGRESS`; completion (`DONE`) keeps the reduction, abortion\n * (`ABORTED`) restores the balance. `snapshot.canceledAmount` therefore *includes*\n * in-flight amounts, and the final confirmed amount lies in\n * `[canceledAmount - pendingCancelAmount, canceledAmount]`.\n *\n * - `'settled'` — `snapshot.canceledAmount` equals the ledger target **and no cancel is in\n *   flight** (`pendingCancelAmount` is always `0` here). Only then is recording the refund\n *   as final safe: an `IN_PROGRESS` cancel could still resolve `ABORTED` and take the\n *   balance back up (money that never moved).\n * - `'unconfirmed'` — at least one `IN_PROGRESS` cancel keeps the verdict provisional, and\n *   the target lies within the possible final range above, so it may still settle without\n *   any new provider action. Do not settle the ledger yet; re-fetch the payment (a\n *   `CANCEL_STATUS_CHANGED` webhook is `unverified`) and compare again. This mirrors\n *   `lifecycle: 'cancellation-pending'` taking priority over amount-based `'full'`.\n * - `'mismatch'` — the target is outside every possible outcome (`direction` says which\n *   way; with `requestedAmount` supplied, `shortfall` splits `'provider-below-ledger'` into\n *   `'at-prior-state'` / `'unexplained'`), or the comparison is impossible\n *   (`direction: 'indeterminate'`): the snapshot's amounts carry consistency issues\n *   (attached in `consistencyIssues`), or the ledger target itself was invalid\n *   (`invalidLedgerTarget: true`).\n */\ntype LedgerRefundComparison = {\n    readonly kind: \"settled\";\n    /** Provider-confirmed cumulative canceled amount (`snapshot.canceledAmount`). */\n    readonly canceledAmount: number;\n    /** Always `0` in this verdict — any in-flight cancel forces `'unconfirmed'`. */\n    readonly pendingCancelAmount: number;\n    readonly expectedRefundedAmount: number;\n} | {\n    readonly kind: \"unconfirmed\";\n    readonly canceledAmount: number;\n    readonly pendingCancelAmount: number;\n    readonly expectedRefundedAmount: number;\n} | {\n    readonly kind: \"mismatch\";\n    readonly direction: LedgerRefundMismatchDirection;\n    readonly canceledAmount: number;\n    readonly pendingCancelAmount: number;\n    readonly expectedRefundedAmount: number;\n    /**\n     * `true` when `expectedRefundedAmount` (or a supplied `requestedAmount`) was not a\n     * valid ledger amount.\n     */\n    readonly invalidLedgerTarget: boolean;\n    /**\n     * Present only when `direction: 'provider-below-ledger'` and the ledger supplied\n     * `requestedAmount` — see {@link LedgerRefundShortfall}.\n     */\n    readonly shortfall?: LedgerRefundShortfall;\n    /**\n     * The amount-integrity issues that blocked the comparison (`invalid-amount`,\n     * `balance-exceeds-total`) — empty for a plain amount mismatch. The snapshot keeps\n     * the full issue list.\n     */\n    readonly consistencyIssues: readonly PaymentStateConsistencyIssue[];\n};",
          "sourceDocumentation": "Verdict of {@link compareLedgerRefund} — a three-way discriminated union.\n\nBalance model (Phase-0 field measurements, enforced by the cancel path's response\nvalidation): an accepted async cancel *already* reduces `balanceAmount` while its\n`cancelStatus` is `IN_PROGRESS`; completion (`DONE`) keeps the reduction, abortion\n(`ABORTED`) restores the balance. `snapshot.canceledAmount` therefore *includes*\nin-flight amounts, and the final confirmed amount lies in\n`[canceledAmount - pendingCancelAmount, canceledAmount]`.\n\n- `'settled'` — `snapshot.canceledAmount` equals the ledger target **and no cancel is in\n  flight** (`pendingCancelAmount` is always `0` here). Only then is recording the refund\n  as final safe: an `IN_PROGRESS` cancel could still resolve `ABORTED` and take the\n  balance back up (money that never moved).\n- `'unconfirmed'` — at least one `IN_PROGRESS` cancel keeps the verdict provisional, and\n  the target lies within the possible final range above, so it may still settle without\n  any new provider action. Do not settle the ledger yet; re-fetch the payment (a\n  `CANCEL_STATUS_CHANGED` webhook is `unverified`) and compare again. This mirrors\n  `lifecycle: 'cancellation-pending'` taking priority over amount-based `'full'`.\n- `'mismatch'` — the target is outside every possible outcome (`direction` says which\n  way; with `requestedAmount` supplied, `shortfall` splits `'provider-below-ledger'` into\n  `'at-prior-state'` / `'unexplained'`), or the comparison is impossible\n  (`direction: 'indeterminate'`): the snapshot's amounts carry consistency issues\n  (attached in `consistencyIssues`), or the ledger target itself was invalid\n  (`invalidLedgerTarget: true`)."
        },
        {
          "name": "LedgerRefundMismatchDirection",
          "slug": "ledger-refund-mismatch-direction",
          "kind": "type",
          "declaration": "/** Which side is ahead in a `'mismatch'` verdict. */\ntype LedgerRefundMismatchDirection = \n/** Provider-confirmed refunds exceed the ledger target — the ledger is missing refunds. */\n\"provider-exceeds-ledger\"\n/**\n * Provider refunds fall short of the target — even if every in-flight cancel completes,\n * the confirmed amount cannot reach it.\n */\n | \"provider-below-ledger\"\n/** The amounts cannot be compared (inconsistent snapshot or invalid ledger target). */\n | \"indeterminate\";",
          "sourceDocumentation": "Which side is ahead in a `'mismatch'` verdict."
        },
        {
          "name": "LedgerRefundShortfall",
          "slug": "ledger-refund-shortfall",
          "kind": "type",
          "declaration": "/**\n * Sub-classification of a `'provider-below-ledger'` mismatch, present only when the ledger\n * supplied {@link LedgerRefundTarget.requestedAmount}.\n *\n * - `'at-prior-state'` — the provider's confirmed amount equals\n *   `expectedRefundedAmount - requestedAmount` and no cancel is in flight: the provider is\n *   exactly where it was before the reconciling refund request, so that request most likely\n *   never reached it. Replaying the persisted (sealed, idempotent) cancel request is safe.\n * - `'unexplained'` — any other shortfall (or one with cancels still in flight). Do not\n *   auto-replay; escalate.\n */\ntype LedgerRefundShortfall = \"at-prior-state\" | \"unexplained\";",
          "sourceDocumentation": "Sub-classification of a `'provider-below-ledger'` mismatch, present only when the ledger\nsupplied {@link LedgerRefundTarget.requestedAmount}.\n\n- `'at-prior-state'` — the provider's confirmed amount equals\n  `expectedRefundedAmount - requestedAmount` and no cancel is in flight: the provider is\n  exactly where it was before the reconciling refund request, so that request most likely\n  never reached it. Replaying the persisted (sealed, idempotent) cancel request is safe.\n- `'unexplained'` — any other shortfall (or one with cancels still in flight). Do not\n  auto-replay; escalate."
        },
        {
          "name": "LedgerRefundTarget",
          "slug": "ledger-refund-target",
          "kind": "interface",
          "declaration": "/**\n * The app-owned reconciliation target for {@link compareLedgerRefund}.\n *\n * The library never derives or stores this number — how much *should* have been refunded is\n * ledger state the consuming app owns, validates and persists. The helper only answers\n * whether the provider snapshot confirms it.\n */\ninterface LedgerRefundTarget {\n    /**\n     * Cumulative amount the app's ledger expects the provider to have refunded for this\n     * payment. Must be a non-negative safe integer; anything else yields\n     * `kind: 'mismatch'` with `invalidLedgerTarget: true` instead of a guessed verdict.\n     */\n    readonly expectedRefundedAmount: number;\n    /**\n     * Optional: the amount of the *single refund request currently being reconciled* —\n     * i.e. `expectedRefundedAmount` = previously-confirmed refunds + `requestedAmount`.\n     *\n     * When provided, a `'mismatch'` / `'provider-below-ledger'` verdict carries\n     * {@link LedgerRefundShortfall} in `shortfall`, distinguishing `'at-prior-state'` (the\n     * provider sits exactly at the pre-request amount with nothing in flight — the request\n     * most likely never reached the provider, so replaying a sealed idempotent cancel request\n     * is the natural recovery) from `'unexplained'` (any other shortfall — hold for a human).\n     * Without it the helper cannot tell those two apart. Must, when present, be a safe\n     * integer with `0 <= requestedAmount <= expectedRefundedAmount`; anything else yields\n     * `kind: 'mismatch'` with `invalidLedgerTarget: true`.\n     */\n    readonly requestedAmount?: number;\n}",
          "sourceDocumentation": "The app-owned reconciliation target for {@link compareLedgerRefund}.\n\nThe library never derives or stores this number — how much *should* have been refunded is\nledger state the consuming app owns, validates and persists. The helper only answers\nwhether the provider snapshot confirms it."
        },
        {
          "name": "LookupError",
          "slug": "lookup-error",
          "kind": "type",
          "declaration": "type LookupError$1 = TossApiFailure<'NOT_FOUND_PAYMENT' | 'UNAUTHORIZED_KEY' | (string & {})> | TransportFailure;"
        },
        {
          "name": "map",
          "slug": "map",
          "kind": "function",
          "declaration": "/** 성공 값만 변환 — 실패는 그대로 통과한다. */\ndeclare function map<T, U, E>(r: Result<T, E>, f: (value: T) => U): Result<U, E>;",
          "sourceDocumentation": "성공 값만 변환 — 실패는 그대로 통과한다."
        },
        {
          "name": "mapErr",
          "slug": "map-err",
          "kind": "function",
          "declaration": "/** 실패 값만 변환 — 성공은 그대로 통과한다. */\ndeclare function mapErr<T, E, F>(r: Result<T, E>, f: (error: E) => F): Result<T, F>;",
          "sourceDocumentation": "실패 값만 변환 — 성공은 그대로 통과한다."
        },
        {
          "name": "matchesRefundObservedPaymentState",
          "slug": "matches-refund-observed-payment-state",
          "kind": "function",
          "declaration": "/** 실행 직전 재조회 Payment가 quote 생성 시점과 같은 상태인지 확인한다. */\ndeclare function matchesRefundObservedPaymentState(observed: RefundObservedPaymentState, payment: Payment): boolean;",
          "sourceDocumentation": "실행 직전 재조회 Payment가 quote 생성 시점과 같은 상태인지 확인한다."
        },
        {
          "name": "MobilePhoneDetails",
          "slug": "mobile-phone-details",
          "kind": "interface",
          "declaration": "interface MobilePhoneDetails {\n    readonly customerMobilePhone: string;\n    readonly settlementStatus: string;\n    readonly receiptUrl: string;\n}"
        },
        {
          "name": "MobilePhonePayment",
          "slug": "mobile-phone-payment",
          "kind": "interface",
          "declaration": "interface MobilePhonePayment extends PaymentBase {\n    readonly method: '휴대폰';\n    readonly mobilePhone: MobilePhoneDetails;\n}"
        },
        {
          "name": "mustQueryOutcomeBeforeRetry",
          "slug": "must-query-outcome-before-retry",
          "kind": "function",
          "declaration": "/**\n * `true` when the caller must look the payment/billing outcome up before retrying or failing\n * the operation, because the provider may have completed it:\n *\n * - every `TransportFailure` (`NETWORK_ERROR` / `TIMEOUT`) — the request may have reached Toss\n *   and the response was lost;\n * - every `TossApiFailure` whose `code` is in {@link OUTCOME_QUERY_FIRST_ERROR_CODES}.\n *\n * Decided by `source` and `code` only — never by HTTP status (`PROVIDER_ERROR` is a 400 that\n * belongs here; `REFUND_REJECTED` is a 400 that does not). `false` means the error is a\n * definitive refusal as far as the library can tell; unregistered codes return `false`.\n *\n * The lookup itself stays with the caller: for confirm use `resolveConfirmFailure`, for cancel\n * and billing approve re-fetch the payment by orderId and compare against your ledger. Note the\n * two CONCURRENCY codes are special among the `true` cases: the *original* request may still be\n * running, so a lookup that finds nothing (`NOT_FOUND_PAYMENT`) does **not** prove it never\n * happened — replay the same key after a delay instead of minting a new attempt.\n */\ndeclare function mustQueryOutcomeBeforeRetry(failure: TossApiFailure | TransportFailure): boolean;",
          "sourceDocumentation": "`true` when the caller must look the payment/billing outcome up before retrying or failing\nthe operation, because the provider may have completed it:\n\n- every `TransportFailure` (`NETWORK_ERROR` / `TIMEOUT`) — the request may have reached Toss\n  and the response was lost;\n- every `TossApiFailure` whose `code` is in {@link OUTCOME_QUERY_FIRST_ERROR_CODES}.\n\nDecided by `source` and `code` only — never by HTTP status (`PROVIDER_ERROR` is a 400 that\nbelongs here; `REFUND_REJECTED` is a 400 that does not). `false` means the error is a\ndefinitive refusal as far as the library can tell; unregistered codes return `false`.\n\nThe lookup itself stays with the caller: for confirm use `resolveConfirmFailure`, for cancel\nand billing approve re-fetch the payment by orderId and compare against your ledger. Note the\ntwo CONCURRENCY codes are special among the `true` cases: the *original* request may still be\nrunning, so a lookup that finds nothing (`NOT_FOUND_PAYMENT`) does **not** prove it never\nhappened — replay the same key after a delay instead of minting a new attempt."
        },
        {
          "name": "NoRefundPreparation",
          "slug": "no-refund-preparation",
          "kind": "interface",
          "declaration": "interface NoRefundPreparation {\n    readonly kind: \"no-refund\";\n    readonly quote: RefundQuote;\n}"
        },
        {
          "name": "NotCancelableError",
          "slug": "not-cancelable-error",
          "kind": "type",
          "declaration": "type NotCancelableError = {\n    readonly source: 'library';\n    readonly kind: 'not-cancelable-status';\n    readonly status: Exclude<PaymentStatus, 'DONE' | 'PARTIAL_CANCELED' | 'WAITING_FOR_DEPOSIT'>;\n}\n/**\n * balanceAmount === 0 — Phase 0 실측(2026-08-09): 부분취소 이력이 있으면 잔액 전액\n * 취소 후에도 status가 PARTIAL_CANCELED로 남는다. 취소 status의 잔액 0만 완료로 보고,\n * 비취소 status의 잔액 0은 상태 불일치로 차단한다.\n */\n | {\n    readonly source: 'library';\n    readonly kind: 'already-fully-canceled';\n    readonly paymentKey: PaymentKey;\n    readonly status: 'CANCELED' | 'PARTIAL_CANCELED';\n} | {\n    /** 타입상 Payment여도 status·balance·취소 이력이 서로 모순이면 실행하지 않는다. */\n    readonly source: 'library';\n    readonly kind: 'inconsistent-payment-state';\n    readonly paymentKey: PaymentKey;\n    readonly status: PaymentStatus;\n    readonly balanceAmount: number;\n    /** summarizePaymentState와 동일한 단일 불변식 판정 결과. */\n    readonly reason: PaymentStateConsistencyIssue['kind'];\n    readonly issue: PaymentStateConsistencyIssue;\n} | {\n    /** 해외 결제 등 provider 취소가 확정되기 전에는 추가 취소를 시작하지 않는다. */\n    readonly source: 'library';\n    readonly kind: 'pending-cancellation';\n    readonly paymentKey: PaymentKey;\n    readonly status: PaymentStatus;\n    readonly transactionKeys: readonly string[];\n};"
        },
        {
          "name": "observeRefundPaymentState",
          "slug": "observe-refund-payment-state",
          "kind": "function",
          "declaration": "/** 민감한 취소 사유·환불계좌 없이 quote 결속에 필요한 Payment 관측값을 만든다. */\ndeclare function observeRefundPaymentState(payment: Payment): RefundObservedPaymentState;",
          "sourceDocumentation": "민감한 취소 사유·환불계좌 없이 quote 결속에 필요한 Payment 관측값을 만든다."
        },
        {
          "name": "ok",
          "slug": "ok",
          "kind": "function",
          "declaration": "declare function ok<T>(value: T): Ok<T>;"
        },
        {
          "name": "Ok",
          "slug": "ok--interface",
          "kind": "interface",
          "declaration": "interface Ok<out T> {\n    readonly ok: true;\n    readonly value: T;\n}"
        },
        {
          "name": "orderId",
          "slug": "order-id",
          "kind": "function",
          "declaration": "declare function orderId(raw: string): Result<OrderId, InvalidInput<'orderId'>>;"
        },
        {
          "name": "OrderId",
          "slug": "order-id--type",
          "kind": "type",
          "declaration": "/**\n * 문자열 도메인 타입 — 스마트 생성자.\n * 검증 통과가 브랜드 획득의 유일한 경로다 (`as` 없이는 제조 불가).\n */\n/**\n * 주문 ID — 6–64자, `^[A-Za-z0-9_-]+$`.\n *\n * `'='`를 거부하는 근거: SDK 문서는 `=`를 포함한 집합을 허용하지만\n * 레퍼런스/빌링 승인의 orderId 규격은 영숫자와 `-`,`_`만 허용한다.\n * 같은 orderId가 일반 결제와 빌링 승인 양쪽에 쓰일 수 있으므로\n * 보수적 교집합을 채택해 빌링 승인 규격과의 충돌을 회피한다.\n */\ntype OrderId = string & Brand<'OrderId'>;",
          "sourceDocumentation": "주문 ID — 6–64자, `^[A-Za-z0-9_-]+$`.\n\n`'='`를 거부하는 근거: SDK 문서는 `=`를 포함한 집합을 허용하지만\n레퍼런스/빌링 승인의 orderId 규격은 영숫자와 `-`,`_`만 허용한다.\n같은 orderId가 일반 결제와 빌링 승인 양쪽에 쓰일 수 있으므로\n보수적 교집합을 채택해 빌링 승인 규격과의 충돌을 회피한다."
        },
        {
          "name": "orderName",
          "slug": "order-name",
          "kind": "function",
          "declaration": "declare function orderName(raw: string): Result<OrderName, InvalidInput<'orderName'>>;"
        },
        {
          "name": "OrderName",
          "slug": "order-name--type",
          "kind": "type",
          "declaration": "/** 주문명 — 1–100자. */\ntype OrderName = string & Brand<'OrderName'>;",
          "sourceDocumentation": "주문명 — 1–100자."
        },
        {
          "name": "OrderStore",
          "slug": "order-store",
          "kind": "interface",
          "declaration": "/** 금액 비교의 원본 — save/load 양쪽 강제. */\ninterface OrderStore {\n    saveOrder(order: StoredOrder): Promise<void>;\n    loadOrder(orderId: OrderId): Promise<StoredOrder | null>;\n}",
          "sourceDocumentation": "금액 비교의 원본 — save/load 양쪽 강제."
        },
        {
          "name": "orThrow",
          "slug": "or-throw",
          "kind": "function",
          "declaration": "/**\n * 유일한 throw 탈출구 — **부팅 시 설정 파싱(키 로드) 전용**.\n *\n * 요청 처리 경로에서는 사용하지 말 것: 이 라이브러리의 모든 공개 작업은 Result를\n * 반환하며, 요청 경로의 실패는 판별자 내로잉(`if (!r.ok)`)으로 다뤄야 한다.\n * 메서드가 아닌 자유 함수인 이유: Result 값은 어디서든 plain 객체로 직렬화 안전해야 한다.\n *\n * @param context - 던지는 Error 메시지 앞에 붙는 식별 문맥 (예: 'TOSS_SECRET_KEY')\n * @throws Error - 실패 변형일 때. `cause`에 원본 에러 값을 보존한다.\n */\ndeclare function orThrow<T, E>(r: Result<T, E>, context?: string): T;",
          "sourceDocumentation": "유일한 throw 탈출구 — **부팅 시 설정 파싱(키 로드) 전용**.\n\n요청 처리 경로에서는 사용하지 말 것: 이 라이브러리의 모든 공개 작업은 Result를\n반환하며, 요청 경로의 실패는 판별자 내로잉(`if (!r.ok)`)으로 다뤄야 한다.\n메서드가 아닌 자유 함수인 이유: Result 값은 어디서든 plain 객체로 직렬화 안전해야 한다."
        },
        {
          "name": "OUTCOME_QUERY_FIRST_ERROR_CODES",
          "slug": "outcome-query-first-error-codes",
          "kind": "constant",
          "declaration": "OUTCOME_QUERY_FIRST_ERROR_CODES: readonly string[]",
          "sourceDocumentation": "Toss error codes after which the caller **must look the outcome up** (`getPaymentByOrderId`\n/ `getPayment`) before retrying with a new key or marking the operation failed — the provider\nmay have completed (or be completing) the operation even though the response is an error.\nMarking such an operation FAILED without a lookup is how \"money left, user told it failed\"\nincidents happen.\n\nMembership reasons (classification from `classifyTossErrorCode`):\n\n- `ALREADY_PROCESSED_PAYMENT` (400, STATE, not retryable) — a confirm for this paymentKey was\n  already completed, typically by a refreshed page or a duplicate worker. The outcome\n  *exists*; fetch it and treat it as success rather than failure.\n- `IDEMPOTENT_REQUEST_PROCESSING` (409, CONCURRENCY) — the original request with this key is\n  still in flight. Documented instruction: request again and read the result.\n- `FORBIDDEN_CONSECUTIVE_REQUEST` (403, CONCURRENCY) — a back-to-back request on the same\n  resource was refused; the earlier one may have succeeded.\n- `PROVIDER_ERROR` (400, TRANSIENT) — the upstream institution (card company/bank) failed\n  mid-flight; Toss may hold a partially recorded state. 400 but retryable, which is why the\n  HTTP status must never drive this decision.\n- `FAILED_INTERNAL_SYSTEM_PROCESSING`, `FAILED_PAYMENT_INTERNAL_SYSTEM_PROCESSING`,\n  `COMMON_ERROR` (500, TRANSIENT) — Toss-side processing failed after the request was accepted;\n  whether the ledger moved is unknown.\n- `FAILED_REFUND_PROCESS`, `FAILED_METHOD_HANDLING_CANCEL`, `FAILED_PARTIAL_REFUND`\n  (500, TRANSIENT) — cancel/refund failed on bank latency or method handling; the bank may have\n  executed the refund. Re-fetch the payment and compare `balanceAmount`/`cancels`.\n- `FAILED_BILLING_AUTO_CANCEL` (500, TRANSIENT) — the automatic reversal of a billing charge\n  failed transiently; the charge and/or its reversal may exist.\n- `FAILED_BILL_KEY_AUTH_CREATION` (500, TRANSIENT) — billing-key issuance failed mid-way.\n  Toss has no billing-key lookup API, so the \"lookup\" here is your own `BillingKeyStore`:\n  check whether a key was already persisted for the customer before issuing again.\n\nInvariant kept by this table: every code the library marks `retryable: true` is in this set,\nbecause `retryable` means \"worth retrying with a **new** key after judgment\" (README §5) and\nthat judgment is exactly an outcome lookup. The unit suite checks it against\n`CLASSIFIED_TOSS_ERROR_CODES` (the code table's own keys), so adding a retryable code to the\ntable without adding it here fails CI. Deliberately **excluded**:\n`NOT_MATCHES_REFUNDABLE_AMOUNT` (measured: the cancel was not executed — re-fetch to recompute\nthe amount, but there is no outcome uncertainty), every REJECTED/AUTH/REQUEST/AMOUNT/DEADLINE\ncode (definitive refusals), and unregistered codes (the library cannot vouch for them; apply\nyour own policy for unknown 5xx responses)."
        },
        {
          "name": "parseApiClientKey",
          "slug": "parse-api-client-key",
          "kind": "function",
          "declaration": "declare function parseApiClientKey(raw: string): Result<ApiClientKey<'test'> | ApiClientKey<'live'>, KeyParseError>;"
        },
        {
          "name": "parseApiSecretKey",
          "slug": "parse-api-secret-key",
          "kind": "function",
          "declaration": "/**\n * secret key 파서 — **\"./server\" 엔트리에서만 export** (§4.2b 격리 규칙).\n *\n * 브랜드 심볼이 비공개이므로 이 파서들이 없는 번들(브라우저)에서는\n * `ApiSecretKey`/`WidgetSecretKey` 타입의 값을 제조할 방법 자체가 없다.\n * core/keys.ts의 진단 골격과 같은 구조지만 core에 두면 \".\"에서 도달 가능해지므로\n * 여기서 별도 구현한다 (의도된 중복).\n */\ndeclare function parseApiSecretKey(raw: string): Result<ApiSecretKey<'test'> | ApiSecretKey<'live'>, KeyParseError>;",
          "sourceDocumentation": "secret key 파서 — **\"./server\" 엔트리에서만 export** (§4.2b 격리 규칙).\n\n브랜드 심볼이 비공개이므로 이 파서들이 없는 번들(브라우저)에서는\n`ApiSecretKey`/`WidgetSecretKey` 타입의 값을 제조할 방법 자체가 없다.\ncore/keys.ts의 진단 골격과 같은 구조지만 core에 두면 \".\"에서 도달 가능해지므로\n여기서 별도 구현한다 (의도된 중복)."
        },
        {
          "name": "parseBillingAuthCallback",
          "slug": "parse-billing-auth-callback",
          "kind": "function",
          "declaration": "/**\n * successUrl 콜백 파싱 — authKey는 공개 필드가 아니다(비공개 심볼·비열거 내부 보관,\n * 로그/JSON에 새지 않음). authKey는 일회용·최대 300자.\n */\ndeclare function parseBillingAuthCallback(input: CallbackQueryInput): Result<BillingAuthCallback, CallbackParseError>;",
          "sourceDocumentation": "successUrl 콜백 파싱 — authKey는 공개 필드가 아니다(비공개 심볼·비열거 내부 보관,\n로그/JSON에 새지 않음). authKey는 일회용·최대 300자."
        },
        {
          "name": "ParsedRefundQuote",
          "slug": "parsed-refund-quote",
          "kind": "type",
          "declaration": "/** JSON 구조·산술 검증은 통과했지만 활성 policy로 재계산되기 전인 비실행 데이터. */\ntype ParsedRefundQuote = Omit<RefundQuote, keyof Brand<\"RefundQuote\">>;",
          "sourceDocumentation": "JSON 구조·산술 검증은 통과했지만 활성 policy로 재계산되기 전인 비실행 데이터."
        },
        {
          "name": "parseFailCallback",
          "slug": "parse-fail-callback",
          "kind": "function",
          "declaration": "declare function parseFailCallback(input: CallbackQueryInput): Result<FailCallbackResult, CallbackParseError>;"
        },
        {
          "name": "parsePaymentStateSnapshot",
          "slug": "parse-payment-state-snapshot",
          "kind": "function",
          "declaration": "/**\n * Validates an untrusted value (a stored/transported\n * {@link SerializedPaymentStateSnapshot}) back into a branded {@link PaymentStateSnapshot}.\n *\n * Structure is checked exhaustively — `schemaVersion: 1`, every field's type, every literal\n * against its closed union (status, lifecycle, amountState, cancelStatus, issue kinds and\n * their per-kind fields) — and `paymentKey`/`orderId` are re-branded through the existing\n * {@link paymentKey}/{@link orderId} smart constructors, keeping validation-as-the-only-path\n * to a brand intact. The first failing location is reported in `error.path`.\n *\n * Two hardening rules beyond the per-field checks:\n *\n * - **Single read.** Every own enumerable property of the untrusted value is read exactly\n *   once (a one-shot shallow copy per level) before validation, so the value that was\n *   type-checked is the value placed in the branded result — an accessor property cannot\n *   return a valid value to the check and a different one to the constructor. Inherited\n *   (prototype-supplied) properties are ignored.\n * - **Pinned arithmetic.** `canceledAmount` must equal `totalAmount - balanceAmount`\n *   whenever both amounts are safe integers — the one derivation `schemaVersion: 1` pins\n *   that {@link compareLedgerRefund}'s verdict hangs on. Snapshots whose amounts already\n *   carry `invalid-amount` issues are left to the comparison's indeterminate gate instead.\n *\n * Otherwise this is a *shape* gate, not a re-summarization: the remaining derived fields\n * (`lifecycle`, `amountState`, `isCancelable`, `consistencyIssues`, …) are trusted as data\n * produced by an earlier {@link summarizePaymentState} and are not re-derived here.\n */\ndeclare function parsePaymentStateSnapshot(value: unknown): Result<PaymentStateSnapshot, InvalidPaymentStateSnapshot>;",
          "sourceDocumentation": "Validates an untrusted value (a stored/transported\n{@link SerializedPaymentStateSnapshot}) back into a branded {@link PaymentStateSnapshot}.\n\nStructure is checked exhaustively — `schemaVersion: 1`, every field's type, every literal\nagainst its closed union (status, lifecycle, amountState, cancelStatus, issue kinds and\ntheir per-kind fields) — and `paymentKey`/`orderId` are re-branded through the existing\n{@link paymentKey }/{@link orderId } smart constructors, keeping validation-as-the-only-path\nto a brand intact. The first failing location is reported in `error.path`.\n\nTwo hardening rules beyond the per-field checks:\n\n- **Single read.** Every own enumerable property of the untrusted value is read exactly\n  once (a one-shot shallow copy per level) before validation, so the value that was\n  type-checked is the value placed in the branded result — an accessor property cannot\n  return a valid value to the check and a different one to the constructor. Inherited\n  (prototype-supplied) properties are ignored.\n- **Pinned arithmetic.** `canceledAmount` must equal `totalAmount - balanceAmount`\n  whenever both amounts are safe integers — the one derivation `schemaVersion: 1` pins\n  that {@link compareLedgerRefund}'s verdict hangs on. Snapshots whose amounts already\n  carry `invalid-amount` issues are left to the comparison's indeterminate gate instead.\n\nOtherwise this is a *shape* gate, not a re-summarization: the remaining derived fields\n(`lifecycle`, `amountState`, `isCancelable`, `consistencyIssues`, …) are trusted as data\nproduced by an earlier {@link summarizePaymentState} and are not re-derived here."
        },
        {
          "name": "parseRefundQuote",
          "slug": "parse-refund-quote",
          "kind": "function",
          "declaration": "/**\n * DB/메시지의 JSON quote를 구조·공통 산술 기준으로 파싱한다.\n * 반환값은 실행할 수 없다. 반드시 활성 policy.restoreQuote(stored, input)로 재계산해야 한다.\n */\ndeclare function parseRefundQuote(input: unknown): Result<ParsedRefundQuote, RefundQuoteParseError>;",
          "sourceDocumentation": "DB/메시지의 JSON quote를 구조·공통 산술 기준으로 파싱한다.\n반환값은 실행할 수 없다. 반드시 활성 policy.restoreQuote(stored, input)로 재계산해야 한다."
        },
        {
          "name": "parseSecretKey",
          "slug": "parse-secret-key",
          "kind": "function",
          "declaration": "/** 접두사 자동 판별 — sk는 ApiSecretKey, gsk는 WidgetSecretKey. 클라이언트 키(ck/gck)는 거부. */\ndeclare function parseSecretKey(raw: string): Result<ApiSecretKey | WidgetSecretKey, KeyParseError>;",
          "sourceDocumentation": "접두사 자동 판별 — sk는 ApiSecretKey, gsk는 WidgetSecretKey. 클라이언트 키(ck/gck)는 거부."
        },
        {
          "name": "parseSuccessCallback",
          "slug": "parse-success-callback",
          "kind": "function",
          "declaration": "declare function parseSuccessCallback(input: CallbackQueryInput, options?: {\n    readonly receivedAt?: Date;\n}): Result<UnverifiedCallback, CallbackParseError>;"
        },
        {
          "name": "parseWidgetClientKey",
          "slug": "parse-widget-client-key",
          "kind": "function",
          "declaration": "declare function parseWidgetClientKey(raw: string): Result<WidgetClientKey<'test'> | WidgetClientKey<'live'>, KeyParseError>;"
        },
        {
          "name": "parseWidgetSecretKey",
          "slug": "parse-widget-secret-key",
          "kind": "function",
          "declaration": "declare function parseWidgetSecretKey(raw: string): Result<WidgetSecretKey<'test'> | WidgetSecretKey<'live'>, KeyParseError>;"
        },
        {
          "name": "PartiallyCancelable",
          "slug": "partially-cancelable",
          "kind": "type",
          "declaration": "type PartiallyCancelable = Extract<SettledCancelable, {\n    readonly partialAllowed: true;\n}> | Extract<DepositedVaCancelable, {\n    readonly partialAllowed: true;\n}>;"
        },
        {
          "name": "Payment",
          "slug": "payment",
          "kind": "type",
          "declaration": "type Payment = CardPayment | VirtualAccountPayment | EasyPayPayment | TransferPayment | MobilePhonePayment | GiftCertificatePayment | PendingMethodPayment;"
        },
        {
          "name": "PaymentAmountState",
          "slug": "payment-amount-state",
          "kind": "type",
          "declaration": "/** 현재 금액만으로 본 취소 정도. full 판정은 기존 {@link isFullyCanceled} 계약을 따른다. */\ntype PaymentAmountState = \"none\" | \"partial\" | \"full\";",
          "sourceDocumentation": "현재 금액만으로 본 취소 정도. full 판정은 기존 {@link isFullyCanceled} 계약을 따른다."
        },
        {
          "name": "PaymentBase",
          "slug": "payment-base",
          "kind": "interface",
          "declaration": "interface PaymentBase {\n    /** API 버전 — CalVer 날짜 문자열. */\n    readonly version: string;\n    readonly paymentKey: PaymentKey;\n    readonly type: 'NORMAL' | 'BILLING' | 'BRANDPAY';\n    readonly orderId: OrderId;\n    readonly orderName: string;\n    readonly mId: string;\n    readonly currency: 'KRW' | 'USD' | 'JPY';\n    readonly totalAmount: number;\n    /** '취소할 수 있는 금액(잔고)' — 완전 취소 판정의 유일한 근거 (status 아님 — Phase 0 실측). */\n    readonly balanceAmount: number;\n    readonly status: PaymentStatus;\n    readonly requestedAt: string;\n    readonly approvedAt: string | null;\n    readonly useEscrow: boolean;\n    readonly lastTransactionKey: string | null;\n    readonly suppliedAmount: number;\n    readonly vat: number;\n    /** 문화비(도서·공연비 등) 지출 여부 — 리서치 문서 Payment 필드 목록에 포함 확인. */\n    readonly cultureExpense: boolean;\n    readonly taxFreeAmount: number;\n    readonly taxExemptionAmount: number;\n    readonly cancels: readonly CancelTransaction[] | null;\n    readonly isPartialCancelable: boolean;\n    /** 가상계좌 웹훅(DEPOSIT_CALLBACK) 검증용 — 승인 시 저장 필수. */\n    readonly secret: string | null;\n    readonly metadata: Readonly<Record<string, string>> | null;\n    readonly receipt: {\n        readonly url: string;\n    } | null;\n    readonly checkout: {\n        readonly url: string;\n    } | null;\n    readonly country: string;\n    readonly failure: {\n        readonly code: string;\n        readonly message: string;\n    } | null;\n    /** 응답 원문 — 타입에 없는 필드(cashReceipt/cashReceipts/discount 등)의 탈출구. */\n    readonly raw: unknown;\n}"
        },
        {
          "name": "PaymentCancelChanges",
          "slug": "payment-cancel-changes",
          "kind": "interface",
          "declaration": "interface PaymentCancelChanges {\n    readonly added: readonly PaymentCancelTransactionSnapshot[];\n    readonly updated: readonly PaymentCancelTransactionUpdate[];\n    readonly removed: readonly PaymentCancelTransactionSnapshot[];\n}"
        },
        {
          "name": "PaymentCancelTransactionSnapshot",
          "slug": "payment-cancel-transaction-snapshot",
          "kind": "interface",
          "declaration": "/** 상태 관리에 필요한 최소 취소 트랜잭션. 사유·영수증 및 Payment.raw는 의도적으로 제외한다. */\ninterface PaymentCancelTransactionSnapshot {\n    readonly transactionKey: string;\n    readonly cancelAmount: number;\n    readonly refundableAmount: number;\n    readonly canceledAt: string;\n    readonly cancelStatus: CancelTransaction[\"cancelStatus\"];\n    readonly cancelRequestId: string | null;\n}",
          "sourceDocumentation": "상태 관리에 필요한 최소 취소 트랜잭션. 사유·영수증 및 Payment.raw는 의도적으로 제외한다."
        },
        {
          "name": "PaymentCancelTransactionUpdate",
          "slug": "payment-cancel-transaction-update",
          "kind": "interface",
          "declaration": "interface PaymentCancelTransactionUpdate {\n    readonly previous: PaymentCancelTransactionSnapshot;\n    readonly next: PaymentCancelTransactionSnapshot;\n}"
        },
        {
          "name": "paymentKey",
          "slug": "payment-key",
          "kind": "function",
          "declaration": "declare function paymentKey(raw: string): Result<PaymentKey, InvalidInput<'paymentKey'>>;"
        },
        {
          "name": "PaymentKey",
          "slug": "payment-key--type",
          "kind": "type",
          "declaration": "/** 결제 키 — 1–200자 (문서: 최대 200자). */\ntype PaymentKey = string & Brand<'PaymentKey'>;",
          "sourceDocumentation": "결제 키 — 1–200자 (문서: 최대 200자)."
        },
        {
          "name": "PaymentLifecycle",
          "slug": "payment-lifecycle",
          "kind": "type",
          "declaration": "/**\n * 결제 상태 스냅샷과 변경 요약.\n *\n * Payment 상태는 단방향 상태 머신이 아니다. 특히 입금 오류로\n * DONE -> WAITING_FOR_DEPOSIT 역전이가 가능하므로, 이 모듈은 전이를 허용/거부하지 않고\n * 두 관측값의 차이만 기술한다. 영속화 순서와 동시성 제어는 호출자의 저장소가 맡는다.\n */\ntype PaymentLifecycle = \"pending\" | \"awaiting-deposit\" | \"paid\" | \"cancellation-pending\" | \"partially-canceled\" | \"fully-canceled\" | \"failed\" | \"expired\" | \"inconsistent\";",
          "sourceDocumentation": "결제 상태 스냅샷과 변경 요약.\n\nPayment 상태는 단방향 상태 머신이 아니다. 특히 입금 오류로\nDONE -> WAITING_FOR_DEPOSIT 역전이가 가능하므로, 이 모듈은 전이를 허용/거부하지 않고\n두 관측값의 차이만 기술한다. 영속화 순서와 동시성 제어는 호출자의 저장소가 맡는다."
        },
        {
          "name": "PaymentMethod",
          "slug": "payment-method",
          "kind": "type",
          "declaration": "/** 응답 원문 그대로의 한글 리터럴 — 영문 enum을 지어내면 런타임 전부 불일치한다. */\ntype PaymentMethod = '카드' | '가상계좌' | '간편결제' | '휴대폰' | '계좌이체' | '문화상품권' | '도서문화상품권' | '게임문화상품권';",
          "sourceDocumentation": "응답 원문 그대로의 한글 리터럴 — 영문 enum을 지어내면 런타임 전부 불일치한다."
        },
        {
          "name": "PaymentStateBalanceChange",
          "slug": "payment-state-balance-change",
          "kind": "interface",
          "declaration": "interface PaymentStateBalanceChange extends PaymentStateValueChange<number> {\n    /** next - previous. 취소가 진행되면 보통 음수다. */\n    readonly delta: number;\n}"
        },
        {
          "name": "PaymentStateConsistencyIssue",
          "slug": "payment-state-consistency-issue",
          "kind": "type",
          "declaration": "type PaymentStateConsistencyIssue = {\n    readonly kind: \"invalid-amount\";\n    readonly field: \"totalAmount\" | \"balanceAmount\";\n    readonly value: number;\n    readonly reason: \"not-safe-integer\" | \"negative\";\n} | {\n    readonly kind: \"balance-exceeds-total\";\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"zero-balance-with-non-canceled-status\";\n    readonly status: Exclude<PaymentStatus, \"CANCELED\" | \"PARTIAL_CANCELED\">;\n} | {\n    readonly kind: \"cancellation-status-without-history\";\n    readonly status: \"CANCELED\" | \"PARTIAL_CANCELED\";\n} | {\n    readonly kind: \"canceled-status-with-balance\";\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"partial-status-without-canceled-amount\";\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"partial-status-without-effective-cancellation\";\n} | {\n    readonly kind: \"paid-status-with-canceled-amount\";\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly kind: \"full-cancellation-status-mismatch\";\n    readonly status: Exclude<PaymentStatus, \"CANCELED\" | \"PARTIAL_CANCELED\">;\n} | {\n    readonly kind: \"completed-cancel-status-mismatch\";\n    readonly status: Exclude<PaymentStatus, \"CANCELED\" | \"PARTIAL_CANCELED\">;\n} | {\n    readonly kind: \"latest-cancel-balance-mismatch\";\n    readonly transactionKey: string;\n    readonly cancelRefundableAmount: number;\n    readonly paymentBalanceAmount: number;\n} | {\n    readonly kind: \"duplicate-cancel-transaction-key\";\n    readonly transactionKey: string;\n};"
        },
        {
          "name": "PaymentStateDiff",
          "slug": "payment-state-diff",
          "kind": "interface",
          "declaration": "interface PaymentStateDiff {\n    readonly previous: PaymentStateSnapshot;\n    readonly next: PaymentStateSnapshot;\n    readonly changed: boolean;\n    readonly statusChange: PaymentStateValueChange<PaymentStatus> | null;\n    readonly lifecycleChange: PaymentStateValueChange<PaymentLifecycle> | null;\n    readonly balanceAmountChange: PaymentStateBalanceChange | null;\n    readonly lastTransactionKeyChange: PaymentStateValueChange<string | null> | null;\n    readonly amountStateChange: PaymentStateValueChange<PaymentAmountState> | null;\n    readonly pendingCancellationChange: PaymentStateValueChange<boolean> | null;\n    readonly abortedCancellationChange: PaymentStateValueChange<boolean> | null;\n    readonly cancelableChange: PaymentStateValueChange<boolean> | null;\n    readonly partiallyCancelableChange: PaymentStateValueChange<boolean> | null;\n    readonly cancelChanges: PaymentCancelChanges;\n    readonly warnings: readonly PaymentStateDiffWarning[];\n}"
        },
        {
          "name": "PaymentStateDiffWarning",
          "slug": "payment-state-diff-warning",
          "kind": "type",
          "declaration": "type PaymentStateDiffWarning = {\n    /** 환불 취소·입금 오류 등 정상적인 역전이일 수도 있으므로 오류로 차단하지 않는다. */\n    readonly kind: \"balance-increased\";\n    readonly previousBalanceAmount: number;\n    readonly nextBalanceAmount: number;\n    readonly delta: number;\n} | {\n    /** 제공자 응답의 취소 배열에서 이전 transactionKey가 사라졌다. */\n    readonly kind: \"cancel-removed\";\n    readonly transactionKey: string;\n};"
        },
        {
          "name": "PaymentStateIdentityError",
          "slug": "payment-state-identity-error",
          "kind": "interface",
          "declaration": "/** 서로 다른 결제의 스냅샷을 비교하려 한 경우에만 반환되는 오류. */\ninterface PaymentStateIdentityError {\n    readonly source: \"library\";\n    readonly kind: \"payment-state-identity-mismatch\";\n    readonly mismatches: readonly PaymentStateIdentityMismatch[];\n}",
          "sourceDocumentation": "서로 다른 결제의 스냅샷을 비교하려 한 경우에만 반환되는 오류."
        },
        {
          "name": "PaymentStateIdentityMismatch",
          "slug": "payment-state-identity-mismatch",
          "kind": "interface",
          "declaration": "interface PaymentStateIdentityMismatch {\n    readonly field: \"paymentKey\" | \"orderId\";\n    readonly previous: string;\n    readonly next: string;\n}"
        },
        {
          "name": "PaymentStateInput",
          "slug": "payment-state-input",
          "kind": "type",
          "declaration": "/**\n * The minimal structural input {@link summarizePaymentState} actually reads — exactly these\n * eight fields, nothing else (verified against the implementation: the lifecycle, amount and\n * consistency judgments consume `status`/`totalAmount`/`balanceAmount`/`lastTransactionKey`/\n * `isPartialCancelable`/`cancels`, and the snapshot carries `paymentKey`/`orderId`).\n *\n * A full `Payment` is always assignable — including a fresh inline object literal:\n * {@link summarizePaymentState} is typed `PaymentStateInput | Payment`, and the `Payment`\n * union member exists solely so TypeScript's excess-property check accepts literals that\n * spell out non-Pick `Payment` fields (`version`, `requestedAt`, …). Existing call sites\n * compile unchanged. The point of the reduced shape is the opposite direction: an app-owned\n * payment view that stripped `raw`/`secret`/card details can still produce a snapshot,\n * **provided its eight fields are faithful copies of a real Payment response**. Do not fabricate `lastTransactionKey`,\n * `isPartialCancelable` or `cancels` to satisfy the type — the consistency and\n * cancelability judgments would then describe your fabrication, not the provider state.\n */\ntype PaymentStateInput = Pick<Payment, \"paymentKey\" | \"orderId\" | \"status\" | \"totalAmount\" | \"balanceAmount\" | \"lastTransactionKey\" | \"isPartialCancelable\" | \"cancels\">;",
          "sourceDocumentation": "The minimal structural input {@link summarizePaymentState} actually reads — exactly these\neight fields, nothing else (verified against the implementation: the lifecycle, amount and\nconsistency judgments consume `status`/`totalAmount`/`balanceAmount`/`lastTransactionKey`/\n`isPartialCancelable`/`cancels`, and the snapshot carries `paymentKey`/`orderId`).\n\nA full `Payment` is always assignable — including a fresh inline object literal:\n{@link summarizePaymentState} is typed `PaymentStateInput | Payment`, and the `Payment`\nunion member exists solely so TypeScript's excess-property check accepts literals that\nspell out non-Pick `Payment` fields (`version`, `requestedAt`, …). Existing call sites\ncompile unchanged. The point of the reduced shape is the opposite direction: an app-owned\npayment view that stripped `raw`/`secret`/card details can still produce a snapshot,\n**provided its eight fields are faithful copies of a real Payment response**. Do not fabricate `lastTransactionKey`,\n`isPartialCancelable` or `cancels` to satisfy the type — the consistency and\ncancelability judgments would then describe your fabrication, not the provider state."
        },
        {
          "name": "PaymentStateSnapshot",
          "slug": "payment-state-snapshot",
          "kind": "interface",
          "declaration": "/**\n * 저장·로그하기 안전한 결제 상태 요약.\n *\n * Payment.secret, Payment.raw, 카드/계좌 상세 및 취소 사유는 포함하지 않는다.\n */\ninterface PaymentStateSnapshot {\n    /** 영속 스키마 진화를 위한 고정 버전. */\n    readonly schemaVersion: 1;\n    readonly paymentKey: PaymentKey;\n    readonly orderId: OrderId;\n    readonly status: PaymentStatus;\n    readonly lifecycle: PaymentLifecycle;\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n    readonly lastTransactionKey: string | null;\n    /** totalAmount - balanceAmount. 비정상 응답에서는 음수일 수 있으며 consistencyIssues에 남는다. */\n    readonly canceledAmount: number;\n    readonly amountState: PaymentAmountState;\n    readonly hasPendingCancellation: boolean;\n    readonly hasAbortedCancellation: boolean;\n    /** 현재 스냅샷에서 새 취소 요청을 시도할 수 있는지에 대한 보수적 힌트. */\n    readonly isCancelable: boolean;\n    /** 입금 전·비동기 취소 진행 중을 제외하고 부분취소가 가능한지에 대한 보수적 힌트. */\n    readonly isPartiallyCancelable: boolean;\n    readonly cancels: readonly PaymentCancelTransactionSnapshot[];\n    readonly consistencyIssues: readonly PaymentStateConsistencyIssue[];\n}",
          "sourceDocumentation": "저장·로그하기 안전한 결제 상태 요약.\n\nPayment.secret, Payment.raw, 카드/계좌 상세 및 취소 사유는 포함하지 않는다."
        },
        {
          "name": "PaymentStateSnapshotParseReason",
          "slug": "payment-state-snapshot-parse-reason",
          "kind": "type",
          "declaration": "/**\n * Why {@link parsePaymentStateSnapshot} rejected a value: the four string-constraint reasons\n * come from re-branding `paymentKey`/`orderId` through the existing id parsers;\n * `'malformed'` covers every structural failure (wrong type, missing field, unknown literal,\n * unsupported `schemaVersion`). The offending location is in\n * {@link InvalidPaymentStateSnapshot.path}.\n */\ntype PaymentStateSnapshotParseReason = InvalidInput<\"paymentStateSnapshot\">[\"reason\"] | \"malformed\";",
          "sourceDocumentation": "Why {@link parsePaymentStateSnapshot} rejected a value: the four string-constraint reasons\ncome from re-branding `paymentKey`/`orderId` through the existing id parsers;\n`'malformed'` covers every structural failure (wrong type, missing field, unknown literal,\nunsupported `schemaVersion`). The offending location is in\n{@link InvalidPaymentStateSnapshot.path}."
        },
        {
          "name": "PaymentStateValueChange",
          "slug": "payment-state-value-change",
          "kind": "interface",
          "declaration": "interface PaymentStateValueChange<T> {\n    readonly previous: T;\n    readonly next: T;\n}"
        },
        {
          "name": "PaymentStatus",
          "slug": "payment-status",
          "kind": "type",
          "declaration": "/**\n * Payment 객체 — method 한글 리터럴 판별 유니언 + `raw: unknown` 탈출구.\n * 필드 목록의 근거: docs/research/toss-payments-v2.md \"Payment 객체 주요 필드\".\n */\ntype PaymentStatus = 'READY' | 'IN_PROGRESS' | 'WAITING_FOR_DEPOSIT' | 'DONE' | 'CANCELED' | 'PARTIAL_CANCELED' | 'ABORTED' | 'EXPIRED';",
          "sourceDocumentation": "Payment 객체 — method 한글 리터럴 판별 유니언 + `raw: unknown` 탈출구.\n필드 목록의 근거: docs/research/toss-payments-v2.md \"Payment 객체 주요 필드\"."
        },
        {
          "name": "PendingBillingAuth",
          "slug": "pending-billing-auth",
          "kind": "interface",
          "declaration": "/**\n * 빌링(정기결제) — PendingBillingAuth → confirmPendingAuth → issue → BillingProfile → approve.\n *\n * - authKey/billingKey는 비공개 심볼(비열거)로 봉인 — 공개 필드·JSON 직렬화·스프레드\n *   어디에도 노출되지 않는다. 봉인 소실(복제본)은 명시적 런타임 Err.\n * - BillingOrder에는 customerKey 필드가 없다 — 봉인 쌍으로만 승인해\n *   NOT_MATCHES_CUSTOMER_KEY를 구조적으로 방지한다.\n * - 갱신 API는 존재하지 않는다(\"빌링키를 갱신하는 별도 과정은 없습니다\") — refresh류\n *   메서드 없음. 재발급 = revoke 후 새 인증부터.\n * - '빌링 승인 완료 웹훅'은 존재하지 않는다(BILLING_DELETED만 존재) — approve 반환값 +\n *   getPayment 재확인이 완결 신호다.\n */\ninterface PendingBillingAuth extends Brand<'PendingBillingAuth'> {\n    /** 쿼리스트링으로 돌아온 값 — 신뢰 금지. confirmPendingAuth로 세션 값과 대조 전에는 사용 불가. */\n    readonly returnedCustomerKey: string;\n}",
          "sourceDocumentation": "빌링(정기결제) — PendingBillingAuth → confirmPendingAuth → issue → BillingProfile → approve.\n\n- authKey/billingKey는 비공개 심볼(비열거)로 봉인 — 공개 필드·JSON 직렬화·스프레드\n  어디에도 노출되지 않는다. 봉인 소실(복제본)은 명시적 런타임 Err.\n- BillingOrder에는 customerKey 필드가 없다 — 봉인 쌍으로만 승인해\n  NOT_MATCHES_CUSTOMER_KEY를 구조적으로 방지한다.\n- 갱신 API는 존재하지 않는다(\"빌링키를 갱신하는 별도 과정은 없습니다\") — refresh류\n  메서드 없음. 재발급 = revoke 후 새 인증부터.\n- '빌링 승인 완료 웹훅'은 존재하지 않는다(BILLING_DELETED만 존재) — approve 반환값 +\n  getPayment 재확인이 완결 신호다."
        },
        {
          "name": "PendingMethodPayment",
          "slug": "pending-method-payment",
          "kind": "interface",
          "declaration": "/** 승인 전 결제 — method nullable. status는 전체 유니언 유지(협착은 미검증 불변식). */\ninterface PendingMethodPayment extends PaymentBase {\n    readonly method: null;\n}",
          "sourceDocumentation": "승인 전 결제 — method nullable. status는 전체 유니언 유지(협착은 미검증 불변식)."
        },
        {
          "name": "PendingOrder",
          "slug": "pending-order",
          "kind": "interface",
          "declaration": "interface PendingOrder extends StoredOrder, Brand<'PendingOrder'> {\n    /** 브라우저로 넘길 직렬화 페이로드 — 위젯 requestPayment 입력과 필드명 일치. */\n    toClientProps(): {\n        orderId: string;\n        amount: number;\n        orderName: string;\n        currency: string;\n    };\n}"
        },
        {
          "name": "PercentageRefundPolicyConfig",
          "slug": "percentage-refund-policy-config",
          "kind": "interface",
          "declaration": "interface PercentageRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"percentage\";\n    /** 0..10,000 정수. */\n    readonly rateBps: number;\n    readonly rounding: RefundRoundingMode;\n    readonly reason?: string;\n}"
        },
        {
          "name": "prepareRefund",
          "slug": "prepare-refund",
          "kind": "function",
          "declaration": "/**\n * 순수 정책 견적을 방금 조회한 취소 가능 결제에 결속한다.\n *\n * core의 canonical quote.kind를 실행 mode로 사용한다. 잔액·정책 산술 불변식은\n * parseRefundQuote가 먼저 검증하며, 서버는 현재 Payment와의 결속만 담당한다.\n */\ndeclare function prepareRefund(target: SettledCancelable, quote: RefundQuote): Result<RefundPreparationFor<\"settled\">, RefundPlanError>;\n\ndeclare function prepareRefund(target: DepositedVaCancelable, quote: RefundQuote): Result<RefundPreparationFor<\"deposited-virtual-account\">, RefundPlanError>;\n\ndeclare function prepareRefund(target: AwaitingDepositCancelable, quote: RefundQuote): Result<RefundPreparationFor<\"awaiting-deposit\">, RefundPlanError>;\n\ndeclare function prepareRefund(target: CancelablePayment, quote: RefundQuote): Result<RefundPreparation, RefundPlanError>;",
          "sourceDocumentation": "순수 정책 견적을 방금 조회한 취소 가능 결제에 결속한다.\n\ncore의 canonical quote.kind를 실행 mode로 사용한다. 잔액·정책 산술 불변식은\nparseRefundQuote가 먼저 검증하며, 서버는 현재 Payment와의 결속만 담당한다."
        },
        {
          "name": "prepareRefundExecution",
          "slug": "prepare-refund-execution",
          "kind": "function",
          "declaration": "declare function prepareRefundExecution<const Plan extends RefundExecutionPlan>(plan: Plan, request: NoInfer<RefundExecutionRequestFor<Plan[\"targetKind\"]>>, options: RefundExecutionAttemptOptions): Result<Extract<RefundExecutionAttempt, {\n    readonly targetKind: Plan[\"targetKind\"];\n}>, RefundPlanError>;"
        },
        {
          "name": "recoverBillingKeyRecord",
          "slug": "recover-billing-key-record",
          "kind": "function",
          "declaration": "/**\n * store-save-failed 에러에 동봉된 봉인 record에서 원본 {@link BillingKeyRecord}를 회수한다 —\n * 반환된 record는 열거 가능한 billingKey 평문을 담으므로 **로그에 남기지 말고** store.save\n * 재시도에만 사용할 것. 스프레드/직렬화 복제본은 봉인이 소실되어 Err('record-detached')다.\n */\ndeclare function recoverBillingKeyRecord(sealed: SealedBillingKeyRecord): Result<BillingKeyRecord, {\n    readonly source: 'library';\n    readonly kind: 'record-detached';\n    readonly customerKey: string;\n}>;",
          "sourceDocumentation": "store-save-failed 에러에 동봉된 봉인 record에서 원본 {@link BillingKeyRecord}를 회수한다 —\n반환된 record는 열거 가능한 billingKey 평문을 담으므로 **로그에 남기지 말고** store.save\n재시도에만 사용할 것. 스프레드/직렬화 복제본은 봉인이 소실되어 Err('record-detached')다."
        },
        {
          "name": "REFUND_RATE_SCALE",
          "slug": "refund-rate-scale",
          "kind": "constant",
          "declaration": "REFUND_RATE_SCALE: 10000",
          "sourceDocumentation": "100% = 10,000 basis points. 부동소수 퍼센트를 공개 계약으로 쓰지 않는다."
        },
        {
          "name": "REFUND_TIME",
          "slug": "refund-time",
          "kind": "constant",
          "declaration": "REFUND_TIME: Readonly<{\n    minute: 60000;\n    hour: number;\n    day: number;\n}>",
          "sourceDocumentation": "경과시간 정책을 읽기 좋게 정의하기 위한 고정 길이 상수. 달력 일수 계산에는 쓰지 않는다."
        },
        {
          "name": "refundAccount",
          "slug": "refund-account",
          "kind": "function",
          "declaration": "declare function refundAccount(input: {\n    bank: string;\n    accountNumber: string;\n    holderName: string;\n}): Result<RefundAccount, InvalidInput<'refundAccount'>>;"
        },
        {
          "name": "RefundAccount",
          "slug": "refund-account--interface",
          "kind": "interface",
          "declaration": "/**\n * 환불 계좌 스마트 생성자.\n * 필드명은 bank다 — bankCode 아님(레퍼런스 원문). accountNumber ≤20자 숫자만(하이픈 불가),\n * holderName ≤60자.\n */\ninterface RefundAccount extends Brand<'RefundAccount'> {\n    readonly bank: string;\n    readonly accountNumber: string;\n    readonly holderName: string;\n}",
          "sourceDocumentation": "환불 계좌 스마트 생성자.\n필드명은 bank다 — bankCode 아님(레퍼런스 원문). accountNumber ≤20자 숫자만(하이픈 불가),\nholderName ≤60자."
        },
        {
          "name": "RefundCalculation",
          "slug": "refund-calculation",
          "kind": "type",
          "declaration": "type RefundCalculation = {\n    readonly kind: \"full\";\n} | {\n    readonly kind: \"percentage\";\n    readonly rateBps: number;\n} | {\n    readonly kind: \"elapsed-time-rate\";\n    readonly elapsedMs: number;\n    /** fallback이면 null. */\n    readonly bracketIndex: number | null;\n    readonly rateBps: number;\n} | {\n    readonly kind: \"remaining-units\";\n    readonly totalUnits: number;\n    readonly remainingUnits: number;\n    readonly rateBps: number;\n} | {\n    readonly kind: \"custom\";\n    readonly entitlementKind: \"rate\" | \"amount\";\n    readonly details: Readonly<Record<string, RefundCalculationDetail>>;\n};"
        },
        {
          "name": "RefundCalculationDetail",
          "slug": "refund-calculation-detail",
          "kind": "type",
          "declaration": "type RefundCalculationDetail = string | number | boolean | null;"
        },
        {
          "name": "RefundCalendarError",
          "slug": "refund-calendar-error",
          "kind": "type",
          "declaration": "type RefundCalendarError = {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-calendar\";\n    readonly field: string;\n    readonly reason: string;\n};"
        },
        {
          "name": "RefundEntitlement",
          "slug": "refund-entitlement",
          "kind": "type",
          "declaration": "/** custom 정책이 반환하는 누적 환불 entitlement. 현재 실행액은 기존 환불을 차감해 계산한다. */\ntype RefundEntitlement = {\n    readonly kind: \"rate\";\n    readonly rateBps: number;\n    readonly reason?: string;\n    readonly details?: Readonly<Record<string, RefundCalculationDetail>>;\n} | {\n    readonly kind: \"amount\";\n    /** 이번 환불액이 아니라 정책상 누적 환불 가능 총액. */\n    readonly amount: number;\n    readonly reason?: string;\n    readonly details?: Readonly<Record<string, RefundCalculationDetail>>;\n};",
          "sourceDocumentation": "custom 정책이 반환하는 누적 환불 entitlement. 현재 실행액은 기존 환불을 차감해 계산한다."
        },
        {
          "name": "RefundExecutionAttempt",
          "slug": "refund-execution-attempt",
          "kind": "type",
          "declaration": "/**\n * 준비된 plan에 실행 request와 멱등키를 불변 결속한 재실행 가능한 요청 서술.\n * 반복 실행은 조회를 다시 하되 Toss POST의 body와 멱등키는 항상 동일하다.\n */\ntype RefundExecutionAttempt = (RefundExecutionAttemptBase & {\n    readonly targetKind: \"settled\";\n    readonly mode: \"full\" | \"partial\";\n}) | (RefundExecutionAttemptBase & {\n    readonly targetKind: \"deposited-virtual-account\";\n    readonly mode: \"full\" | \"partial\";\n}) | (RefundExecutionAttemptBase & {\n    readonly targetKind: \"awaiting-deposit\";\n    readonly mode: \"full\";\n});",
          "sourceDocumentation": "준비된 plan에 실행 request와 멱등키를 불변 결속한 재실행 가능한 요청 서술.\n반복 실행은 조회를 다시 하되 Toss POST의 body와 멱등키는 항상 동일하다."
        },
        {
          "name": "RefundExecutionAttemptOptions",
          "slug": "refund-execution-attempt-options",
          "kind": "interface",
          "declaration": "/** 요청 본문과 함께 attempt에 한 번만 봉인되는 프로젝트 수준의 안정적인 멱등키. */\ninterface RefundExecutionAttemptOptions {\n    readonly idempotencyKey: IdempotencyKey;\n}",
          "sourceDocumentation": "요청 본문과 함께 attempt에 한 번만 봉인되는 프로젝트 수준의 안정적인 멱등키."
        },
        {
          "name": "RefundExecutionError",
          "slug": "refund-execution-error",
          "kind": "type",
          "declaration": "type RefundExecutionError = CancelError | LookupError | NotCancelableError | RefundPlanError;"
        },
        {
          "name": "RefundExecutionPlan",
          "slug": "refund-execution-plan",
          "kind": "type",
          "declaration": "/**\n * 직렬화 가능한 공개 메타만 열거된다. 실제 CancelablePayment는 비열거 symbol로 봉인되어\n * prepareRefund를 통과하지 않은 객체를 실행할 수 없다.\n */\ntype RefundExecutionPlan = (RefundExecutionPlanBase & {\n    readonly mode: \"full\" | \"partial\";\n    readonly targetKind: \"settled\";\n}) | (RefundExecutionPlanBase & {\n    readonly mode: \"full\" | \"partial\";\n    readonly targetKind: \"deposited-virtual-account\";\n}) | (RefundExecutionPlanBase & {\n    readonly mode: \"full\";\n    readonly targetKind: \"awaiting-deposit\";\n});",
          "sourceDocumentation": "직렬화 가능한 공개 메타만 열거된다. 실제 CancelablePayment는 비열거 symbol로 봉인되어\nprepareRefund를 통과하지 않은 객체를 실행할 수 없다."
        },
        {
          "name": "RefundExecutionRequest",
          "slug": "refund-execution-request",
          "kind": "type",
          "declaration": "type RefundExecutionRequest = SettledRefundRequest | DepositedVirtualAccountRefundRequest | AwaitingDepositRefundRequest;"
        },
        {
          "name": "RefundObservedCancelState",
          "slug": "refund-observed-cancel-state",
          "kind": "interface",
          "declaration": "interface RefundObservedCancelState {\n    readonly transactionKey: string;\n    readonly cancelAmount: number;\n    readonly refundableAmount: number;\n    readonly canceledAt: string;\n    readonly cancelStatus: CancelTransaction[\"cancelStatus\"];\n}"
        },
        {
          "name": "RefundObservedPaymentState",
          "slug": "refund-observed-payment-state",
          "kind": "interface",
          "declaration": "/** quote를 만든 Payment와 실행 직전 재조회 Payment가 같은 관측 상태인지 대조하는 값. */\ninterface RefundObservedPaymentState {\n    readonly status: PaymentStatus;\n    readonly method: Payment[\"method\"];\n    readonly lastTransactionKey: string | null;\n    readonly isPartialCancelable: boolean;\n    readonly cancels: readonly RefundObservedCancelState[];\n}",
          "sourceDocumentation": "quote를 만든 Payment와 실행 직전 재조회 Payment가 같은 관측 상태인지 대조하는 값."
        },
        {
          "name": "RefundPlanError",
          "slug": "refund-plan-error",
          "kind": "interface",
          "declaration": "/** source/kind가 기존 Result 오류 모델과 같은 서버 미도달 오류. */\ninterface RefundPlanError {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-plan\";\n    readonly reason: RefundPlanErrorReason;\n    readonly field?: string;\n    readonly expected?: unknown;\n    readonly actual?: unknown;\n}",
          "sourceDocumentation": "source/kind가 기존 Result 오류 모델과 같은 서버 미도달 오류."
        },
        {
          "name": "RefundPlanErrorReason",
          "slug": "refund-plan-error-reason",
          "kind": "type",
          "declaration": "type RefundPlanErrorReason = \"invalid-quote\" | \"payment-key-mismatch\" | \"order-id-mismatch\" | \"currency-mismatch\" | \"stale-quote\" | \"expired-quote\" | \"payment-state-mismatch\" | \"pending-cancellation\" | \"partial-refund-not-allowed\" | \"partial-refund-before-deposit\" | \"forged-plan\" | \"forged-attempt\" | \"plan-metadata-mismatch\" | \"attempt-metadata-mismatch\" | \"missing-idempotency-key\" | \"invalid-request\" | \"refund-account-required\" | \"refund-account-not-allowed\" | \"tax-free-amount-not-allowed\";"
        },
        {
          "name": "RefundPolicy",
          "slug": "refund-policy",
          "kind": "interface",
          "declaration": "interface RefundPolicy<Input extends RefundQuoteInput = RefundQuoteInput> extends Brand<\"RefundPolicy\"> {\n    readonly id: string;\n    readonly version: string;\n    readonly kind: RefundPolicyKind;\n    quote(input: Input): Result<RefundQuote, RefundQuoteError>;\n    /** 저장 JSON을 활성 policy와 동일 입력으로 재계산해 실행 가능한 quote로 복원한다. */\n    restoreQuote(stored: unknown, input: Input): Result<RefundQuote, RefundQuoteRestoreError>;\n}"
        },
        {
          "name": "RefundPolicyConfigError",
          "slug": "refund-policy-config-error",
          "kind": "type",
          "declaration": "type RefundPolicyConfigError = {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-policy\";\n    readonly policyId: string;\n    readonly field: string;\n    readonly reason: string;\n};"
        },
        {
          "name": "RefundPolicyIdentity",
          "slug": "refund-policy-identity",
          "kind": "interface",
          "declaration": "interface RefundPolicyIdentity {\n    /** CS·원장에 남길 안정적인 정책 ID. */\n    readonly id: string;\n    /** 정책 변경 뒤에도 과거 계산을 재현하기 위한 버전. */\n    readonly version: string;\n    /** quote의 최대 수명. 생략하면 {@link DEFAULT_REFUND_QUOTE_TTL_MS}. */\n    readonly quoteTtlMs?: number;\n}"
        },
        {
          "name": "RefundPolicyKind",
          "slug": "refund-policy-kind",
          "kind": "type",
          "declaration": "type RefundPolicyKind = BuiltInRefundPolicyConfig[\"kind\"] | \"custom\";"
        },
        {
          "name": "RefundPreparation",
          "slug": "refund-preparation",
          "kind": "type",
          "declaration": "type RefundPreparation = NoRefundPreparation | RefundExecutionPlan;"
        },
        {
          "name": "RefundQuote",
          "slug": "refund-quote",
          "kind": "interface",
          "declaration": "/**\n * 정책 계산 결과. plain serializable 값이며 실제 cancel 실행 전 server의 prepareRefund를\n * 통과해야 한다. quote.amount는 항상 이번에 추가로 실행할 금액이다.\n */\ninterface RefundQuote extends Brand<\"RefundQuote\"> {\n    readonly kind: \"none\" | \"full\" | \"partial\";\n    readonly policy: {\n        readonly id: string;\n        readonly version: string;\n        readonly kind: RefundPolicyKind;\n    };\n    readonly paymentKey: PaymentKey;\n    readonly orderId: OrderId;\n    readonly currency: Payment[\"currency\"];\n    readonly evaluatedAt: string;\n    /** exclusive. 실행 시각이 이 값 이상이면 반드시 재조회·재견적한다. */\n    readonly validUntil: string;\n    readonly observedPaymentState: RefundObservedPaymentState;\n    /** quote 생성 때 관찰한 Toss Payment.balanceAmount. */\n    readonly observedBalanceAmount: number;\n    /** 프로젝트 장부가 기대한 잔액. 생성 성공 시 observed와 같다. */\n    readonly expectedBalanceAmount: number;\n    readonly basisAmount: number;\n    readonly alreadyRefundedAmount: number;\n    /** 정책상 누적 환불 가능 총액(반올림 후). */\n    readonly entitlementAmount: number;\n    /** entitlement에서 기존 확정 환불을 뺀 이번 실행액. */\n    readonly amount: number;\n    readonly balanceAfterRefund: number;\n    /** 과거 확정 환불이 현재 정책 entitlement를 이미 넘은 금액. */\n    readonly overRefundedAmount: number;\n    /** 금액 직접 산출 정책이면 null. */\n    readonly rateBps: number | null;\n    readonly rounding: RefundRoundingMode;\n    readonly calculation: RefundCalculation;\n    readonly reason: string | null;\n}",
          "sourceDocumentation": "정책 계산 결과. plain serializable 값이며 실제 cancel 실행 전 server의 prepareRefund를\n통과해야 한다. quote.amount는 항상 이번에 추가로 실행할 금액이다."
        },
        {
          "name": "RefundQuoteError",
          "slug": "refund-quote-error",
          "kind": "type",
          "declaration": "type RefundQuoteError = {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-input\";\n    readonly policyId: string;\n    readonly field: string;\n    readonly reason: string;\n} | {\n    readonly source: \"library\";\n    readonly kind: \"expected-refund-balance-mismatch\";\n    readonly policyId: string;\n    readonly expected: number;\n    readonly actual: number;\n} | {\n    readonly source: \"library\";\n    readonly kind: \"calculated-refund-exceeds-balance\";\n    readonly policyId: string;\n    readonly calculatedAmount: number;\n    readonly balanceAmount: number;\n} | {\n    readonly source: \"library\";\n    readonly kind: \"custom-refund-calculation-failed\";\n    readonly policyId: string;\n    readonly cause: unknown;\n};"
        },
        {
          "name": "RefundQuoteInput",
          "slug": "refund-quote-input",
          "kind": "interface",
          "declaration": "/**\n * 모든 정책 quote의 공통 입력.\n *\n * basisAmount/alreadyRefundedAmount/expectedBalanceAmount는 프로젝트 장부가 제공한다.\n * 라이브러리는 expectedBalanceAmount를 최신 Payment.balanceAmount와 대조하므로 외부\n * 부분취소나 장부 drift가 있으면 계산 전에 멈춘다.\n */\ninterface RefundQuoteInput {\n    readonly payment: Payment;\n    /** 정책 비율을 곱할 프로젝트 장부 기준 금액. */\n    readonly basisAmount: number;\n    /** 프로젝트 장부에 provider 완료로 확정된 누적 환불액. */\n    readonly alreadyRefundedAmount: number;\n    /** 프로젝트 장부가 기대하는 현재 Toss 환불 가능 잔액. */\n    readonly expectedBalanceAmount: number;\n    /** 정책을 평가한 시각. 테스트 가능한 명시 입력이며 quote에 ISO로 남는다. */\n    readonly evaluatedAt: Date;\n    /**\n     * 프로젝트 상태가 반드시 다시 평가되어야 하는 더 이른 경계(선택).\n     * 예: 잔여 달력 일수는 다음 현지 자정. 정책 TTL/시간 구간 경계와의 최솟값이 적용된다.\n     */\n    readonly validUntil?: Date;\n}",
          "sourceDocumentation": "모든 정책 quote의 공통 입력.\n\nbasisAmount/alreadyRefundedAmount/expectedBalanceAmount는 프로젝트 장부가 제공한다.\n라이브러리는 expectedBalanceAmount를 최신 Payment.balanceAmount와 대조하므로 외부\n부분취소나 장부 drift가 있으면 계산 전에 멈춘다."
        },
        {
          "name": "RefundQuoteParseError",
          "slug": "refund-quote-parse-error",
          "kind": "interface",
          "declaration": "interface RefundQuoteParseError {\n    readonly source: \"library\";\n    readonly kind: \"invalid-refund-quote\";\n    readonly field: string;\n    readonly reason: string;\n}"
        },
        {
          "name": "RefundQuotePolicyMismatchError",
          "slug": "refund-quote-policy-mismatch-error",
          "kind": "interface",
          "declaration": "interface RefundQuotePolicyMismatchError {\n    readonly source: \"library\";\n    readonly kind: \"refund-quote-policy-mismatch\";\n    readonly policyId: string;\n    readonly reason: \"stored-quote-does-not-match-recalculation\";\n}"
        },
        {
          "name": "RefundQuoteRestoreError",
          "slug": "refund-quote-restore-error",
          "kind": "type",
          "declaration": "type RefundQuoteRestoreError = RefundQuoteParseError | RefundQuoteError | RefundQuotePolicyMismatchError;"
        },
        {
          "name": "RefundRoundingMode",
          "slug": "refund-rounding-mode",
          "kind": "type",
          "declaration": "type RefundRoundingMode = \"floor\" | \"ceil\" | \"half-up\";"
        },
        {
          "name": "RefundRuntimeOptions",
          "slug": "refund-runtime-options",
          "kind": "type",
          "declaration": "/** 실행 시점에만 결정되는 transport 옵션. body와 멱등키에는 영향을 주지 않는다. */\ntype RefundRuntimeOptions<E extends Env> = Pick<CallOptions<E>, \"signal\" | \"testCode\">;",
          "sourceDocumentation": "실행 시점에만 결정되는 transport 옵션. body와 멱등키에는 영향을 주지 않는다."
        },
        {
          "name": "RefundTargetKind",
          "slug": "refund-target-kind",
          "kind": "type",
          "declaration": "/**\n * 비즈니스 환불 견적을 Toss 취소 primitive에 안전하게 결속하는 서버 계층.\n *\n * 정책 계산은 core의 RefundQuote가 소유하고, 이 모듈은 현재 조회 스냅샷과 견적의\n * 신원/잔액을 다시 대조한 뒤 기존 TossCancels에만 위임한다. 견적 금액을 현재 잔액에\n * 조용히 맞추지 않는다 — stale 견적은 새 조회·새 견적·새 멱등키가 필요한 별도 요청이다.\n */\ntype RefundTargetKind = CancelablePayment[\"kind\"];",
          "sourceDocumentation": "비즈니스 환불 견적을 Toss 취소 primitive에 안전하게 결속하는 서버 계층.\n\n정책 계산은 core의 RefundQuote가 소유하고, 이 모듈은 현재 조회 스냅샷과 견적의\n신원/잔액을 다시 대조한 뒤 기존 TossCancels에만 위임한다. 견적 금액을 현재 잔액에\n조용히 맞추지 않는다 — stale 견적은 새 조회·새 견적·새 멱등키가 필요한 별도 요청이다."
        },
        {
          "name": "remainingCalendarDays",
          "slug": "remaining-calendar-days",
          "kind": "function",
          "declaration": "/**\n * IANA 시간대의 달력 일수로 totalUnits/remainingUnits를 만든다.\n * 반환값은 remaining-units 정책 quote 입력에 그대로 펼칠 수 있다.\n */\ndeclare function remainingCalendarDays(input: RemainingCalendarDaysInput): Result<RemainingCalendarDays, RefundCalendarError>;",
          "sourceDocumentation": "IANA 시간대의 달력 일수로 totalUnits/remainingUnits를 만든다.\n반환값은 remaining-units 정책 quote 입력에 그대로 펼칠 수 있다."
        },
        {
          "name": "RemainingCalendarDays",
          "slug": "remaining-calendar-days--interface",
          "kind": "interface",
          "declaration": "interface RemainingCalendarDays {\n    readonly startsOn: string;\n    readonly endsOnExclusive: string;\n    readonly evaluatedOn: string;\n    readonly timeZone: string;\n    readonly requestDay: \"refundable\" | \"consumed\";\n    /** 다음 현지 달력 날짜가 시작되는 exclusive 재평가 시각(ISO instant). */\n    readonly validUntil: string;\n    readonly totalUnits: number;\n    readonly remainingUnits: number;\n}"
        },
        {
          "name": "RemainingCalendarDaysInput",
          "slug": "remaining-calendar-days-input",
          "kind": "interface",
          "declaration": "interface RemainingCalendarDaysInput {\n    /** YYYY-MM-DD, 서비스 시작일 포함. */\n    readonly startsOn: string;\n    /** YYYY-MM-DD, 서비스 종료일 미포함 — 기간은 [startsOn, endsOnExclusive). */\n    readonly endsOnExclusive: string;\n    readonly evaluatedAt: Date;\n    /** IANA time zone (예: Asia/Seoul). */\n    readonly timeZone: string;\n    /** 요청 당일을 환불 대상에 포함할지 명시한다. */\n    readonly requestDay: \"refundable\" | \"consumed\";\n}"
        },
        {
          "name": "RemainingUnitsRefundPolicyConfig",
          "slug": "remaining-units-refund-policy-config",
          "kind": "interface",
          "declaration": "interface RemainingUnitsRefundPolicyConfig extends RefundPolicyIdentity {\n    readonly kind: \"remaining-units\";\n    /** 잔여 비율에 추가로 곱할 비율. 기본 10,000(100%). */\n    readonly rateBps?: number;\n    readonly rounding: RefundRoundingMode;\n    readonly reason?: string;\n}"
        },
        {
          "name": "RemainingUnitsRefundQuoteInput",
          "slug": "remaining-units-refund-quote-input",
          "kind": "interface",
          "declaration": "interface RemainingUnitsRefundQuoteInput extends RefundQuoteInput {\n    /** 전체 일수·회차·사용량 단위. 양수 안전한 정수. */\n    readonly totalUnits: number;\n    /** 0..totalUnits 안전한 정수. */\n    readonly remainingUnits: number;\n}"
        },
        {
          "name": "resolveConfirmFailure",
          "slug": "resolve-confirm-failure",
          "kind": "function",
          "declaration": "/**\n * confirm 실패를 조회 기반으로 판정한다 (설계 §3.7 확정 로직):\n * - `source === 'network'`(transport) 또는 `ALREADY_PROCESSED_PAYMENT` →\n *   `getPaymentByOrderId` 조회 → status가 DONE|WAITING_FOR_DEPOSIT이면 결제를 확인한다.\n *   단, 가상계좌 조회의 `secret:null`은 정상 응답이므로\n *   'confirmed-without-deposit-secret'으로 명시한다.\n * - `NOT_FOUND_PAYMENT_SESSION`(10분 초과 — 라이브러리 시한 초과 에러 동일 취급) →\n *   조회 없이 'retry-payment'.\n * - 그 외 REJECT/AUTH 계열 → 조회 없이 'definitively-failed'.\n *\n * ⚠ 조회 자체가 Err면 진실 미확정이다 — **성공/실패 어느 쪽으로도 사용자에게 단정 안내하지\n * 말 것**(재시도 또는 수동 확인으로 넘겨라).\n *\n * 미해결(Phase 6 실측 항목): ALREADY_PROCESSED_PAYMENT인데 조회 status가 CANCELED인 희귀\n * 케이스(다른 경로로 이미 취소) — 현재는 'definitively-failed'로 분류된다.\n */\ndeclare function resolveConfirmFailure<E extends Env>(client: Pick<TossServerClient<E>, 'getPaymentByOrderId'>, orderId: OrderId, error: ConfirmError): Promise<Result<ConfirmResolution, LookupError$1>>;",
          "sourceDocumentation": "confirm 실패를 조회 기반으로 판정한다 (설계 §3.7 확정 로직):\n- `source === 'network'`(transport) 또는 `ALREADY_PROCESSED_PAYMENT` →\n  `getPaymentByOrderId` 조회 → status가 DONE|WAITING_FOR_DEPOSIT이면 결제를 확인한다.\n  단, 가상계좌 조회의 `secret:null`은 정상 응답이므로\n  'confirmed-without-deposit-secret'으로 명시한다.\n- `NOT_FOUND_PAYMENT_SESSION`(10분 초과 — 라이브러리 시한 초과 에러 동일 취급) →\n  조회 없이 'retry-payment'.\n- 그 외 REJECT/AUTH 계열 → 조회 없이 'definitively-failed'.\n\n⚠ 조회 자체가 Err면 진실 미확정이다 — **성공/실패 어느 쪽으로도 사용자에게 단정 안내하지\n말 것**(재시도 또는 수동 확인으로 넘겨라).\n\n미해결(Phase 6 실측 항목): ALREADY_PROCESSED_PAYMENT인데 조회 status가 CANCELED인 희귀\n케이스(다른 경로로 이미 취소) — 현재는 'definitively-failed'로 분류된다."
        },
        {
          "name": "ResolvedConfirmedPayment",
          "slug": "resolved-confirmed-payment",
          "kind": "type",
          "declaration": "/** 조회 기반 복구가 확인한 결제. 가상계좌 secret은 조회에서 null일 수 있다. */\ntype ResolvedConfirmedPayment = Payment & {\n    readonly status: ConfirmedStatus;\n};",
          "sourceDocumentation": "조회 기반 복구가 확인한 결제. 가상계좌 secret은 조회에서 null일 수 있다."
        },
        {
          "name": "Result",
          "slug": "result",
          "kind": "type",
          "declaration": "/**\n * Result — plain 판별 유니언 + 자유 함수 콤비네이터.\n * 메서드 클래스 금지(직렬화 안전) — 값은 어디서든 plain 객체다.\n */\ntype Result<T, E> = Ok<T> | Err<E>;",
          "sourceDocumentation": "Result — plain 판별 유니언 + 자유 함수 콤비네이터.\n메서드 클래스 금지(직렬화 안전) — 값은 어디서든 plain 객체다."
        },
        {
          "name": "RetryOptions",
          "slug": "retry-options",
          "kind": "interface",
          "declaration": "/**\n * retry — 실측 근거 하드 가드 자동 재시도 (설계 §3.4, 기본 꺼짐).\n *\n * 재시도 허용 조건은 **설정으로 확장 불가, 코드에 고정**이다(Phase 5 실측이 근거인 하드 불변식):\n * 1. GET: TransportFailure만 재시도 (자체 멱등 — 문서).\n * 2. Idempotency-Key가 실제 부착된 POST/DELETE:\n *    (a) TransportFailure — 동일 키+동일 body 재전송은 서버 도달 시 바이트 동일 재생,\n *        미도달 시 재실행(Phase 0 실측 — 이중 실행 없음).\n *    (b) 409 IDEMPOTENT_REQUEST_PROCESSING — 문서 지시(\"다시 요청해서 응답을 확인하세요\") 준수.\n * 3. 키 없는 POST/DELETE(confirm 기본 정책): 어떤 실패든 자동 재시도 절대 없음 — 이중 승인\n *    방지. `retryable: true`여도 무시. confirm에 retry 효과를 받으려면\n *    `options.idempotencyKey` 명시가 전제다.\n * 4. 토스 4xx/5xx 에러 응답: 재시도 안 함 — 4xx는 멱등 재생 실측 확정(같은 키 재시도 =\n *    15일간 같은 에러 재생), 5xx는 재생 여부 미실측이라 보수 배제. PROVIDER_ERROR 등\n *    `retryable: true`도 포함해 배제 — 그 재시도는 \"새 멱등키 + 상황 판단\"이 필요한\n *    호출자 의사결정이다(§7-3).\n *\n * 역할 구분: 이 옵션은 \"요청 내\" 자동화다 — cancel의 CancelRetryTicket은 \"요청 간(큐 저장\n * 후)\" 수동 재실행용으로 그대로 유지·동봉된다. 409 재시도 후 원 요청이 4xx로 끝났으면 그\n * 에러를 재생받고 종료한다 — 처리 결과 확인이라는 올바른 동작이다.\n *\n * ⚠ 기본값 최악 지연 +10.5s(+ 시도별 timeout) — 요청 경로가 아닌 배치/큐 소비자에서 켜라.\n * confirm 경로 권장값은 maxAttempts 2. 409 폴링은 테스트 환경 분당 100건 쿼터를 소모한다.\n */\ninterface RetryOptions {\n    /** 총 시도 횟수(최초 포함). 기본 3. 리터럴 유니언 — 폭주 설정 원천 차단. */\n    readonly maxAttempts?: 2 | 3 | 4 | 5;\n    /**\n     * 시도 간 지연(ms). 기본 [500, 2_000, 8_000], full jitter ±25% 자동. 부족하면 마지막 값 재사용.\n     * 각 값은 0~60_000의 안전한 정수여야 하며 빈 배열은 허용하지 않는다.\n     */\n    readonly delaysMs?: readonly number[];\n    /**\n     * reason이 2종 리터럴로 고정 — toss retryable류로 확장하려면 공개 타입 변경이 필요하도록\n     * 봉인(§7-3). nextDelayMs는 jitter 적용 후 값. 이 콜백의 throw는 삼켜진다(요청 무간섭).\n     */\n    readonly onRetry?: (info: {\n        readonly attempt: number;\n        readonly reason: 'transport' | 'idempotent-processing';\n        readonly nextDelayMs: number;\n        readonly path: string;\n    }) => void;\n}",
          "sourceDocumentation": "retry — 실측 근거 하드 가드 자동 재시도 (설계 §3.4, 기본 꺼짐).\n\n재시도 허용 조건은 **설정으로 확장 불가, 코드에 고정**이다(Phase 5 실측이 근거인 하드 불변식):\n1. GET: TransportFailure만 재시도 (자체 멱등 — 문서).\n2. Idempotency-Key가 실제 부착된 POST/DELETE:\n   (a) TransportFailure — 동일 키+동일 body 재전송은 서버 도달 시 바이트 동일 재생,\n       미도달 시 재실행(Phase 0 실측 — 이중 실행 없음).\n   (b) 409 IDEMPOTENT_REQUEST_PROCESSING — 문서 지시(\"다시 요청해서 응답을 확인하세요\") 준수.\n3. 키 없는 POST/DELETE(confirm 기본 정책): 어떤 실패든 자동 재시도 절대 없음 — 이중 승인\n   방지. `retryable: true`여도 무시. confirm에 retry 효과를 받으려면\n   `options.idempotencyKey` 명시가 전제다.\n4. 토스 4xx/5xx 에러 응답: 재시도 안 함 — 4xx는 멱등 재생 실측 확정(같은 키 재시도 =\n   15일간 같은 에러 재생), 5xx는 재생 여부 미실측이라 보수 배제. PROVIDER_ERROR 등\n   `retryable: true`도 포함해 배제 — 그 재시도는 \"새 멱등키 + 상황 판단\"이 필요한\n   호출자 의사결정이다(§7-3).\n\n역할 구분: 이 옵션은 \"요청 내\" 자동화다 — cancel의 CancelRetryTicket은 \"요청 간(큐 저장\n후)\" 수동 재실행용으로 그대로 유지·동봉된다. 409 재시도 후 원 요청이 4xx로 끝났으면 그\n에러를 재생받고 종료한다 — 처리 결과 확인이라는 올바른 동작이다.\n\n⚠ 기본값 최악 지연 +10.5s(+ 시도별 timeout) — 요청 경로가 아닌 배치/큐 소비자에서 켜라.\nconfirm 경로 권장값은 maxAttempts 2. 409 폴링은 테스트 환경 분당 100건 쿼터를 소모한다."
        },
        {
          "name": "RevokeBillingKeyError",
          "slug": "revoke-billing-key-error",
          "kind": "type",
          "declaration": "type RevokeBillingKeyError = TossApiFailure<'ALREADY_REMOVED_BILLING_KEY' | (string & {})> | TransportFailure | StoreFailure\n/** 봉인 소실 복제본 — billing.load()로 재수화하라. */\n | {\n    readonly source: 'library';\n    readonly kind: 'profile-detached';\n    readonly customerKey: CustomerKey;\n};"
        },
        {
          "name": "RevokeBillingKeyOutcome",
          "slug": "revoke-billing-key-outcome",
          "kind": "interface",
          "declaration": "/**\n * 원격 billing key revoke 뒤 현재 로컬 credential도 제거됐는지의 명시적 결과.\n *\n * `false`는 profile이 오래되어 같은 customerKey에 더 새 billing key가 있거나, 이미\n * 로컬 행이 없어서 현재 credential을 건드리지 않았다는 뜻이다. 원격 DELETE 자체는\n * 성공했으므로 Err가 아니지만, 호출자가 이를 \"현재 결제수단 해제\"로 오해하면 안 된다.\n */\ninterface RevokeBillingKeyOutcome {\n    readonly currentStoredKeyDeleted: boolean;\n}",
          "sourceDocumentation": "원격 billing key revoke 뒤 현재 로컬 credential도 제거됐는지의 명시적 결과.\n\n`false`는 profile이 오래되어 같은 customerKey에 더 새 billing key가 있거나, 이미\n로컬 행이 없어서 현재 credential을 건드리지 않았다는 뜻이다. 원격 DELETE 자체는\n성공했으므로 Err가 아니지만, 호출자가 이를 \"현재 결제수단 해제\"로 오해하면 안 된다."
        },
        {
          "name": "SealedBillingKeyRecord",
          "slug": "sealed-billing-key-record",
          "kind": "interface",
          "declaration": "/**\n * store-save-failed 에러에 동봉되는 발급 record — **billingKey는 봉인 상태**(비공개 심볼,\n * 비열거)다. `JSON.stringify(error)`·스프레드·`Object.values` 어디에도 billingKey 평문이\n * 새지 않는다(BillingProfile과 동일한 봉인 규칙). 재저장하려면 {@link recoverBillingKeyRecord}로\n * 원본 {@link BillingKeyRecord}를 회수해 store.save에 직접 재시도하라.\n */\ninterface SealedBillingKeyRecord extends Omit<BillingKeyRecord, 'billingKey'>, Brand<'SealedBillingKeyRecord'> {\n}",
          "sourceDocumentation": "store-save-failed 에러에 동봉되는 발급 record — **billingKey는 봉인 상태**(비공개 심볼,\n비열거)다. `JSON.stringify(error)`·스프레드·`Object.values` 어디에도 billingKey 평문이\n새지 않는다(BillingProfile과 동일한 봉인 규칙). 재저장하려면 {@link recoverBillingKeyRecord}로\n원본 {@link BillingKeyRecord}를 회수해 store.save에 직접 재시도하라."
        },
        {
          "name": "SerializedPaymentStateSnapshot",
          "slug": "serialized-payment-state-snapshot",
          "kind": "interface",
          "declaration": "/**\n * Brand-free, JSON-ready form of {@link PaymentStateSnapshot}.\n *\n * Identical field-for-field, except `paymentKey`/`orderId` are plain `string`s — safe to put\n * in a response DTO, a queue message or a jsonb column without exporting the branded id types\n * across your provider boundary. Every other field is already a JSON primitive, a plain\n * object array, or `null`. A `PaymentStateSnapshot` is assignable to this type (brands are\n * strings underneath); the reverse direction must go through\n * {@link parsePaymentStateSnapshot}.\n *\n * JSON caveat: a snapshot whose `consistencyIssues` include `invalid-amount` with\n * `reason: 'not-safe-integer'` may carry non-finite numbers (`NaN`/`Infinity`), which\n * `JSON.stringify` silently turns into `null` — the later parse then rejects the value\n * honestly instead of resurrecting a fake amount. Check `consistencyIssues` before\n * persisting a snapshot as JSON.\n */\ninterface SerializedPaymentStateSnapshot {\n    readonly schemaVersion: 1;\n    readonly paymentKey: string;\n    readonly orderId: string;\n    readonly status: PaymentStatus;\n    readonly lifecycle: PaymentLifecycle;\n    readonly totalAmount: number;\n    readonly balanceAmount: number;\n    readonly lastTransactionKey: string | null;\n    readonly canceledAmount: number;\n    readonly amountState: PaymentAmountState;\n    readonly hasPendingCancellation: boolean;\n    readonly hasAbortedCancellation: boolean;\n    readonly isCancelable: boolean;\n    readonly isPartiallyCancelable: boolean;\n    readonly cancels: readonly PaymentCancelTransactionSnapshot[];\n    readonly consistencyIssues: readonly PaymentStateConsistencyIssue[];\n}",
          "sourceDocumentation": "Brand-free, JSON-ready form of {@link PaymentStateSnapshot}.\n\nIdentical field-for-field, except `paymentKey`/`orderId` are plain `string`s — safe to put\nin a response DTO, a queue message or a jsonb column without exporting the branded id types\nacross your provider boundary. Every other field is already a JSON primitive, a plain\nobject array, or `null`. A `PaymentStateSnapshot` is assignable to this type (brands are\nstrings underneath); the reverse direction must go through\n{@link parsePaymentStateSnapshot}.\n\nJSON caveat: a snapshot whose `consistencyIssues` include `invalid-amount` with\n`reason: 'not-safe-integer'` may carry non-finite numbers (`NaN`/`Infinity`), which\n`JSON.stringify` silently turns into `null` — the later parse then rejects the value\nhonestly instead of resurrecting a fake amount. Check `consistencyIssues` before\npersisting a snapshot as JSON."
        },
        {
          "name": "serializePaymentStateSnapshot",
          "slug": "serialize-payment-state-snapshot",
          "kind": "function",
          "declaration": "/**\n * Strips the id brands off a snapshot for transport/persistence.\n *\n * Pure structural copy — no field is renamed, derived or dropped, so\n * `parsePaymentStateSnapshot(JSON.parse(JSON.stringify(serialized)))` round-trips back to a\n * deep-equal branded snapshot (for JSON-safe snapshots; see the type's JSON caveat). The\n * result shares no object references with the input: mutating it cannot corrupt the\n * original snapshot.\n */\ndeclare function serializePaymentStateSnapshot(snapshot: PaymentStateSnapshot): SerializedPaymentStateSnapshot;",
          "sourceDocumentation": "Strips the id brands off a snapshot for transport/persistence.\n\nPure structural copy — no field is renamed, derived or dropped, so\n`parsePaymentStateSnapshot(JSON.parse(JSON.stringify(serialized)))` round-trips back to a\ndeep-equal branded snapshot (for JSON-safe snapshots; see the type's JSON caveat). The\nresult shares no object references with the input: mutating it cannot corrupt the\noriginal snapshot."
        },
        {
          "name": "SettledCancelable",
          "slug": "settled-cancelable",
          "kind": "type",
          "declaration": "type SettledCancelable = (SettledCancelableBase & {\n    readonly partialAllowed: true;\n    readonly payment: SettledCancelableBase['payment'] & {\n        readonly isPartialCancelable: true;\n    };\n}) | (SettledCancelableBase & {\n    readonly partialAllowed: false;\n    readonly payment: SettledCancelableBase['payment'] & {\n        readonly isPartialCancelable: false;\n    };\n});"
        },
        {
          "name": "SettledRefundRequest",
          "slug": "settled-refund-request",
          "kind": "interface",
          "declaration": "interface SettledRefundRequest {\n    readonly reason: CancelReason;\n    readonly refundAccount?: never;\n    readonly taxFreeAmount?: number;\n    readonly cancelRequestId?: CancelRequestId;\n    /** 견적의 결제 통화가 자동 전송된다. */\n    readonly currency?: never;\n}"
        },
        {
          "name": "StoredOrder",
          "slug": "stored-order",
          "kind": "interface",
          "declaration": "/**\n * 저장소 인터페이스 — 검증 플로우가 성립하기 위한 필수 주입 지점.\n *\n * - OrderStore: 금액 비교의 단일 진실 공급원. createOrder가 save를 호출하므로\n *   '저장을 잊는' 실수가 플로우 안에서 불가능해진다.\n * - BillingKeyStore: 토스에 빌링키 조회 API가 없다 — 저장 실패 = 복구 불가.\n */\ninterface StoredOrder {\n    readonly orderId: OrderId;\n    /** requestPayment 시점에 고정한 금액 — 단일 진실 공급원. */\n    readonly amount: number;\n    readonly currency: 'KRW' | 'USD' | 'JPY';\n    readonly orderName: string;\n    /** ISO 8601. */\n    readonly createdAt: string;\n}",
          "sourceDocumentation": "저장소 인터페이스 — 검증 플로우가 성립하기 위한 필수 주입 지점.\n\n- OrderStore: 금액 비교의 단일 진실 공급원. createOrder가 save를 호출하므로\n  '저장을 잊는' 실수가 플로우 안에서 불가능해진다.\n- BillingKeyStore: 토스에 빌링키 조회 API가 없다 — 저장 실패 = 복구 불가."
        },
        {
          "name": "StoreFailure",
          "slug": "store-failure",
          "kind": "interface",
          "declaration": "interface StoreFailure {\n    readonly source: 'library';\n    readonly kind: 'store-failure';\n    readonly operation: 'save' | 'find' | 'delete';\n    readonly cause: unknown;\n}"
        },
        {
          "name": "summarizePaymentState",
          "slug": "summarize-payment-state",
          "kind": "function",
          "declaration": "/**\n * Payment에서 민감 필드를 제거한 현재 상태 스냅샷을 만든다.\n *\n * Accepts the structural {@link PaymentStateInput} (the eight fields the summary actually\n * reads) rather than a full `Payment`, so app-owned reduced payment views can produce\n * snapshots too. Full `Payment` values remain assignable unchanged — the explicit\n * `| Payment` union member keeps fresh inline full-`Payment` literals free of\n * excess-property errors.\n */\ndeclare function summarizePaymentState(payment: PaymentStateInput | Payment): PaymentStateSnapshot;",
          "sourceDocumentation": "Payment에서 민감 필드를 제거한 현재 상태 스냅샷을 만든다.\n\nAccepts the structural {@link PaymentStateInput} (the eight fields the summary actually\nreads) rather than a full `Payment`, so app-owned reduced payment views can produce\nsnapshots too. Full `Payment` values remain assignable unchanged — the explicit\n`| Payment` union member keeps fresh inline full-`Payment` literals free of\nexcess-property errors."
        },
        {
          "name": "TOSS_IDEMPOTENCY_KEY_TTL_MS",
          "slug": "toss-idempotency-key-ttl-ms",
          "kind": "constant",
          "declaration": "TOSS_IDEMPOTENCY_KEY_TTL_MS: number",
          "sourceDocumentation": "How long Toss binds an `Idempotency-Key` to its first response: **15 days from first use**,\nin milliseconds.\n\nSource: Toss Payments API reference, \"Using the API › Authorization\", `Idempotency-Key`\nsection (docs.tosspayments.com/reference/using-api/authorization): max 300 characters, valid\nfor 15 days from the first use, applies to every POST. What happens to a key reused *after*\nthe window is not explicitly documented; treat it as unsafe — the same key **may be executed\nas a brand-new request** — so a long-lived retry queue must stop resubmitting with the same\nkey once the window has passed and fall back to a lookup.\n\nMeasured against the test environment: 4xx error responses are bound to the key for the same\nwindow, so a key that received a definitive 4xx cannot be \"fixed\" by resending — derive a new\nattempt instead (see {@link deriveIdempotencyKey})."
        },
        {
          "name": "TossApiFailure",
          "slug": "toss-api-failure",
          "kind": "interface",
          "declaration": "/**\n * 에러 모델 — 최상위 판별자 `source`: 'library'(API 미도달 보장) / 'toss'(서버 응답) / 'network'(전송).\n *\n * retryable 판정은 **코드 테이블**로만 한다 — HTTP status 판정 금지.\n * 근거: PROVIDER_ERROR는 400이지만 재시도 가능, REFUND_REJECTED는 400이지만 비재시도.\n */\ninterface TossApiFailure<Code extends string = string> {\n    readonly source: 'toss';\n    /** 토스 응답 {code, message} 원문 무손실 보존. */\n    readonly code: Code;\n    readonly message: string;\n    /** 보존하되 판정에 쓰지 않는다. */\n    readonly httpStatus: number;\n    readonly category: ErrorCategory;\n    /** ⚠ 코드 테이블 판정 — HTTP status 아님. */\n    readonly retryable: boolean;\n    /** x-tosspayments-trace-id */\n    readonly traceId: string | null;\n}",
          "sourceDocumentation": "에러 모델 — 최상위 판별자 `source`: 'library'(API 미도달 보장) / 'toss'(서버 응답) / 'network'(전송).\n\nretryable 판정은 **코드 테이블**로만 한다 — HTTP status 판정 금지.\n근거: PROVIDER_ERROR는 400이지만 재시도 가능, REFUND_REJECTED는 400이지만 비재시도."
        },
        {
          "name": "TossCancels",
          "slug": "toss-cancels",
          "kind": "interface",
          "declaration": "/** 취소 실행 네임스페이스 — TossServerClient.cancels 의 타입. */\ninterface TossCancels<E extends Env> {\n    /**\n     * 전액 환불. expectedAmount는 필수이며 **호출자 장부(자체 DB)의 기대 금액**이어야 한다 —\n     * 서버 balanceAmount(= target.balanceAmount)를 되돌려 넣으면 검증이 항진식이 된다.\n     * 불일치 시 API 호출 전 Err. refundableAmount는 항상 자동 전송(서버 낙관적 잠금).\n     * 멱등키 미지정 시 실행 전에 UUID 생성·body와 함께 봉인 — 실패 시 retry 티켓으로 회수.\n     * ⚠ 유니언 오버로드 없음 — kind 내로잉 없이는 호출 자체가 컴파일 에러.\n     */\n    cancelFully(target: SettledCancelable, request: {\n        readonly reason: CancelReason;\n        readonly expectedAmount: number;\n        /** 가상계좌 아님 — 변수/스프레드 경유도 차단. */\n        readonly refundAccount?: never;\n        readonly taxFreeAmount?: number;\n        readonly currency?: 'KRW' | 'USD' | 'JPY';\n        /** 중국·동남아 비동기(Alipay 등) 결제 취소에만 필수 — 상점 발급 고유값(문서 ID 53 §5). */\n        readonly cancelRequestId?: CancelRequestId;\n    }, options?: CallOptions<E>): Promise<Result<CancelOutcome, CancelError>>;\n    cancelFully(target: DepositedVaCancelable, request: {\n        readonly reason: CancelReason;\n        readonly expectedAmount: number;\n        /** 입금 완료 가상계좌 — 필수. */\n        readonly refundAccount: RefundAccount;\n        readonly taxFreeAmount?: number;\n        readonly currency?: 'KRW' | 'USD' | 'JPY';\n        /** 중국·동남아 비동기(Alipay 등) 결제 취소에만 필수 — 상점 발급 고유값(문서 ID 53 §5). */\n        readonly cancelRequestId?: CancelRequestId;\n    }, options?: CallOptions<E>): Promise<Result<CancelOutcome, CancelError>>;\n    cancelFully(target: AwaitingDepositCancelable, request: {\n        readonly reason: CancelReason;\n        readonly expectedAmount: number;\n        /** 입금 전 — 환불할 금액이 없으므로 금지. */\n        readonly refundAccount?: never;\n        /** 중국·동남아 비동기(Alipay 등) 결제 취소에만 필수 — 상점 발급 고유값(문서 ID 53 §5). */\n        readonly cancelRequestId?: CancelRequestId;\n    }, options?: CallOptions<E>): Promise<Result<CancelOutcome, CancelError>>;\n    /**\n     * 부분 환불. AwaitingDepositCancelable 오버로드 없음 → 입금 전 부분취소는 컴파일 에러\n     * (서버: NOT_ALLOWED_PARTIAL_REFUND_WAITING_DEPOSIT). 사전검증: amount ≤ balanceAmount.\n     */\n    cancelPartially(target: Extract<SettledCancelable, {\n        readonly partialAllowed: true;\n    }>, request: {\n        readonly reason: CancelReason;\n        readonly amount: number;\n        readonly refundAccount?: never;\n        readonly taxFreeAmount?: number;\n        readonly currency?: 'KRW' | 'USD' | 'JPY';\n        /** 중국·동남아 비동기(Alipay 등) 결제 취소에만 필수 — 상점 발급 고유값(문서 ID 53 §5). */\n        readonly cancelRequestId?: CancelRequestId;\n    }, options?: CallOptions<E>): Promise<Result<CancelOutcome, CancelError>>;\n    cancelPartially(target: Extract<DepositedVaCancelable, {\n        readonly partialAllowed: true;\n    }>, request: {\n        readonly reason: CancelReason;\n        readonly amount: number;\n        readonly refundAccount: RefundAccount;\n        readonly taxFreeAmount?: number;\n        readonly currency?: 'KRW' | 'USD' | 'JPY';\n        /** 중국·동남아 비동기(Alipay 등) 결제 취소에만 필수 — 상점 발급 고유값(문서 ID 53 §5). */\n        readonly cancelRequestId?: CancelRequestId;\n    }, options?: CallOptions<E>): Promise<Result<CancelOutcome, CancelError>>;\n    /** transport 실패 티켓 재실행 — 봉인된 동일 멱등키+body. 서버에 도달했었다면 멱등 재생, 아니면 재실행. */\n    retry(ticket: CancelRetryTicket, options?: Pick<CallOptions<E>, 'signal'>): Promise<Result<CancelOutcome, CancelError>>;\n    /** 영속 CancelRetryStore의 opaque ticketId로 프로세스 재시작 후 재실행. */\n    retryById(ticketId: string, options?: Pick<CallOptions<E>, 'signal'>): Promise<Result<CancelOutcome, CancelError>>;\n}",
          "sourceDocumentation": "취소 실행 네임스페이스 — TossServerClient.cancels 의 타입."
        },
        {
          "name": "TossClientOptions",
          "slug": "toss-client-options",
          "kind": "interface",
          "declaration": "interface TossClientOptions {\n    /** 기본 globalThis.fetch (Node 20+ 내장). 테스트에서는 모킹 주입 지점. */\n    readonly fetch?: typeof fetch;\n    /** 기본 https://api.tosspayments.com */\n    readonly baseUrl?: string;\n    /**\n     * live 키로 공식 API 호스트 외 주소를 사용하는 위험한 탈출구.\n     * 일반 운영에서는 절대 켜지 말 것.\n     */\n    readonly dangerouslyAllowCustomLiveBaseUrl?: true;\n    /** 기본 30_000ms — AbortSignal.timeout과 호출자 signal을 결합해 적용한다(재시도 시 시도별 독립 적용). */\n    readonly timeoutMs?: number;\n    /** §3.2 아웃바운드 req/res 증거 기록 — 기본 꺼짐. 시도 1건 = AuditEntry 1건. */\n    readonly audit?: AuditOptions;\n    /** §3.4 자동 재시도 — 기본 꺼짐(미설정 시 1회 시도, 현행 동작과 동일). */\n    readonly retry?: RetryOptions;\n    /** §3.3 이벤트 버스 — 'api.call' 전용(논리 요청당 최종 1회). createTossEvents 산출물만 발행이 흐른다. */\n    readonly events?: TossEvents;\n    /** 취소 transport 실패 티켓을 프로세스 재시작 후에도 재실행하기 위한 영속 저장소. */\n    readonly cancelRetries?: CancelRetryStore;\n}"
        },
        {
          "name": "TossEvent",
          "slug": "toss-event",
          "kind": "type",
          "declaration": "/**\n * at: ISO 8601 발화 시각.\n * 분배 조건부 — 구체 K에서는 `{type: K; at} & TossEventMap[K]`와 동일하고,\n * 무인자 별칭(TossEvent)은 type으로 내로잉되는 판별 유니언이 된다.\n */\ntype TossEvent<K extends TossEventName = TossEventName> = K extends TossEventName ? {\n    readonly type: K;\n    readonly at: string;\n} & TossEventMap[K] : never;",
          "sourceDocumentation": "at: ISO 8601 발화 시각.\n분배 조건부 — 구체 K에서는 `{type: K; at} & TossEventMap[K]`와 동일하고,\n무인자 별칭(TossEvent)은 type으로 내로잉되는 판별 유니언이 된다."
        },
        {
          "name": "TossEventMap",
          "slug": "toss-event-map",
          "kind": "interface",
          "declaration": "interface TossEventMap {\n    /**\n     * 요청 라이프사이클 — started/succeeded/failed 3분할 대신 완료 1종(과설계 금지).\n     * 논리 요청당 1회(최종 outcome). durationMs는 첫 시도 시작부터 최종 outcome 확정까지의\n     * 총 경과(재시도 대기 포함) — 시도별 소요는 audit(§3.2)의 AuditEntry.durationMs로.\n     */\n    'api.call': {\n        readonly method: string;\n        readonly path: string;\n        readonly outcome: 'ok' | 'toss-error' | 'transport';\n        readonly httpStatus: number | null;\n        readonly durationMs: number;\n        readonly traceId: string | null;\n        /** retry(§3.4) 결합 시 총 시도 수 — 미결합이면 1. */\n        readonly attempts: number;\n    };\n    /**\n     * store 검증 통과 후 Ok 확정 시점.\n     * ⚠ payment에 secret 포함 가능(실측: BILLING 카드 결제도 non-null) — payload 통짜 로깅\n     * 금지. 기록 용도는 audit(§3.2)으로(redaction 통과본만 기록된다).\n     */\n    'payment.confirmed': {\n        readonly payment: ConfirmedPayment;\n    };\n    'payment.confirm-failed': {\n        /** parse 단계 실패면 null. */\n        readonly orderId: OrderId | null;\n        readonly error: CallbackParseError | VerifyCheckoutError | ConfirmError;\n    };\n    'cancel.executed': {\n        readonly outcome: CancelOutcome;\n    };\n    'cancel.failed': {\n        readonly paymentKey: PaymentKey;\n        readonly error: CancelError;\n    };\n    /** billingKey는 payload 원천 부재 — 봉인 원칙 유지(유출 원천 차단). */\n    'billing.issued': {\n        readonly customerKey: CustomerKey;\n    };\n    'billing.approved': {\n        readonly payment: BillingPayment;\n        readonly customerKey: CustomerKey;\n    };\n    'billing.approve-failed': {\n        readonly customerKey: CustomerKey;\n        readonly error: BillingApproveError;\n    };\n    'billing.revoked': {\n        readonly customerKey: CustomerKey;\n    };\n    /** §3.1 depositSecrets 연동. */\n    'deposit.secret-saved': {\n        readonly orderId: OrderId;\n    };\n    'deposit.secret-save-failed': {\n        readonly orderId: OrderId;\n        readonly paymentKey: PaymentKey;\n        readonly cause: unknown;\n    };\n    /** 요약만 — AcceptedWebhook 통짜 전달 대신 secret 제거·타입 순환 회피가 보장되는 최소 필드. */\n    'webhook.accepted': {\n        readonly trust: 'signature' | 'secret' | 'unverified';\n        readonly eventType: string;\n        readonly transmissionId: string;\n    };\n    'webhook.duplicate': {\n        readonly transmissionId: string;\n    };\n    'webhook.rejected': {\n        readonly rejection: WebhookRejection;\n    };\n}"
        },
        {
          "name": "TossEventName",
          "slug": "toss-event-name",
          "kind": "type",
          "declaration": "type TossEventName = keyof TossEventMap;"
        },
        {
          "name": "TossEvents",
          "slug": "toss-events",
          "kind": "interface",
          "declaration": "/** 공개 표면은 구독 전용 — emit은 내부 인터페이스로만 흐른다(라이브러리만 발행). */\ninterface TossEvents {\n    /**\n     * 반환값 = 구독 해제. 핸들러 파라미터는 구체 K에서 `TossEvent<K>`와 동일한 교차 형태 —\n     * 제네릭 K에서 분배 조건부(TossEvent)가 지연 평가되는 것을 피해 core 이미터와의\n     * 구조적 호환을 유지한다.\n     */\n    on<K extends TossEventName>(type: K, handler: (event: {\n        readonly type: K;\n        readonly at: string;\n    } & TossEventMap[K]) => void | Promise<void>): () => void;\n}",
          "sourceDocumentation": "공개 표면은 구독 전용 — emit은 내부 인터페이스로만 흐른다(라이브러리만 발행)."
        },
        {
          "name": "TossPaymentsApiConfig",
          "slug": "toss-payments-api-config",
          "kind": "interface",
          "declaration": "interface TossPaymentsApiConfig<E extends Env> extends TossPaymentsBaseConfig<E> {\n    /** 브랜드 키만 수용 — raw string 미수용 (§7-1 기각: Env phantom 소실·실패 시점 이원화). */\n    readonly secretKey: ApiSecretKey<E>;\n    /** billing 플로우 배선 — 미지정 시 반환 타입에 `billing` 부재. */\n    readonly billingKeys?: BillingKeyStore;\n    readonly billing?: {\n        readonly capabilities?: BillingCapabilities;\n    };\n}"
        },
        {
          "name": "TossPaymentsBaseConfig",
          "slug": "toss-payments-base-config",
          "kind": "interface",
          "declaration": "/**\n * createTossPayments 파사드 — 배선을 누락할 수 없는 조립층 (설계 §2, G2).\n *\n * **순수 조립층**이다: 기존 팩토리 4종(createTossClient/createConfirmFlow/\n * createBillingFlow/createWebhookVerifier)에 전량 위임하고 검증 로직 중복이 0이다.\n * 배선하지 않은 플로우는 반환 타입에 **프로퍼티 자체가 없어** 사용 시점에 컴파일\n * 에러가 난다 — \"스토어 미제공 시 플로우 생성 불가\"라는 기존 런타임 보장을 타입으로\n * 옮긴 것뿐, 새 동작이 없다.\n *\n * 확정 판정(§2):\n * - flat config + 오버로드 2종 — 중첩(keys/stores/options)은 조건부 타입 판정 경로를\n *   깊게 만들어 추론 취약성만 늘린다.\n * - 단일 키 = 파사드 1개 — 위젯 결제 + 빌링 병용 상점은 파사드 2개(gsk용/sk용)를\n *   만든다. 키 쌍 규칙이 파사드 경계와 일치해 \"confirm은 위젯 client 우선\" 같은\n *   새 암묵 규칙이 생기지 않는다.\n * - raw string 키 미수용(§7-1 기각) — Env phantom 소실·실패 시점 이원화.\n */\ninterface TossPaymentsBaseConfig<E extends Env> {\n    /** confirm 플로우 배선 — 미지정 시 반환 타입에 `confirm` 부재. */\n    readonly orders?: OrderStore;\n    /**\n     * G1 — 1회 배선으로 confirm측 자동 저장 + webhook측 getSecret 대조 양쪽 커버 (§3.1).\n     * README의 수동 저장 한 줄(`db.deposits.save`)이 사라진다.\n     */\n    readonly depositSecrets?: DepositSecretStore;\n    /**\n     * §3.1 saveSecret 실패 통지 — payload에 secret 원문 미포함(로그 유출 방지).\n     * 미지정 시 실패 1건당 console.warn 1회. 복구: `getPaymentByOrderId(orderId)` →\n     * `Payment.secret` → `saveSecret` 재시도.\n     */\n    readonly onDepositSecretSaveFailed?: (info: {\n        readonly orderId: OrderId;\n        readonly paymentKey: PaymentKey;\n        readonly cause: unknown;\n    }) => void;\n    /** webhook 배선 — 미지정 시 반환 타입에 `webhook` 부재. depositSecrets는 위 필드가 자동 배선. */\n    readonly webhook?: {\n        readonly dedupe: WebhookDedupeStore;\n        readonly securityKeys?: readonly SecurityKey[];\n        readonly allowedSourceIps?: readonly string[] | false;\n        /** 서명 이벤트 전송 시각의 과거/미래 허용 폭. 기본 5분. */\n        readonly transmissionTimeToleranceMs?: number | false;\n        /** 테스트 또는 통제된 런타임의 시계 주입용. */\n        readonly clock?: () => Date;\n        /** true → 파사드 내부 client를 PaymentLookup으로 자동 결속 (§3.5 배선 1비트). */\n        readonly autoRefetch?: true;\n    };\n    /**\n     * 옵션 3종 — 기본 전부 꺼짐. events는 client·confirm·billing·webhook 4곳에 자동 배선된다.\n     * 미주입 시 반환 kit의 `events`는 no-op 구독 표면(발행 지점 순회 0회)이다.\n     */\n    readonly events?: TossEvents;\n    readonly audit?: AuditOptions;\n    readonly retry?: RetryOptions;\n    /** 취소 transport 실패 재시도 티켓의 영속 저장소. */\n    readonly cancelRetries?: CancelRetryStore;\n    /** fetch/baseUrl/timeoutMs — audit/retry/events는 파사드가 위 필드에서 병합 주입한다. */\n    readonly client?: Pick<TossClientOptions, 'fetch' | 'baseUrl' | 'timeoutMs' | 'dangerouslyAllowCustomLiveBaseUrl'>;\n    readonly confirm?: Pick<ConfirmFlowOptions, 'approvalWindowMs' | 'clock'>;\n}",
          "sourceDocumentation": "createTossPayments 파사드 — 배선을 누락할 수 없는 조립층 (설계 §2, G2).\n\n**순수 조립층**이다: 기존 팩토리 4종(createTossClient/createConfirmFlow/\ncreateBillingFlow/createWebhookVerifier)에 전량 위임하고 검증 로직 중복이 0이다.\n배선하지 않은 플로우는 반환 타입에 **프로퍼티 자체가 없어** 사용 시점에 컴파일\n에러가 난다 — \"스토어 미제공 시 플로우 생성 불가\"라는 기존 런타임 보장을 타입으로\n옮긴 것뿐, 새 동작이 없다.\n\n확정 판정(§2):\n- flat config + 오버로드 2종 — 중첩(keys/stores/options)은 조건부 타입 판정 경로를\n  깊게 만들어 추론 취약성만 늘린다.\n- 단일 키 = 파사드 1개 — 위젯 결제 + 빌링 병용 상점은 파사드 2개(gsk용/sk용)를\n  만든다. 키 쌍 규칙이 파사드 경계와 일치해 \"confirm은 위젯 client 우선\" 같은\n  새 암묵 규칙이 생기지 않는다.\n- raw string 키 미수용(§7-1 기각) — Env phantom 소실·실패 시점 이원화."
        },
        {
          "name": "TossPaymentsKit",
          "slug": "toss-payments-kit",
          "kind": "type",
          "declaration": "/**\n * 파사드 산출물 — 배선한 플로우만 프로퍼티가 존재한다.\n *\n * 기지 리스크(§2): 조건부 교차 타입의 에러 메시지는 \"`billing` 프로퍼티가 없다\"고만\n * 말하고 원인(billingKeys 미배선)을 직접 말하지 않는다 — 각 프로퍼티 TSDoc의\n * \"이 프로퍼티가 없다면\" 매핑과 README 에러↔원인 표를 참조하라.\n */\ntype TossPaymentsKit<E extends Env, K extends KeyKind, C> = {\n    readonly client: TossServerClient<E, K>;\n    /**\n     * 항상 존재 — config.events 미주입 시 no-op 구독 표면(구독해도 아무 이벤트도 발화되지\n     * 않는다 — 발행 지점 순회 0회). 이벤트를 받으려면 `createTossEvents()`를 config.events에\n     * 주입하라.\n     */\n    readonly events: TossEvents;\n} & (C extends {\n    readonly orders: OrderStore;\n} ? {\n    /** confirm 플로우. **이 프로퍼티가 없다면** → config에 `orders`(OrderStore)가 빠진 것이다. */\n    readonly confirm: ConfirmFlow<E>;\n} : {}) & (C extends {\n    readonly billingKeys: BillingKeyStore;\n} ? {\n    /**\n     * 빌링 플로우. **이 프로퍼티가 없다면** → config에 `billingKeys`(BillingKeyStore)가\n     * 빠졌거나, 위젯 시크릿 키 파사드다(빌링은 API 키 전용 — 키 쌍 규칙).\n     */\n    readonly billing: BillingFlow<E, CapabilitiesOf<C>>;\n} : {}) & (C extends {\n    readonly webhook: object;\n} ? {\n    /** 웹훅 verifier. **이 프로퍼티가 없다면** → config에 `webhook`({ dedupe })이 빠진 것이다. */\n    readonly webhook: WebhookVerifier;\n} : {});",
          "sourceDocumentation": "파사드 산출물 — 배선한 플로우만 프로퍼티가 존재한다.\n\n기지 리스크(§2): 조건부 교차 타입의 에러 메시지는 \"`billing` 프로퍼티가 없다\"고만\n말하고 원인(billingKeys 미배선)을 직접 말하지 않는다 — 각 프로퍼티 TSDoc의\n\"이 프로퍼티가 없다면\" 매핑과 README 에러↔원인 표를 참조하라."
        },
        {
          "name": "TossPaymentsWidgetConfig",
          "slug": "toss-payments-widget-config",
          "kind": "interface",
          "declaration": "interface TossPaymentsWidgetConfig<E extends Env> extends TossPaymentsBaseConfig<E> {\n    readonly secretKey: WidgetSecretKey<E>;\n    /** 빌링은 API 키 전용(키 쌍 규칙) — 위젯 키 + 빌링 배선은 컴파일 에러(400 INVALID_API_KEY 선차단). */\n    readonly billingKeys?: never;\n    readonly billing?: never;\n}"
        },
        {
          "name": "TossServerClient",
          "slug": "toss-server-client",
          "kind": "interface",
          "declaration": "interface TossServerClient<E extends Env = Env, K extends KeyKind = KeyKind> {\n    readonly env: E;\n    readonly keyKind: K;\n    getPayment(key: PaymentKey, options?: Pick<CallOptions<E>, 'signal'>): Promise<Result<Payment, LookupError$1>>;\n    /** DEPOSIT_CALLBACK에는 paymentKey가 없다 — orderId 재조회가 1급 경로. */\n    getPaymentByOrderId(orderId: OrderId, options?: Pick<CallOptions<E>, 'signal'>): Promise<Result<Payment, LookupError$1>>;\n    readonly cancels: TossCancels<E>;\n}"
        },
        {
          "name": "TransferDetails",
          "slug": "transfer-details",
          "kind": "interface",
          "declaration": "interface TransferDetails {\n    readonly bankCode: string;\n    readonly settlementStatus: string;\n}"
        },
        {
          "name": "TransferPayment",
          "slug": "transfer-payment",
          "kind": "interface",
          "declaration": "interface TransferPayment extends PaymentBase {\n    readonly method: '계좌이체';\n    readonly transfer: TransferDetails;\n}"
        },
        {
          "name": "TransportFailure",
          "slug": "transport-failure",
          "kind": "interface",
          "declaration": "interface TransportFailure {\n    readonly source: 'network';\n    readonly code: 'NETWORK_ERROR' | 'TIMEOUT';\n    readonly retryable: true;\n    readonly cause: unknown;\n}"
        },
        {
          "name": "UnverifiedCallback",
          "slug": "unverified-callback",
          "kind": "interface",
          "declaration": "/** successUrl 쿼리의 유일한 파싱 결과 — confirm은 이 타입을 받지 않는다. */\ninterface UnverifiedCallback extends Brand<'UnverifiedCallback'> {\n    readonly paymentKey: PaymentKey;\n    readonly orderId: OrderId;\n    /** 쿼리 문자열 → number 변환·검증 완료. */\n    readonly amount: number;\n    /** 문서 간 불일치(위젯 가이드에만 등장) — 옵셔널 파싱. */\n    readonly paymentType: 'NORMAL' | 'BILLING' | 'BRANDPAY' | null;\n    /** 10분 승인 시한 판정 기준. */\n    readonly receivedAt: Date;\n}",
          "sourceDocumentation": "successUrl 쿼리의 유일한 파싱 결과 — confirm은 이 타입을 받지 않는다."
        },
        {
          "name": "unwrapOr",
          "slug": "unwrap-or",
          "kind": "function",
          "declaration": "/** 실패 시 대체 값을 반환한다. */\ndeclare function unwrapOr<T, E>(r: Result<T, E>, fallback: T): T;",
          "sourceDocumentation": "실패 시 대체 값을 반환한다."
        },
        {
          "name": "VerifiedCheckout",
          "slug": "verified-checkout",
          "kind": "interface",
          "declaration": "interface VerifiedCheckout extends Brand<'VerifiedCheckout'> {\n    readonly paymentKey: PaymentKey;\n    readonly orderId: OrderId;\n    readonly amount: number;\n    readonly verifiedAt: Date;\n    /** receivedAt + approvalWindowMs — UI 시한 안내용. */\n    readonly approvalDeadline: Date;\n}"
        },
        {
          "name": "VerifyCheckoutError",
          "slug": "verify-checkout-error",
          "kind": "type",
          "declaration": "type VerifyCheckoutError = {\n    readonly source: 'library';\n    readonly kind: 'order-not-found';\n    readonly orderId: OrderId;\n} | {\n    /** 문서 \"반드시 확인하세요\"의 강제 지점 — 금액 변조 시도 신호. */\n    readonly source: 'library';\n    readonly kind: 'amount-mismatch';\n    readonly orderId: OrderId;\n    readonly expected: number;\n    readonly received: number;\n} | {\n    readonly source: 'library';\n    readonly kind: 'approval-window-exceeded';\n    readonly deadline: Date;\n    readonly now: Date;\n} | {\n    readonly source: 'library';\n    readonly kind: 'store-failure';\n    readonly operation: 'load';\n    readonly cause: unknown;\n};"
        },
        {
          "name": "VirtualAccountDetails",
          "slug": "virtual-account-details",
          "kind": "interface",
          "declaration": "interface VirtualAccountDetails {\n    readonly accountNumber: string;\n    readonly accountType: string;\n    readonly bankCode: string;\n    readonly customerName: string;\n    readonly dueDate: string;\n    readonly expired: boolean;\n    readonly settlementStatus: string;\n    readonly refundStatus: string;\n    readonly refundReceiveAccount: unknown | null;\n}"
        },
        {
          "name": "VirtualAccountPayment",
          "slug": "virtual-account-payment",
          "kind": "interface",
          "declaration": "interface VirtualAccountPayment extends PaymentBase {\n    readonly method: '가상계좌';\n    readonly virtualAccount: VirtualAccountDetails;\n    /**\n     * 가상계좌 승인 응답에서만 내려오는 DEPOSIT_CALLBACK 대조값.\n     *\n     * 결제 조회 API는 같은 가상계좌 결제라도 `null`을 반환할 수 있다. 따라서 일반\n     * `Payment` 조회 결과에서 이 값을 복구할 수 있다고 가정하면 안 된다. confirm 응답에서\n     * secret을 보장해야 하는 코드는 server의 `ConfirmedPayment`를 사용한다.\n     */\n    readonly secret: string | null;\n    readonly card: null;\n}"
        },
        {
          "name": "WidgetClientKey",
          "slug": "widget-client-key",
          "kind": "type",
          "declaration": "type WidgetClientKey<E extends Env = Env> = (E extends 'test' ? `test_gck_${string}` : `live_gck_${string}`) & Brand<'WidgetClientKey'> & EnvTag<E>;"
        },
        {
          "name": "widgetCustomerKey",
          "slug": "widget-customer-key",
          "kind": "function",
          "declaration": "declare function widgetCustomerKey(raw: string): Result<WidgetCustomerKey, InvalidInput<'customerKey'>>;"
        },
        {
          "name": "WidgetCustomerKey",
          "slug": "widget-customer-key--type",
          "kind": "type",
          "declaration": "/**\n * CustomerKey ∧ 길이 ≤50 (SDK 문서 한도) — 브라우저 API는 이것만 받는다.\n * 50자(SDK) vs 300자(서버 실측) 문서 모순을 서브타입 분리로 해소한다.\n */\ntype WidgetCustomerKey = CustomerKey & Brand<'WidgetCustomerKey'>;",
          "sourceDocumentation": "CustomerKey ∧ 길이 ≤50 (SDK 문서 한도) — 브라우저 API는 이것만 받는다.\n50자(SDK) vs 300자(서버 실측) 문서 모순을 서브타입 분리로 해소한다."
        },
        {
          "name": "WidgetSecretKey",
          "slug": "widget-secret-key",
          "kind": "type",
          "declaration": "type WidgetSecretKey<E extends Env = Env> = (E extends 'test' ? `test_gsk_${string}` : `live_gsk_${string}`) & Brand<'WidgetSecretKey'> & EnvTag<E>;"
        }
      ]
    },
    {
      "subpath": "./testing",
      "id": "testing",
      "declarationTarget": "./dist/testing.d.cts",
      "symbols": [
        {
          "name": "memoryAuditSink",
          "slug": "memory-audit-sink",
          "kind": "function",
          "declaration": "/**\n * 인메모리 AuditSink — 단위 테스트·프로토타이핑용 (설계 §3.2).\n * `entries`는 기록 순서를 보존한다(클라이언트는 시도 순서대로 동기 record 호출).\n */\ndeclare function memoryAuditSink(): AuditSink & {\n    readonly entries: readonly AuditEntry[];\n};",
          "sourceDocumentation": "인메모리 AuditSink — 단위 테스트·프로토타이핑용 (설계 §3.2).\n`entries`는 기록 순서를 보존한다(클라이언트는 시도 순서대로 동기 record 호출)."
        },
        {
          "name": "memoryBillingKeyStore",
          "slug": "memory-billing-key-store",
          "kind": "function",
          "declaration": "/**\n * customerKey 키 인메모리 BillingKeyStore — save는 같은 customerKey를 덮어쓴다(upsert).\n *\n * `delete` 비교와 제거 사이에 await가 없어 한 JavaScript 프로세스 안에서는 조건부\n * 삭제가 원자적이다. 다중 프로세스/인스턴스 환경에는 DB CAS 또는 transaction 구현을\n * 써야 하며, 이 테스트용 구현을 프로덕션에 사용하면 안 된다.\n */\ndeclare function memoryBillingKeyStore(): BillingKeyStore & {\n    recordOf(customerKey: string): BillingKeyRecord | undefined;\n};",
          "sourceDocumentation": "customerKey 키 인메모리 BillingKeyStore — save는 같은 customerKey를 덮어쓴다(upsert).\n\n`delete` 비교와 제거 사이에 await가 없어 한 JavaScript 프로세스 안에서는 조건부\n삭제가 원자적이다. 다중 프로세스/인스턴스 환경에는 DB CAS 또는 transaction 구현을\n써야 하며, 이 테스트용 구현을 프로덕션에 사용하면 안 된다."
        },
        {
          "name": "memoryCancelRetryStore",
          "slug": "memory-cancel-retry-store",
          "kind": "function",
          "declaration": "/**\n * 취소 재시도 레코드 인메모리 저장소 — 단위 테스트/프로토타입 전용.\n *\n * `recordOf` is a side-effect-free readonly inspection returning a defensive copy.\n */\ndeclare function memoryCancelRetryStore(): CancelRetryStore & {\n    recordOf(ticketId: string): CancelRetryRecord | undefined;\n};",
          "sourceDocumentation": "취소 재시도 레코드 인메모리 저장소 — 단위 테스트/프로토타입 전용.\n\n`recordOf` is a side-effect-free readonly inspection returning a defensive copy."
        },
        {
          "name": "memoryDedupeStore",
          "slug": "memory-dedupe-store",
          "kind": "function",
          "declaration": "/**\n * 인메모리 dedupe — 단일 프로세스 한정. 분산 환경은 Redis `SET NX` 등으로 대체할 것.\n *\n * `stateOf` answers \"what state is this key in right now?\" without the side effect a probing\n * `claim()` has (a claim after `release` would re-occupy the key as `processing` and poison\n * later assertions). `undefined` means the key is unknown or was released — i.e. claimable.\n */\ndeclare function memoryDedupeStore(): WebhookDedupeStore & {\n    stateOf(dedupeKey: string): 'processing' | 'completed' | undefined;\n};",
          "sourceDocumentation": "인메모리 dedupe — 단일 프로세스 한정. 분산 환경은 Redis `SET NX` 등으로 대체할 것.\n\n`stateOf` answers \"what state is this key in right now?\" without the side effect a probing\n`claim()` has (a claim after `release` would re-occupy the key as `processing` and poison\nlater assertions). `undefined` means the key is unknown or was released — i.e. claimable."
        },
        {
          "name": "memoryDepositSecretStore",
          "slug": "memory-deposit-secret-store",
          "kind": "function",
          "declaration": "/**\n * orderId 키 인메모리 DepositSecretStore — saveSecret은 같은 orderId를 덮어쓴다(upsert 계약).\n * confirm측 자동 저장 + 웹훅측 getSecret 대조를 한 객체로 배선하는 §3.1 인터페이스의\n * 테스트용 구현이다.\n */\ndeclare function memoryDepositSecretStore(): DepositSecretStore & {\n    secretOf(orderId: string): string | undefined;\n};",
          "sourceDocumentation": "orderId 키 인메모리 DepositSecretStore — saveSecret은 같은 orderId를 덮어쓴다(upsert 계약).\nconfirm측 자동 저장 + 웹훅측 getSecret 대조를 한 객체로 배선하는 §3.1 인터페이스의\n테스트용 구현이다."
        },
        {
          "name": "memoryOrderStore",
          "slug": "memory-order-store",
          "kind": "function",
          "declaration": "/**\n * 인메모리 스토어 4종 + 인메모리 AuditSink — 단위 테스트·프로토타이핑용.\n *\n * 프로세스 생존 기간만 유지된다 — 프로덕션 사용 금지(특히 빌링키는 토스에 조회\n * API가 없어 저장 유실 = 복구 불가).\n *\n * 스토어 인터페이스는 전부 타입 전용 import다 — \"./testing\" 엔트리가 server/webhook\n * 모듈의 런타임 코드를 번들에 끌고 가지 않는다(격리 규칙 §2 유지).\n */\n/**\n * orderId 키 인메모리 OrderStore — saveOrder는 같은 orderId를 덮어쓴다.\n *\n * `orderOf` is a side-effect-free inspection hook for assertions (mirrors the\n * `memoryAuditSink().entries` convention): it never creates, claims or mutates an entry, and\n * it returns a defensive copy — mutating the returned object cannot corrupt the store.\n */\ndeclare function memoryOrderStore(): OrderStore & {\n    orderOf(orderId: string): StoredOrder | undefined;\n};",
          "sourceDocumentation": "orderId 키 인메모리 OrderStore — saveOrder는 같은 orderId를 덮어쓴다.\n\n`orderOf` is a side-effect-free inspection hook for assertions (mirrors the\n`memoryAuditSink().entries` convention): it never creates, claims or mutates an entry, and\nit returns a defensive copy — mutating the returned object cannot corrupt the store."
        },
        {
          "name": "signWebhookPayload",
          "slug": "sign-webhook-payload",
          "kind": "function",
          "declaration": "/**\n * `tosspayments-webhook-signature` 헤더 값 생성 —\n * HMAC-SHA256(\"{rawBody}:{transmissionTime}\", 보안 키) → base64에 \"v1:\" 접두사.\n *\n * ⚠ 설계 문서(§3.6)의 동기 시그니처에서 Promise 반환으로 변형했다: 서명은\n * WebCrypto(`crypto.subtle`)로만 계산하는데(플랫폼 중립 — node:crypto 금지)\n * subtle API가 Promise 전용이라 동기 반환이 불가능하다.\n */\ndeclare function signWebhookPayload(rawBody: string, transmissionTime: string, key: SecurityKey): Promise<string>;",
          "sourceDocumentation": "`tosspayments-webhook-signature` 헤더 값 생성 —\nHMAC-SHA256(\"{rawBody}:{transmissionTime}\", 보안 키) → base64에 \"v1:\" 접두사.\n\n⚠ 설계 문서(§3.6)의 동기 시그니처에서 Promise 반환으로 변형했다: 서명은\nWebCrypto(`crypto.subtle`)로만 계산하는데(플랫폼 중립 — node:crypto 금지)\nsubtle API가 Promise 전용이라 동기 반환이 불가능하다."
        },
        {
          "name": "TEST_BILLING_CARD",
          "slug": "test-billing-card",
          "kind": "constant",
          "declaration": "TEST_BILLING_CARD: {\n    readonly cardNumber: \"9410001234567890\";\n    readonly cardExpirationYear: \"30\";\n    readonly cardExpirationMonth: \"12\";\n    readonly customerIdentityNumber: \"900101\";\n    readonly cardPassword: \"12\";\n}",
          "sourceDocumentation": "발급(신용/개인) + 승인(DONE)까지 통과하는 유일 확인 테스트 카드 (위 실측 근거).\n\n`DirectCardIssueInput`에서 customerKey만 빠진 형태 — 스프레드로 바로 쓴다:\n```ts\nflow.issueWithCard({ customerKey, ...TEST_BILLING_CARD });\n```"
        },
        {
          "name": "webhookFixture",
          "slug": "webhook-fixture",
          "kind": "constant",
          "declaration": "webhookFixture: {\n    /**\n     * 평탄 구조 DEPOSIT_CALLBACK — eventType·paymentKey 없이 5필드\n     * (createdAt/secret/status/transactionKey/orderId)뿐인 실제 형식.\n     *\n     * verify를 통과시키려면 `depositSecrets.getSecret(orderId)`가 여기 넣은\n     * secret과 같은 값을 돌려줘야 한다(승인 시 저장한 Payment.secret 대조 모델).\n     * 검증 통과 후 이벤트에는 secret이 남지 않는다(로그 유출 방지 — verifier 규약).\n     */\n    depositCallback(input: {\n        orderId: string;\n        secret: string;\n        status?: DepositCallbackEvent[\"status\"];\n        transactionKey?: string;\n    }): WebhookFixturePayload;\n    /**\n     * 구형 PAYMENT_STATUS_CHANGED — data는 카드 결제 원문 기본값 위에 입력을\n     * 덮어쓴 Payment 전체 객체다(입력이 항상 우선 — 상태와 금액의 정합은 호출자 책임).\n     *\n     * paymentKey/orderId를 raw string으로 받도록 브랜드 필드만 Omit했다 —\n     * 설계 문서 표기(`Partial<Payment> & {...}`) 그대로는 교집합이 브랜드 타입을\n     * 요구해 plain string 입력이 컴파일 에러가 된다(문서 의도 보존 변형).\n     */\n    paymentStatusChanged(input: {\n        payment: DistributiveOmit<Partial<Payment>, \"paymentKey\" | \"orderId\"> & {\n            paymentKey: string;\n            orderId: string;\n            status: \"DONE\" | \"CANCELED\" | \"PARTIAL_CANCELED\" | \"ABORTED\" | \"EXPIRED\";\n        };\n    }): WebhookFixturePayload;\n    /**\n     * 구형 봉투 {eventType, createdAt, data} 저수준 합성기 — BILLING_DELETED 등\n     * 나머지 구형 이벤트와 미지 이벤트(UNKNOWN 전방 호환 경로) 테스트용.\n     * data 구조는 검증하지 않는다 — 기대 구조와 다르면 verifier가 UNKNOWN으로 수용한다.\n     */\n    legacyEvent(eventType: string, data: unknown): WebhookFixturePayload;\n    /**\n     * 신형 서명 이벤트(payout.changed / seller.changed) — 유효 HMAC 서명 헤더 포함.\n     * 생성→검증 왕복으로 SignatureVerified 등급을 테스트한다.\n     *\n     * ⚠ 설계 문서(§3.6)의 동기 시그니처에서 Promise 반환으로 변형 —\n     * 서명 계산이 WebCrypto 전용이라 비동기가 불가피하다({@link signWebhookPayload} 참조).\n     */\n    signedEvent(input: {\n        eventType: \"payout.changed\" | \"seller.changed\";\n        entityBody: unknown;\n        securityKey: SecurityKey;\n        transmissionTime?: string;\n    }): Promise<WebhookFixturePayload>;\n}",
          "sourceDocumentation": "verify(rawBody, headers) 왕복 테스트용 웹훅 페이로드 합성기.\n\n봉투 3종(구형 {eventType,data} / 평탄 DEPOSIT_CALLBACK / 신형 {eventId,entityBody})\n을 실제 전송 형식(헤더·createdAt 포맷 포함)대로 만든다."
        }
      ]
    },
    {
      "subpath": "./webhook",
      "id": "webhook",
      "declarationTarget": "./dist/webhook.d.cts",
      "symbols": [
        {
          "name": "AcceptedWebhook",
          "slug": "accepted-webhook",
          "kind": "type",
          "declaration": "type AcceptedWebhook = SignatureVerified | SecretVerified | Unverified;"
        },
        {
          "name": "ArsReservationChangedEvent",
          "slug": "ars-reservation-changed-event",
          "kind": "interface",
          "declaration": "/** ARS 결제 예약 상태 통지 — 서명 헤더는 payout/seller에만 명시돼 있어 Unverified 등급이다. */\ninterface ArsReservationChangedEvent {\n    readonly envelope: 'v2';\n    readonly eventType: 'ars-reservation.changed';\n    readonly createdAt: string;\n    readonly eventId: string;\n    readonly entityType: 'ars-reservation';\n    readonly entityBody: unknown;\n}",
          "sourceDocumentation": "ARS 결제 예약 상태 통지 — 서명 헤더는 payout/seller에만 명시돼 있어 Unverified 등급이다."
        },
        {
          "name": "BillingDeletedEvent",
          "slug": "billing-deleted-event",
          "kind": "interface",
          "declaration": "interface BillingDeletedEvent {\n    readonly envelope: 'legacy';\n    readonly eventType: 'BILLING_DELETED';\n    readonly createdAt: string;\n    readonly data: {\n        readonly billingKey: string;\n        readonly reason: string;\n    };\n}"
        },
        {
          "name": "CancelStatusChangedEvent",
          "slug": "cancel-status-changed-event",
          "kind": "interface",
          "declaration": "/**\n * 해외 간편결제(PayPal 등) 전용 — 국내 결제 취소에는 발송되지 않는다(문서).\n *\n * data는 문서상 'Cancel 객체'이며 상세 필드 구성은 열린 질문이다 — 문서화된 Cancel 필드\n * 목록에 paymentKey/orderId가 없어 **nullable**로 둔다(필수 요구 시 정상 웹훅이 UNKNOWN\n * 강등). 판별 기준은 cancelStatus만이다. Phase 5 실측 후 재협착 예정.\n */\ninterface CancelStatusChangedEvent {\n    readonly envelope: 'legacy';\n    readonly eventType: 'CANCEL_STATUS_CHANGED';\n    readonly createdAt: string;\n    readonly data: {\n        /** 문서 근거 없음(Cancel 객체 필드 아님) — 있으면 refetch 1순위 키로만 활용. */\n        readonly paymentKey: string | null;\n        /** 문서 근거 없음(Cancel 객체 필드 아님) — 있으면 refetch 2순위 키로만 활용. */\n        readonly orderId: string | null;\n        readonly cancelStatus: 'IN_PROGRESS' | 'DONE' | 'ABORTED';\n        readonly cancelRequestId: string | null;\n        /** Cancel 객체의 취소 건 구분 키(문서) — 최대 64자, nullable 수용. */\n        readonly transactionKey: string | null;\n    };\n}",
          "sourceDocumentation": "해외 간편결제(PayPal 등) 전용 — 국내 결제 취소에는 발송되지 않는다(문서).\n\ndata는 문서상 'Cancel 객체'이며 상세 필드 구성은 열린 질문이다 — 문서화된 Cancel 필드\n목록에 paymentKey/orderId가 없어 **nullable**로 둔다(필수 요구 시 정상 웹훅이 UNKNOWN\n강등). 판별 기준은 cancelStatus만이다. Phase 5 실측 후 재협착 예정."
        },
        {
          "name": "createWebhookVerifier",
          "slug": "create-webhook-verifier",
          "kind": "function",
          "declaration": "declare function createWebhookVerifier(config: WebhookVerifierConfig): WebhookVerifier;"
        },
        {
          "name": "CustomerStatusChangedEvent",
          "slug": "customer-status-changed-event",
          "kind": "interface",
          "declaration": "/** 브랜드페이 고객 상태 변경 통지. */\ninterface CustomerStatusChangedEvent {\n    readonly envelope: 'legacy';\n    readonly eventType: 'CUSTOMER_STATUS_CHANGED';\n    readonly createdAt: string;\n    readonly data: {\n        readonly customerKey: string;\n        readonly status: 'CREATED' | 'REMOVED' | 'PASSWORD_CHANGED' | 'ONE_TOUCH_ACTIVATED' | 'ONE_TOUCH_DEACTIVATED';\n        readonly changedAt: string;\n    };\n}",
          "sourceDocumentation": "브랜드페이 고객 상태 변경 통지."
        },
        {
          "name": "DEFAULT_WEBHOOK_MAX_BODY_BYTES",
          "slug": "default-webhook-max-body-bytes",
          "kind": "constant",
          "declaration": "DEFAULT_WEBHOOK_MAX_BODY_BYTES: number",
          "sourceDocumentation": "Fetch/Node 어댑터가 기본으로 수용하는 최대 raw webhook body 크기(256 KiB)."
        },
        {
          "name": "DepositCallbackEvent",
          "slug": "deposit-callback-event",
          "kind": "interface",
          "declaration": "/**\n * 가상계좌 입금/입금취소 통지 — 원문은 eventType 필드가 없는 평탄 구조라\n * 파서가 구조 판별 후 eventType을 합성한다.\n *\n * ⚠ paymentKey가 없다 — orderId가 1급 키다(승인 시 orderId↔secret 저장 필수).\n * 원문의 secret은 검증에 소비된 뒤 이벤트에서 제거된다(로그 유출 방지) — 타입에도 없다.\n */\ninterface DepositCallbackEvent {\n    readonly envelope: 'flat';\n    readonly eventType: 'DEPOSIT_CALLBACK';\n    /** ±hh:mm 오프셋 형식 — 구형(legacy) 이벤트의 마이크로초 형식과 다르다(문서 예시). */\n    readonly createdAt: string;\n    readonly orderId: string;\n    /** DONE → WAITING_FOR_DEPOSIT 역전이(입금 오류) 케이스가 존재한다. */\n    readonly status: 'WAITING_FOR_DEPOSIT' | 'DONE' | 'CANCELED' | 'PARTIAL_CANCELED';\n    readonly transactionKey: string;\n}",
          "sourceDocumentation": "가상계좌 입금/입금취소 통지 — 원문은 eventType 필드가 없는 평탄 구조라\n파서가 구조 판별 후 eventType을 합성한다.\n\n⚠ paymentKey가 없다 — orderId가 1급 키다(승인 시 orderId↔secret 저장 필수).\n원문의 secret은 검증에 소비된 뒤 이벤트에서 제거된다(로그 유출 방지) — 타입에도 없다."
        },
        {
          "name": "DepositSecretSource",
          "slug": "deposit-secret-source",
          "kind": "interface",
          "declaration": "interface DepositSecretSource {\n    /** 승인 시 저장해 둔 Payment.secret 조회 — DEPOSIT_CALLBACK에는 paymentKey가 없으므로 orderId가 유일한 키. */\n    getSecret(orderId: string): Promise<string | null>;\n}"
        },
        {
          "name": "FetchHandlerOptions",
          "slug": "fetch-handler-options",
          "kind": "interface",
          "declaration": "/**\n * 프레임워크 어댑터 — fetchHandler(Next.js Route Handler / Hono) + nodeHandler(Express).\n *\n * raw body 보존·검증·dedupe·처리 claim 수명주기를 라이브러리가 소유한다.\n * 검증 거부는 400, body 상한 초과는 413, store 장애와 이미 처리 중인 전달은 503으로 재전송을 유도한다.\n * duplicate는 정상 200 ack — 400을 돌려주면 3일 19시간 재전송 폭탄을 맞는다.\n */\ninterface FetchHandlerOptions {\n    /**\n     * 수신 raw body의 최대 바이트 수. 기본 256 KiB.\n     *\n     * Content-Length가 이 값을 넘으면 body를 읽기 전에 413을 반환하고, 길이 헤더가\n     * 없거나 거짓이어도 스트림을 이 값까지만 누적한다. webhook payload는 작아야 하므로\n     * 앱의 reverse proxy/body-parser 제한과 같은 값으로 맞추는 것을 권장한다.\n     */\n    readonly maxBodyBytes?: number;\n    /**\n     * 신뢰할 수 있는 런타임/ingress 메타데이터에서 원본 클라이언트 IP를 추출한다.\n     * X-Forwarded-For를 무조건 믿지 말고, 해당 ingress가 재작성한 값만 사용할 것.\n     */\n    readonly sourceIp?: (request: Request) => string | null | undefined;\n}",
          "sourceDocumentation": "프레임워크 어댑터 — fetchHandler(Next.js Route Handler / Hono) + nodeHandler(Express).\n\nraw body 보존·검증·dedupe·처리 claim 수명주기를 라이브러리가 소유한다.\n검증 거부는 400, body 상한 초과는 413, store 장애와 이미 처리 중인 전달은 503으로 재전송을 유도한다.\nduplicate는 정상 200 ack — 400을 돌려주면 3일 19시간 재전송 폭탄을 맞는다."
        },
        {
          "name": "IncomingHeaders",
          "slug": "incoming-headers",
          "kind": "type",
          "declaration": "type IncomingHeaders = Headers | Readonly<Record<string, string | readonly string[] | undefined>>;"
        },
        {
          "name": "LookupError",
          "slug": "lookup-error",
          "kind": "type",
          "declaration": "/** 조회 실패 — `./server` 엔트리의 LookupError와 동일 형태(구조적 호환). */\ntype LookupError = TossApiFailure<'NOT_FOUND_PAYMENT' | 'UNAUTHORIZED_KEY' | (string & {})> | TransportFailure;",
          "sourceDocumentation": "조회 실패 — `./server` 엔트리의 LookupError와 동일 형태(구조적 호환)."
        },
        {
          "name": "MethodUpdatedEvent",
          "slug": "method-updated-event",
          "kind": "interface",
          "declaration": "/** 브랜드페이 결제수단 변경 통지. */\ninterface MethodUpdatedEvent {\n    readonly envelope: 'legacy';\n    readonly eventType: 'METHOD_UPDATED';\n    readonly createdAt: string;\n    readonly data: {\n        readonly customerKey: string;\n        readonly methodKey: string;\n        readonly status: 'ENABLED' | 'DISABLED' | 'ALIAS_UPDATED';\n    };\n}",
          "sourceDocumentation": "브랜드페이 결제수단 변경 통지."
        },
        {
          "name": "NodeHandlerOptions",
          "slug": "node-handler-options",
          "kind": "interface",
          "declaration": "interface NodeHandlerOptions {\n    /**\n     * 수신 raw body의 최대 바이트 수. 기본 256 KiB.\n     *\n     * Content-Length 초과는 body를 읽기 전에 413으로 거부한다. 스트림 경로도 누적 상한을\n     * 적용한다. `express.raw()`가 이미 Buffer를 만들었다면 그 할당을 되돌릴 수 없으므로\n     * `express.raw({ limit: maxBodyBytes })`를 함께 설정해야 한다.\n     */\n    readonly maxBodyBytes?: number;\n    /** 프록시 트러스트 설정을 반영한 원본 IP 추출기. 생략 시 socket.remoteAddress. */\n    readonly sourceIp?: (request: NodeIncomingMessageLike) => string | null | undefined;\n}"
        },
        {
          "name": "NodeIncomingMessageLike",
          "slug": "node-incoming-message-like",
          "kind": "interface",
          "declaration": "/** Node IncomingMessage와 구조 호환 — node: 빌트인 타입 import 없이 플랫폼 중립을 유지한다. */\ninterface NodeIncomingMessageLike extends AsyncIterable<unknown> {\n    readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;\n    /** express.raw() 사용 시 Buffer가 이미 실려 온다 — 스트림 대신 그것을 쓴다. */\n    readonly body?: unknown;\n    readonly socket?: {\n        readonly remoteAddress?: string | undefined;\n    };\n}",
          "sourceDocumentation": "Node IncomingMessage와 구조 호환 — node: 빌트인 타입 import 없이 플랫폼 중립을 유지한다."
        },
        {
          "name": "NodeServerResponseLike",
          "slug": "node-server-response-like",
          "kind": "interface",
          "declaration": "/** Node ServerResponse와 구조 호환. */\ninterface NodeServerResponseLike {\n    statusCode: number;\n    end(): unknown;\n}",
          "sourceDocumentation": "Node ServerResponse와 구조 호환."
        },
        {
          "name": "NoPaymentReference",
          "slug": "no-payment-reference",
          "kind": "interface",
          "declaration": "interface NoPaymentReference {\n    readonly source: 'library';\n    readonly kind: 'no-payment-reference';\n}"
        },
        {
          "name": "OrderPaymentStatusChangedEvent",
          "slug": "order-payment-status-changed-event",
          "kind": "interface",
          "declaration": "/** 링크페이(Link Pay) 주문 결제 상태 통지. */\ninterface OrderPaymentStatusChangedEvent {\n    readonly envelope: 'legacy';\n    readonly eventType: 'ORDER_PAYMENT_STATUS_CHANGED';\n    readonly createdAt: string;\n    readonly data: {\n        readonly orderKey: string;\n        readonly amount: number;\n        readonly currency: string;\n        readonly customerName: string | null;\n        readonly customerPhoneNumber: string | null;\n        readonly payment: Payment;\n        readonly orderItems: readonly unknown[];\n    };\n}",
          "sourceDocumentation": "링크페이(Link Pay) 주문 결제 상태 통지."
        },
        {
          "name": "parseSecurityKey",
          "slug": "parse-security-key",
          "kind": "function",
          "declaration": "declare function parseSecurityKey(raw: string): Result<SecurityKey, KeyParseError>;"
        },
        {
          "name": "parseTossTimestamp",
          "slug": "parse-toss-timestamp",
          "kind": "function",
          "declaration": "/**\n * 토스 웹훅 createdAt 3형식 관대 파서.\n *\n * 이벤트마다 형식이 다르다(문서):\n * 1. 구형 이벤트: `yyyy-MM-dd'T'HH:mm:ss.SSSSSS` — 마이크로초 6자리, 오프셋 없음\n * 2. DEPOSIT_CALLBACK/신형 이벤트: `yyyy-MM-dd'T'HH:mm:ss±hh:mm` — 오프셋 형식\n * 3. 밀리초 형식(`...ss.SSS`, 오프셋 유무 무관) — 관대 수용\n *\n * 오프셋이 없는 형식은 **KST(+09:00)로 해석**한다 — 비공식 유도: 토스 문서의\n * 오프셋 포함 예시가 전부 +09:00이고 무오프셋 형식과 같은 시스템에서 발신되므로,\n * JS 기본(로컬 타임존) 해석은 UTC 서버에서 9시간 어긋난다.\n * 초과 정밀도(마이크로초 이하)는 밀리초로 절단한다.\n */\ndeclare function parseTossTimestamp(raw: string): Result<Date, {\n    readonly kind: 'bad-timestamp';\n    readonly raw: string;\n}>;",
          "sourceDocumentation": "토스 웹훅 createdAt 3형식 관대 파서.\n\n이벤트마다 형식이 다르다(문서):\n1. 구형 이벤트: `yyyy-MM-dd'T'HH:mm:ss.SSSSSS` — 마이크로초 6자리, 오프셋 없음\n2. DEPOSIT_CALLBACK/신형 이벤트: `yyyy-MM-dd'T'HH:mm:ss±hh:mm` — 오프셋 형식\n3. 밀리초 형식(`...ss.SSS`, 오프셋 유무 무관) — 관대 수용\n\n오프셋이 없는 형식은 **KST(+09:00)로 해석**한다 — 비공식 유도: 토스 문서의\n오프셋 포함 예시가 전부 +09:00이고 무오프셋 형식과 같은 시스템에서 발신되므로,\nJS 기본(로컬 타임존) 해석은 UTC 서버에서 9시간 어긋난다.\n초과 정밀도(마이크로초 이하)는 밀리초로 절단한다."
        },
        {
          "name": "PaymentLookup",
          "slug": "payment-lookup",
          "kind": "interface",
          "declaration": "/**\n * {@link Unverified.refetch}가 요구하는 최소 조회 능력 — 구조적 인터페이스.\n *\n * `@gj-kit/toss-payments/server`의 `TossServerClient`가 그대로 구조 호환된다.\n * webhook 엔트리는 Edge 등에서 서버 클라이언트 없이 단독 사용 가능해야 하므로\n * server 모듈을 import하지 않고 여기서 구조적으로 정의한다.\n */\ninterface PaymentLookup {\n    getPayment(key: PaymentKey, options?: {\n        readonly signal?: AbortSignal;\n    }): Promise<Result<Payment, LookupError>>;\n    getPaymentByOrderId(orderId: OrderId, options?: {\n        readonly signal?: AbortSignal;\n    }): Promise<Result<Payment, LookupError>>;\n}",
          "sourceDocumentation": "{@link Unverified.refetch}가 요구하는 최소 조회 능력 — 구조적 인터페이스.\n\n`@gj-kit/toss-payments/server`의 `TossServerClient`가 그대로 구조 호환된다.\nwebhook 엔트리는 Edge 등에서 서버 클라이언트 없이 단독 사용 가능해야 하므로\nserver 모듈을 import하지 않고 여기서 구조적으로 정의한다."
        },
        {
          "name": "PaymentStatusChangedEvent",
          "slug": "payment-status-changed-event",
          "kind": "interface",
          "declaration": "/**\n * 웹훅 이벤트 타입 + 신뢰 3등급 + 전방 호환 UNKNOWN 래퍼.\n *\n * '검증됨' 단일 타입은 의도적으로 없다 — 토스가 전 이벤트에 서명을 제공하지 않는다:\n * 서명(HMAC)은 payout.changed/seller.changed에만, secret 대조는 DEPOSIT_CALLBACK에만\n * 존재하고 나머지는 암호학적 진위 검증 수단이 없다.\n * 출처: docs/research/toss-payments-v2.md \"웹훅과 보안\".\n */\ninterface PaymentStatusChangedEvent {\n    readonly envelope: 'legacy';\n    readonly eventType: 'PAYMENT_STATUS_CHANGED';\n    /** 마이크로초 6자리 무오프셋 형식(yyyy-MM-dd'T'HH:mm:ss.SSSSSS) — {@link import('./envelope').parseTossTimestamp} 권장. */\n    readonly createdAt: string;\n    /**\n     * core Payment 재사용(웹훅 전용 타입의 이중 관리 회피) + 종결 status 협착 —\n     * 문서: EXPIRED/DONE/ABORTED/CANCELED/PARTIAL_CANCELED로의 전이 시에만 발송된다.\n     */\n    readonly data: Payment & {\n        readonly status: 'DONE' | 'CANCELED' | 'PARTIAL_CANCELED' | 'ABORTED' | 'EXPIRED';\n    };\n}",
          "sourceDocumentation": "웹훅 이벤트 타입 + 신뢰 3등급 + 전방 호환 UNKNOWN 래퍼.\n\n'검증됨' 단일 타입은 의도적으로 없다 — 토스가 전 이벤트에 서명을 제공하지 않는다:\n서명(HMAC)은 payout.changed/seller.changed에만, secret 대조는 DEPOSIT_CALLBACK에만\n존재하고 나머지는 암호학적 진위 검증 수단이 없다.\n출처: docs/research/toss-payments-v2.md \"웹훅과 보안\"."
        },
        {
          "name": "PayoutChangedEvent",
          "slug": "payout-changed-event",
          "kind": "interface",
          "declaration": "/** 지급대행 상태 통지 — v1 범위 밖이라 entityBody는 원문(unknown) 전달, 서명 검증만 제공. */\ninterface PayoutChangedEvent {\n    readonly envelope: 'v2';\n    readonly eventType: 'payout.changed';\n    readonly createdAt: string;\n    readonly eventId: string;\n    readonly entityType: 'payout';\n    readonly entityBody: unknown;\n}",
          "sourceDocumentation": "지급대행 상태 통지 — v1 범위 밖이라 entityBody는 원문(unknown) 전달, 서명 검증만 제공."
        },
        {
          "name": "SecretVerified",
          "slug": "secret-verified",
          "kind": "interface",
          "declaration": "/** DEPOSIT_CALLBACK — 승인 시 저장해 둔 Payment.secret 대조 통과. */\ninterface SecretVerified {\n    readonly trust: 'secret';\n    readonly event: DepositCallbackEvent;\n    readonly meta: WebhookMeta;\n}",
          "sourceDocumentation": "DEPOSIT_CALLBACK — 승인 시 저장해 둔 Payment.secret 대조 통과."
        },
        {
          "name": "SecurityKey",
          "slug": "security-key",
          "kind": "type",
          "declaration": "/**\n * createWebhookVerifier — verify(rawBody, headers) + 신뢰 3등급 판정.\n *\n * raw body 강제: 파싱된 객체를 받는 오버로드는 없다 — 서명 검증이 원천 불가능해진다.\n * HMAC은 WebCrypto(globalThis.crypto.subtle)만 사용한다 — Edge 런타임 호환(node: import 금지).\n */\ntype SecurityKey = string & Brand<'SecurityKey'>;",
          "sourceDocumentation": "createWebhookVerifier — verify(rawBody, headers) + 신뢰 3등급 판정.\n\nraw body 강제: 파싱된 객체를 받는 오버로드는 없다 — 서명 검증이 원천 불가능해진다.\nHMAC은 WebCrypto(globalThis.crypto.subtle)만 사용한다 — Edge 런타임 호환(node: import 금지)."
        },
        {
          "name": "SellerChangedEvent",
          "slug": "seller-changed-event",
          "kind": "interface",
          "declaration": "interface SellerChangedEvent {\n    readonly envelope: 'v2';\n    readonly eventType: 'seller.changed';\n    readonly createdAt: string;\n    readonly eventId: string;\n    readonly entityType: 'seller';\n    readonly entityBody: unknown;\n}"
        },
        {
          "name": "SignatureVerified",
          "slug": "signature-verified",
          "kind": "interface",
          "declaration": "/** payout.changed / seller.changed — HMAC-SHA256 서명 검증 통과. */\ninterface SignatureVerified {\n    readonly trust: 'signature';\n    readonly event: SignedWebhookEvent;\n    readonly meta: WebhookMeta;\n}",
          "sourceDocumentation": "payout.changed / seller.changed — HMAC-SHA256 서명 검증 통과."
        },
        {
          "name": "SignedWebhookEvent",
          "slug": "signed-webhook-event",
          "kind": "type",
          "declaration": "/** 서명(HMAC) 검증이 제공되는 이벤트 — payout/seller 2종뿐이다(문서). */\ntype SignedWebhookEvent = PayoutChangedEvent | SellerChangedEvent;",
          "sourceDocumentation": "서명(HMAC) 검증이 제공되는 이벤트 — payout/seller 2종뿐이다(문서)."
        },
        {
          "name": "TOSS_WEBHOOK_SOURCE_IPS",
          "slug": "toss-webhook-source-ips",
          "kind": "constant",
          "declaration": "TOSS_WEBHOOK_SOURCE_IPS: readonly string[]",
          "sourceDocumentation": "토스 웹훅 발신 IP 목록 — 테스트/라이브 구분 없는 단일 목록(문서).\n\n갱신 이력: 최초 4개(13.124.x, 3.3x.x) + 2024년 12월 추가 6개(115.92.221.121–127 중\n`.124`는 문서 목록에 없다 — 원문 그대로 반영).\n출처: docs/research/toss-payments-v2.md \"웹훅과 보안\" / \"누락 보강 조사\"."
        },
        {
          "name": "UnknownWebhookEvent",
          "slug": "unknown-webhook-event",
          "kind": "interface",
          "declaration": "/** 전방 호환 래퍼 — 새 이벤트·알 수 없는 구조가 와도 verify는 깨지지 않는다. */\ninterface UnknownWebhookEvent {\n    readonly envelope: 'legacy' | 'v2' | 'flat';\n    readonly eventType: 'UNKNOWN';\n    /** 원문의 eventType 문자열 — 구조상 존재하지 않았으면 빈 문자열. */\n    readonly rawEventType: string;\n    readonly createdAt: string | null;\n    readonly raw: unknown;\n}",
          "sourceDocumentation": "전방 호환 래퍼 — 새 이벤트·알 수 없는 구조가 와도 verify는 깨지지 않는다."
        },
        {
          "name": "Unverified",
          "slug": "unverified",
          "kind": "interface",
          "declaration": "/** 나머지 전부 — 이름부터 신뢰 금지. payload를 직접 믿지 말고 refetch로 승격하라. */\ninterface Unverified {\n    readonly trust: 'unverified';\n    readonly event: UnverifiedWebhookEvent;\n    readonly meta: WebhookMeta;\n    /** 조회 API 재확인 — Unverified를 신뢰 가능한 Payment로 승격하는 유일한 경로(한 줄, 단언 없음). */\n    refetch(client: PaymentLookup): Promise<Result<Payment, LookupError | NoPaymentReference>>;\n    /**\n     * §3.5 autoRefetch 설정 + 어댑터(fetchHandler/nodeHandler) 경유 시에만 채워짐 —\n     * undefined = 옵션 꺼짐 또는 수동 verify 경로. Err여도 이벤트는 핸들러에 도달하며,\n     * 핸들러가 실패를 던지면 어댑터는 claim을 해제하고 5xx로 재전송을 유도한다.\n     *\n     * ⚠ trust는 여전히 'unverified'다 — 조회 성공은 웹훅 발신자 진위를 증명하지 않는다\n     * (위조 웹훅이 실존 orderId를 찍으면 조회는 성공한다, §7-2). payload가 아닌 이 조회\n     * 결과로 상태를 갱신하라. prefetched 실패 시 payload 폴백은 금물.\n     */\n    readonly prefetched?: Result<Payment, LookupError | NoPaymentReference>;\n}",
          "sourceDocumentation": "나머지 전부 — 이름부터 신뢰 금지. payload를 직접 믿지 말고 refetch로 승격하라."
        },
        {
          "name": "UnverifiedWebhookEvent",
          "slug": "unverified-webhook-event",
          "kind": "type",
          "declaration": "type UnverifiedWebhookEvent = PaymentStatusChangedEvent | CancelStatusChangedEvent | BillingDeletedEvent | MethodUpdatedEvent | CustomerStatusChangedEvent | OrderPaymentStatusChangedEvent | ArsReservationChangedEvent | UnknownWebhookEvent;"
        },
        {
          "name": "WebhookClaimState",
          "slug": "webhook-claim-state",
          "kind": "type",
          "declaration": "/**\n * claim은 원자적이어야 한다 — 조회 후 생성하는 2단계 구현은 TOCTOU 레이스라 금지.\n * PROCESSING에는 crash-recovery lease를, COMPLETED에는 토스의 최장 재전송 기간보다 긴\n * TTL(권장 5일)을 적용한다.\n */\ntype WebhookClaimState = 'claimed' | 'processing' | 'completed';",
          "sourceDocumentation": "claim은 원자적이어야 한다 — 조회 후 생성하는 2단계 구현은 TOCTOU 레이스라 금지.\nPROCESSING에는 crash-recovery lease를, COMPLETED에는 토스의 최장 재전송 기간보다 긴\nTTL(권장 5일)을 적용한다."
        },
        {
          "name": "WebhookDedupeStore",
          "slug": "webhook-dedupe-store",
          "kind": "interface",
          "declaration": "interface WebhookDedupeStore {\n    /** 원자적 상태 전이. processing 레코드는 lease 만료 후 재점유 가능해야 한다. */\n    claim(dedupeKey: string): Promise<WebhookClaimState>;\n    /** 비즈니스 핸들러가 내구적 처리를 완료한 후만 호출된다. */\n    complete(dedupeKey: string): Promise<void>;\n    /** 처리 실패 시 재전송이 다시 점유할 수 있게 한다. */\n    release(dedupeKey: string): Promise<void>;\n}"
        },
        {
          "name": "WebhookHandlers",
          "slug": "webhook-handlers",
          "kind": "interface",
          "declaration": "/**\n * 핸들러 키 = 구독 가능한 전체 이벤트.\n * `onBillingApproved`는 존재하지 않는다 — 토스가 빌링 승인 웹훅을 제공하지 않는다\n * (BILLING_DELETED만 존재). approve 반환값 + getPayment 재확인이 완결 신호다.\n */\ninterface WebhookHandlers {\n    onDepositCallback?: (w: SecretVerified) => void | Promise<void>;\n    onPaymentStatusChanged?: (w: Unverified & {\n        event: PaymentStatusChangedEvent;\n    }) => void | Promise<void>;\n    /** 해외 간편결제 전용. */\n    onCancelStatusChanged?: (w: Unverified & {\n        event: CancelStatusChangedEvent;\n    }) => void | Promise<void>;\n    onBillingDeleted?: (w: Unverified & {\n        event: BillingDeletedEvent;\n    }) => void | Promise<void>;\n    onMethodUpdated?: (w: Unverified & {\n        event: MethodUpdatedEvent;\n    }) => void | Promise<void>;\n    onCustomerStatusChanged?: (w: Unverified & {\n        event: CustomerStatusChangedEvent;\n    }) => void | Promise<void>;\n    onOrderPaymentStatusChanged?: (w: Unverified & {\n        event: OrderPaymentStatusChangedEvent;\n    }) => void | Promise<void>;\n    onPayoutChanged?: (w: SignatureVerified & {\n        event: PayoutChangedEvent;\n    }) => void | Promise<void>;\n    onSellerChanged?: (w: SignatureVerified & {\n        event: SellerChangedEvent;\n    }) => void | Promise<void>;\n    onArsReservationChanged?: (w: Unverified & {\n        event: ArsReservationChangedEvent;\n    }) => void | Promise<void>;\n    /** 전방 호환 — 새 이벤트가 와도 여기로 흐른다. */\n    onUnknownEvent?: (w: Unverified & {\n        event: UnknownWebhookEvent;\n    }) => void | Promise<void>;\n}",
          "sourceDocumentation": "핸들러 키 = 구독 가능한 전체 이벤트.\n`onBillingApproved`는 존재하지 않는다 — 토스가 빌링 승인 웹훅을 제공하지 않는다\n(BILLING_DELETED만 존재). approve 반환값 + getPayment 재확인이 완결 신호다."
        },
        {
          "name": "WebhookMeta",
          "slug": "webhook-meta",
          "kind": "interface",
          "declaration": "/** 모든 웹훅 공통 HTTP 헤더에서 추출한 메타데이터. */\ninterface WebhookMeta {\n    /** tosspayments-webhook-transmission-id — 전송 시도 식별자. */\n    readonly transmissionId: string;\n    /** tosspayments-webhook-transmission-time — 서명 대상에 포함되는 전송 시각. */\n    readonly transmissionTime: string;\n    /** tosspayments-webhook-transmission-retried-count — 누락/비정상이면 0. */\n    readonly retriedCount: number;\n    /** 재전송 시도가 바뀌어도 같은 사업 이벤트를 찾는 안정 dedupe 키. */\n    readonly dedupeKey: string;\n}",
          "sourceDocumentation": "모든 웹훅 공통 HTTP 헤더에서 추출한 메타데이터."
        },
        {
          "name": "WebhookRejection",
          "slug": "webhook-rejection",
          "kind": "type",
          "declaration": "type WebhookRejection = {\n    readonly kind: 'invalid-signature';\n    readonly signatureCount: number;\n    readonly keysTried: number;\n}\n/** 위조 의심 — 저장된 secret과 불일치. */\n | {\n    readonly kind: 'secret-mismatch';\n    readonly orderId: string;\n}\n/** depositSecrets가 null 반환 — 승인 시 저장 누락. */\n | {\n    readonly kind: 'unknown-order';\n    readonly orderId: string;\n} | {\n    readonly kind: 'missing-config';\n    readonly needed: 'securityKeys' | 'depositSecrets';\n} | {\n    readonly kind: 'untrusted-source-ip';\n    readonly ip: string;\n}\n/** 서명·secret이 없는 이벤트에서 출처 IP 미제공 — 기본 fail-closed. */\n | {\n    readonly kind: 'missing-source-ip';\n} | {\n    readonly kind: 'invalid-transmission-time';\n    readonly value: string;\n} | {\n    readonly kind: 'stale-transmission-time';\n    readonly value: string;\n}\n/** 동일 이벤트가 아직 처리 중 — 어댑터는 503으로 재전송을 유도한다. */\n | {\n    readonly kind: 'processing';\n    readonly dedupeKey: string;\n} | {\n    readonly kind: 'parse-failed';\n    readonly detail: string;\n} | {\n    readonly kind: 'store-failure';\n    readonly cause: unknown;\n};"
        },
        {
          "name": "WebhookVerdict",
          "slug": "webhook-verdict",
          "kind": "type",
          "declaration": "/** duplicate는 Err가 아닌 정상 verdict — 200 ack 후 스킵 (400 반환 시 3일 19시간 재전송 폭탄). */\ntype WebhookVerdict = {\n    readonly duplicate: false;\n    readonly webhook: AcceptedWebhook;\n} | {\n    readonly duplicate: true;\n    readonly transmissionId: string;\n};",
          "sourceDocumentation": "duplicate는 Err가 아닌 정상 verdict — 200 ack 후 스킵 (400 반환 시 3일 19시간 재전송 폭탄)."
        },
        {
          "name": "WebhookVerifier",
          "slug": "webhook-verifier",
          "kind": "interface",
          "declaration": "interface WebhookVerifier {\n    /**\n     * raw body 강제 — 파싱된 객체를 받는 오버로드는 없다(서명 검증 원천 보장).\n     * 순서: 헤더 추출 → 봉투 판별 → 진위 검증(일반 이벤트는 sourceIp 필수) →\n     * dedupe.claim(진위 통과 후에만) → verdict.\n     */\n    verify(rawBody: string | Uint8Array, headers: IncomingHeaders, context?: {\n        readonly sourceIp?: string;\n    }): Promise<Result<WebhookVerdict, WebhookRejection>>;\n    /** 수동 verify 경로의 비즈니스 처리 완료 표시. 어댑터는 자동 호출한다. */\n    complete(webhook: AcceptedWebhook): Promise<void>;\n    /** 수동 verify 경로의 처리 실패 보상. 어댑터는 자동 호출한다. */\n    release(webhook: AcceptedWebhook): Promise<void>;\n    /**\n     * Fetch 표준 어댑터(Next.js Route Handler / Hono) — raw body 추출·검증·dedupe와\n     * 처리 완료/실패 claim 전이를 소유한다. 핸들러 완료 후에만 200을 반환하며,\n     * options.maxBodyBytes를 넘는 수신 body는 검증 전에 413으로 거부한다.\n     */\n    fetchHandler(handlers: WebhookHandlers, options?: FetchHandlerOptions): (request: Request) => Promise<Response>;\n    /**\n     * Express/Node — 모든 content-type을 받는 `express.raw()` 뒤에 장착(JSON 파싱 미들웨어 금지).\n     * options.maxBodyBytes를 넘는 수신 body는 검증 전에 413으로 거부한다.\n     */\n    nodeHandler(handlers: WebhookHandlers, options?: NodeHandlerOptions): (req: NodeIncomingMessageLike, res: NodeServerResponseLike) => Promise<void>;\n}"
        },
        {
          "name": "WebhookVerifierConfig",
          "slug": "webhook-verifier-config",
          "kind": "interface",
          "declaration": "interface WebhookVerifierConfig {\n    /** 필수 — 재전송 최대 7회 + 가상계좌 이중 이벤트(PAYMENT_STATUS_CHANGED+DEPOSIT_CALLBACK 동시 구독). */\n    readonly dedupe: WebhookDedupeStore;\n    /** 키 로테이션(재발급 병행 기간) 대비 배열 — 서명×키 조합 중 1개 일치 시 통과. */\n    readonly securityKeys?: readonly SecurityKey[];\n    /** 미주입 상태서 DEPOSIT_CALLBACK 수신 → Err missing-config. */\n    readonly depositSecrets?: DepositSecretSource;\n    /**\n     * 기본: 문서 IP 목록({@link TOSS_WEBHOOK_SOURCE_IPS}) 내장. `false` = 끔.\n     * 서명·secret이 없는 이벤트는 sourceIp가 없으면 거부한다(fail-closed).\n     * 프록시/로드밸런서 뒤에서는 검증된 ingress가 복원한 주소만 전달해야 한다.\n     * Unverified 이벤트의 보조 방어선일 뿐 암호학적 검증을 대체하지 않는다.\n     *\n     * IPv4-mapped IPv6(`::ffff:x.x.x.x`)는 비교 전에 순수 IPv4 표기로 정규화한다 —\n     * Node dual-stack 리스너의 `req.socket.remoteAddress`가 이 형태이기 때문(목록 항목\n     * 쪽도 동일 정규화). 항목은 순수 IPv4 표기 권장.\n     */\n    readonly allowedSourceIps?: readonly string[] | false;\n    /** 서명 전송 시각의 과거/미래 허용 폭. 기본 5분, false는 비권장 비활성화. */\n    readonly transmissionTimeToleranceMs?: number | false;\n    /** 테스트와 시계 주입용. */\n    readonly clock?: () => Date;\n    /**\n     * §3.3 이벤트 버스 — webhook.accepted/duplicate/rejected 발행 지점(요약 필드만 —\n     * DEPOSIT_CALLBACK rawBody의 secret은 어떤 이벤트 payload에도 실리지 않는다).\n     * createTossEvents 산출물만 발행이 흐른다(구조적 모조 객체는 no-op).\n     */\n    readonly events?: TossEvents;\n    /**\n     * §3.5 — 설정 시 fetchHandler/nodeHandler의 핸들러 디스패치 직전에\n     * 결제 참조가 있는 Unverified 이벤트를 자동 재조회해 `prefetched`로 첨부한다.\n     * dedupe 통과분에만 수행(재전송 7회가 조회 7회가 되지 않음).\n     * 어댑터는 prefetch와 핸들러가 성공하고 claim을 COMPLETED로 바꾼 뒤에만 200을 반환한다.\n     * 빠른 응답이 필요하면 핸들러가 내구적 큐에 적재하는 지점까지 책임져야 한다.\n     * 수동 verify() 경로에는 네트워크 호출을 넣지 않으며 trust 등급도 승격하지 않는다.\n     */\n    readonly autoRefetch?: {\n        /** 기존 PaymentLookup 구조적 인터페이스 재사용 — webhook→server 런타임 의존 없음. */\n        readonly client: PaymentLookup;\n        /** 생략 시 결제 참조 보유 이벤트 전부. 분당 100건 쿼터 방어용 필터. */\n        readonly eventTypes?: readonly ('PAYMENT_STATUS_CHANGED' | 'CANCEL_STATUS_CHANGED' | 'ORDER_PAYMENT_STATUS_CHANGED')[];\n    };\n}"
        }
      ]
    }
  ]
}
