### Python requests example to retrieve a cash receipt Source: https://docs.paysync.kr/cash-receipts/file Uses the requests library to GET a cash receipt. The API key is read from the PAYSYNC_API_KEY environment variable. ```python import os, requests cash_receipt_id = "crt_a1b2c3d4e5f6g7h8i9j0k1l2" res = requests.get( f"https://api.paysync.kr/v1/cash-receipts/{cash_receipt_id}", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, ) print(res.json()) ``` -------------------------------- ### Webhook Event Handling Source: https://docs.paysync.kr/api-reference/webhooks/overview Guidance on handling webhook events, including idempotency checks and local development setup. ```APIDOC ## Webhook Event Handling ### Description This section provides guidance on how to handle webhook events, including idempotency checks and local development setup. ### Supported Events - `invoice.created` - A new order has been created. - `invoice.paid` - The order has been converted to a paid status. - `invoice.deleted` - The order has been deleted. ### Response Requirements - **HTTP 200** - Recorded as success. - **Other (3xx, 4xx, 5xx, timeout)** - Recorded as failure and a failure notification email is sent. - Only an exact **200** is treated as success; `201`, `204`, etc. are considered failures. - Response timeout is **10 seconds**. For heavy operations, send a 200 response first and process asynchronously. ### Failure Handling - **No automatic retries** - PaySync does not perform automatic retries. It only records the result after a single send. - **Failure notification email** - If the `webhookFailures` notification is enabled, a failure notification email is sent to the registered email. This can be toggled in the dashboard under `Settings → Notifications`. - **Manual resend** - Failed deliveries can be resent from the dashboard under `Webhook Management → Delivery History`. ### Idempotency - The `webhook-id` header is a unique identifier for each delivery. Consider idempotent processing based on the domain resource ID, as the same event may arrive twice due to network issues. #### Example javascript // Ignore if already processed if (await db.isEventProcessed(event.invoice.id, event.type)) { return res.sendStatus(200); } await processEvent(event); await db.markEventProcessed(event.invoice.id, event.type); ### Local Development - To test in an internal network, use a tunneling tool like ngrok to expose your local server to the outside and register that URL. #### Example bash ngrok http 3000 # Outputs a URL like https://abcd-1234.ngrok-free.app # Register this URL as your PaySync webhook endpoint. ### ID Prefixes - `whd_` - Webhook endpoint (Destination) - `whm_` - Webhook delivery (Delivery / Message) ``` -------------------------------- ### Create Invoice with Node.js Source: https://docs.paysync.kr/invoices/issue Uses fetch to POST invoice data. Reads API key from environment variable PAYSYNC_API_KEY. Parses response JSON to get code and data. ```javascript const res = await fetch("https://api.paysync.kr/v1/invoices", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAYSYNC_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ customer: { name: "홍길동", email: "hong@example.com", phoneNumber: "01012345678", }, amount: 50000, cashReceipt: { type: "PERSONAL", identifier: "01012345678" }, expireAfter: "1d", metadata: { orderId: "ORDER-2026-0001" }, }), }); const { code, data } = await res.json(); ``` -------------------------------- ### Create order with bankAccountIds using Node.js fetch Source: https://docs.paysync.kr/multi-bank-account-routing Use this Node.js fetch example to create an order with a specific receiving account. The bankAccountIds field must contain the account ID(s) to route deposits to this order. ```javascript await fetch("https://api.paysync.kr/v1/invoices", { method: "POST", headers: { Authorization: `Bearer ${process.env.PAYSYNC_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ bankAccountIds: ["bac_xxxxxxxxxxxx"], customer: { name: "홍길동" }, amount: 10000, }), }); ``` -------------------------------- ### POST /v1/invoices Source: https://docs.paysync.kr/api-reference/introduction Example of creating a single invoice using the Paysync API. ```APIDOC ## POST /v1/invoices ### Description Creates a new invoice (order) in Paysync. This endpoint is used to generate a payment request for a customer. ### Method POST ### Endpoint https://api.paysync.kr/v1/invoices ### Parameters #### Request Body - **customer** (object) - Required - Customer information - **name** (string) - Required - Customer name - **email** (string) - Required - Customer email - **phoneNumber** (string) - Required - Customer phone number - **amount** (number) - Required - Payment amount in KRW - **expireAfter** (string) - Required - Expiration period (e.g., "1d") - **metadata** (object) - Optional - Additional metadata - **orderId** (string) - Optional - Order identifier ### Request Example { "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "amount": 50000, "expireAfter": "1d", "metadata": { "orderId": "ORDER-2026-0001" } } ### Response #### Success Response (200) - Response body format follows the standard Paysync API response format. #### Response Example (Not provided in source) ### Authentication Bearer Token (Authorization: Bearer YOUR_API_KEY) ### Content-Type application/json ``` -------------------------------- ### Create order with bankAccountIds using Python requests Source: https://docs.paysync.kr/multi-bank-account-routing Use this Python requests example to create an order with a specific receiving account. The bankAccountIds field must contain the account ID(s) to route deposits to this order. ```python import os import requests requests.post( "https://api.paysync.kr/v1/invoices", headers={ "Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}", "Content-Type": "application/json", }, json={ "bankAccountIds": ["bac_xxxxxxxxxxxx"], "customer": {"name": "홍길동"}, "amount": 10000, }, ) ``` -------------------------------- ### Node.js Handling Example for invoice.deleted Source: https://docs.paysync.kr/api-reference/webhooks/invoice-deleted Example Node.js code to handle the invoice.deleted event. It updates the internal order table to cancelled status using the orderId from metadata. ```javascript if (event.type === "invoice.deleted") { const orderId = event.invoice.metadata?.orderId; if (!orderId) return res.sendStatus(200); // 자체 주문도 취소 상태로 전환 await db.orders.update(orderId, { status: "CANCELLED", cancelledAt: new Date(), cancelReason: "INVOICE_DELETED", }); } ``` -------------------------------- ### Successful Invoice Creation Response Source: https://docs.paysync.kr/invoices/issue Example response body for a successful invoice creation, containing invoice details such as id, customer, amount, and timestamps. ```json { "code": "CREATED", "data": { "id": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2", "issuerId": "acc_x9y8z7w6v5u4t3s2r1q0p9o8", "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "cashReceipt": { "type": "PERSONAL", "identifier": "01012345678" }, "amount": 50000, "paid": false, "metadata": { "orderId": "ORDER-2026-0001" }, "issuedAt": "2026-04-28T03:14:15.926Z", "expiresAt": "2026-04-29T03:14:15.926Z" } } ``` -------------------------------- ### List transactions with cURL, Node.js, and Python Source: https://docs.paysync.kr/transactions/list List deposit transactions with pagination and filters (offset, limit, matched, dateAfter). The cURL example uses YOUR_API_KEY placeholder; Node.js reads PAYSYNC_API_KEY from environment; Python reads PAYSYNC_API_KEY from environment. Response includes code, totalItems, and data. ```bash curl "https://api.paysync.kr/v1/transactions?offset=0&limit=20&matched=true&dateAfter=2026-04-01" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript const params = new URLSearchParams({ offset: "0", limit: "20", matched: "true", dateAfter: "2026-04-01", }); const res = await fetch(`https://api.paysync.kr/v1/transactions?${params}`, { headers: { Authorization: `Bearer ${process.env.PAYSYNC_API_KEY}` }, }); const { code, totalItems, data } = await res.json(); ``` ```python import os, requests res = requests.get( "https://api.paysync.kr/v1/transactions", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, params={ "offset": 0, "limit": 20, "matched": True, "dateAfter": "2026-04-01", }, ) print(res.json()) ``` -------------------------------- ### 200 OK response for list invoices Source: https://docs.paysync.kr/invoices/list Example 200 OK response for the list invoices endpoint. Shows the structure with code "OK", totalItems, and an array of invoice objects. ```json { "code": "OK", "totalItems": 142, "data": [ { "id": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2", "issuerId": "acc_x9y8z7w6v5u4t3s2r1q0p9o8", "bankAccountIds": [], "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "cashReceipt": null, "amount": 50000, "paid": true, "metadata": { "orderId": "ORDER-2026-0001" }, "issuedAt": "2026-04-28T03:14:15.926Z", "expiresAt": "2026-04-29T03:14:15.926Z" } ] } ``` -------------------------------- ### List invoices with cURL, Node.js, and Python Source: https://docs.paysync.kr/invoices/list Lists invoices with pagination and filters (offset, limit, paid, dateAfter). The cURL example uses YOUR_API_KEY placeholder; Node.js and Python read the key from environment variable PAYSYNC_API_KEY. The response is parsed as JSON with code, totalItems, and data fields. ```bash curl "https://api.paysync.kr/v1/invoices?offset=0&limit=20&paid=true&dateAfter=2026-04-01" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript const params = new URLSearchParams({ offset: "0", limit: "20", paid: "true", dateAfter: "2026-04-01", }); const res = await fetch(`https://api.paysync.kr/v1/invoices?${params}`, { headers: { Authorization: `Bearer ${process.env.PAYSYNC_API_KEY}` }, }); const { code, totalItems, data } = await res.json(); ``` ```python import os, requests res = requests.get( "https://api.paysync.kr/v1/invoices", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, params={ "offset": 0, "limit": 20, "paid": True, "dateAfter": "2026-04-01", }, ) print(res.json()) ``` -------------------------------- ### Example response — successful order creation Source: https://docs.paysync.kr/api-reference/introduction Shows the JSON structure returned when an order (invoice) is created successfully. The code field is "CREATED" and data contains the new invoice object with its id, customer details, amount, and timestamps. ```json { "code": "CREATED", "data": { "id": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2", "issuerId": "acc_...", "bankAccountIds": [], "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "amount": 50000, "paid": false, "metadata": { "orderId": "ORDER-2026-0001" }, "issuedAt": "2026-04-28T03:14:15.926Z", "expiresAt": "2026-04-29T03:14:15.926Z" } } ``` -------------------------------- ### Example response — deposit transaction list Source: https://docs.paysync.kr/api-reference/introduction Shows the paginated response for a list of deposit transactions. The code field is "OK", totalItems is 142, and data is an array of transaction objects with matched invoice id, amount, and timestamps. ```json { "code": "OK", "totalItems": 142, "data": [ { "id": "trx_a1b2c3d4e5f6g7h8i9j0k1l2", "matchedInvoiceId": "ivc_...", "amount": 50000, "description": "홍길동", "hidden": false, "receivedAt": "2026-04-28T03:20:11.000Z", "matchedAt": "2026-04-28T03:20:12.412Z", "createdAt": "2026-04-28T03:20:12.000Z" } ] } ``` -------------------------------- ### List cash receipts with cURL, Node.js, and Python Source: https://docs.paysync.kr/cash-receipts/list List cash receipts with pagination and filters (offset, limit, status, dateAfter). The cURL example uses YOUR_API_KEY placeholder; Node.js and Python read the key from PAYSYNC_API_KEY environment variable. The response contains code, totalItems, and data fields. ```bash curl "https://api.paysync.kr/v1/cash-receipts?offset=0&limit=20&status=ISSUED&dateAfter=2026-04-01" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript const params = new URLSearchParams({ offset: "0", limit: "20", status: "ISSUED", dateAfter: "2026-04-01", }); const res = await fetch(`https://api.paysync.kr/v1/cash-receipts?${params}`, { headers: { Authorization: `Bearer ${process.env.PAYSYNC_API_KEY}` }, }); const { code, totalItems, data } = await res.json(); ``` ```python import os, requests res = requests.get( "https://api.paysync.kr/v1/cash-receipts", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, params={ "offset": 0, "limit": 20, "status": "ISSUED", "dateAfter": "2026-04-01", }, ) print(res.json()) ``` -------------------------------- ### ngrok으로 로컬 서버 터널링 Source: https://docs.paysync.kr/api-reference/webhooks/overview 로컬 개발 시 내부망의 서버를 외부에 노출하기 위해 ngrok 터널링 도구를 사용하는 예시입니다. 출력된 URL을 페이싱크 웹훅 엔드포인트로 등록하면 됩니다. ```bash ngrok http 3000 # https://abcd-1234.ngrok-free.app 같은 URL 이 출력됩니다. # 이 URL 을 페이싱크 웹훅 엔드포인트로 등록하세요. ``` -------------------------------- ### GET /v1/transactions Source: https://docs.paysync.kr/transactions/list Retrieves a paginated list of deposit transactions with filtering options. Includes request examples in cURL, Node.js, and Python, along with success and error response examples. ```APIDOC ## GET /v1/transactions ### Description Retrieves a paginated list of deposit transactions linked to the API key's account. Supports filtering by bank account, matching status, and date range. ### Method GET ### Endpoint https://api.paysync.kr/v1/transactions ### Parameters #### Query Parameters - **offset** (integer) - Optional - Pagination offset. Default is 0. - **limit** (integer) - Optional - Maximum number of items to return. Default is 20. - **matched** (boolean) - Optional - Filter by matching status (true/false). - **dateAfter** (string, date) - Optional - Return transactions received after this date (format: YYYY-MM-DD). ### Request Example ``` curl "https://api.paysync.kr/v1/transactions?offset=0&limit=20&matched=true&dateAfter=2026-04-01" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response #### Success Response (200 OK) - **code** (string) - Response code, e.g., "OK". - **totalItems** (integer) - Total number of matching transactions. - **data** (array) - List of transaction objects. - **id** (string) - Transaction ID. - **bankAccount** (object) - Bank account details. - **id** (string) - Bank account ID. - **alias** (string) - Account alias. - **provider** (string) - Bank provider code. - **type** (string) - Account type (e.g., PERSONAL). - **number** (string) - Account number. - **matchedInvoiceId** (string) - ID of the matched invoice, if any. - **amount** (number) - Transaction amount. - **description** (string) - Transaction description. - **hidden** (boolean) - Whether the transaction is hidden. - **receivedAt** (string, date-time) - When the transaction was received. - **matchedAt** (string, date-time) - When the transaction was matched. - **createdAt** (string, date-time) - When the transaction record was created. #### Response Example ``` { "code": "OK", "totalItems": 87, "data": [ { "id": "trx_a1b2c3d4e5f6g7h8i9j0k1l2", "bankAccount": { "id": "bac_x9y8z7w6v5u4t3s2r1q0p9o8", "alias": "사업용 계좌", "provider": "KB_KOOKMIN_BANK", "type": "PERSONAL", "number": "1234567890" }, "matchedInvoiceId": "ivc_n0o1p2q3r4s5t6u7v8w9x0y1", "amount": 50000, "description": "홍길동", "hidden": false, "receivedAt": "2026-04-28T03:20:11.000Z", "matchedAt": "2026-04-28T03:20:12.412Z", "createdAt": "2026-04-28T03:20:12.000Z" } ] } ``` ### Error Handling #### 401 NOT_AUTHORIZED ``` { "code": "NOT_AUTHORIZED", "data": null } ``` #### 403 LIMIT_EXCEEDS_MAXIMUM ``` { "code": "LIMIT_EXCEEDS_MAXIMUM", "data": null } ``` #### 422 INVALID_OFFSET ``` { "code": "INVALID_OFFSET", "data": null } ``` #### 422 INVALID_LIMIT ``` { "code": "INVALID_LIMIT", "data": null } ``` #### 422 INVALID_DATE_RANGE_FORMAT ``` { "code": "INVALID_DATE_RANGE_FORMAT", "data": null } ``` ``` -------------------------------- ### Get transaction by ID with cURL, Node.js, and Python Source: https://docs.paysync.kr/transactions/get Retrieve a single transaction by its ID using the Paysync API. The cURL example uses YOUR_API_KEY placeholder; the Node.js example reads the key from process.env.PAYSYNC_API_KEY; the Python example reads it from the PAYSYNC_API_KEY environment variable. The response is parsed as JSON and printed in the Python example. ```bash curl https://api.paysync.kr/v1/transactions/trx_a1b2c3d4e5f6g7h8i9j0k1l2 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ```javascript const res = await fetch( `https://api.paysync.kr/v1/transactions/${transactionId}`, { headers: { Authorization: `Bearer ${process.env.PAYSYNC_API_KEY}` } } ); const { code, data } = await res.json(); ``` ```python import os, requests transaction_id = "trx_a1b2c3d4e5f6g7h8i9j0k1l2" res = requests.get( f"https://api.paysync.kr/v1/transactions/{transaction_id}", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, ) print(res.json()) ``` -------------------------------- ### Create Invoice with Python Source: https://docs.paysync.kr/invoices/issue Uses requests library to POST invoice data. Reads API key from environment variable PAYSYNC_API_KEY. Prints the JSON response. ```python import os, requests res = requests.post( "https://api.paysync.kr/v1/invoices", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, json={ "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678", }, "amount": 50000, "cashReceipt": {"type": "PERSONAL", "identifier": "01012345678"}, "expireAfter": "1d", "metadata": {"orderId": "ORDER-2026-0001"}, }, ) print(res.json()) ``` -------------------------------- ### Payload Example for invoice.deleted Event Source: https://docs.paysync.kr/api-reference/webhooks/invoice-deleted Example JSON payload for the invoice.deleted event. The invoice object is a snapshot of the deleted invoice, with paid always false. ```json { "type": "invoice.deleted", "invoice": { "id": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2", "issuerId": "acc_x9y8z7w6v5u4t3s2r1q0p9o8", "bankAccountIds": [], "customer": { "name": "홍길동", "email": null, "phoneNumber": null }, "cashReceipt": null, "amount": 50000, "paid": false, "metadata": { "orderId": "ORDER-2026-0001" }, "issuedAt": "2026-04-28T03:14:15.926Z", "expiresAt": "2026-04-29T03:14:15.926Z" } } ``` -------------------------------- ### Create Checkout Page in Next.js Source: https://docs.paysync.kr/online-shop Displays payment instructions including bank details and customer-specific deposit information. Ensure the deposit name and amount are clearly highlighted to facilitate automatic matching. ```tsx export default async function CheckoutPage({ params }) { const order = await db.orders.find(params.orderId); const account = await db.bankAccounts.findActive(); // 등록된 페이싱크 계좌 return (

