셀러 묶음결제
셀러 묶음결제는 요청 가맹점과 셀러의 주문을 한 번에 결제하는 기능입니다. 요청 가맹점이 모든 주문번호와 결제 대상별 금액을 지정하면 캔디페이가 각 가맹점에 주문을 생성하고, 해당 금액으로 결제를 각각 승인합니다. 캔디페이가 주문번호를 자동으로 발급하지 않습니다.
상점 서버에서 결제 링크를 생성하고 응답의 checkoutUrl을 구매자에게 전달하세요. 전용 결제창에서는 카드 한 개로만 결제할 수 있습니다. 캔디페이가 총 결제 금액을 가맹점별 금액으로 나누어 승인합니다.
구매자 인증 후 상점 서버에서 결제 승인 API를 호출합니다. 캔디페이는 가맹점별 승인을 처리하고 모든 결제 결과를 응답합니다. 하나라도 승인에 실패하면 이미 승인된 결제를 모두 취소합니다.
이용 조건
섹션 제목: “이용 조건”- 요청 가맹점의 시크릿 키로 인증합니다.
- 요청 가맹점 자신의 결제 금액과 한 명 이상 셀러의 결제 금액을 함께 지정합니다.
- 요청 가맹점 주문을 포함해 결제 대상 주문은 최대 10개까지 지정할 수 있습니다.
- 셀러는 요청 가맹점에 등록되어 있고 삭제되지 않았으며, 실제 캔디페이 가맹점과 연결되어 있어야 합니다.
- 모든 결제 대상 가맹점은 결제가 가능한 상태여야 합니다.
- 모든 개별 주문의
orderId를 요청 가맹점이 지정해야 합니다. - 각 주문의 거래내역서 텍스트를 필수로 입력합니다.
셀러 식별자는 Seller 객체의 id입니다. 요청 가맹점이 지정한 refSellerId나 연결된 가맹점 번호와 구분하세요.
주문 식별자
섹션 제목: “주문 식별자”| 필드 | 발급 주체 | 의미 |
|---|---|---|
orderId |
요청 가맹점 | 요청 가맹점 자신의 주문번호이자 링크 생성과 승인을 연결하는 대표 주문번호입니다. 같은 요청 가맹점 내에서 고유해야 합니다. |
orders[].orderId |
요청 가맹점 | 각 개별 주문의 주문번호입니다. 본인 주문의 값은 대표 orderId와 같아야 하며, 각 주문이 귀속되는 가맹점 내에서 고유해야 합니다. |
orders[].payment.intentKey |
캔디페이 | 개별 주문의 결제 식별자입니다. |
대표 주문에는 요청 가맹점 자신의 배분 금액을 저장합니다. 셀러 주문에는 해당 셀러의 배분 금액을 저장하며, 실제 결제와 매출은 각 주문에 귀속됩니다.
예를 들어 본인 몫 10만 원과 셀러 몫 2만 원을 함께 결제하면 다음 두 주문이 생성됩니다. 결제창과 승인 응답의 총금액은 12만 원입니다.
| 주문 | 주문번호 | 결제 금액 |
|---|---|---|
| 요청 가맹점의 대표 주문 | 요청의 orderId |
100,000원 |
| 셀러 주문 | 요청 가맹점이 지정한 orderId |
20,000원 |
결제 링크 생성
섹션 제목: “결제 링크 생성”Request
섹션 제목: “Request”POST https://api.candypay.co.kr/px/seller-payments/sessionsContent-Type: application/json시크릿 키 뒤에 :을 붙인 값을 Base64로 인코딩해 Basic 인증 헤더에 넣습니다. 자세한 내용은 시크릿 키 사용하기를 참고하세요.
Authorization: Basic base64("{API_SECRET_KEY}:")요청 본문
섹션 제목: “요청 본문”interface CreateSellerPaymentBody { /** * 요청 가맹점에서 구매자를 식별하는 추측 불가능한 고객 ID입니다. * 비회원 결제에는 "ANONYMOUS"를 사용합니다. * @maxLength 50 */ customerId: string
/** * 요청 가맹점 자신의 주문번호이며, 묶음결제의 대표 주문번호로 사용합니다. * @minLength 16 * @maxLength 50 * @pattern ^[a-zA-Z0-9_-]+$ */ orderId: string
/** * 구매자에게 표시할 주문명입니다. * @maxLength 100 */ orderName: string
/** * sellerId를 생략한 본인 주문을 정확히 하나 포함합니다. 같은 셀러는 한 번만 지정합니다. * @minItems 2 * @maxItems 10 */ orders: { /** * 결제할 셀러의 ID입니다. 생략하면 요청 가맹점 자신입니다. * @format uuid */ sellerId?: string
/** * 요청 가맹점이 지정하는 개별 주문번호입니다. * 본인 주문은 대표 orderId와 같은 값을 사용합니다. * @minLength 16 * @maxLength 50 * @pattern ^[a-zA-Z0-9_-]+$ */ orderId: string
/** * 해당 개별 주문의 거래내역서 텍스트입니다. * @minLength 1 * @pattern \S */ statementText: string
/** 해당 가맹점에 결제할 금액입니다. */ amount: { /** * @minimum 1 * @multipleOf 1 */ value: number } }[]
/** * 요청 가맹점 자신의 주문에 저장할 1차원 키-값 객체입니다. * 최대 5개, JSON 직렬화 기준 2,000자입니다. */ metadata?: Record<string, string | number | boolean | null>
/** * 구매자 인증이 완료된 뒤 이동할 URL입니다. * @format uri */ successUrl: string
/** * 구매자 인증 실패·취소·만료 시 이동할 URL입니다. * @format uri */ failUrl: string}총 결제 금액은 orders[].amount.value의 합계로 계산합니다. 금액은 원화(KRW) 기준입니다. 각 항목에 하나의 주문을 생성하며, 개별 주문명에는 orderName을 사용합니다.
결제자는 카드 한 개로 총 금액을 결제합니다. 캔디페이가 요청에 지정된 금액대로 각 가맹점의 승인 금액을 나눕니다.
요청 예제
섹션 제목: “요청 예제”curl --request POST \ --url https://api.candypay.co.kr/px/seller-payments/sessions \ --user '{API_SECRET_KEY}:' \ --header 'Content-Type: application/json' \ --data '{ "customerId": "p_1Gq3nudQxklUWpOUkj9", "orderId": "platform_order_20260914_001", "orderName": "서비스 이용료", "orders": [ { "orderId": "platform_order_20260914_001", "statementText": "상담 서비스 거래내역\n상담 2회: 100,000원", "amount": { "value": 100000 } }, { "sellerId": "0198f0cc-0d16-7bd2-8f2c-e9064ad0bb0e", "orderId": "seller_order_20260914_001", "statementText": "자료 제작 거래내역\n자료 제작 1건: 20,000원", "amount": { "value": 20000 } } ], "successUrl": "https://merchant.example/payment/success", "failUrl": "https://merchant.example/payment/fail" }'Response
섹션 제목: “Response”HTTP 201 Created와 함께 결제 링크를 반환합니다. 모든 결제 대상과 요청 값을 검증한 뒤 결제 요청과 개별 주문을 함께 생성합니다. 하나라도 유효하지 않으면 어떤 주문도 생성하지 않습니다.
interface SellerPaymentSession { /** * 대표 주문번호입니다. * @minLength 16 * @maxLength 50 * @pattern ^[a-zA-Z0-9_-]+$ */ orderId: string
/** * 구매자에게 전달할 전용 결제창 URL입니다. * @format uri */ checkoutUrl: string
/** * 승인 요청을 시작할 수 있는 기한입니다. * @format date-time */ expiresAt: string}{ "orderId": "platform_order_20260914_001", "checkoutUrl": "https://payfront.candypay.co.kr/px/seller-checkout?token=kM0Qx9r2V7nP", "expiresAt": "2026-09-21T01:23:45.678Z"}대표 주문번호와 요청 내용을 상점 서버에 저장하고 checkoutUrl을 그대로 구매자에게 전달하세요.
HTTP 상태 코드와 함께 에러 객체를 반환합니다. 주요 오류는 오류 응답을 참고하세요.
인증 결과 처리
섹션 제목: “인증 결과 처리”구매자 인증이 완료되면 successUrl로 이동하며 대표 주문번호를 전달합니다.
/payment/success?orderId={ORDER_ID}상점 서버에서 저장한 주문을 확인하고 결제 승인 API를 호출하세요. 결제 완료는 승인 응답의 DONE 상태로 판단합니다.
인증 실패·구매자 취소·만료 시에는 failUrl로 이동합니다.
/payment/fail?orderId={ORDER_ID}&code={ERROR_CODE}&message={ERROR_MESSAGE}인증 결과와 카드 결제 정보는 캔디페이가 대표 주문번호에 연결해 저장합니다. 승인 API는 서버에 저장된 인증 결과와 가맹점별 금액을 검증한 뒤 결제를 진행합니다.
SellerPaymentResult 객체
섹션 제목: “SellerPaymentResult 객체”승인 API의 응답입니다. 대표 주문번호, 전체 처리 상태와 가맹점별 결제 정보를 반환합니다.
interface SellerPaymentResult { /** * 대표 주문번호입니다. * @minLength 16 * @maxLength 50 * @pattern ^[a-zA-Z0-9_-]+$ */ orderId: string
/** @maxLength 100 */ orderName: string
/** 개별 주문 금액의 합계입니다. */ amount: { /** * @minimum 1 * @multipleOf 1 */ value: number }
status: 'DONE' | 'ABORTED'
/** @minItems 2 */ orders: { /** * 결제할 셀러의 ID입니다. 생략하면 요청 가맹점 자신입니다. * @format uuid */ sellerId?: string
/** 링크 생성 요청에서 지정한 개별 주문번호입니다. */ orderId: string
amount: { /** * @minimum 1 * @multipleOf 1 */ value: number }
/** 개별 결제 정보입니다. */ payment: PaymentIntent }[]
/** 결제를 완료하지 못한 사유입니다. */ error: { code: string; message: string } | null}orders[].payment는 PaymentIntent 객체입니다. 전체 승인이 완료된 DONE 응답에는 요청 가맹점과 셀러의 모든 결제 정보를 포함합니다. 응답 배열의 순서는 요청 순서와 다를 수 있으므로, 결제 대상은 sellerId로 확인하세요. sellerId가 없는 항목은 요청 가맹점 자신의 주문이며, 그 orderId는 대표 주문번호와 같습니다.
amount.value는 전체 결제 금액, orders[].amount.value는 각 주문의 결제 금액입니다. 전체 처리 결과는 status로 판단하세요.
결제 승인
섹션 제목: “결제 승인”Request
섹션 제목: “Request”POST https://api.candypay.co.kr/px/seller-payments/confirmContent-Type: application/json링크 생성에 사용한 요청 가맹점의 시크릿 키로 인증합니다. 해당 가맹점이 생성한 요청만 승인할 수 있습니다.
interface ConfirmSellerPaymentBody { /** * 링크 생성 시 지정한 대표 주문번호입니다. * @minLength 16 * @maxLength 50 * @pattern ^[a-zA-Z0-9_-]+$ */ orderId: string}curl --request POST \ --url https://api.candypay.co.kr/px/seller-payments/confirm \ --user '{API_SECRET_KEY}:' \ --header 'Content-Type: application/json' \ --data '{"orderId":"platform_order_20260914_001"}'Response
섹션 제목: “Response”승인 결과
섹션 제목: “승인 결과”승인 API는 결제 처리를 기다린 뒤 HTTP 200 OK와 SellerPaymentResult 객체를 반환합니다. DONE이면 대표 주문번호와 개별 주문번호·결제 정보를 저장하고 전체 결제를 완료 처리하세요. ABORTED이면 error를 확인합니다.
승인 응답의 개별 주문번호와 결제 정보를 저장하세요. 이후에는 각 가맹점의 권한으로 개별 주문의 결제 정보를 조회합니다. 회원으로 로그인한 구매자의 결제 내역에도 가맹점별 결제가 각각 표시됩니다.
요청 오류
섹션 제목: “요청 오류”인증·권한·요청 값 검증에 실패하면 HTTP 4XX와 에러 객체를 반환합니다. 구매자 인증이 완료되기 전에는 400 INVALID_REQUEST를 반환합니다.
결제 자체의 실패는 SellerPaymentResult.status와 error로 전달합니다. HTTP 200은 승인 요청의 결과가 확정됐다는 의미입니다.
승인 상태와 전체 취소
섹션 제목: “승인 상태와 전체 취소”| 상태 | 의미 | 상점의 처리 |
|---|---|---|
DONE |
모든 주문과 카드의 승인이 완료됐습니다. | 전체 결제를 완료 처리합니다. |
ABORTED |
결제에 실패했습니다. | error를 확인합니다. 재결제 시 새 대표 주문번호를 사용합니다. |
주문별로 선택한 카드의 승인을 진행합니다. 일부 승인에 실패하면 다음 주문의 승인을 중단하고, 같은 요청에서 이미 승인된 결제를 모두 취소합니다.
카드사의 중복 승인 거부를 방지하기 위해 주문별 승인 요청 사이에 최소 3초의 간격을 둡니다. 주문이 여러 개이면 승인 API 응답 시간이 그만큼 늘어날 수 있습니다.
전체 취소는 승인 금액 전액을 취소하는 것을 의미합니다. 승인·취소 이력은 보존되며, 카드사 알림이나 한도 복원에는 시차가 있을 수 있습니다. 취소 처리에 실패하면 기본 결제와 같이 PAYMENT_RECONCILIATION_REQUIRED 오류를 반환합니다. 이 경우 남은 승인 내역을 캔디페이에 확인하세요.
개별 결제의 성공이나 웹훅만으로 전체 결제를 완료 처리하지 마세요. 전체 결과는 승인 응답의 status로 확인합니다.
유효기간과 재요청
섹션 제목: “유효기간과 재요청”승인 시작 기한은 링크 생성 응답의 expiresAt으로 확인합니다. 구매자 인증이 끝났더라도 이 기한 안에 승인 요청을 시작해야 합니다. 이미 시작된 승인·취소 처리는 기한이 지나도 계속됩니다.
- 유효한 미처리 링크에 대해 같은 대표 주문번호로 같은 대상 가맹점·개별 주문번호·금액·거래내역서·주문명·고객 ID·메타데이터·리다이렉트 URL을 보내면 HTTP
200 OK와 기존 링크 응답을 반환합니다. 주문·링크·만료 시각을 유지하며,orders배열 순서는 비교에 영향을 주지 않습니다. - 같은 대표 주문번호로 생성 요청 내용을 변경하면 HTTP
400 Bad Request와DUPLICATED_ORDER_ID를 반환합니다. - 승인 API는 최초 유효한 호출에서만 승인을 실행합니다. 이미 승인을 시작했거나 처리한 요청을 다시 승인하면 HTTP
400과ALREADY_PROCESSED_PAYMENT를 반환합니다. - 구매자 취소·만료·승인 실패 후 다시 결제하려면 새 대표 주문번호로 링크를 생성합니다.
오류 응답
섹션 제목: “오류 응답”아래는 이 API의 주요 오류입니다. API 요청 오류와 승인 응답의 결제 결과를 구분하세요.
| HTTP 상태 | 코드 | 의미 |
|---|---|---|
400 |
INVALID_REQUEST |
본인 주문 누락, 본인·대표 주문번호 불일치, 중복 결제 대상, 잘못된 금액 또는 필수 주문번호·거래내역서 텍스트 누락·빈 문자열·공백만 있는 값 등 요청 내용이 유효하지 않습니다. |
400 |
ALREADY_PROCESSED_PAYMENT |
이미 승인을 시작했거나 처리한 요청입니다. |
404 |
NOT_FOUND_PAYMENT_SESSION |
결제 승인 시작 기한이 지났습니다. |
401 |
UNAUTHORIZED_KEY |
요청 가맹점의 시크릿 키로 인증하지 못했습니다. |
403 |
FORBIDDEN_REQUEST |
요청 가맹점이 결제 불가능하거나, 셀러가 요청 가맹점 소유가 아니거나 삭제됐거나 결제 가능한 가맹점과 연결되어 있지 않습니다. |
404 |
NOT_FOUND_PAYMENT |
대표 주문번호에 해당하는 요청이 없거나 접근 권한이 없습니다. |
400 |
DUPLICATED_ORDER_ID |
같은 대표 주문번호로 다른 내용의 생성 요청이 존재합니다. |
400 |
INVALID_REQUEST |
구매자 인증이 아직 완료되지 않았습니다. |
결제 대상의 이용 조건은 최초 링크 생성 시와 승인 시작 전에 검증합니다. 승인 시작 전 조건을 충족하지 못하면 해당 요청 오류를 반환합니다.
구매자 취소 시 failUrl에 PAY_PROCESS_CANCELED를 전달합니다. 승인 실패는 정규화된 결제 오류 코드를 error에 담습니다. 승인 후 취소 처리에 실패한 경우 ABORTED와 PAYMENT_RECONCILIATION_REQUIRED를 반환합니다.