개발자/연동
휴대폰 충전
휴대폰 충전은 선불 휴대폰 번호에 요금을 바로 충전하는 상품입니다. 고객의 회선에 통화 요금이나 데이터가 충전되며, 따로 전달할 코드는 없습니다. 다른 주문과 마찬가지로 CardV 지갑에서 결제됩니다.
#전체 흐름
GET /recharge/countries 충전할 수 있는 국가
GET /recharge/operators?country=US 통신사, 충전 유형, 금액
POST /recharge/quote 귀사 가격과 300초 동안 유효한 quote_token
POST /recharge/orders 주문(지갑에서 결제)
GET /recharge/orders/{order_id} 상태 조회 또는 Webhook 대기- 모든 경로는
/api/v1로 시작합니다. 요청 헤더는 다른 요청과 똑같이 보냅니다. - 두
POST요청에는 서명이 필요합니다.POST/orders와 같은 방법으로 서명하되, 각 요청의 경로를 사용합니다. 예:/api/v1/recharge/quote - 충전 주문은 기프트카드 주문과 별개입니다. 조회할 때는
/recharge엔드포인트를 사용하세요. - 직접 충전만 제공합니다. PIN 상품(고객이 코드를 직접 입력하는 상품)은 제공하지 않습니다.
#국가
GET/api/v1/recharge/countries는 지금 충전할 수 있는 국가 목록을 반환합니다.
{
"count": 2,
"results": [
{"code": "MX", "name": "Mexico", "currency_codes": ["MXN"], "operator_count": 4, "offer_count": 37},
{"code": "US", "name": "United States", "currency_codes": ["USD"], "operator_count": 6, "offer_count": 52}
]
}code는 ISO 3166-1 alpha-2 국가 코드입니다. 이후 요청에서country로 보냅니다.currency_codes는 이 국가의 통신사들이 판매에 쓰는 현지 통화입니다.- 통신사가 추가되거나 이용할 수 없게 되면 목록이 바뀝니다. 몇 시간마다 다시 불러오세요.
#통신사
GET/api/v1/recharge/operators?country=US는 한 국가의 통신사 목록을 반환합니다.
search=att를 추가하면 통신사 이름으로 걸러 낼 수 있습니다.
{
"count": 1,
"results": [
{
"operator_key": "us-att",
"name": "AT&T",
"country": "US",
"country_name": "United States",
"logo_url": "https://b2b.cardv.net/api/v1/catalog-assets/3f5c...a1.png",
"subtypes": ["airtime", "data"],
"amount_model": "range",
"currency_codes": ["USD"],
"amounts": [
{"min": "5.0000", "max": "100.0000", "currency": "USD", "subtype": "airtime", "label": "5.0000-100.0000 USD"},
{"min": "15.0000", "max": "15.0000", "currency": "USD", "subtype": "data", "label": "15.0000 USD"}
],
"offer_count": 3
}
]
}| 필드 | 설명 |
|---|---|
operator_key | 가격 조회와 주문에 보내는 ID입니다. 예: us-att. 문자열로 저장하세요. |
subtypes | 구매할 수 있는 유형입니다. airtime(통화 요금), data, bundle(통화와 데이터) 중 하나입니다. |
amount_model | 모든 금액이 정해진 값이면 fixed, 범위 금액이 하나라도 있으면 range입니다. |
amounts[] | 선택할 수 있는 금액입니다. min과 max가 같으면 고정 금액이고, 다르면 그 사이의 어떤 금액이든 됩니다. |
amounts[].currency | 해당 금액의 현지 통화입니다. local_currency로 보냅니다. |
logo_url | CardV가 호스팅하는 이미지이거나 ""입니다. |
- 금액은 현지 금액입니다. 즉 휴대폰 회선에 충전되는 금액이며, 현지 통화로 표시됩니다.
country가 없는 국가이거나 형식이 잘못되면 HTTP 400이 반환됩니다.
#가격 조회
POST/api/v1/recharge/quote는 충전 1건에 대한 귀사 가격을 반환합니다. 이 요청에는 서명이 필요합니다.
{
"country": "US",
"operator_key": "us-att",
"amount": "10.00",
"local_currency": "USD",
"subtype": "airtime"
}| 필드 | 필수 여부 | 의미 |
|---|---|---|
country | 필수 | 국가 목록의 국가 코드입니다. |
operator_key | 필수 | 통신사 목록에서 가져온 값입니다. |
amount | 필수 | 현지 금액(문자열)입니다. 고정 금액이면 목록에 있는 값 중 하나, 범위 금액이면 min과 max 사이 값입니다. |
local_currency | 권장 | amount의 ISO 4217 통화 코드로, amounts[].currency에서 가져옵니다. 통신사가 통화를 둘 이상 제공할 때는 꼭 보내세요. |
subtype | 선택 | airtime(기본값), data, bundle 중 하나입니다. |
응답:
{
"country": "US",
"country_name": "United States",
"operator": {"operator_key": "us-att", "name": "AT&T", "logo_url": ""},
"subtype": "airtime",
"local_amount": "10.0000",
"local_currency": "USD",
"merchant_price": "9.6200",
"merchant_currency": "USD",
"expires_at": "2026-09-30T08:20:30.123456+00:00",
"quote_token": "eyJ2ZXJzaW9uIjox...:1uXyZa:8c1f..."
}merchant_price는 지갑에서 결제될 금액이며, 통화는merchant_currency입니다.quote_token은 이 가격을expires_at까지 300초 동안 고정합니다. 주문할 때 그대로 보내세요.- 이 토큰은 귀사 계정과 이번 국가, 통신사, 유형, 금액에 묶여 있습니다.
- 주문하기 전에
local_currency가 예상한 통화인지 확인하세요. - 가격 조회는 금액을 미리 잡아 두지 않습니다. 언제든 가격을 다시 조회할 수 있습니다.
#충전 주문하기
POST/api/v1/recharge/orders는 휴대폰을 충전하고 지갑에서 결제합니다. 이 요청에는 서명이 필요합니다.
{
"external_order_id": "SHOP-RC-20260930-0001",
"country": "US",
"operator_key": "us-att",
"amount": "10.00",
"local_currency": "USD",
"subtype": "airtime",
"account": "12125550100",
"quote_token": "eyJ2ZXJzaW9uIjox...:1uXyZa:8c1f..."
}| 필드 | 필수 여부 | 의미 |
|---|---|---|
external_order_id | 필수 | 자체 주문번호입니다. 기프트카드 주문을 포함한 모든 주문에서 중복될 수 없습니다. |
country, operator_key, amount, local_currency, subtype | 필수 | 가격 조회 때 보낸 값과 같은 값입니다. |
account | 필수 | 충전할 휴대폰 번호입니다. 국가 번호를 포함한 숫자만 쓰고, +나 공백은 넣지 않습니다. |
quote_token | 필수 | 가격 조회에서 받은 값으로, 만료되기 전에 보냅니다. |
휴대폰 번호 예: 12125550100(미국), 525512345678(멕시코).
번호가 선택한 통신사의 번호인지 확인하세요. 잘못된 번호로 보낸 충전은 되돌릴 수 없습니다.
CardV는 가격 조회 결과를 확인하고, 지갑에서 merchant_price를 바로 결제한 뒤, 백그라운드에서 충전을 시작합니다.
새 주문이면 HTTP 201이 반환됩니다.
{
"idempotent_replay": false,
"order": {
"order_id": "O-00005678",
"external_order_id": "SHOP-RC-20260930-0001",
"status": "accepted",
"status_title": "Recharge accepted",
"poll_after_seconds": 12,
"account": "12***00",
"local_amount": "10.0000",
"local_currency": "USD",
"merchant_price": "9.6200",
"merchant_currency": "USD",
"...": "more fields"
}
}order.order_id를 저장하세요.- 휴대폰 번호는 마스킹되어 반환되며, 전체 번호는 절대 반환되지 않습니다.
#안전한 재시도
external_order_id가 있으면 같은 번호가 두 번 충전되는 일을 막을 수 있습니다.
| 보낸 요청 | 결과 |
|---|---|
| 새 주문번호 | HTTP 201. 새 주문이 생성되고 지갑에서 결제됩니다. |
| 같은 주문번호, 같은 충전 내용 | HTTP 200과 "idempotent_replay": true. 기존 주문이 반환되며 결제되지 않습니다. |
| 같은 주문번호, 다른 충전 내용 | external_order_id에 대한 HTTP 400. 아무 일도 일어나지 않습니다. |
"같은 충전 내용"이란 국가, 통신사, 유형, 금액, 휴대폰 번호가 모두 같다는 뜻입니다.
중복 주문 여부는 가격 조회 결과를 확인하기 전에 판단하므로, quote_token이 만료되었더라도 처음 주문이 반환됩니다.
- 타임아웃, 5xx, 연결 끊김이 발생하면 같은 주문번호로 같은 본문을 다시 보내세요. 타임스탬프와 nonce는 새로 만들어 다시 서명합니다.
- 응답을 받지 못했다고 새 주문번호를 만들면 안 됩니다. 같은 휴대폰이 두 번 충전될 수 있습니다.
#충전 주문 조회
GET/api/v1/recharge/orders/{order_id}는 주문 1건을 반환합니다.
CardV 주문 ID(O-00005678)를 사용할 수 있습니다.
{
"order_id": "O-00005678",
"external_order_id": "SHOP-RC-20260930-0001",
"status": "processing",
"order_status": "processing",
"status_title": "Recharge processing",
"status_message": "The recharge request is being processed. Delivery is not confirmed yet.",
"next_step": "Keep this order open and wait for confirmation before placing another recharge.",
"poll_after_seconds": 12,
"country": "US",
"operator": {"operator_key": "us-att", "name": "AT&T", "logo_url": ""},
"subtype": "airtime",
"account": "12***00",
"local_amount": "10.0000",
"local_currency": "USD",
"merchant_price": "9.6200",
"merchant_currency": "USD",
"attempts": [{"attempt_no": 1, "status": "processing", "error_message": "", "submitted_at": "2026-09-30T08:16:02.511201Z", "completed_at": null}],
"created_at": "2026-09-30T08:16:01.004211Z",
"updated_at": "2026-09-30T08:16:02.611978Z",
"...": "more fields"
}- 존재하지 않는 주문 ID이거나 다른 계정의 주문이면 HTTP 404가 반환됩니다.
status_title,status_message,next_step은 영어 문구이며, 귀사 직원에게 그대로 보여 줄 수 있습니다.poll_after_seconds는 다음 조회까지 기다릴 시간입니다.0이면 주문이 완료된 것입니다.
#충전 주문 목록
GET/api/v1/recharge/orders는 귀사의 충전 주문을 최신순으로 반환합니다.
GET /api/v1/recharge/orders?status=processing&limit=50&offset=0{"count": 3, "limit": 50, "results": ["..."]}- 필터:
status,search(CardV 주문 ID, 자체 주문번호 또는 통신사 이름). limit기본값은 20, 최대값은 100입니다. 100보다 크면 100으로 처리됩니다.limit이나offset이 음수이거나 숫자가 아니면 HTTP 400이 반환됩니다.
#상태
accepted ──► processing ──► succeeded
│
├──► manual_review ──► succeeded 또는 refunded
│
└──► failed ──► refunded (지갑으로 환불됨)| 상태 | 완료 여부 | 할 일 |
|---|---|---|
accepted | 아니요 | 기다립니다. 지갑 결제는 끝났고 충전은 아직 시작되지 않았습니다. |
processing | 아니요 | 기다립니다. 몇 분 걸릴 수 있습니다. 같은 주문을 다시 넣지 마세요. |
manual_review | 아니요 | CardV가 통신사와 결과를 확인하고 있습니다. 기다립니다. |
succeeded | 예 | 휴대폰이 충전되었습니다. 고객에게 알려 주세요. |
failed | 아직 아님 | 충전이 이루어지지 않았습니다. refunded가 될 때까지 기다립니다. |
refunded | 예 | 대금이 지갑으로 돌아왔습니다. 새 주문을 넣어도 됩니다. |
poll_after_seconds만큼 기다린 뒤 조회하고, 이후에는 간격을 늘려 30초, 60초, 그다음부터는 5분마다 조회하세요. 요청 한도를 넘지 않도록 주의하세요.- 모르는 상태는 "아직 완료되지 않음"으로 처리하세요.
- 주문이 완료되지 않은 동안에는 같은 번호에 새 주문번호로 다른 충전을 보내지 마세요. 첫 주문도 성공하면 같은 휴대폰이 두 번 충전됩니다.
#Webhook과 환불
충전 주문도 다른 주문과 같은 Webhook을 보냅니다.
order.succeeded, order.failed, order.refunded입니다.
Webhook에는 CardV 주문 ID와 자체 주문번호가 들어 있고, items 목록은 비어 있습니다.
Webhook을 받은 뒤 GET/api/v1/recharge/orders/{order_id}로 주문을 조회하세요.
환불은 자동으로 처리됩니다. 통신사가 실패를 확인하면 CardV가
merchant_price 전액을 지갑으로 돌려주고, 주문은 refunded가 됩니다.
환불 내역은 포털의 거래 내역 페이지에서 확인할 수 있습니다.
성공한 충전은 환불하거나 취소할 수 없습니다.
#오류
오류 형식은 공통 규칙을 따릅니다. 거절된 가격 조회나 주문은 HTTP 400을 반환하며, 결제되지 않습니다.
| 키 | 발생 위치 | 해결 방법 |
|---|---|---|
detail | 가격 조회, 주문 | 국가, 통신사, 유형 또는 금액을 이용할 수 없습니다. 통신사 목록을 확인하세요. |
amount | 가격 조회, 주문 | 숫자가 아니거나, 0이거나, 범위를 벗어났습니다. |
local_currency | 가격 조회, 주문 | 3자리 ISO 4217 코드가 아닙니다. |
account | 주문 | 휴대폰 번호가 없습니다. |
quote_token | 주문 | 없거나, 만료되었거나, 변경되었거나, 내용과 맞지 않습니다. code를 확인한 뒤 가격을 다시 조회하세요. |
balance | 주문 | 포털에서 지갑을 충전하세요. |
risk | 주문 | 주문 한도에 걸렸습니다. CardV에 문의하세요. |
external_order_id | 주문 | 다른 주문에 이미 쓰인 번호입니다. 안전한 재시도를 참고하세요. |
wallet | 주문 | 활성화된 지갑이 없습니다. CardV에 문의하세요. |
quote_token 오류에는 code가 함께 반환됩니다.
{"code": "quote_expired", "quote_token": "This quote expired. Refresh the price and confirm again."}code | 의미 |
|---|---|
quote_required | quote_token을 보내지 않았습니다. |
quote_expired | 300초가 지났습니다. 가격을 다시 조회하세요. |
quote_invalid | 변경되었거나 다른 충전 내용의 토큰입니다. 가격을 다시 조회하세요. |
price_changed | 가격 조회 후 귀사 가격이 바뀌었습니다. 다시 조회하고 새 가격을 확인하세요. |
HTTP 403은 인증 정보, 서명, IP 또는 승인 문제입니다. 인증을 참고하세요.
연동 관련 문의는 Merchant ID와 주문 ID 또는 요청 ID를 포함해 [email protected]로 보내 주세요.