결제 안내

{order.amount.function toLocaleString() { [native code] }()}원
입금 은행
{account.bankName}
계좌번호
{account.number}
예금주
{account.holder}
⭐ 입금자명
{order.customerName} 이 이름으로 정확히 이체해 주세요!
입금 금액
{order.amount.function toLocaleString() { [native code] }()}원
{/* 결제 상태 실시간 확인 */}
); } ``` -------------------------------- ### invoice.paid Event Payload Example Source: https://docs.paysync.kr/api-reference/webhooks/invoice-paid Example JSON payload for the invoice.paid event. The invoice object matches the Invoice schema and includes id, issuerId, customer, cashReceipt, amount, paid, metadata, issuedAt, and expiresAt. The trigger field indicates the cause of payment completion. ```json { "type": "invoice.paid", "invoice": { "id": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2", "issuerId": "acc_x9y8z7w6v5u4t3s2r1q0p9o8", "bankAccountIds": [], "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "cashReceipt": null, "amount": 50000, "paid": true, "metadata": { "orderId": "ORDER-2026-0001" }, "issuedAt": "2026-04-28T03:14:15.926Z", "expiresAt": "2026-04-29T03:14:15.926Z" }, "trigger": "AUTOMATIC_MATCHING" } ``` -------------------------------- ### Kotlin: 주문 생성 (Ktor Client) Source: https://docs.paysync.kr/api-reference/introduction Kotlin의 Ktor Client를 사용하여 페이싱크 API로 주문을 생성하는 예제입니다. API 키는 환경 변수 PAYSYNC_API_KEY에서 읽어옵니다. ```kotlin import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* val client = HttpClient(CIO) { install(ContentNegotiation) { json() } } val response: HttpResponse = client.post("https://api.paysync.kr/v1/invoices") { bearerAuth(System.getenv("PAYSYNC_API_KEY")) contentType(ContentType.Application.Json) setBody(mapOf( "customer" to mapOf( "name" to "홍길동", "email" to "hong@example.com", "phoneNumber" to "01012345678", ), "amount" to 50000, "expireAfter" to "1d", "metadata" to mapOf("orderId" to "ORDER-2026-0001"), )) } println(response.bodyAsText()) ``` -------------------------------- ### GET /cash-receipts Source: https://docs.paysync.kr/cash-receipts/list 현금영수증 목록 조회 API 문서입니다. 쿼리 파라미터와 응답, 오류 코드를 포함합니다. ```APIDOC ## [GET] /cash-receipts ### Description 현금영수증 목록을 조회합니다. 다양한 필터 조건(검색어, 발급 상태, 사업자등록번호, 종류, 식별자, 날짜 범위)을 지원하며, 페이지네이션을 위한 offset과 limit 파라미터를 사용합니다. ### Method GET ### Endpoint /cash-receipts ### Parameters #### Query Parameters - **offset** (integer) - Required - 건너뛸 아이템 수. **0 이상.** - **limit** (integer) - Required - 한 번에 조회할 아이템 수. **1 이상 100 이하.** - **search** (string) - Optional - 검색어. **식별자(`identifier`)** 등에서 부분 일치로 검색합니다. - **status** (string) - Optional - 발급 상태로 필터링. 가능한 값: `PENDING`, `ISSUED`, `FAILED`, `REVOKED` - **corpNum** (string) - Optional - 발행자 사업자등록번호로 필터링. - **type** (string) - Optional - 현금영수증 종류로 필터링 (`PERSONAL` / `CORPORATE`). - **identifier** (string) - Optional - 식별자로 정확히 일치 필터링. 휴대폰 번호 또는 사업자번호. - **dateAfter** (string) - Optional - 이 날짜 **이후**(포함) 발급된 내역만 조회. `YYYY-MM-DD` 형식. - **dateBefore** (string) - Optional - 이 날짜 **이전**(포함) 발급된 내역만 조회. `YYYY-MM-DD` 형식. ### Response #### Success Response (200) - **code** (string) - Required - 항상 `"OK"` - **totalItems** (integer) - Required - 필터 조건에 부합하는 **전체** 아이템 수. - **data** (array) - Required - 현재 페이지의 현금영수증 객체 배열. 각 객체는 현금영수증 단건 조회 응답의 `data` 와 동일한 스키마입니다. #### Response Example { "code": "OK", "totalItems": 0, "data": [] } #### Error Responses ##### 401 UNAUTHORIZED — 인증 실패 - **NOT_AUTHORIZED** - `Authorization` 헤더가 없거나 만료/폐기된 키입니다. ##### 403 FORBIDDEN — 권한 오류 - **LIMIT_EXCEEDS_MAXIMUM** - `limit` 이 100 을 초과했습니다. ##### 422 UNPROCESSABLE_CONTENT — 검증 실패 - **INVALID_OFFSET** - `offset` 이 0 미만입니다. - **INVALID_LIMIT** - `limit` 이 0 이하입니다. - **INVALID_DATE_RANGE_FORMAT** - `dateAfter` 또는 `dateBefore` 가 `YYYY-MM-DD` 형식이 아닙니다. ``` -------------------------------- ### 200 OK response for get invoice Source: https://docs.paysync.kr/invoices/get Successful response with invoice data. The `paid` field indicates payment status. ```json { "code": "OK", "data": { "id": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2", "issuerId": "acc_x9y8z7w6v5u4t3s2r1q0p9o8", "bankAccountIds": [], "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "cashReceipt": null, "amount": 50000, "paid": false, "metadata": { "orderId": "ORDER-2026-0001" }, "issuedAt": "2026-04-28T03:14:15.926Z", "expiresAt": "2026-04-29T03:14:15.926Z" } } ``` -------------------------------- ### Python (Flask) 웹훅 시그니처 검증 Source: https://docs.paysync.kr/api-reference/webhooks/overview Flask를 사용한 Python 웹훅 검증 예시입니다. raw body를 사용하고, 5분 타임스탬프 윈도우, HMAC-SHA256 서명 검증, hmac.compare_digest로 안전하게 비교합니다. 검증 후 event["type"]으로 분기 처리합니다. ```python import base64 import hmac import hashlib import os import time from flask import Flask, request, abort app = Flask(__name__) SECRET = os.environ["PAYSYNC_WEBHOOK_SECRET"] # whsec_... @app.post("/paysync/webhook") def paysync_webhook(): id_ = request.headers["webhook-id"] ts = request.headers["webhook-timestamp"] sig_header = request.headers["webhook-signature"] body = request.get_data(as_text=True) if abs(int(time.time()) - int(ts)) > 300: abort(401, "expired") key = base64.b64decode(SECRET.removeprefix("whsec_")) expected = base64.b64encode( hmac.new(key, f"{id_}.{ts}.{body}".encode(), hashlib.sha256).digest() ).decode() provided = sig_header.split(",", 1)[1] if not hmac.compare_digest(expected, provided): abort(401, "invalid signature") event = request.get_json() # event["type"] 으로 분기 처리 return "", 200 ``` -------------------------------- ### cURL request to get invoice Source: https://docs.paysync.kr/invoices/get Fetches invoice details using cURL. Requires an API key in the Authorization header. ```bash curl https://api.paysync.kr/v1/invoices/ivc_a1b2c3d4e5f6g7h8i9j0k1l2 \ -H "Authorization: Bearer YOUR_API_KEY" ``` -------------------------------- ### Java: 주문 생성 (HttpClient) Source: https://docs.paysync.kr/api-reference/introduction Java의 java.net.http.HttpClient를 사용하여 페이싱크 API로 주문을 생성하는 예제입니다. API 키는 환경 변수 PAYSYNC_API_KEY에서 읽어옵니다. ```java import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class IssueInvoice { public static void main(String[] args) throws Exception { String body = """ { "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "amount": 50000, "expireAfter": "1d", "metadata": { "orderId": "ORDER-2026-0001" } } """; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.paysync.kr/v1/invoices")) .header("Authorization", "Bearer " + System.getenv("PAYSYNC_API_KEY")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse response = HttpClient.newHttpClient() .send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } } ``` -------------------------------- ### routes/orders.js - 주문 조회 시 만료 처리 Source: https://docs.paysync.kr/online-shop 주문 조회 API에서 PENDING 상태인 주문이 생성 후 24시간이 지났으면 EXPIRED로 업데이트하고 반환합니다. 만료된 주문은 더 이상 매칭되지 않으며, 조회 시 lazy하게 만료 처리하는 방식입니다. ```javascript router.get("/orders/:orderId", async (req, res) => { const order = await db.orders.find(req.params.orderId); if (!order) return res.sendStatus(404); // 자체 만료 판정 const expired = order.status === "PENDING" && new Date() > new Date(order.createdAt.getTime() + 24 * 3600 * 1000); if (expired) { await db.orders.update(order.id, { status: "EXPIRED" }); return res.json({ ...order, status: "EXPIRED" }); } res.json(order); }); ``` -------------------------------- ### GET /v1/invoices/{id} Source: https://docs.paysync.kr/invoices/get Documentation for retrieving a single invoice by ID, including path parameters, response schema, and error codes. ```APIDOC ## GET /v1/invoices/{id} ### Description Retrieves detailed information for a single invoice by its ID (prefixed with `ivc_`). Use the `paid` field in the response to quickly check payment status; for real-time updates, prefer the `invoice.paid` webhook over polling. ### Method GET ### Endpoint `https://api.paysync.kr/v1/invoices/{id}` ### Parameters #### Path Parameters - **id** (string) - Required - The ID of the invoice to retrieve (prefixed with `ivc_`). ### Request Example ``` curl https://api.paysync.kr/v1/invoices/ivc_a1b2c3d4e5f6g7h8i9j0k1l2 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### Response #### Success Response (200 OK) - **code** (string) - Always `"OK"`. - **data** (object) - The invoice object. - **data.id** (string) - Invoice ID. - **data.issuerId** (string) - Issuer account ID. - **data.bankAccountIds** (string[]) - List of receiving bank account IDs. - **data.customer** (object) - Customer information (`name`, `email`, `phoneNumber`). - **data.cashReceipt** (object | null) - Cash receipt information, or `null` if none. - **data.amount** (integer) - Invoice amount in KRW. - **data.paid** (boolean) - Whether payment has been completed. - **data.metadata** (object | null) - Metadata stored at invoice creation. - **data.issuedAt** (string) - Invoice issue time in ISO 8601 UTC. - **data.expiresAt** (string | null) - Invoice expiration time in ISO 8601 UTC. #### Response Example ``` { "code": "OK", "data": { "id": "ivc_a1b2c3d4e5f6g7h8i9j0k1l2", "issuerId": "acc_x9y8z7w6v5u4t3s2r1q0p9o8", "bankAccountIds": [], "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678" }, "cashReceipt": null, "amount": 50000, "paid": false, "metadata": { "orderId": "ORDER-2026-0001" }, "issuedAt": "2026-04-28T03:14:15.926Z", "expiresAt": "2026-04-29T03:14:15.926Z" } } ``` #### Error Responses - **401 UNAUTHORIZED** - Authentication failed. `code`: `NOT_AUTHORIZED` - `Authorization` header missing or key expired/revoked. - **403 FORBIDDEN** - Permission error. `code`: `INSUFFICIENT_PERMISSIONS` - Attempted to access an invoice from another account. - **404 NOT_FOUND** - Invoice not found. `code`: `INVOICE_NOT_FOUND` - No invoice with the given ID or invoice deleted. #### Error Response Examples ``` { "code": "NOT_AUTHORIZED", "data": null } ``` ``` { "code": "INSUFFICIENT_PERMISSIONS", "data": null } ``` ``` { "code": "INVOICE_NOT_FOUND", "data": null } ``` ``` -------------------------------- ### Node.js 코드 처리 권장 패턴 Source: https://docs.paysync.kr/api-reference/error-codes Node.js에서 페이싱크 API 응답의 code 필드를 처리하는 권장 패턴입니다. 성공, 인증 오류, 사용자 입력 오류, 일시적 오류를 구분하여 처리합니다. ```javascript const res = await fetch(url, { ... }); const { code, data } = await res.json(); switch (code) { // 성공 case "OK": case "CREATED": case "DELETED": return data; // 인증 만료 → 키 갱신 필요 case "NOT_AUTHORIZED": throw new Error("API 키를 확인하세요"); // 사용자 입력 오류 → 사용자에게 안내 case "INVALID_AMOUNT": case "INVALID_CUSTOMER_NAME": case "INVALID_EMAIL": case "INVALID_PHONE_NUMBER": throw new ValidationError(code); // 일시적 오류 → 재시도 가능 case "INTERNAL_SERVER_ERROR": case "POPBILL_API_FAILED": await retry(); return; // 그 외 default: throw new Error(`알 수 없는 응답 코드: ${code}`); } ``` -------------------------------- ### Python requests request to get invoice Source: https://docs.paysync.kr/invoices/get Fetches invoice details using Python requests. Uses the PAYSYNC_API_KEY environment variable for authorization. ```python import os, requests invoice_id = "ivc_a1b2c3d4e5f6g7h8i9j0k1l2" res = requests.get( f"https://api.paysync.kr/v1/invoices/{invoice_id}", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, ) print(res.json()) ``` -------------------------------- ### Node.js fetch request to get invoice Source: https://docs.paysync.kr/invoices/get Fetches invoice details using Node.js fetch. Uses the PAYSYNC_API_KEY environment variable for authorization. ```javascript const res = await fetch( `https://api.paysync.kr/v1/invoices/${invoiceId}`, { headers: { Authorization: `Bearer ${process.env.PAYSYNC_API_KEY}` } } ); const { code, data } = await res.json(); ``` -------------------------------- ### Python: 주문 생성 (requests) Source: https://docs.paysync.kr/api-reference/introduction Python의 requests 라이브러리를 사용하여 페이싱크 API로 주문을 생성하는 예제입니다. API 키는 환경 변수 PAYSYNC_API_KEY에서 읽어옵니다. ```python import os, requests res = requests.post( "https://api.paysync.kr/v1/invoices", headers={"Authorization": f"Bearer {os.environ['PAYSYNC_API_KEY']}"}, json={ "customer": { "name": "홍길동", "email": "hong@example.com", "phoneNumber": "01012345678", }, "amount": 50000, "expireAfter": "1d", "metadata": {"orderId": "ORDER-2026-0001"}, }, ) print(res.json()) ```