{"content_id":"gyaevhk2bq","slug":"x402-payments-2026-v2-base-usdc-settlement-guide","locale":"ko","schema_type":"HowTo","category":"tutorial","category_name":"튜토리얼","title":"x402 결제 2026 v2 구현과 정산 검증","summary":"x402 v2로 API에 소액 결제를 붙이는 Node.js 실습입니다. Base Sepolia에서 결제 조건 선택, 서명 재요청, 정산 검증을 수행하고 재전송과 메인넷 전환 조건을 점검하세요.","sponsorship_disclosure":null,"affiliate_disclosure":null,"commerce_disclosure":null,"author":{"name":"인조이스 편집팀","url":"https://injoys.com/ko/about"},"key_points":["수취 주소와 테스트 결제 지갑을 준비하고 공식 x402 SDK를 설치하세요.","Base Sepolia용 결제 미들웨어를 연결하고 무결제 요청의 402 응답을 확인하세요.","클라이언트에서 허용한 체인·토큰·금액·수취인에 맞는 조건만 선택해 서명하세요.","정산 응답의 거래 해시로 USDC Transfer 이벤트를 조회하세요.","실패 차단과 중복 요청 복구를 시험한 뒤 메인넷 설정을 검토하세요."],"content_markdown":"x402 결제는 API의 402 응답에 결제 조건을 담아 구현합니다. Node.js와 테스트 지갑을 준비하세요. 먼저 Base Sepolia에서 v2 서명과 정산을 검증합니다. 유료 응답은 정산 성공 뒤에 전달하도록 설정하세요.\n\n금액·체인 정보는 출처에 연결한 x402·Base·Circle 공식 문서 기준입니다.\n\n## 실습 준비와 설치\n\nAPI를 수정할 수 있는 개발 환경과 테스트 지갑을 준비하세요. 예제는 Node.js 22와 x402 SDK 2.28.0을 기준으로 구성했습니다. 설치 구조는 [공식 판매자 가이드](https://docs.x402.org/getting-started/quickstart-for-sellers)를 따릅니다.\n\n1. 결제를 받을 EVM 지갑 주소를 준비하세요.\n2. 결제용 테스트 지갑을 별도로 준비하세요.\n3. 결제용 지갑에 Base Sepolia 테스트 USDC를 확보하세요.\n4. 아래 명령으로 실습 프로젝트를 만드세요.\n5. 이후의 TypeScript 파일을 같은 폴더에 저장하세요.\n\n```bash\nmkdir x402-demo\ncd x402-demo\nnpm init -y\nnpm pkg set type=module\nnpm install --save-exact express@5.3.0 viem@2.57.12 @x402/core@2.28.0 @x402/express@2.28.0 @x402/evm@2.28.0 @x402/fetch@2.28.0\nnpm install --save-dev --save-exact typescript tsx @types/node @types/express\n```\n\n수취 서버에는 수취 지갑의 개인키가 필요하지 않습니다. 결제용 개인키는 클라이언트 프로세스에서만 읽도록 구성하세요. 지갑 서명 방식은 [공식 구매자 가이드](https://docs.x402.org/getting-started/quickstart-for-buyers)를 기준으로 합니다.\n\n이 예제의 검증 범위는 타입 검사와 모의 결제 흐름입니다. 실제 테스트넷 송금까지 완료된 예제는 아닙니다. 아래 실행 절차에서 온체인 결과를 직접 확인하세요.\n\n## x402 v1과 v2 비교\n\nv2를 구현할 때는 헤더와 본문 필드를 함께 맞춰야 합니다. 헤더 이름만 바꾸면 데이터 구조가 맞지 않습니다. 다음 표는 [공식 이전 가이드](https://docs.x402.org/guides/migration-v1-to-v2)를 기준으로 합니다.\n\n| 구분 | v1 | v2 |\n|---|---|---|\n| 프로토콜 버전 | `x402Version: 1` | `x402Version: 2` |\n| 최초 결제 조건 | 402 응답의 JSON 본문 | `PAYMENT-REQUIRED` 응답 헤더 |\n| 서명한 결제 데이터 | `X-PAYMENT` 요청 헤더 | `PAYMENT-SIGNATURE` 요청 헤더 |\n| 정산 결과 | `X-PAYMENT-RESPONSE` 응답 헤더 | `PAYMENT-RESPONSE` 응답 헤더 |\n| Base 메인넷 식별자 | `base` | `eip155:8453` |\n| Base Sepolia 식별자 | `base-sepolia` | `eip155:84532` |\n| 결제 조건의 금액 필드 | `maxAmountRequired` | `amount` |\n| 리소스 설명 | 결제 조건 안의 `resource` 등 | 최상위 `resource` 객체 |\n| 결제 페이로드의 조건 | 최상위 `scheme`, `network` 등 | 선택한 조건을 담는 `accepted` 객체 |\n| TypeScript 패키지 예 | `x402-express` | `@x402/express` |\n\nv1의 결제 조건 전달 위치는 [HTTP v1 사양](https://github.com/x402-foundation/x402/blob/main/specs/transports-v1/http.md)에 명시돼 있습니다. v2의 표준 전달 위치는 `PAYMENT-REQUIRED` 헤더입니다. 구현체가 본문도 반환할 수 있지만 헤더를 기준으로 처리하세요.\n\n[HTTP v2 사양](https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md)은 상태 코드를 다음과 같이 설명합니다.\n\n\u003e The server indicates payment is required using the HTTP 402 \"Payment Required\" status code.\n\u003e\n\u003e 출처: x402 HTTP v2 사양\n\nv2 결제 헤더는 JSON을 Base64로 인코딩한 값입니다. Base64는 암호화 방식이 아닙니다. 외부 서비스에서는 HTTPS를 사용하세요.\n\n## HTTP 402 요청과 결제 순서\n\n이 실습은 EVM `exact` 방식의 기본 승인 흐름을 사용합니다. `exact`는 정해진 금액을 결제하는 방식입니다. 순서는 [x402 v2 핵심 사양](https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md)에 따릅니다.\n\n1. 클라이언트가 결제 정보 없이 유료 API를 요청합니다.\n2. 서버가 HTTP 402와 `PAYMENT-REQUIRED`를 반환합니다.\n3. 클라이언트가 `accepts`에서 허용된 조건을 선택합니다.\n4. SDK가 선택한 조건에 맞는 결제 승인을 서명합니다.\n5. 클라이언트가 `PAYMENT-SIGNATURE`를 붙여 재요청합니다.\n6. 서버가 facilitator의 `/verify`로 승인을 검증합니다.\n7. 검증을 통과하면 서버가 리소스 처리를 실행합니다.\n8. 서버가 `/settle`로 정산을 요청합니다.\n9. 정산 성공 뒤 유료 본문과 `PAYMENT-RESPONSE`를 전달합니다.\n\n이 순서는 모든 결제 방식에 공통인 고정 순서가 아닙니다. v2에는 정산 시점이 다른 흐름도 정의돼 있습니다. 아래 코드는 기본 `authorization` 흐름만 허용합니다.\n\n### Resource server와 facilitator의 역할\n\nResource server는 가격과 콘텐츠 접근 조건을 결정합니다. Facilitator는 결제 검증과 정산을 담당합니다. 역할 구분은 [공식 facilitator 문서](https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md)를 기준으로 합니다.\n\n| 구성 요소 | 맡는 일 | 성공으로 간주하면 안 되는 상태 |\n|---|---|---|\n| 클라이언트 | 조건 선택과 결제 승인 서명 | 서명 생성만 완료된 상태 |\n| Resource server | 가격 선언과 유료 응답 통제 | 클라이언트가 성공했다고 주장하는 상태 |\n| Facilitator `/verify` | 서명과 결제 조건의 유효성 확인 | `isValid: true`만 받은 상태 |\n| Facilitator `/settle` | 이 실습에서는 온체인 결제 실행과 결과 반환 | 전송했지만 결과를 확인하지 못한 상태 |\n\n`/verify`는 온체인 상태를 변경하지 않는 검사입니다. 따라서 검증 성공은 입금 완료를 뜻하지 않습니다. 검증 이후에도 잔액이나 승인 상태가 달라질 수 있습니다.\n\n검증이나 정산이 실패하면 유료 본문을 차단하세요. 성공을 확인하지 못했을 때 차단하는 처리를 fail-closed라고 합니다. 이 동작은 [공식 Express 구현](https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts)에서도 확인할 수 있습니다.\n\n## Base 메인넷과 테스트넷 비교\n\n첫 실행에서는 Base Sepolia만 사용하세요. Circle은 테스트넷 USDC에 금융 가치가 없다고 명시합니다. 주소는 [Circle의 USDC 컨트랙트 목록](https://developers.circle.com/stablecoins/usdc-contract-addresses)을 기준으로 합니다.\n\n| 항목 | Base 메인넷 | Base Sepolia 테스트넷 |\n|---|---|---|\n| 용도 | 실제 자산 결제 | 구현과 장애 처리 시험 |\n| Chain ID | `8453` | `84532` |\n| x402 v2 네트워크 | `eip155:8453` | `eip155:84532` |\n| USDC 컨트랙트 | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |\n| 공개 RPC | `https://mainnet.base.org` | `https://sepolia.base.org` |\n| 토큰의 금융 가치 | 실제 금융 가치가 있는 자산 | 금융 가치 없음 |\n\nChain ID와 RPC는 [Base 네트워크 문서](https://docs.base.org/get-started/connect-to-base)에서 확인하세요. 같은 지갑 주소라도 체인이 다르면 잔액이 별개입니다. 테스트넷 성공을 실제 매출로 기록하면 안 됩니다.\n\n테스트 USDC는 [Circle Testnet Faucet](https://faucet.circle.com/)에서 요청할 수 있습니다. 네트워크는 Base Sepolia로 선택하세요. 수령 주소에는 결제용 테스트 지갑 주소를 입력합니다.\n\n## Node.js 유료 API 구현\n\n유료 경로 앞에 `paymentMiddleware`를 연결하세요. 아래 예제는 호출당 **0.001 USDC**를 요구합니다. 금액은 공식 판매자 가이드의 예시 가격을 사용합니다. (확인값 호출당 0.001 USDC · 출처 docs.x402.org · 2026-10-28 확인)\n\n### 서버 파일\n\n다음 코드를 `server.ts`로 저장하세요. 서버는 로컬 인터페이스에서만 요청을 받습니다. 수취 주소는 `PAY_TO` 환경변수에서 읽습니다.\n\n```typescript\nimport express from \"express\";\nimport { getAddress, zeroAddress } from \"viem\";\nimport { paymentMiddleware } from \"@x402/express\";\nimport { HTTPFacilitatorClient, x402ResourceServer } from \"@x402/core/server\";\nimport { ExactEvmScheme } from \"@x402/evm/exact/server\";\n\nconst payTo = getAddress(process.env.PAY_TO ?? \"\");\nif (payTo === zeroAddress) throw new Error(\"Invalid payTo\");\nconst network = \"eip155:84532\";\nconst resourceServer = new x402ResourceServer(\n  new HTTPFacilitatorClient({ url: \"https://x402.org/facilitator\" }),\n).register(network, new ExactEvmScheme());\n\nconst app = express();\napp.use((_req, res, next) =\u003e {\n  res.setHeader(\"Cache-Control\", \"private, no-store\");\n  next();\n});\napp.use(paymentMiddleware({\n  \"GET /paid-data\": {\n    accepts: [{ scheme: \"exact\", price: \"$0.001\", network, payTo }],\n    description: \"Sample paid JSON\",\n    mimeType: \"application/json\",\n  },\n}, resourceServer));\napp.get(\"/paid-data\", (_req, res) =\u003e {\n  res.json({ content: \"Paid sample content\" });\n});\napp.listen(4021, \"127.0.0.1\");\n```\n\n`https://x402.org/facilitator`는 공식 가이드의 테스트용 주소입니다. 이 예제에서는 Base Sepolia 정산에 사용합니다. 운영용 facilitator 선택은 [판매자 가이드의 메인넷 절차](https://docs.x402.org/getting-started/quickstart-for-sellers#running-on-mainnet)를 확인하세요.\n\n### 가격 계산 예시\n\n예제 가격은 USDC 최소 단위로 `1000`입니다. USDC의 소수 자릿수는 6자리입니다. SDK의 가격 변환은 [EVM 서버 구현](https://github.com/x402-foundation/x402/blob/main/typescript/packages/mechanisms/evm/src/exact/server/scheme.ts)을 기준으로 합니다.\n\n```text\n0.001 USDC × 10^6 = 1000 최소 단위\n```\n\n| 확인할 값 | 이 예제의 기대값 |\n|---|---|\n| 서버 설정의 `price` | `\"$0.001\"` |\n| 402 결제 조건의 `amount` | `\"1000\"` |\n| 결제 조건의 `asset` | Base Sepolia USDC 컨트랙트 |\n| 결제 조건의 `payTo` | 설정한 수취 주소 |\n| 결제 조건의 `network` | `eip155:84532` |\n\n`price`는 SDK 설정값입니다. 실제 v2 결제 조건에는 `amount`가 전달됩니다. 정수 금액은 문자열이나 `bigint`로 비교하세요.\n\n### 서버 실행과 402 확인\n\n첫 번째 Bash 터미널에서 서버를 실행하세요. 입력하는 값은 개인키가 아닌 공개 수취 주소입니다. 실행 중에는 이 터미널을 유지합니다.\n\n```bash\nread -r -p '수취 지갑 주소: ' PAY_TO\nexport PAY_TO\nnpx tsx server.ts\n```\n\n두 번째 터미널에서 무결제 요청을 보내세요. 아래 명령은 결제를 서명하지 않습니다. HTTP 상태와 디코딩한 결제 조건을 출력합니다.\n\n```bash\nnode --input-type=module \u003c\u003c'JS'\nconst response = await fetch(\"http://127.0.0.1:4021/paid-data\");\nconst header = response.headers.get(\"PAYMENT-REQUIRED\");\nconsole.log(response.status);\nconsole.log(header \u0026\u0026 JSON.parse(\n  Buffer.from(header, \"base64\").toString(\"utf8\")\n));\nJS\n```\n\n정상적인 무결제 응답은 HTTP 402입니다. 출력된 조건을 위 표와 대조하세요. 유료 콘텐츠가 바로 반환되면 경로 보호 설정부터 수정합니다.\n\n## 결제 조건 선택과 클라이언트 구현\n\n허용한 조건에만 서명하도록 클라이언트를 제한하세요. 이 예제는 Base Sepolia USDC의 정해진 가격만 허용합니다. 결제 옵션 필터는 [공식 x402Client 구현](https://github.com/x402-foundation/x402/blob/main/typescript/packages/core/src/client/x402Client.ts)의 `registerPolicy`를 사용합니다.\n\n1. 아래 코드를 `client.ts`로 저장하세요.\n2. 다음 절의 `check-transfer.ts`도 저장하세요.\n3. 두 파일을 준비한 뒤 실행 절차를 진행하세요.\n\n```typescript\nimport { writeFile } from \"node:fs/promises\";\nimport { getAddress, zeroAddress, type Hex, type Hash } from \"viem\";\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport { x402Client, wrapFetchWithPayment } from \"@x402/fetch\";\nimport { decodePaymentResponseHeader } from \"@x402/core/http\";\nimport { ExactEvmScheme } from \"@x402/evm/exact/client\";\nimport { checkTransfer } from \"./check-transfer.js\";\n\nconst key = process.env.EVM_PRIVATE_KEY ?? \"\";\nif (!/^0x[0-9a-fA-F]{64}$/.test(key)) throw new Error(\"Invalid test key\");\nconst signer = privateKeyToAccount(key as Hex);\nconst payTo = getAddress(process.env.PAY_TO ?? \"\");\nif (payTo === zeroAddress) throw new Error(\"Invalid payTo\");\nconst network = \"eip155:84532\";\nconst token = \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\";\nconst client = new x402Client().register(network, new ExactEvmScheme(signer));\nclient.registerPolicy((version, options) =\u003e version === 2\n  ? options.filter(r =\u003e\n      r.scheme === \"exact\" \u0026\u0026 r.network === network \u0026\u0026\n      r.asset.toLowerCase() === token.toLowerCase() \u0026\u0026\n      r.payTo.toLowerCase() === payTo.toLowerCase() \u0026\u0026 r.amount === \"1000\" \u0026\u0026\n      (!r.extra?.assetTransferMethod || r.extra.assetTransferMethod === \"eip3009\") \u0026\u0026\n      (!r.extra?.paymentFlow || r.extra.paymentFlow === \"authorization\"))\n  : []);\n\nconst paidFetch = wrapFetchWithPayment(fetch, client);\nconst response = await paidFetch(\"http://127.0.0.1:4021/paid-data\", {\n  redirect: \"error\",\n});\nconst raw = response.headers.get(\"PAYMENT-RESPONSE\");\nif (!raw) throw new Error(`No settlement receipt: HTTP ${response.status}`);\nconst settlement = decodePaymentResponseHeader(raw);\nawait writeFile(\"settlement.json\", JSON.stringify(settlement, null, 2));\nif (!response.ok || !settlement.success || settlement.network !== network) {\n  throw new Error(\"Payment unresolved; inspect settlement.json before retrying\");\n}\nconst evidence = await checkTransfer(\n  network, settlement.transaction as Hash, signer.address, payTo, 1000n,\n);\nawait writeFile(\"transfer.json\", JSON.stringify(evidence, null, 2));\nconsole.log(await response.text());\n```\n\n`wrapFetchWithPayment`가 402 처리와 서명 재요청을 담당합니다. 사용 방식은 [공식 fetch 예제](https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/fetch)를 따릅니다. 허용된 결제 조건이 없으면 결제 생성을 중단합니다.\n\nAI 에이전트에서도 이 클라이언트를 사용할 수 있습니다. 다만 호출당 가격 제한은 누적 지출 제한이 아닙니다. 에이전트의 반복 호출 예산은 별도로 관리하세요.\n\n## USDC Transfer 이벤트 검증\n\n정산 결과의 거래 해시로 USDC 전송 로그를 확인하세요. 거래 성공 상태만으로 수취인까지 확인되지는 않습니다. `Transfer` 필드는 [ERC-20 표준](https://eips.ethereum.org/EIPS/eip-20)을 기준으로 합니다.\n\n검증에는 다음 항목이 필요합니다.\n\n| 검증 항목 | 비교 대상 |\n|---|---|\n| RPC의 Chain ID | 서버가 허용한 체인 |\n| 거래 영수증의 `status` | 성공 상태 |\n| 이벤트를 발생시킨 주소 | 해당 체인의 공식 USDC 컨트랙트 |\n| `Transfer.from` | 확인된 결제자 |\n| `Transfer.to` | 서버가 정한 수취 주소 |\n| `Transfer.value` | 서버가 요구한 최소 단위 금액 |\n\n아래 코드를 `check-transfer.ts`로 저장하세요. 조회는 viem의 [getTransactionReceipt](https://viem.sh/docs/actions/public/getTransactionReceipt)를 사용합니다. 이벤트 해석은 [parseEventLogs](https://viem.sh/docs/contract/parseEventLogs)를 기준으로 합니다.\n\n```typescript\nimport {\n  createPublicClient, http, getAddress, parseEventLogs,\n  erc20Abi, type Address, type Hash,\n} from \"viem\";\nimport { base, baseSepolia } from \"viem/chains\";\n\nconst configs = {\n  \"eip155:84532\": {\n    chain: baseSepolia,\n    rpc: \"https://sepolia.base.org\",\n    token: \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\",\n  },\n  \"eip155:8453\": {\n    chain: base,\n    rpc: \"https://mainnet.base.org\",\n    token: \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n  },\n} as const;\n\nexport async function checkTransfer(\n  network: keyof typeof configs, hash: Hash,\n  payer: Address, payTo: Address, amount: bigint,\n) {\n  const cfg = configs[network];\n  const rpc = createPublicClient({ chain: cfg.chain, transport: http(cfg.rpc) });\n  if (await rpc.getChainId() !== cfg.chain.id) throw new Error(\"Wrong chain\");\n  const receipt = await rpc.getTransactionReceipt({ hash });\n  if (receipt.status !== \"success\") throw new Error(\"Transaction failed\");\n  const logs = parseEventLogs({\n    abi: erc20Abi,\n    eventName: \"Transfer\",\n    logs: receipt.logs.filter(\n      log =\u003e getAddress(log.address) === getAddress(cfg.token),\n    ),\n    strict: true,\n  });\n  const transfer = logs.find(log =\u003e\n    getAddress(log.args.from) === getAddress(payer) \u0026\u0026\n    getAddress(log.args.to) === getAddress(payTo) \u0026\u0026\n    log.args.value === amount,\n  );\n  if (!transfer) throw new Error(\"Matching USDC Transfer not found\");\n  return {\n    network, transaction: hash, logIndex: transfer.logIndex,\n    blockHash: receipt.blockHash, blockNumber: receipt.blockNumber.toString(),\n  };\n}\n```\n\n이 함수는 해당 거래의 USDC 전송 사실을 확인합니다. 특정 API 주문에 귀속됐는지는 별도로 확인해야 합니다. 운영 서버에서는 저장된 주문 조건을 비교값으로 사용하세요.\n\n클라이언트가 올린 `transfer.json`을 접근 허가 증거로 믿으면 안 됩니다. 이 예제의 서버는 facilitator의 검증·정산 결과를 사용합니다. 독립 조회를 접근 허가 조건으로 삼으려면 서버에서 실행해야 합니다.\n\n영수증 조회 실패는 곧 결제 실패를 뜻하지 않습니다. RPC 오류라면 저장된 거래 해시를 다시 조회하세요. 추가 확인 수와 체인 재구성 대응은 운영 정책으로 정합니다.\n\n## 테스트 결제 실행과 결과 확인\n\n파일을 모두 저장한 뒤 테스트 지갑으로 실행하세요. 타입 검사는 서명이나 송금을 수행하지 않습니다. 아래 명령은 앞서 설치한 TypeScript 도구를 사용합니다.\n\n1. 다음 명령으로 타입을 검사하세요.\n2. 두 번째 터미널에 서버와 같은 수취 주소를 입력하세요.\n3. 테스트 결제 지갑의 개인키를 숨김 입력하세요.\n4. 클라이언트를 한 번 실행하세요.\n5. 저장된 정산 결과와 전송 로그를 확인하세요.\n\n```bash\nnpx tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext server.ts client.ts check-transfer.ts\nread -r -p '같은 수취 지갑 주소: ' PAY_TO\nread -r -s -p '테스트 결제 지갑 개인키: ' EVM_PRIVATE_KEY\nexport PAY_TO EVM_PRIVATE_KEY\nnpx tsx client.ts\nunset EVM_PRIVATE_KEY\n```\n\n| 결과물 | 의미 | 확인할 내용 |\n|---|---|---|\n| `settlement.json` | 서버가 반환한 정산 결과 | `success`, `network`, `transaction` |\n| `transfer.json` | RPC 조회에서 찾은 전송 기록 | 체인, 거래 해시, 로그 위치, 블록 정보 |\n| 콘솔의 유료 본문 | 이 클라이언트의 검증 이후 출력한 데이터 | 요청한 콘텐츠인지 확인 |\n\n클라이언트를 새로 실행하면 새 결제가 발생할 수 있습니다. 이 최소 예제에는 주문별 중복 방지 저장소가 없습니다. 재시도 기능은 다음 절의 조건을 갖춘 뒤 추가하세요.\n\n## 검증 실패 조건별 처리\n\n실패 시험에서는 유료 본문이 노출되지 않는지 확인하세요. HTTP 상태만 검사하면 차단 누락을 놓칠 수 있습니다. 기준 동작은 [공식 Express 미들웨어](https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts)를 참고합니다.\n\n| 시험 조건 | 기대하는 처리 | 추가 확인 |\n|---|---|---|\n| 결제 헤더 없음 | 402와 결제 조건 반환 | 유료 본문 없음 |\n| 다른 토큰·수취인 제시 | 클라이언트 정책에서 거절 | 서명 생성 안 함 |\n| 잘못된 서명 | 검증 실패로 차단 | 유료 핸들러 실행 안 함 |\n| 잔액 부족 | 검증 또는 정산 실패로 차단 | 정산 성공으로 기록 안 함 |\n| 정산 결과가 실패 | 유료 응답 차단 | 준비된 본문도 전송 안 함 |\n| facilitator 응답 지연 | 성공 미확인 상태로 보류 | 새 승인 자동 생성 금지 |\n| RPC 영수증 조회 오류 | 거래 해시를 보관하고 재조회 | 결제 실패로 단정 금지 |\n\n기본 승인 흐름에서는 정산 전에 핸들러가 실행됩니다. 정산 실패가 외부 작업까지 되돌려 주지는 않습니다. 이 예제처럼 부수 효과 없는 조회부터 적용하세요.\n\n이메일 발송이나 외부 주문 생성은 별도 설계가 필요합니다. 결제와 작업 상태를 함께 기록하세요. 실패 시 재실행 범위를 정해야 중복 작업을 줄일 수 있습니다.\n\n## 정산 후 응답 유실과 재전송 복구\n\n결제 완료와 콘텐츠 전달 완료를 별도 상태로 저장하세요. 결제는 끝났지만 응답 연결이 끊길 수 있습니다. 이런 상황을 처리하는 근거는 [Payment-Identifier 확장 문서](https://docs.x402.org/extensions/payment-identifier)입니다.\n\n동일 작업을 다시 요청해도 중복 처리하지 않는 성질을 멱등성이라고 합니다. `payment-identifier`는 이를 위한 결제 식별자를 제공합니다. 식별자만 추가해도 자동으로 완성되는 기능은 아닙니다.\n\n### 중복 결제와 재전송 비교\n\n같은 서명의 재사용 방지와 주문 중복 방지는 구분해야 합니다. EIP-3009는 사용한 승인 nonce의 재사용을 막습니다. 이 동작은 [ERC-3009 사양](https://eips.ethereum.org/EIPS/eip-3009)에 정의돼 있습니다.\n\n| 상황 | 온체인 승인 관점 | API 운영 관점 |\n|---|---|---|\n| 같은 승인을 다시 정산 | 이미 사용한 nonce 검사 대상 | 기존 결과 조회 필요 |\n| 같은 주문에 새 승인 생성 | 별개의 승인으로 처리될 수 있음 | 중복 결제 방지 필요 |\n| 정산 성공 후 응답 유실 | 자산 이동은 완료될 수 있음 | 콘텐츠 재전달 필요 |\n| 정산 요청 후 시간 초과 | 결과가 아직 불명확함 | 보류 상태와 정산 확인 필요 |\n\n다음 절차는 공식 확장을 운영 저장소에 연결하는 설계안입니다. 앞의 최소 코드에는 포함돼 있지 않습니다. 운영에 적용하려면 영속 저장소와 동시 요청 제어를 추가하세요.\n\n1. 서버에서 `payment-identifier` 확장 지원을 선언하세요.\n2. 클라이언트에서 논리적 요청의 식별자를 보관하세요.\n3. 서버에서 식별자를 결제자와 요청 내용에 연결하세요.\n4. 처리 중 상태를 원자적으로 기록해 동시 실행을 막으세요.\n5. 정산 성공 시 거래 증거와 응답 결과를 저장하세요.\n6. 재요청의 권한과 내용이 같으면 저장된 결과를 반환하세요.\n7. 정산 결과가 불명확하면 기존 승인부터 확인하세요.\n\n결제 식별자는 비밀번호나 접근 토큰이 아닙니다. 식별자만 아는 요청에 유료 캐시를 반환하면 안 됩니다. 다른 사용자나 다른 요청 본문에 재사용되는지도 검사하세요.\n\n운영 기록에는 체인·거래 해시·로그 위치를 함께 남기세요. 이 기록을 주문과 연결해 다른 주문의 재사용을 차단합니다. `Transfer` 일치만으로 주문 귀속까지 입증되지는 않습니다.\n\n## 결제 구현의 흔한 실수\n\n버전 필드와 성공 신호를 혼동하면 결제 판정이 틀어집니다. 아래 표를 배포 검토에 활용하세요. 버전 차이는 [x402 이전 가이드](https://docs.x402.org/guides/migration-v1-to-v2)를 기준으로 합니다.\n\n| 흔한 실수 | 바로잡을 기준 |\n|---|---|\n| `extra.version: \"2\"`를 x402 버전으로 해석 | 프로토콜 버전은 `x402Version`으로 확인 |\n| v2에 `maxAmountRequired` 사용 | 결제 조건의 금액은 `amount` 사용 |\n| `X-PAYMENT-REQUIRED` 헤더를 임의로 생성 | v1은 본문, v2는 `PAYMENT-REQUIRED` 사용 |\n| `isValid: true`를 입금 완료로 처리 | `/settle` 결과를 별도로 확인 |\n| 거래의 최상위 `to`를 USDC 수취인으로 처리 | USDC `Transfer.to` 확인 |\n| 서명자가 거래 전송자와 같다고 가정 | 승인 결제자와 `Transfer.from` 비교 |\n| 테스트넷 수령액을 매출로 합산 | 메인넷과 테스트넷 장부 분리 |\n| 시간 초과 때마다 새 결제 생성 | 기존 주문과 승인 상태부터 복구 |\n| SDK 패키지 버전과 프로토콜 버전을 동일시 | 패키지 버전과 `x402Version`을 별도 관리 |\n\nUSDC 서명의 도메인 정보에도 버전 문자열이 들어갑니다. 그 값은 x402 프로토콜 버전과 목적이 다릅니다. 서명 도메인 구조는 [ERC-3009 사양](https://eips.ethereum.org/EIPS/eip-3009)을 확인하세요.\n\n## 보안 점검과 외부 서비스의 신뢰 범위\n\n결제 성공 판정은 서버에서 통제하세요. 외부 facilitator의 응답도 명시적인 신뢰 대상입니다. 자체 운영도 가능한 구조는 [facilitator 문서](https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md)에 설명돼 있습니다.\n\n- 클라이언트의 성공 메시지로 콘텐츠를 해제하지 마세요.\n- 서버가 선언한 체인·토큰·금액·수취인을 비교하세요.\n- 개인키와 시드 문구를 로그에 남기지 마세요.\n- 승인 서명 전체도 일반 접근 로그에서 제외하세요.\n- 결제 헤더의 크기와 요청 처리 시간을 제한하세요.\n- 유료 응답이 공유 캐시에 남지 않도록 점검하세요.\n- 테스트 키와 운영 키의 보관 위치를 분리하세요.\n- facilitator 장애 시 무결제 우회 경로를 만들지 마세요.\n- 승인 만료와 중복 요청을 실제로 시험하세요.\n\n실습의 고정 의존성에는 보안 검사 경고가 남아 있습니다. `npm audit`는 zod 관련 권고를 보고했습니다. 관련 내용은 [GitHub 보안 권고](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5)에서 확인하세요.\n\n```bash\nnpm audit\n```\n\n운영 배포 전 수정 버전의 SDK 호환성을 확인하세요. 주요 버전을 강제로 교체하면 동작이 달라질 수 있습니다. 이 실습을 보안 검증이 끝난 배포 구성으로 사용하지 마세요.\n\n### 자체 서버와 Cloudflare Gateway 비교\n\nCloudflare Gateway를 쓰면 원본 서버의 검증 방식이 달라집니다. 2026년 9월 30일자 문서는 x402 v2 사용을 명시합니다. 다음 구분은 [Cloudflare x402 문서](https://developers.cloudflare.com/monetization-gateway/x402/)를 기준으로 합니다.\n\n| 구분 | 이 글의 Express 방식 | Cloudflare Monetization Gateway |\n|---|---|---|\n| 클라이언트 결제 처리 | Express 미들웨어 | Gateway |\n| 원본 서버의 검증 지점 | 선택한 facilitator와 연동 | `PAYMENT-CONTEXT` JWT 검증 |\n| 정산 결과 전달 | 미들웨어의 `PAYMENT-RESPONSE` | Gateway가 정산 영수증 생성 |\n| 추가로 확인할 사항 | facilitator 지원 체인과 인증 | JWT 서명 검증과 원본 접근 보호 |\n\n`PAYMENT-CONTEXT`는 Gateway와 원본 서버 사이의 헤더입니다. 일반 x402 클라이언트의 서명 헤더가 아닙니다. 원본 검증은 [Cloudflare Payment validation 문서](https://developers.cloudflare.com/monetization-gateway/configuration/payment-validation/)를 따르세요.\n\nGateway를 거쳐도 JWT를 단순 디코딩해서는 안 됩니다. 문서가 지정한 키로 서명을 검증하세요. 헤더가 없거나 검증이 실패하면 유료 콘텐츠를 차단합니다.\n\n## 실제 사용량과 정산 기록 구분\n\n온체인 송금 건수만으로 콘텐츠 소비를 입증할 수는 없습니다. 운영 지표는 정산과 응답 전달을 연결해 집계하세요. 이는 결제 흐름과 재전송 동작을 바탕으로 한 설계 제안입니다.\n\n| 기록 | 답할 수 있는 질문 | 추가로 필요한 기록 |\n|---|---|---|\n| 402 응답 | 결제 조건을 얼마나 요청했는지 | 실제 결제 여부 |\n| 검증 성공 | 승인이 유효했는지 | 정산 결과 |\n| USDC 전송 | 자산이 이동했는지 | 주문 귀속 |\n| 주문별 정산 완료 | 유료 요청이 결제됐는지 | 응답 전달 상태 |\n| 재전달 기록 | 이미 결제한 요청을 복구했는지 | 신규 결제와 구분하는 식별자 |\n\n테스트 요청은 실제 사용량 집계에서 구분하세요. 같은 주문의 재시도도 별도로 표시합니다. 거래량 자체를 API 수요나 수익 보장으로 설명하면 안 됩니다.\n\n## Base 메인넷 전환 절차\n\n테스트넷의 정상·실패 경로를 확인한 뒤 메인넷을 검토하세요. 네트워크 문자열만 바꾸면 전환이 끝나지 않습니다. 기준 절차는 [공식 판매자 가이드](https://docs.x402.org/getting-started/quickstart-for-sellers#running-on-mainnet)를 따릅니다.\n\n1. 선택한 facilitator의 Base 메인넷 지원을 확인하세요.\n2. `/supported`에서 v2·`exact`·`eip155:8453` 지원을 확인하세요.\n3. 제공자가 요구하는 인증과 이용 조건을 설정하세요.\n4. 서버와 클라이언트의 네트워크를 함께 변경하세요.\n5. 클라이언트의 USDC 허용 주소를 메인넷 주소로 변경하세요.\n6. 402 응답의 금액과 실제 수취 주소를 다시 대조하세요.\n7. 중복 요청 저장소와 장애 복구 절차를 연결하세요.\n8. 운영 의존성의 보안 검사를 완료하세요.\n9. 설정한 소액으로 실제 전송과 콘텐츠 전달을 확인하세요.\n\n다중 체인 지원은 모든 조합의 지원을 뜻하지 않습니다. SDK와 facilitator의 지원 범위를 함께 확인하세요. 확인 기준은 [네트워크·토큰 지원 문서](https://docs.x402.org/core-concepts/network-and-token-support)입니다.","content_html":"\u003cp\u003ex402 결제는 API의 402 응답에 결제 조건을 담아 구현합니다. Node.js와 테스트 지갑을 준비하세요. 먼저 Base Sepolia에서 v2 서명과 정산을 검증합니다. 유료 응답은 정산 성공 뒤에 전달하도록 설정하세요.\u003c/p\u003e\n\u003cp\u003e금액·체인 정보는 출처에 연결한 x402·Base·Circle 공식 문서 기준입니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%EC%8B%A4%EC%8A%B5-%EC%A4%80%EB%B9%84%EC%99%80-%EC%84%A4%EC%B9%98\" class=\"anchor\" id=\"실습-준비와-설치\"\u003e\u003c/a\u003e실습 준비와 설치\u003c/h2\u003e\n\u003cp\u003eAPI를 수정할 수 있는 개발 환경과 테스트 지갑을 준비하세요. 예제는 Node.js 22와 x402 SDK 2.28.0을 기준으로 구성했습니다. 설치 구조는 \u003ca href=\"https://docs.x402.org/getting-started/quickstart-for-sellers\"\u003e공식 판매자 가이드\u003c/a\u003e를 따릅니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e결제를 받을 EVM 지갑 주소를 준비하세요.\u003c/li\u003e\n\u003cli\u003e결제용 테스트 지갑을 별도로 준비하세요.\u003c/li\u003e\n\u003cli\u003e결제용 지갑에 Base Sepolia 테스트 USDC를 확보하세요.\u003c/li\u003e\n\u003cli\u003e아래 명령으로 실습 프로젝트를 만드세요.\u003c/li\u003e\n\u003cli\u003e이후의 TypeScript 파일을 같은 폴더에 저장하세요.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003emkdir\u003c/span\u003e\u003cspan\u003e x402-demo\n\u003c/span\u003e\u003cspan\u003ecd\u003c/span\u003e\u003cspan\u003e x402-demo\n\u003c/span\u003e\u003cspan\u003enpm\u003c/span\u003e\u003cspan\u003e init\u003c/span\u003e\u003cspan\u003e -y\n\u003c/span\u003e\u003cspan\u003enpm\u003c/span\u003e\u003cspan\u003e pkg set type=module\n\u003c/span\u003e\u003cspan\u003enpm\u003c/span\u003e\u003cspan\u003e install\u003c/span\u003e\u003cspan\u003e --save-exact\u003c/span\u003e\u003cspan\u003e express@5.3.0 viem@2.57.12 @x402/core@2.28.0 @x402/express@2.28.0 @x402/evm@2.28.0 @x402/fetch@2.28.0\n\u003c/span\u003e\u003cspan\u003enpm\u003c/span\u003e\u003cspan\u003e install\u003c/span\u003e\u003cspan\u003e --save-dev --save-exact\u003c/span\u003e\u003cspan\u003e typescript tsx @types/node @types/express\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e수취 서버에는 수취 지갑의 개인키가 필요하지 않습니다. 결제용 개인키는 클라이언트 프로세스에서만 읽도록 구성하세요. 지갑 서명 방식은 \u003ca href=\"https://docs.x402.org/getting-started/quickstart-for-buyers\"\u003e공식 구매자 가이드\u003c/a\u003e를 기준으로 합니다.\u003c/p\u003e\n\u003cp\u003e이 예제의 검증 범위는 타입 검사와 모의 결제 흐름입니다. 실제 테스트넷 송금까지 완료된 예제는 아닙니다. 아래 실행 절차에서 온체인 결과를 직접 확인하세요.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#x402-v1%EA%B3%BC-v2-%EB%B9%84%EA%B5%90\" class=\"anchor\" id=\"x402-v1과-v2-비교\"\u003e\u003c/a\u003ex402 v1과 v2 비교\u003c/h2\u003e\n\u003cp\u003ev2를 구현할 때는 헤더와 본문 필드를 함께 맞춰야 합니다. 헤더 이름만 바꾸면 데이터 구조가 맞지 않습니다. 다음 표는 \u003ca href=\"https://docs.x402.org/guides/migration-v1-to-v2\"\u003e공식 이전 가이드\u003c/a\u003e를 기준으로 합니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e구분\u003c/th\u003e\n\u003cth\u003ev1\u003c/th\u003e\n\u003cth\u003ev2\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e프로토콜 버전\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e\u003ccode\u003ex402Version: 1\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\u003ccode\u003ex402Version: 2\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e최초 결제 조건\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e402 응답의 JSON 본문\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\n\u003ccode\u003ePAYMENT-REQUIRED\u003c/code\u003e 응답 헤더\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e서명한 결제 데이터\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e\n\u003ccode\u003eX-PAYMENT\u003c/code\u003e 요청 헤더\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\n\u003ccode\u003ePAYMENT-SIGNATURE\u003c/code\u003e 요청 헤더\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e정산 결과\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e\n\u003ccode\u003eX-PAYMENT-RESPONSE\u003c/code\u003e 응답 헤더\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\n\u003ccode\u003ePAYMENT-RESPONSE\u003c/code\u003e 응답 헤더\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003eBase 메인넷 식별자\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e\u003ccode\u003ebase\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\u003ccode\u003eeip155:8453\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003eBase Sepolia 식별자\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e\u003ccode\u003ebase-sepolia\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\u003ccode\u003eeip155:84532\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e결제 조건의 금액 필드\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e\u003ccode\u003emaxAmountRequired\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\u003ccode\u003eamount\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e리소스 설명\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e결제 조건 안의 \u003ccode\u003eresource\u003c/code\u003e 등\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e최상위 \u003ccode\u003eresource\u003c/code\u003e 객체\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e결제 페이로드의 조건\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e최상위 \u003ccode\u003escheme\u003c/code\u003e, \u003ccode\u003enetwork\u003c/code\u003e 등\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e선택한 조건을 담는 \u003ccode\u003eaccepted\u003c/code\u003e 객체\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003eTypeScript 패키지 예\u003c/td\u003e\n\u003ctd data-label=\"v1\"\u003e\u003ccode\u003ex402-express\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"v2\"\u003e\u003ccode\u003e@x402/express\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003ev1의 결제 조건 전달 위치는 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/specs/transports-v1/http.md\"\u003eHTTP v1 사양\u003c/a\u003e에 명시돼 있습니다. v2의 표준 전달 위치는 \u003ccode\u003ePAYMENT-REQUIRED\u003c/code\u003e 헤더입니다. 구현체가 본문도 반환할 수 있지만 헤더를 기준으로 처리하세요.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md\"\u003eHTTP v2 사양\u003c/a\u003e은 상태 코드를 다음과 같이 설명합니다.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe server indicates payment is required using the HTTP 402 \"Payment Required\" status code.\u003c/p\u003e\n\u003cp\u003e출처: x402 HTTP v2 사양\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003ev2 결제 헤더는 JSON을 Base64로 인코딩한 값입니다. Base64는 암호화 방식이 아닙니다. 외부 서비스에서는 HTTPS를 사용하세요.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#http-402-%EC%9A%94%EC%B2%AD%EA%B3%BC-%EA%B2%B0%EC%A0%9C-%EC%88%9C%EC%84%9C\" class=\"anchor\" id=\"http-402-요청과-결제-순서\"\u003e\u003c/a\u003eHTTP 402 요청과 결제 순서\u003c/h2\u003e\n\u003cp\u003e이 실습은 EVM \u003ccode\u003eexact\u003c/code\u003e 방식의 기본 승인 흐름을 사용합니다. \u003ccode\u003eexact\u003c/code\u003e는 정해진 금액을 결제하는 방식입니다. 순서는 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md\"\u003ex402 v2 핵심 사양\u003c/a\u003e에 따릅니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e클라이언트가 결제 정보 없이 유료 API를 요청합니다.\u003c/li\u003e\n\u003cli\u003e서버가 HTTP 402와 \u003ccode\u003ePAYMENT-REQUIRED\u003c/code\u003e를 반환합니다.\u003c/li\u003e\n\u003cli\u003e클라이언트가 \u003ccode\u003eaccepts\u003c/code\u003e에서 허용된 조건을 선택합니다.\u003c/li\u003e\n\u003cli\u003eSDK가 선택한 조건에 맞는 결제 승인을 서명합니다.\u003c/li\u003e\n\u003cli\u003e클라이언트가 \u003ccode\u003ePAYMENT-SIGNATURE\u003c/code\u003e를 붙여 재요청합니다.\u003c/li\u003e\n\u003cli\u003e서버가 facilitator의 \u003ccode\u003e/verify\u003c/code\u003e로 승인을 검증합니다.\u003c/li\u003e\n\u003cli\u003e검증을 통과하면 서버가 리소스 처리를 실행합니다.\u003c/li\u003e\n\u003cli\u003e서버가 \u003ccode\u003e/settle\u003c/code\u003e로 정산을 요청합니다.\u003c/li\u003e\n\u003cli\u003e정산 성공 뒤 유료 본문과 \u003ccode\u003ePAYMENT-RESPONSE\u003c/code\u003e를 전달합니다.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e이 순서는 모든 결제 방식에 공통인 고정 순서가 아닙니다. v2에는 정산 시점이 다른 흐름도 정의돼 있습니다. 아래 코드는 기본 \u003ccode\u003eauthorization\u003c/code\u003e 흐름만 허용합니다.\u003c/p\u003e\n\u003ch3\u003e\n\u003ca href=\"#resource-server%EC%99%80-facilitator%EC%9D%98-%EC%97%AD%ED%95%A0\" class=\"anchor\" id=\"resource-server와-facilitator의-역할\"\u003e\u003c/a\u003eResource server와 facilitator의 역할\u003c/h3\u003e\n\u003cp\u003eResource server는 가격과 콘텐츠 접근 조건을 결정합니다. Facilitator는 결제 검증과 정산을 담당합니다. 역할 구분은 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md\"\u003e공식 facilitator 문서\u003c/a\u003e를 기준으로 합니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e구성 요소\u003c/th\u003e\n\u003cth\u003e맡는 일\u003c/th\u003e\n\u003cth\u003e성공으로 간주하면 안 되는 상태\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구성 요소\"\u003e클라이언트\u003c/td\u003e\n\u003ctd data-label=\"맡는 일\"\u003e조건 선택과 결제 승인 서명\u003c/td\u003e\n\u003ctd data-label=\"성공으로 간주하면 안 되는 상태\"\u003e서명 생성만 완료된 상태\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구성 요소\"\u003eResource server\u003c/td\u003e\n\u003ctd data-label=\"맡는 일\"\u003e가격 선언과 유료 응답 통제\u003c/td\u003e\n\u003ctd data-label=\"성공으로 간주하면 안 되는 상태\"\u003e클라이언트가 성공했다고 주장하는 상태\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구성 요소\"\u003eFacilitator \u003ccode\u003e/verify\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"맡는 일\"\u003e서명과 결제 조건의 유효성 확인\u003c/td\u003e\n\u003ctd data-label=\"성공으로 간주하면 안 되는 상태\"\u003e\n\u003ccode\u003eisValid: true\u003c/code\u003e만 받은 상태\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구성 요소\"\u003eFacilitator \u003ccode\u003e/settle\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"맡는 일\"\u003e이 실습에서는 온체인 결제 실행과 결과 반환\u003c/td\u003e\n\u003ctd data-label=\"성공으로 간주하면 안 되는 상태\"\u003e전송했지만 결과를 확인하지 못한 상태\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003e/verify\u003c/code\u003e는 온체인 상태를 변경하지 않는 검사입니다. 따라서 검증 성공은 입금 완료를 뜻하지 않습니다. 검증 이후에도 잔액이나 승인 상태가 달라질 수 있습니다.\u003c/p\u003e\n\u003cp\u003e검증이나 정산이 실패하면 유료 본문을 차단하세요. 성공을 확인하지 못했을 때 차단하는 처리를 fail-closed라고 합니다. 이 동작은 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts\"\u003e공식 Express 구현\u003c/a\u003e에서도 확인할 수 있습니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#base-%EB%A9%94%EC%9D%B8%EB%84%B7%EA%B3%BC-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%84%B7-%EB%B9%84%EA%B5%90\" class=\"anchor\" id=\"base-메인넷과-테스트넷-비교\"\u003e\u003c/a\u003eBase 메인넷과 테스트넷 비교\u003c/h2\u003e\n\u003cp\u003e첫 실행에서는 Base Sepolia만 사용하세요. Circle은 테스트넷 USDC에 금융 가치가 없다고 명시합니다. 주소는 \u003ca href=\"https://developers.circle.com/stablecoins/usdc-contract-addresses\"\u003eCircle의 USDC 컨트랙트 목록\u003c/a\u003e을 기준으로 합니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e항목\u003c/th\u003e\n\u003cth\u003eBase 메인넷\u003c/th\u003e\n\u003cth\u003eBase Sepolia 테스트넷\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"항목\"\u003e용도\u003c/td\u003e\n\u003ctd data-label=\"Base 메인넷\"\u003e실제 자산 결제\u003c/td\u003e\n\u003ctd data-label=\"Base Sepolia 테스트넷\"\u003e구현과 장애 처리 시험\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"항목\"\u003eChain ID\u003c/td\u003e\n\u003ctd data-label=\"Base 메인넷\"\u003e\u003ccode\u003e8453\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"Base Sepolia 테스트넷\"\u003e\u003ccode\u003e84532\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"항목\"\u003ex402 v2 네트워크\u003c/td\u003e\n\u003ctd data-label=\"Base 메인넷\"\u003e\u003ccode\u003eeip155:8453\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"Base Sepolia 테스트넷\"\u003e\u003ccode\u003eeip155:84532\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"항목\"\u003eUSDC 컨트랙트\u003c/td\u003e\n\u003ctd data-label=\"Base 메인넷\"\u003e\u003ccode\u003e0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"Base Sepolia 테스트넷\"\u003e\u003ccode\u003e0x036CbD53842c5426634e7929541eC2318f3dCF7e\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"항목\"\u003e공개 RPC\u003c/td\u003e\n\u003ctd data-label=\"Base 메인넷\"\u003e\u003ccode\u003ehttps://mainnet.base.org\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"Base Sepolia 테스트넷\"\u003e\u003ccode\u003ehttps://sepolia.base.org\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"항목\"\u003e토큰의 금융 가치\u003c/td\u003e\n\u003ctd data-label=\"Base 메인넷\"\u003e실제 금융 가치가 있는 자산\u003c/td\u003e\n\u003ctd data-label=\"Base Sepolia 테스트넷\"\u003e금융 가치 없음\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003eChain ID와 RPC는 \u003ca href=\"https://docs.base.org/get-started/connect-to-base\"\u003eBase 네트워크 문서\u003c/a\u003e에서 확인하세요. 같은 지갑 주소라도 체인이 다르면 잔액이 별개입니다. 테스트넷 성공을 실제 매출로 기록하면 안 됩니다.\u003c/p\u003e\n\u003cp\u003e테스트 USDC는 \u003ca href=\"https://faucet.circle.com/\"\u003eCircle Testnet Faucet\u003c/a\u003e에서 요청할 수 있습니다. 네트워크는 Base Sepolia로 선택하세요. 수령 주소에는 결제용 테스트 지갑 주소를 입력합니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#nodejs-%EC%9C%A0%EB%A3%8C-api-%EA%B5%AC%ED%98%84\" class=\"anchor\" id=\"nodejs-유료-api-구현\"\u003e\u003c/a\u003eNode.js 유료 API 구현\u003c/h2\u003e\n\u003cp\u003e유료 경로 앞에 \u003ccode\u003epaymentMiddleware\u003c/code\u003e를 연결하세요. 아래 예제는 호출당 \u003cstrong\u003e0.001 USDC\u003c/strong\u003e를 요구합니다. 금액은 공식 판매자 가이드의 예시 가격을 사용합니다. (확인값 호출당 0.001 USDC · 출처 docs.x402.org · 2026-10-28 확인)\u003c/p\u003e\n\u003ch3\u003e\n\u003ca href=\"#%EC%84%9C%EB%B2%84-%ED%8C%8C%EC%9D%BC\" class=\"anchor\" id=\"서버-파일\"\u003e\u003c/a\u003e서버 파일\u003c/h3\u003e\n\u003cp\u003e다음 코드를 \u003ccode\u003eserver.ts\u003c/code\u003e로 저장하세요. 서버는 로컬 인터페이스에서만 요청을 받습니다. 수취 주소는 \u003ccode\u003ePAY_TO\u003c/code\u003e 환경변수에서 읽습니다.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003eimport express from \"express\";\n\u003c/span\u003e\u003cspan\u003eimport { getAddress, zeroAddress } from \"viem\";\n\u003c/span\u003e\u003cspan\u003eimport { paymentMiddleware } from \"@x402/express\";\n\u003c/span\u003e\u003cspan\u003eimport { HTTPFacilitatorClient, x402ResourceServer } from \"@x402/core/server\";\n\u003c/span\u003e\u003cspan\u003eimport { ExactEvmScheme } from \"@x402/evm/exact/server\";\n\u003c/span\u003e\u003cspan\u003e\n\u003c/span\u003e\u003cspan\u003econst payTo = getAddress(process.env.PAY_TO ?? \"\");\n\u003c/span\u003e\u003cspan\u003eif (payTo === zeroAddress) throw new Error(\"Invalid payTo\");\n\u003c/span\u003e\u003cspan\u003econst network = \"eip155:84532\";\n\u003c/span\u003e\u003cspan\u003econst resourceServer = new x402ResourceServer(\n\u003c/span\u003e\u003cspan\u003e  new HTTPFacilitatorClient({ url: \"https://x402.org/facilitator\" }),\n\u003c/span\u003e\u003cspan\u003e).register(network, new ExactEvmScheme());\n\u003c/span\u003e\u003cspan\u003e\n\u003c/span\u003e\u003cspan\u003econst app = express();\n\u003c/span\u003e\u003cspan\u003eapp.use((_req, res, next) =\u0026gt; {\n\u003c/span\u003e\u003cspan\u003e  res.setHeader(\"Cache-Control\", \"private, no-store\");\n\u003c/span\u003e\u003cspan\u003e  next();\n\u003c/span\u003e\u003cspan\u003e});\n\u003c/span\u003e\u003cspan\u003eapp.use(paymentMiddleware({\n\u003c/span\u003e\u003cspan\u003e  \"GET /paid-data\": {\n\u003c/span\u003e\u003cspan\u003e    accepts: [{ scheme: \"exact\", price: \"$0.001\", network, payTo }],\n\u003c/span\u003e\u003cspan\u003e    description: \"Sample paid JSON\",\n\u003c/span\u003e\u003cspan\u003e    mimeType: \"application/json\",\n\u003c/span\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003cspan\u003e}, resourceServer));\n\u003c/span\u003e\u003cspan\u003eapp.get(\"/paid-data\", (_req, res) =\u0026gt; {\n\u003c/span\u003e\u003cspan\u003e  res.json({ content: \"Paid sample content\" });\n\u003c/span\u003e\u003cspan\u003e});\n\u003c/span\u003e\u003cspan\u003eapp.listen(4021, \"127.0.0.1\");\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ccode\u003ehttps://x402.org/facilitator\u003c/code\u003e는 공식 가이드의 테스트용 주소입니다. 이 예제에서는 Base Sepolia 정산에 사용합니다. 운영용 facilitator 선택은 \u003ca href=\"https://docs.x402.org/getting-started/quickstart-for-sellers#running-on-mainnet\"\u003e판매자 가이드의 메인넷 절차\u003c/a\u003e를 확인하세요.\u003c/p\u003e\n\u003ch3\u003e\n\u003ca href=\"#%EA%B0%80%EA%B2%A9-%EA%B3%84%EC%82%B0-%EC%98%88%EC%8B%9C\" class=\"anchor\" id=\"가격-계산-예시\"\u003e\u003c/a\u003e가격 계산 예시\u003c/h3\u003e\n\u003cp\u003e예제 가격은 USDC 최소 단위로 \u003ccode\u003e1000\u003c/code\u003e입니다. USDC의 소수 자릿수는 6자리입니다. SDK의 가격 변환은 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/typescript/packages/mechanisms/evm/src/exact/server/scheme.ts\"\u003eEVM 서버 구현\u003c/a\u003e을 기준으로 합니다.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003e0.001 USDC × 10^6 = 1000 최소 단위\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e확인할 값\u003c/th\u003e\n\u003cth\u003e이 예제의 기대값\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"확인할 값\"\u003e서버 설정의 \u003ccode\u003eprice\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"이 예제의 기대값\"\u003e\u003ccode\u003e\"$0.001\"\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"확인할 값\"\u003e402 결제 조건의 \u003ccode\u003eamount\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"이 예제의 기대값\"\u003e\u003ccode\u003e\"1000\"\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"확인할 값\"\u003e결제 조건의 \u003ccode\u003easset\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"이 예제의 기대값\"\u003eBase Sepolia USDC 컨트랙트\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"확인할 값\"\u003e결제 조건의 \u003ccode\u003epayTo\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"이 예제의 기대값\"\u003e설정한 수취 주소\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"확인할 값\"\u003e결제 조건의 \u003ccode\u003enetwork\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"이 예제의 기대값\"\u003e\u003ccode\u003eeip155:84532\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003eprice\u003c/code\u003e는 SDK 설정값입니다. 실제 v2 결제 조건에는 \u003ccode\u003eamount\u003c/code\u003e가 전달됩니다. 정수 금액은 문자열이나 \u003ccode\u003ebigint\u003c/code\u003e로 비교하세요.\u003c/p\u003e\n\u003ch3\u003e\n\u003ca href=\"#%EC%84%9C%EB%B2%84-%EC%8B%A4%ED%96%89%EA%B3%BC-402-%ED%99%95%EC%9D%B8\" class=\"anchor\" id=\"서버-실행과-402-확인\"\u003e\u003c/a\u003e서버 실행과 402 확인\u003c/h3\u003e\n\u003cp\u003e첫 번째 Bash 터미널에서 서버를 실행하세요. 입력하는 값은 개인키가 아닌 공개 수취 주소입니다. 실행 중에는 이 터미널을 유지합니다.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003eread \u003c/span\u003e\u003cspan\u003e-r -p \u003c/span\u003e\u003cspan\u003e'\u003c/span\u003e\u003cspan\u003e수취 지갑 주소: \u003c/span\u003e\u003cspan\u003e' PAY_TO\n\u003c/span\u003e\u003cspan\u003eexport \u003c/span\u003e\u003cspan\u003ePAY_TO\n\u003c/span\u003e\u003cspan\u003enpx\u003c/span\u003e\u003cspan\u003e tsx server.ts\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e두 번째 터미널에서 무결제 요청을 보내세요. 아래 명령은 결제를 서명하지 않습니다. HTTP 상태와 디코딩한 결제 조건을 출력합니다.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003enode\u003c/span\u003e\u003cspan\u003e --input-type\u003c/span\u003e\u003cspan\u003e=module \u0026lt;\u0026lt;'\u003c/span\u003e\u003cspan\u003eJS\u003c/span\u003e\u003cspan\u003e'\n\u003c/span\u003e\u003cspan\u003econst response = await fetch(\"http://127.0.0.1:4021/paid-data\");\n\u003c/span\u003e\u003cspan\u003econst header = response.headers.get(\"PAYMENT-REQUIRED\");\n\u003c/span\u003e\u003cspan\u003econsole.log(response.status);\n\u003c/span\u003e\u003cspan\u003econsole.log(header \u0026amp;\u0026amp; JSON.parse(\n\u003c/span\u003e\u003cspan\u003e  Buffer.from(header, \"base64\").toString(\"utf8\")\n\u003c/span\u003e\u003cspan\u003e));\n\u003c/span\u003e\u003cspan\u003eJS\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e정상적인 무결제 응답은 HTTP 402입니다. 출력된 조건을 위 표와 대조하세요. 유료 콘텐츠가 바로 반환되면 경로 보호 설정부터 수정합니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%EA%B2%B0%EC%A0%9C-%EC%A1%B0%EA%B1%B4-%EC%84%A0%ED%83%9D%EA%B3%BC-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EA%B5%AC%ED%98%84\" class=\"anchor\" id=\"결제-조건-선택과-클라이언트-구현\"\u003e\u003c/a\u003e결제 조건 선택과 클라이언트 구현\u003c/h2\u003e\n\u003cp\u003e허용한 조건에만 서명하도록 클라이언트를 제한하세요. 이 예제는 Base Sepolia USDC의 정해진 가격만 허용합니다. 결제 옵션 필터는 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/typescript/packages/core/src/client/x402Client.ts\"\u003e공식 x402Client 구현\u003c/a\u003e의 \u003ccode\u003eregisterPolicy\u003c/code\u003e를 사용합니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e아래 코드를 \u003ccode\u003eclient.ts\u003c/code\u003e로 저장하세요.\u003c/li\u003e\n\u003cli\u003e다음 절의 \u003ccode\u003echeck-transfer.ts\u003c/code\u003e도 저장하세요.\u003c/li\u003e\n\u003cli\u003e두 파일을 준비한 뒤 실행 절차를 진행하세요.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003eimport { writeFile } from \"node:fs/promises\";\n\u003c/span\u003e\u003cspan\u003eimport { getAddress, zeroAddress, type Hex, type Hash } from \"viem\";\n\u003c/span\u003e\u003cspan\u003eimport { privateKeyToAccount } from \"viem/accounts\";\n\u003c/span\u003e\u003cspan\u003eimport { x402Client, wrapFetchWithPayment } from \"@x402/fetch\";\n\u003c/span\u003e\u003cspan\u003eimport { decodePaymentResponseHeader } from \"@x402/core/http\";\n\u003c/span\u003e\u003cspan\u003eimport { ExactEvmScheme } from \"@x402/evm/exact/client\";\n\u003c/span\u003e\u003cspan\u003eimport { checkTransfer } from \"./check-transfer.js\";\n\u003c/span\u003e\u003cspan\u003e\n\u003c/span\u003e\u003cspan\u003econst key = process.env.EVM_PRIVATE_KEY ?? \"\";\n\u003c/span\u003e\u003cspan\u003eif (!/^0x[0-9a-fA-F]{64}$/.test(key)) throw new Error(\"Invalid test key\");\n\u003c/span\u003e\u003cspan\u003econst signer = privateKeyToAccount(key as Hex);\n\u003c/span\u003e\u003cspan\u003econst payTo = getAddress(process.env.PAY_TO ?? \"\");\n\u003c/span\u003e\u003cspan\u003eif (payTo === zeroAddress) throw new Error(\"Invalid payTo\");\n\u003c/span\u003e\u003cspan\u003econst network = \"eip155:84532\";\n\u003c/span\u003e\u003cspan\u003econst token = \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\";\n\u003c/span\u003e\u003cspan\u003econst client = new x402Client().register(network, new ExactEvmScheme(signer));\n\u003c/span\u003e\u003cspan\u003eclient.registerPolicy((version, options) =\u0026gt; version === 2\n\u003c/span\u003e\u003cspan\u003e  ? options.filter(r =\u0026gt;\n\u003c/span\u003e\u003cspan\u003e      r.scheme === \"exact\" \u0026amp;\u0026amp; r.network === network \u0026amp;\u0026amp;\n\u003c/span\u003e\u003cspan\u003e      r.asset.toLowerCase() === token.toLowerCase() \u0026amp;\u0026amp;\n\u003c/span\u003e\u003cspan\u003e      r.payTo.toLowerCase() === payTo.toLowerCase() \u0026amp;\u0026amp; r.amount === \"1000\" \u0026amp;\u0026amp;\n\u003c/span\u003e\u003cspan\u003e      (!r.extra?.assetTransferMethod || r.extra.assetTransferMethod === \"eip3009\") \u0026amp;\u0026amp;\n\u003c/span\u003e\u003cspan\u003e      (!r.extra?.paymentFlow || r.extra.paymentFlow === \"authorization\"))\n\u003c/span\u003e\u003cspan\u003e  : []);\n\u003c/span\u003e\u003cspan\u003e\n\u003c/span\u003e\u003cspan\u003econst paidFetch = wrapFetchWithPayment(fetch, client);\n\u003c/span\u003e\u003cspan\u003econst response = await paidFetch(\"http://127.0.0.1:4021/paid-data\", {\n\u003c/span\u003e\u003cspan\u003e  redirect: \"error\",\n\u003c/span\u003e\u003cspan\u003e});\n\u003c/span\u003e\u003cspan\u003econst raw = response.headers.get(\"PAYMENT-RESPONSE\");\n\u003c/span\u003e\u003cspan\u003eif (!raw) throw new Error(`No settlement receipt: HTTP ${response.status}`);\n\u003c/span\u003e\u003cspan\u003econst settlement = decodePaymentResponseHeader(raw);\n\u003c/span\u003e\u003cspan\u003eawait writeFile(\"settlement.json\", JSON.stringify(settlement, null, 2));\n\u003c/span\u003e\u003cspan\u003eif (!response.ok || !settlement.success || settlement.network !== network) {\n\u003c/span\u003e\u003cspan\u003e  throw new Error(\"Payment unresolved; inspect settlement.json before retrying\");\n\u003c/span\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003cspan\u003econst evidence = await checkTransfer(\n\u003c/span\u003e\u003cspan\u003e  network, settlement.transaction as Hash, signer.address, payTo, 1000n,\n\u003c/span\u003e\u003cspan\u003e);\n\u003c/span\u003e\u003cspan\u003eawait writeFile(\"transfer.json\", JSON.stringify(evidence, null, 2));\n\u003c/span\u003e\u003cspan\u003econsole.log(await response.text());\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ccode\u003ewrapFetchWithPayment\u003c/code\u003e가 402 처리와 서명 재요청을 담당합니다. 사용 방식은 \u003ca href=\"https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/fetch\"\u003e공식 fetch 예제\u003c/a\u003e를 따릅니다. 허용된 결제 조건이 없으면 결제 생성을 중단합니다.\u003c/p\u003e\n\u003cp\u003eAI 에이전트에서도 이 클라이언트를 사용할 수 있습니다. 다만 호출당 가격 제한은 누적 지출 제한이 아닙니다. 에이전트의 반복 호출 예산은 별도로 관리하세요.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#usdc-transfer-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B2%80%EC%A6%9D\" class=\"anchor\" id=\"usdc-transfer-이벤트-검증\"\u003e\u003c/a\u003eUSDC Transfer 이벤트 검증\u003c/h2\u003e\n\u003cp\u003e정산 결과의 거래 해시로 USDC 전송 로그를 확인하세요. 거래 성공 상태만으로 수취인까지 확인되지는 않습니다. \u003ccode\u003eTransfer\u003c/code\u003e 필드는 \u003ca href=\"https://eips.ethereum.org/EIPS/eip-20\"\u003eERC-20 표준\u003c/a\u003e을 기준으로 합니다.\u003c/p\u003e\n\u003cp\u003e검증에는 다음 항목이 필요합니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e검증 항목\u003c/th\u003e\n\u003cth\u003e비교 대상\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"검증 항목\"\u003eRPC의 Chain ID\u003c/td\u003e\n\u003ctd data-label=\"비교 대상\"\u003e서버가 허용한 체인\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"검증 항목\"\u003e거래 영수증의 \u003ccode\u003estatus\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"비교 대상\"\u003e성공 상태\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"검증 항목\"\u003e이벤트를 발생시킨 주소\u003c/td\u003e\n\u003ctd data-label=\"비교 대상\"\u003e해당 체인의 공식 USDC 컨트랙트\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"검증 항목\"\u003e\u003ccode\u003eTransfer.from\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"비교 대상\"\u003e확인된 결제자\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"검증 항목\"\u003e\u003ccode\u003eTransfer.to\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"비교 대상\"\u003e서버가 정한 수취 주소\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"검증 항목\"\u003e\u003ccode\u003eTransfer.value\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"비교 대상\"\u003e서버가 요구한 최소 단위 금액\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e아래 코드를 \u003ccode\u003echeck-transfer.ts\u003c/code\u003e로 저장하세요. 조회는 viem의 \u003ca href=\"https://viem.sh/docs/actions/public/getTransactionReceipt\"\u003egetTransactionReceipt\u003c/a\u003e를 사용합니다. 이벤트 해석은 \u003ca href=\"https://viem.sh/docs/contract/parseEventLogs\"\u003eparseEventLogs\u003c/a\u003e를 기준으로 합니다.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003eimport {\n\u003c/span\u003e\u003cspan\u003e  createPublicClient, http, getAddress, parseEventLogs,\n\u003c/span\u003e\u003cspan\u003e  erc20Abi, type Address, type Hash,\n\u003c/span\u003e\u003cspan\u003e} from \"viem\";\n\u003c/span\u003e\u003cspan\u003eimport { base, baseSepolia } from \"viem/chains\";\n\u003c/span\u003e\u003cspan\u003e\n\u003c/span\u003e\u003cspan\u003econst configs = {\n\u003c/span\u003e\u003cspan\u003e  \"eip155:84532\": {\n\u003c/span\u003e\u003cspan\u003e    chain: baseSepolia,\n\u003c/span\u003e\u003cspan\u003e    rpc: \"https://sepolia.base.org\",\n\u003c/span\u003e\u003cspan\u003e    token: \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\",\n\u003c/span\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003cspan\u003e  \"eip155:8453\": {\n\u003c/span\u003e\u003cspan\u003e    chain: base,\n\u003c/span\u003e\u003cspan\u003e    rpc: \"https://mainnet.base.org\",\n\u003c/span\u003e\u003cspan\u003e    token: \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n\u003c/span\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003cspan\u003e} as const;\n\u003c/span\u003e\u003cspan\u003e\n\u003c/span\u003e\u003cspan\u003eexport async function checkTransfer(\n\u003c/span\u003e\u003cspan\u003e  network: keyof typeof configs, hash: Hash,\n\u003c/span\u003e\u003cspan\u003e  payer: Address, payTo: Address, amount: bigint,\n\u003c/span\u003e\u003cspan\u003e) {\n\u003c/span\u003e\u003cspan\u003e  const cfg = configs[network];\n\u003c/span\u003e\u003cspan\u003e  const rpc = createPublicClient({ chain: cfg.chain, transport: http(cfg.rpc) });\n\u003c/span\u003e\u003cspan\u003e  if (await rpc.getChainId() !== cfg.chain.id) throw new Error(\"Wrong chain\");\n\u003c/span\u003e\u003cspan\u003e  const receipt = await rpc.getTransactionReceipt({ hash });\n\u003c/span\u003e\u003cspan\u003e  if (receipt.status !== \"success\") throw new Error(\"Transaction failed\");\n\u003c/span\u003e\u003cspan\u003e  const logs = parseEventLogs({\n\u003c/span\u003e\u003cspan\u003e    abi: erc20Abi,\n\u003c/span\u003e\u003cspan\u003e    eventName: \"Transfer\",\n\u003c/span\u003e\u003cspan\u003e    logs: receipt.logs.filter(\n\u003c/span\u003e\u003cspan\u003e      log =\u0026gt; getAddress(log.address) === getAddress(cfg.token),\n\u003c/span\u003e\u003cspan\u003e    ),\n\u003c/span\u003e\u003cspan\u003e    strict: true,\n\u003c/span\u003e\u003cspan\u003e  });\n\u003c/span\u003e\u003cspan\u003e  const transfer = logs.find(log =\u0026gt;\n\u003c/span\u003e\u003cspan\u003e    getAddress(log.args.from) === getAddress(payer) \u0026amp;\u0026amp;\n\u003c/span\u003e\u003cspan\u003e    getAddress(log.args.to) === getAddress(payTo) \u0026amp;\u0026amp;\n\u003c/span\u003e\u003cspan\u003e    log.args.value === amount,\n\u003c/span\u003e\u003cspan\u003e  );\n\u003c/span\u003e\u003cspan\u003e  if (!transfer) throw new Error(\"Matching USDC Transfer not found\");\n\u003c/span\u003e\u003cspan\u003e  return {\n\u003c/span\u003e\u003cspan\u003e    network, transaction: hash, logIndex: transfer.logIndex,\n\u003c/span\u003e\u003cspan\u003e    blockHash: receipt.blockHash, blockNumber: receipt.blockNumber.toString(),\n\u003c/span\u003e\u003cspan\u003e  };\n\u003c/span\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e이 함수는 해당 거래의 USDC 전송 사실을 확인합니다. 특정 API 주문에 귀속됐는지는 별도로 확인해야 합니다. 운영 서버에서는 저장된 주문 조건을 비교값으로 사용하세요.\u003c/p\u003e\n\u003cp\u003e클라이언트가 올린 \u003ccode\u003etransfer.json\u003c/code\u003e을 접근 허가 증거로 믿으면 안 됩니다. 이 예제의 서버는 facilitator의 검증·정산 결과를 사용합니다. 독립 조회를 접근 허가 조건으로 삼으려면 서버에서 실행해야 합니다.\u003c/p\u003e\n\u003cp\u003e영수증 조회 실패는 곧 결제 실패를 뜻하지 않습니다. RPC 오류라면 저장된 거래 해시를 다시 조회하세요. 추가 확인 수와 체인 재구성 대응은 운영 정책으로 정합니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B2%B0%EC%A0%9C-%EC%8B%A4%ED%96%89%EA%B3%BC-%EA%B2%B0%EA%B3%BC-%ED%99%95%EC%9D%B8\" class=\"anchor\" id=\"테스트-결제-실행과-결과-확인\"\u003e\u003c/a\u003e테스트 결제 실행과 결과 확인\u003c/h2\u003e\n\u003cp\u003e파일을 모두 저장한 뒤 테스트 지갑으로 실행하세요. 타입 검사는 서명이나 송금을 수행하지 않습니다. 아래 명령은 앞서 설치한 TypeScript 도구를 사용합니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e다음 명령으로 타입을 검사하세요.\u003c/li\u003e\n\u003cli\u003e두 번째 터미널에 서버와 같은 수취 주소를 입력하세요.\u003c/li\u003e\n\u003cli\u003e테스트 결제 지갑의 개인키를 숨김 입력하세요.\u003c/li\u003e\n\u003cli\u003e클라이언트를 한 번 실행하세요.\u003c/li\u003e\n\u003cli\u003e저장된 정산 결과와 전송 로그를 확인하세요.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003enpx\u003c/span\u003e\u003cspan\u003e tsc\u003c/span\u003e\u003cspan\u003e --noEmit --strict --skipLibCheck --target\u003c/span\u003e\u003cspan\u003e ES2022\u003c/span\u003e\u003cspan\u003e --module\u003c/span\u003e\u003cspan\u003e NodeNext\u003c/span\u003e\u003cspan\u003e --moduleResolution\u003c/span\u003e\u003cspan\u003e NodeNext server.ts client.ts check-transfer.ts\n\u003c/span\u003e\u003cspan\u003eread \u003c/span\u003e\u003cspan\u003e-r -p \u003c/span\u003e\u003cspan\u003e'\u003c/span\u003e\u003cspan\u003e같은 수취 지갑 주소: \u003c/span\u003e\u003cspan\u003e' PAY_TO\n\u003c/span\u003e\u003cspan\u003eread \u003c/span\u003e\u003cspan\u003e-r -s -p \u003c/span\u003e\u003cspan\u003e'\u003c/span\u003e\u003cspan\u003e테스트 결제 지갑 개인키: \u003c/span\u003e\u003cspan\u003e' EVM_PRIVATE_KEY\n\u003c/span\u003e\u003cspan\u003eexport \u003c/span\u003e\u003cspan\u003ePAY_TO EVM_PRIVATE_KEY\n\u003c/span\u003e\u003cspan\u003enpx\u003c/span\u003e\u003cspan\u003e tsx client.ts\n\u003c/span\u003e\u003cspan\u003eunset\u003c/span\u003e\u003cspan\u003e EVM_PRIVATE_KEY\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e결과물\u003c/th\u003e\n\u003cth\u003e의미\u003c/th\u003e\n\u003cth\u003e확인할 내용\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"결과물\"\u003e\u003ccode\u003esettlement.json\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"의미\"\u003e서버가 반환한 정산 결과\u003c/td\u003e\n\u003ctd data-label=\"확인할 내용\"\u003e\n\u003ccode\u003esuccess\u003c/code\u003e, \u003ccode\u003enetwork\u003c/code\u003e, \u003ccode\u003etransaction\u003c/code\u003e\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"결과물\"\u003e\u003ccode\u003etransfer.json\u003c/code\u003e\u003c/td\u003e\n\u003ctd data-label=\"의미\"\u003eRPC 조회에서 찾은 전송 기록\u003c/td\u003e\n\u003ctd data-label=\"확인할 내용\"\u003e체인, 거래 해시, 로그 위치, 블록 정보\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"결과물\"\u003e콘솔의 유료 본문\u003c/td\u003e\n\u003ctd data-label=\"의미\"\u003e이 클라이언트의 검증 이후 출력한 데이터\u003c/td\u003e\n\u003ctd data-label=\"확인할 내용\"\u003e요청한 콘텐츠인지 확인\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e클라이언트를 새로 실행하면 새 결제가 발생할 수 있습니다. 이 최소 예제에는 주문별 중복 방지 저장소가 없습니다. 재시도 기능은 다음 절의 조건을 갖춘 뒤 추가하세요.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%EA%B2%80%EC%A6%9D-%EC%8B%A4%ED%8C%A8-%EC%A1%B0%EA%B1%B4%EB%B3%84-%EC%B2%98%EB%A6%AC\" class=\"anchor\" id=\"검증-실패-조건별-처리\"\u003e\u003c/a\u003e검증 실패 조건별 처리\u003c/h2\u003e\n\u003cp\u003e실패 시험에서는 유료 본문이 노출되지 않는지 확인하세요. HTTP 상태만 검사하면 차단 누락을 놓칠 수 있습니다. 기준 동작은 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts\"\u003e공식 Express 미들웨어\u003c/a\u003e를 참고합니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e시험 조건\u003c/th\u003e\n\u003cth\u003e기대하는 처리\u003c/th\u003e\n\u003cth\u003e추가 확인\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"시험 조건\"\u003e결제 헤더 없음\u003c/td\u003e\n\u003ctd data-label=\"기대하는 처리\"\u003e402와 결제 조건 반환\u003c/td\u003e\n\u003ctd data-label=\"추가 확인\"\u003e유료 본문 없음\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"시험 조건\"\u003e다른 토큰·수취인 제시\u003c/td\u003e\n\u003ctd data-label=\"기대하는 처리\"\u003e클라이언트 정책에서 거절\u003c/td\u003e\n\u003ctd data-label=\"추가 확인\"\u003e서명 생성 안 함\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"시험 조건\"\u003e잘못된 서명\u003c/td\u003e\n\u003ctd data-label=\"기대하는 처리\"\u003e검증 실패로 차단\u003c/td\u003e\n\u003ctd data-label=\"추가 확인\"\u003e유료 핸들러 실행 안 함\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"시험 조건\"\u003e잔액 부족\u003c/td\u003e\n\u003ctd data-label=\"기대하는 처리\"\u003e검증 또는 정산 실패로 차단\u003c/td\u003e\n\u003ctd data-label=\"추가 확인\"\u003e정산 성공으로 기록 안 함\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"시험 조건\"\u003e정산 결과가 실패\u003c/td\u003e\n\u003ctd data-label=\"기대하는 처리\"\u003e유료 응답 차단\u003c/td\u003e\n\u003ctd data-label=\"추가 확인\"\u003e준비된 본문도 전송 안 함\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"시험 조건\"\u003efacilitator 응답 지연\u003c/td\u003e\n\u003ctd data-label=\"기대하는 처리\"\u003e성공 미확인 상태로 보류\u003c/td\u003e\n\u003ctd data-label=\"추가 확인\"\u003e새 승인 자동 생성 금지\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"시험 조건\"\u003eRPC 영수증 조회 오류\u003c/td\u003e\n\u003ctd data-label=\"기대하는 처리\"\u003e거래 해시를 보관하고 재조회\u003c/td\u003e\n\u003ctd data-label=\"추가 확인\"\u003e결제 실패로 단정 금지\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e기본 승인 흐름에서는 정산 전에 핸들러가 실행됩니다. 정산 실패가 외부 작업까지 되돌려 주지는 않습니다. 이 예제처럼 부수 효과 없는 조회부터 적용하세요.\u003c/p\u003e\n\u003cp\u003e이메일 발송이나 외부 주문 생성은 별도 설계가 필요합니다. 결제와 작업 상태를 함께 기록하세요. 실패 시 재실행 범위를 정해야 중복 작업을 줄일 수 있습니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%EC%A0%95%EC%82%B0-%ED%9B%84-%EC%9D%91%EB%8B%B5-%EC%9C%A0%EC%8B%A4%EA%B3%BC-%EC%9E%AC%EC%A0%84%EC%86%A1-%EB%B3%B5%EA%B5%AC\" class=\"anchor\" id=\"정산-후-응답-유실과-재전송-복구\"\u003e\u003c/a\u003e정산 후 응답 유실과 재전송 복구\u003c/h2\u003e\n\u003cp\u003e결제 완료와 콘텐츠 전달 완료를 별도 상태로 저장하세요. 결제는 끝났지만 응답 연결이 끊길 수 있습니다. 이런 상황을 처리하는 근거는 \u003ca href=\"https://docs.x402.org/extensions/payment-identifier\"\u003ePayment-Identifier 확장 문서\u003c/a\u003e입니다.\u003c/p\u003e\n\u003cp\u003e동일 작업을 다시 요청해도 중복 처리하지 않는 성질을 멱등성이라고 합니다. \u003ccode\u003epayment-identifier\u003c/code\u003e는 이를 위한 결제 식별자를 제공합니다. 식별자만 추가해도 자동으로 완성되는 기능은 아닙니다.\u003c/p\u003e\n\u003ch3\u003e\n\u003ca href=\"#%EC%A4%91%EB%B3%B5-%EA%B2%B0%EC%A0%9C%EC%99%80-%EC%9E%AC%EC%A0%84%EC%86%A1-%EB%B9%84%EA%B5%90\" class=\"anchor\" id=\"중복-결제와-재전송-비교\"\u003e\u003c/a\u003e중복 결제와 재전송 비교\u003c/h3\u003e\n\u003cp\u003e같은 서명의 재사용 방지와 주문 중복 방지는 구분해야 합니다. EIP-3009는 사용한 승인 nonce의 재사용을 막습니다. 이 동작은 \u003ca href=\"https://eips.ethereum.org/EIPS/eip-3009\"\u003eERC-3009 사양\u003c/a\u003e에 정의돼 있습니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e상황\u003c/th\u003e\n\u003cth\u003e온체인 승인 관점\u003c/th\u003e\n\u003cth\u003eAPI 운영 관점\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"상황\"\u003e같은 승인을 다시 정산\u003c/td\u003e\n\u003ctd data-label=\"온체인 승인 관점\"\u003e이미 사용한 nonce 검사 대상\u003c/td\u003e\n\u003ctd data-label=\"API 운영 관점\"\u003e기존 결과 조회 필요\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"상황\"\u003e같은 주문에 새 승인 생성\u003c/td\u003e\n\u003ctd data-label=\"온체인 승인 관점\"\u003e별개의 승인으로 처리될 수 있음\u003c/td\u003e\n\u003ctd data-label=\"API 운영 관점\"\u003e중복 결제 방지 필요\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"상황\"\u003e정산 성공 후 응답 유실\u003c/td\u003e\n\u003ctd data-label=\"온체인 승인 관점\"\u003e자산 이동은 완료될 수 있음\u003c/td\u003e\n\u003ctd data-label=\"API 운영 관점\"\u003e콘텐츠 재전달 필요\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"상황\"\u003e정산 요청 후 시간 초과\u003c/td\u003e\n\u003ctd data-label=\"온체인 승인 관점\"\u003e결과가 아직 불명확함\u003c/td\u003e\n\u003ctd data-label=\"API 운영 관점\"\u003e보류 상태와 정산 확인 필요\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e다음 절차는 공식 확장을 운영 저장소에 연결하는 설계안입니다. 앞의 최소 코드에는 포함돼 있지 않습니다. 운영에 적용하려면 영속 저장소와 동시 요청 제어를 추가하세요.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e서버에서 \u003ccode\u003epayment-identifier\u003c/code\u003e 확장 지원을 선언하세요.\u003c/li\u003e\n\u003cli\u003e클라이언트에서 논리적 요청의 식별자를 보관하세요.\u003c/li\u003e\n\u003cli\u003e서버에서 식별자를 결제자와 요청 내용에 연결하세요.\u003c/li\u003e\n\u003cli\u003e처리 중 상태를 원자적으로 기록해 동시 실행을 막으세요.\u003c/li\u003e\n\u003cli\u003e정산 성공 시 거래 증거와 응답 결과를 저장하세요.\u003c/li\u003e\n\u003cli\u003e재요청의 권한과 내용이 같으면 저장된 결과를 반환하세요.\u003c/li\u003e\n\u003cli\u003e정산 결과가 불명확하면 기존 승인부터 확인하세요.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e결제 식별자는 비밀번호나 접근 토큰이 아닙니다. 식별자만 아는 요청에 유료 캐시를 반환하면 안 됩니다. 다른 사용자나 다른 요청 본문에 재사용되는지도 검사하세요.\u003c/p\u003e\n\u003cp\u003e운영 기록에는 체인·거래 해시·로그 위치를 함께 남기세요. 이 기록을 주문과 연결해 다른 주문의 재사용을 차단합니다. \u003ccode\u003eTransfer\u003c/code\u003e 일치만으로 주문 귀속까지 입증되지는 않습니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%EA%B2%B0%EC%A0%9C-%EA%B5%AC%ED%98%84%EC%9D%98-%ED%9D%94%ED%95%9C-%EC%8B%A4%EC%88%98\" class=\"anchor\" id=\"결제-구현의-흔한-실수\"\u003e\u003c/a\u003e결제 구현의 흔한 실수\u003c/h2\u003e\n\u003cp\u003e버전 필드와 성공 신호를 혼동하면 결제 판정이 틀어집니다. 아래 표를 배포 검토에 활용하세요. 버전 차이는 \u003ca href=\"https://docs.x402.org/guides/migration-v1-to-v2\"\u003ex402 이전 가이드\u003c/a\u003e를 기준으로 합니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e흔한 실수\u003c/th\u003e\n\u003cth\u003e바로잡을 기준\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003e\n\u003ccode\u003eextra.version: \"2\"\u003c/code\u003e를 x402 버전으로 해석\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003e프로토콜 버전은 \u003ccode\u003ex402Version\u003c/code\u003e으로 확인\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003ev2에 \u003ccode\u003emaxAmountRequired\u003c/code\u003e 사용\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003e결제 조건의 금액은 \u003ccode\u003eamount\u003c/code\u003e 사용\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003e\n\u003ccode\u003eX-PAYMENT-REQUIRED\u003c/code\u003e 헤더를 임의로 생성\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003ev1은 본문, v2는 \u003ccode\u003ePAYMENT-REQUIRED\u003c/code\u003e 사용\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003e\n\u003ccode\u003eisValid: true\u003c/code\u003e를 입금 완료로 처리\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003e\n\u003ccode\u003e/settle\u003c/code\u003e 결과를 별도로 확인\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003e거래의 최상위 \u003ccode\u003eto\u003c/code\u003e를 USDC 수취인으로 처리\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003eUSDC \u003ccode\u003eTransfer.to\u003c/code\u003e 확인\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003e서명자가 거래 전송자와 같다고 가정\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003e승인 결제자와 \u003ccode\u003eTransfer.from\u003c/code\u003e 비교\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003e테스트넷 수령액을 매출로 합산\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003e메인넷과 테스트넷 장부 분리\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003e시간 초과 때마다 새 결제 생성\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003e기존 주문과 승인 상태부터 복구\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"흔한 실수\"\u003eSDK 패키지 버전과 프로토콜 버전을 동일시\u003c/td\u003e\n\u003ctd data-label=\"바로잡을 기준\"\u003e패키지 버전과 \u003ccode\u003ex402Version\u003c/code\u003e을 별도 관리\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003eUSDC 서명의 도메인 정보에도 버전 문자열이 들어갑니다. 그 값은 x402 프로토콜 버전과 목적이 다릅니다. 서명 도메인 구조는 \u003ca href=\"https://eips.ethereum.org/EIPS/eip-3009\"\u003eERC-3009 사양\u003c/a\u003e을 확인하세요.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%EB%B3%B4%EC%95%88-%EC%A0%90%EA%B2%80%EA%B3%BC-%EC%99%B8%EB%B6%80-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%9D%98-%EC%8B%A0%EB%A2%B0-%EB%B2%94%EC%9C%84\" class=\"anchor\" id=\"보안-점검과-외부-서비스의-신뢰-범위\"\u003e\u003c/a\u003e보안 점검과 외부 서비스의 신뢰 범위\u003c/h2\u003e\n\u003cp\u003e결제 성공 판정은 서버에서 통제하세요. 외부 facilitator의 응답도 명시적인 신뢰 대상입니다. 자체 운영도 가능한 구조는 \u003ca href=\"https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md\"\u003efacilitator 문서\u003c/a\u003e에 설명돼 있습니다.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e클라이언트의 성공 메시지로 콘텐츠를 해제하지 마세요.\u003c/li\u003e\n\u003cli\u003e서버가 선언한 체인·토큰·금액·수취인을 비교하세요.\u003c/li\u003e\n\u003cli\u003e개인키와 시드 문구를 로그에 남기지 마세요.\u003c/li\u003e\n\u003cli\u003e승인 서명 전체도 일반 접근 로그에서 제외하세요.\u003c/li\u003e\n\u003cli\u003e결제 헤더의 크기와 요청 처리 시간을 제한하세요.\u003c/li\u003e\n\u003cli\u003e유료 응답이 공유 캐시에 남지 않도록 점검하세요.\u003c/li\u003e\n\u003cli\u003e테스트 키와 운영 키의 보관 위치를 분리하세요.\u003c/li\u003e\n\u003cli\u003efacilitator 장애 시 무결제 우회 경로를 만들지 마세요.\u003c/li\u003e\n\u003cli\u003e승인 만료와 중복 요청을 실제로 시험하세요.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e실습의 고정 의존성에는 보안 검사 경고가 남아 있습니다. \u003ccode\u003enpm audit\u003c/code\u003e는 zod 관련 권고를 보고했습니다. 관련 내용은 \u003ca href=\"https://github.com/advisories/GHSA-mrhx-c7xc-m3w5\"\u003eGitHub 보안 권고\u003c/a\u003e에서 확인하세요.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u003cspan\u003enpm\u003c/span\u003e\u003cspan\u003e audit\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e운영 배포 전 수정 버전의 SDK 호환성을 확인하세요. 주요 버전을 강제로 교체하면 동작이 달라질 수 있습니다. 이 실습을 보안 검증이 끝난 배포 구성으로 사용하지 마세요.\u003c/p\u003e\n\u003ch3\u003e\n\u003ca href=\"#%EC%9E%90%EC%B2%B4-%EC%84%9C%EB%B2%84%EC%99%80-cloudflare-gateway-%EB%B9%84%EA%B5%90\" class=\"anchor\" id=\"자체-서버와-cloudflare-gateway-비교\"\u003e\u003c/a\u003e자체 서버와 Cloudflare Gateway 비교\u003c/h3\u003e\n\u003cp\u003eCloudflare Gateway를 쓰면 원본 서버의 검증 방식이 달라집니다. 2026년 9월 30일자 문서는 x402 v2 사용을 명시합니다. 다음 구분은 \u003ca href=\"https://developers.cloudflare.com/monetization-gateway/x402/\"\u003eCloudflare x402 문서\u003c/a\u003e를 기준으로 합니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e구분\u003c/th\u003e\n\u003cth\u003e이 글의 Express 방식\u003c/th\u003e\n\u003cth\u003eCloudflare Monetization Gateway\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e클라이언트 결제 처리\u003c/td\u003e\n\u003ctd data-label=\"이 글의 Express 방식\"\u003eExpress 미들웨어\u003c/td\u003e\n\u003ctd data-label=\"Cloudflare Monetization Gateway\"\u003eGateway\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e원본 서버의 검증 지점\u003c/td\u003e\n\u003ctd data-label=\"이 글의 Express 방식\"\u003e선택한 facilitator와 연동\u003c/td\u003e\n\u003ctd data-label=\"Cloudflare Monetization Gateway\"\u003e\n\u003ccode\u003ePAYMENT-CONTEXT\u003c/code\u003e JWT 검증\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e정산 결과 전달\u003c/td\u003e\n\u003ctd data-label=\"이 글의 Express 방식\"\u003e미들웨어의 \u003ccode\u003ePAYMENT-RESPONSE\u003c/code\u003e\n\u003c/td\u003e\n\u003ctd data-label=\"Cloudflare Monetization Gateway\"\u003eGateway가 정산 영수증 생성\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"구분\"\u003e추가로 확인할 사항\u003c/td\u003e\n\u003ctd data-label=\"이 글의 Express 방식\"\u003efacilitator 지원 체인과 인증\u003c/td\u003e\n\u003ctd data-label=\"Cloudflare Monetization Gateway\"\u003eJWT 서명 검증과 원본 접근 보호\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e\u003ccode\u003ePAYMENT-CONTEXT\u003c/code\u003e는 Gateway와 원본 서버 사이의 헤더입니다. 일반 x402 클라이언트의 서명 헤더가 아닙니다. 원본 검증은 \u003ca href=\"https://developers.cloudflare.com/monetization-gateway/configuration/payment-validation/\"\u003eCloudflare Payment validation 문서\u003c/a\u003e를 따르세요.\u003c/p\u003e\n\u003cp\u003eGateway를 거쳐도 JWT를 단순 디코딩해서는 안 됩니다. 문서가 지정한 키로 서명을 검증하세요. 헤더가 없거나 검증이 실패하면 유료 콘텐츠를 차단합니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#%EC%8B%A4%EC%A0%9C-%EC%82%AC%EC%9A%A9%EB%9F%89%EA%B3%BC-%EC%A0%95%EC%82%B0-%EA%B8%B0%EB%A1%9D-%EA%B5%AC%EB%B6%84\" class=\"anchor\" id=\"실제-사용량과-정산-기록-구분\"\u003e\u003c/a\u003e실제 사용량과 정산 기록 구분\u003c/h2\u003e\n\u003cp\u003e온체인 송금 건수만으로 콘텐츠 소비를 입증할 수는 없습니다. 운영 지표는 정산과 응답 전달을 연결해 집계하세요. 이는 결제 흐름과 재전송 동작을 바탕으로 한 설계 제안입니다.\u003c/p\u003e\n\u003cdiv class=\"overflow-x-auto\"\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e기록\u003c/th\u003e\n\u003cth\u003e답할 수 있는 질문\u003c/th\u003e\n\u003cth\u003e추가로 필요한 기록\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"기록\"\u003e402 응답\u003c/td\u003e\n\u003ctd data-label=\"답할 수 있는 질문\"\u003e결제 조건을 얼마나 요청했는지\u003c/td\u003e\n\u003ctd data-label=\"추가로 필요한 기록\"\u003e실제 결제 여부\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"기록\"\u003e검증 성공\u003c/td\u003e\n\u003ctd data-label=\"답할 수 있는 질문\"\u003e승인이 유효했는지\u003c/td\u003e\n\u003ctd data-label=\"추가로 필요한 기록\"\u003e정산 결과\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"기록\"\u003eUSDC 전송\u003c/td\u003e\n\u003ctd data-label=\"답할 수 있는 질문\"\u003e자산이 이동했는지\u003c/td\u003e\n\u003ctd data-label=\"추가로 필요한 기록\"\u003e주문 귀속\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"기록\"\u003e주문별 정산 완료\u003c/td\u003e\n\u003ctd data-label=\"답할 수 있는 질문\"\u003e유료 요청이 결제됐는지\u003c/td\u003e\n\u003ctd data-label=\"추가로 필요한 기록\"\u003e응답 전달 상태\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd data-label=\"기록\"\u003e재전달 기록\u003c/td\u003e\n\u003ctd data-label=\"답할 수 있는 질문\"\u003e이미 결제한 요청을 복구했는지\u003c/td\u003e\n\u003ctd data-label=\"추가로 필요한 기록\"\u003e신규 결제와 구분하는 식별자\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/div\u003e\n\u003cp\u003e테스트 요청은 실제 사용량 집계에서 구분하세요. 같은 주문의 재시도도 별도로 표시합니다. 거래량 자체를 API 수요나 수익 보장으로 설명하면 안 됩니다.\u003c/p\u003e\n\u003ch2\u003e\n\u003ca href=\"#base-%EB%A9%94%EC%9D%B8%EB%84%B7-%EC%A0%84%ED%99%98-%EC%A0%88%EC%B0%A8\" class=\"anchor\" id=\"base-메인넷-전환-절차\"\u003e\u003c/a\u003eBase 메인넷 전환 절차\u003c/h2\u003e\n\u003cp\u003e테스트넷의 정상·실패 경로를 확인한 뒤 메인넷을 검토하세요. 네트워크 문자열만 바꾸면 전환이 끝나지 않습니다. 기준 절차는 \u003ca href=\"https://docs.x402.org/getting-started/quickstart-for-sellers#running-on-mainnet\"\u003e공식 판매자 가이드\u003c/a\u003e를 따릅니다.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e선택한 facilitator의 Base 메인넷 지원을 확인하세요.\u003c/li\u003e\n\u003cli\u003e\n\u003ccode\u003e/supported\u003c/code\u003e에서 v2·\u003ccode\u003eexact\u003c/code\u003e·\u003ccode\u003eeip155:8453\u003c/code\u003e 지원을 확인하세요.\u003c/li\u003e\n\u003cli\u003e제공자가 요구하는 인증과 이용 조건을 설정하세요.\u003c/li\u003e\n\u003cli\u003e서버와 클라이언트의 네트워크를 함께 변경하세요.\u003c/li\u003e\n\u003cli\u003e클라이언트의 USDC 허용 주소를 메인넷 주소로 변경하세요.\u003c/li\u003e\n\u003cli\u003e402 응답의 금액과 실제 수취 주소를 다시 대조하세요.\u003c/li\u003e\n\u003cli\u003e중복 요청 저장소와 장애 복구 절차를 연결하세요.\u003c/li\u003e\n\u003cli\u003e운영 의존성의 보안 검사를 완료하세요.\u003c/li\u003e\n\u003cli\u003e설정한 소액으로 실제 전송과 콘텐츠 전달을 확인하세요.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e다중 체인 지원은 모든 조합의 지원을 뜻하지 않습니다. SDK와 facilitator의 지원 범위를 함께 확인하세요. 확인 기준은 \u003ca href=\"https://docs.x402.org/core-concepts/network-and-token-support\"\u003e네트워크·토큰 지원 문서\u003c/a\u003e입니다.\u003c/p\u003e\n","tags":["스테이블코인","AI 에이전트","AI 개발","콘텐츠 수익화","개발 도구"],"faqs":[{"question":"x402는 일반 USDC 입금 확인과 무엇이 다른가요?","answer":"HTTP 요청 과정에서 결제 조건을 전달하고 서명한 결제 승인으로 재요청합니다. 입금 내역을 나중에 대조하는 방식과 달리 유료 리소스 요청에 결제 절차를 연결합니다."},{"question":"x402 v2의 결제 헤더는 무엇인가요?","answer":"결제 조건은 PAYMENT-REQUIRED, 결제 페이로드는 PAYMENT-SIGNATURE로 전달합니다. 정산 결과는 PAYMENT-RESPONSE로 전달합니다."},{"question":"v1에 X-PAYMENT-REQUIRED 헤더가 있나요?","answer":"공식 HTTP v1 사양은 최초 결제 조건을 JSON 응답 본문에 담습니다. 결제 페이로드와 정산 결과에는 각각 X-PAYMENT와 X-PAYMENT-RESPONSE를 사용합니다."},{"question":"/verify가 성공하면 결제가 완료된 것인가요?","answer":"/verify 성공은 결제 승인이 유효하다는 뜻입니다. 이 실습에서는 /settle의 성공 결과를 별도로 확인해야 합니다."},{"question":"Base Sepolia 테스트 USDC를 실제 수익으로 볼 수 있나요?","answer":"Circle은 테스트넷 USDC에 금융 가치가 없다고 명시합니다. 테스트넷 정산은 실제 매출과 분리해서 기록하세요."},{"question":"수취 서버에 개인키를 넣어야 하나요?","answer":"이 예제의 수취 서버에는 공개 수취 주소만 설정합니다. 결제 승인에 쓰는 개인키는 결제 클라이언트에서 읽습니다."},{"question":"0.001 USDC의 amount 값은 얼마인가요?","answer":"USDC의 소수 자릿수 6자리를 적용하면 최소 단위로 1000입니다. v2 결제 조건에는 문자열 \"1000\"을 사용합니다."},{"question":"거래 영수증이 성공이면 USDC 수령도 확인된 것인가요?","answer":"거래 성공 상태만으로는 충분하지 않습니다. 공식 USDC 컨트랙트의 Transfer 이벤트에서 결제자·수취인·금액을 대조하세요."},{"question":"같은 결제 요청을 다시 보내도 안전한가요?","answer":"동일 승인 nonce의 재사용 방지와 주문 중복 방지는 별개입니다. 재시도 전에 기존 결제 상태를 확인하고 주문별 멱등성 처리를 구현하세요."},{"question":"PAYMENT-RESPONSE가 없으면 다시 결제하면 되나요?","answer":"응답 유실이나 시간 초과라면 정산 결과가 불명확할 수 있습니다. 새 승인부터 만들지 말고 기존 요청의 정산 상태를 확인하세요."},{"question":"x402는 특정 회사의 facilitator만 사용해야 하나요?","answer":"공식 구조는 외부 facilitator 이용과 자체 운영을 모두 허용합니다. 선택한 구현이 필요한 프로토콜 버전·결제 방식·체인을 지원하는지 확인하세요."},{"question":"AI 에이전트의 지출도 자동으로 제한되나요?","answer":"이 예제는 요청별 결제 조건만 제한합니다. 반복 호출에 따른 누적 지출은 별도의 예산 정책으로 관리하세요."},{"question":"Cloudflare PAYMENT-CONTEXT는 표준 결제 서명 헤더인가요?","answer":"PAYMENT-CONTEXT는 Gateway가 원본 서버에 전달하는 JWT입니다. 클라이언트의 PAYMENT-SIGNATURE와 용도가 다릅니다."},{"question":"예제를 바로 메인넷에 배포해도 되나요?","answer":"이 코드는 테스트용 최소 구성입니다. 운영 배포 전 의존성 보안 경고, 중복 요청 처리, facilitator 인증과 실제 정산을 검증하세요."}],"sources":[{"url":"https://github.com/x402-foundation/x402/blob/main/specs/x402-specification-v2.md","title":"x402 Protocol Specification v2","type":"source"},{"url":"https://github.com/x402-foundation/x402/blob/main/specs/transports-v1/http.md","title":"x402 HTTP Transport v1","type":"source"},{"url":"https://github.com/x402-foundation/x402/blob/main/specs/transports-v2/http.md","title":"x402 HTTP Transport v2","type":"source"},{"url":"https://docs.x402.org/guides/migration-v1-to-v2","title":"x402 Migration Guide: V1 to V2","type":"source"},{"url":"https://github.com/x402-foundation/x402/blob/main/docs/core-concepts/facilitator.md","title":"x402 Facilitator 역할과 책임","type":"source"},{"url":"https://docs.x402.org/getting-started/quickstart-for-sellers","title":"x402 Quickstart for Sellers","type":"source"},{"url":"https://docs.x402.org/getting-started/quickstart-for-buyers","title":"x402 Quickstart for Buyers","type":"source"},{"url":"https://github.com/x402-foundation/x402/tree/main/examples/typescript/clients/fetch","title":"x402 공식 TypeScript fetch 클라이언트 예제","type":"source"},{"url":"https://github.com/x402-foundation/x402/blob/main/typescript/packages/http/express/src/index.ts","title":"x402 Express 미들웨어 구현","type":"source"},{"url":"https://github.com/x402-foundation/x402/blob/main/typescript/packages/core/src/client/x402Client.ts","title":"x402Client 결제 조건 선택과 정책 구현","type":"source"},{"url":"https://github.com/x402-foundation/x402/blob/main/typescript/packages/mechanisms/evm/src/exact/server/scheme.ts","title":"x402 ExactEvmScheme 서버 가격 변환 구현","type":"source"},{"url":"https://developers.circle.com/stablecoins/usdc-contract-addresses","title":"Circle USDC 컨트랙트 주소","type":"data_point"},{"url":"https://docs.base.org/get-started/connect-to-base","title":"Base 메인넷과 Base Sepolia 네트워크 정보","type":"data_point"},{"url":"https://faucet.circle.com/","title":"Circle Testnet Faucet","type":"source"},{"url":"https://viem.sh/docs/actions/public/getTransactionReceipt","title":"viem getTransactionReceipt","type":"source"},{"url":"https://viem.sh/docs/contract/parseEventLogs","title":"viem parseEventLogs","type":"source"},{"url":"https://eips.ethereum.org/EIPS/eip-20","title":"ERC-20 Token Standard","type":"source"},{"url":"https://eips.ethereum.org/EIPS/eip-3009","title":"ERC-3009 Transfer With Authorization","type":"source"},{"url":"https://docs.x402.org/extensions/payment-identifier","title":"x402 Payment-Identifier 멱등성 확장","type":"source"},{"url":"https://developers.cloudflare.com/monetization-gateway/x402/","title":"Cloudflare Monetization Gateway x402 protocol","type":"source"},{"url":"https://developers.cloudflare.com/monetization-gateway/configuration/payment-validation/","title":"Cloudflare Monetization Gateway Payment validation","type":"source"},{"url":"https://docs.x402.org/core-concepts/network-and-token-support","title":"x402 Networks \u0026 Token Support","type":"source"},{"url":"https://github.com/advisories/GHSA-mrhx-c7xc-m3w5","title":"GitHub 보안 권고: Zod의 대용량 컬렉션 검증에 따른 메모리 고갈","type":"source"}],"images":[{"id":2199,"url":"https://injoys.com/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzM4MDcsInB1ciI6ImJsb2JfaWQifX0=--49c502fd8da16158a617c3586dfa81ced4bc95df/ai-f0ae2b5c.webp","is_representative":true,"generation_method":"ai_photo","license":"ai_generated","mime_type":"image/webp","width":1536,"height":1024,"translations":{"ko":{"alt":"짙은 회색 스웨터를 입은 남성이 서버 랙 선반에 놓인 태블릿 화면을 살펴보고 있다.","caption":"메인넷으로 전환하기 전에 테스트 환경에서 x402 결제 연동을 검증하세요.","description":null},"en":{"alt":"A man in a dark gray sweater examines a tablet resting on a server rack shelf.","caption":"Validate the x402 payment integration in a test environment before switching to mainnet.","description":null},"ja":{"alt":"濃いグレーのセーターを着た男性が、サーバーラックの棚に置かれたタブレットを確認している。","caption":"メインネットへ移行する前に、テスト環境でx402の決済連携を検証しましょう。","description":null},"es":{"alt":"Un hombre con un suéter gris oscuro revisa una tableta sobre la bandeja de un rack de servidores.","caption":"Valida la integración de pagos x402 en un entorno de prueba antes de pasar a la red principal.","description":null},"id":{"alt":"Seorang pria bersweater abu-abu gelap memeriksa tablet di atas ambalan rak server.","caption":"Validasi integrasi pembayaran x402 di lingkungan uji sebelum beralih ke mainnet.","description":null},"pt":{"alt":"Um homem de suéter cinza-escuro examina um tablet apoiado na prateleira de um rack de servidores.","caption":"Valide a integração de pagamentos x402 em um ambiente de teste antes de migrar para a rede principal.","description":null},"zh-hant":{"alt":"一名穿著深灰色毛衣的男子正在查看伺服器機架層板上的平板電腦。","caption":"切換至主網前，先在測試環境中驗證 x402 支付整合。","description":null},"de":{"alt":"Ein Mann im dunkelgrauen Pullover prüft ein Tablet auf der Ablage eines Serverracks.","caption":"Prüfen Sie die x402-Zahlungsintegration in einer Testumgebung, bevor Sie ins Mainnet wechseln.","description":null}}},{"id":2200,"url":"https://injoys.com/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzM4MTMsInB1ciI6ImJsb2JfaWQifX0=--4e70727f132519277c68d1c6279d95585a936549/ai-3dbd1c62.webp","is_representative":false,"generation_method":"ai_semi","license":"ai_generated","mime_type":"image/webp","width":1536,"height":1024,"translations":{"ko":{"alt":"창가에서 여성이 평평하게 펼친 노트북으로 입력하고 있으며, 화면에는 목록과 두 경로가 합쳐지는 흐름도가 보인다.","caption":"중복 요청 복구를 시험해 결제 재시도가 이중 정산으로 이어지지 않는지 확인하세요.","description":null},"en":{"alt":"A woman types on a laptop opened flat, with a list and two paths converging on one endpoint on its screen.","caption":"Test duplicate-request recovery to ensure payment retries do not cause a second settlement.","description":null},"ja":{"alt":"窓辺で女性が平らに開いたノートパソコンを操作し、画面には一覧と2本の経路が合流するフロー図が表示されている。","caption":"重複リクエストの復旧をテストし、決済の再試行で二重精算が起きないことを確認しましょう。","description":null},"es":{"alt":"Una mujer teclea en un portátil abierto en plano; la pantalla muestra una lista y dos rutas que convergen.","caption":"Prueba la recuperación de solicitudes duplicadas para evitar una segunda liquidación al reintentar el pago.","description":null},"id":{"alt":"Seorang perempuan mengetik di laptop yang dibuka rata, dengan daftar dan diagram dua jalur yang menyatu di layar.","caption":"Uji pemulihan permintaan duplikat untuk memastikan percobaan ulang pembayaran tidak memicu penyelesaian ganda.","description":null},"pt":{"alt":"Uma mulher digita num notebook aberto na horizontal, com uma lista e duas rotas convergentes na tela.","caption":"Teste a recuperação de solicitações duplicadas para garantir que novas tentativas de pagamento não gerem liquidação dupla.","description":null},"zh-hant":{"alt":"一名女子在窗邊操作完全攤平的筆記型電腦，螢幕顯示清單及兩條路徑匯合的流程圖。","caption":"測試重複請求的復原流程，確認付款重試不會造成重複結算。","description":null},"de":{"alt":"Eine Frau tippt auf einem flach aufgeklappten Laptop; das Display zeigt eine Liste und zwei zusammenlaufende Pfade.","caption":"Testen Sie die Wiederherstellung bei doppelten Anfragen, damit Zahlungswiederholungen keine zweite Abrechnung auslösen.","description":null}}},{"id":2201,"url":"https://injoys.com/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzM4MjAsInB1ciI6ImJsb2JfaWQifX0=--00778307dfd57c04756bddde9e198926440f17c1/ai-3e46671d.webp","is_representative":false,"generation_method":"ai_infographic","license":"ai_generated","mime_type":"image/webp","width":1024,"height":1536,"visible_locales":["ko"],"translations":{"ko":{"alt":"준비, 402 결제 요청과 서명 재요청, 조건 선택, 거래 해시로 정산 검증, 전환 점검을 정리한 인포그래픽","caption":"서명 검증만으로 입금이 완료된 것은 아니므로 거래 해시로 USDC Transfer 이벤트를 확인하세요.","description":null},"en":{"alt":"Infographic covering setup, the 402 payment flow, condition checks, settlement verification, and transition checks","caption":"Signature verification alone does not confirm payment; use the transaction hash to check the USDC Transfer event.","description":null},"ja":{"alt":"準備、402決済と署名後の再リクエスト、条件選択、取引ハッシュによる決済確認、移行点検をまとめた図解","caption":"署名の検証だけでは入金完了とはいえないため、取引ハッシュでUSDCのTransferイベントを確認しましょう。","description":null},"es":{"alt":"Infografía sobre preparación, pago 402, selección de condiciones, verificación de liquidación y revisión de migración","caption":"Validar la firma no confirma el pago; usa el hash de la transacción para comprobar el evento Transfer de USDC.","description":null},"id":{"alt":"Infografik persiapan, alur pembayaran 402, pemilihan syarat, verifikasi penyelesaian, dan pemeriksaan migrasi","caption":"Verifikasi tanda tangan saja belum memastikan pembayaran; gunakan hash transaksi untuk memeriksa event Transfer USDC.","description":null},"pt":{"alt":"Infográfico de preparação, fluxo de pagamento 402, seleção de condições, verificação da liquidação e revisão da migração","caption":"Validar a assinatura não confirma o pagamento; use o hash da transação para verificar o evento Transfer de USDC.","description":null},"zh-hant":{"alt":"整理準備工作、402付款與簽署後重送請求、條件選擇、交易雜湊結算驗證及切換檢查的資訊圖表","caption":"簽章驗證不代表款項已到帳，應透過交易雜湊確認 USDC Transfer 事件。","description":null},"de":{"alt":"Infografik zu Vorbereitung, 402-Zahlungsablauf, Bedingungsauswahl, Abrechnungsprüfung und Wechselprüfung","caption":"Eine gültige Signatur bestätigt noch keinen Zahlungseingang; prüfe das USDC-Transfer-Ereignis anhand des Transaktionshashs.","description":null}}}],"published_at":"2026-10-28T19:20:55+09:00","updated_at":"2026-10-28T19:20:55+09:00","license":"cc_by","translation_status":"original","available_locales":["ko","en","ja","es"],"data_locales":["ko","en","ja","es","id","pt","zh-hant","de"],"url":"https://injoys.com/ko/articles/x402-payments-2026-v2-base-usdc-settlement-guide"}