API 레퍼런스: 청구서와 환불
청구서 메서드는 createInvoice, getInvoices, deleteInvoice,
refundInvoice예요. 공통 규칙(인증, 응답 형식, 금액, spend_id)은
Merchant API 레퍼런스에 있고, 흐름을 따라가는
설명은 청구서로 결제 받기에 있어요.
createInvoice
POST /pay/api/createInvoice — invoices 권한, 분당 60회.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
currency_type | string | 아니요 | crypto(기본값) 또는 fiat |
asset | string | crypto 모드 | 청구할 자산, 예: USDT. fiat과 함께 쓸 수 없어요 |
fiat | string | fiat 모드 | 가격을 매길 법정화폐(getCurrencies에서 is_fiat인 행) |
accepted_assets | string / array | 아니요 | fiat 모드 전용. 내는 사람이 결제에 쓸 수 있는 자산이에요 — 쉼표로 이은 문자열("USDT,GRAM")이나 JSON 배열. 비워 두면 지원하는 모든 자산 |
amount | string | open_amount를 안 쓰면 필수 | 양수 소수점 문자열. crypto 모드에서는 자산 단위, fiat 모드에서는 법정화폐 단위(소수점 2자리까지) |
open_amount | boolean | 아니요 | 확장 기능, crypto 모드 전용. 금액을 정해 두지 않고 내는 사람이 결제할 때 넣어요(후원, 팁). amount와 함께 쓸 수 없어요 |
description | string | 아니요 | 1024자까지, 내는 사람에게 보여요 |
hidden_message | string | 아니요 | 2048자까지, 결제한 뒤에만 보여요 |
payload | string | 아니요 | 내가 넣는 데이터 4096자까지. 청구서와 webhook에 그대로 돌아와요 |
allow_comments | boolean | 아니요 | 내는 사람이 코멘트를 남길 수 있게 해요(기본값 true) |
allow_anonymous | boolean | 아니요 | 내는 사람이 자기를 숨길 수 있게 해요(기본값 true) |
paid_btn_name | string | 아니요 | 결제 후 버튼: viewItem, openChannel, openBot, callback |
paid_btn_url | string | 아니요 | 버튼이 열 http(s) 주소. paid_btn_name을 쓰면 꼭 넣어야 해요 |
swap_to | string | 아니요 | 받은 결제를 이 자산으로 자동 교환해요. 가능할 때만 바꿔 주는 방식이라, 결제 시점에 교환이 안 되면 교환 없이 결제만 끝나요 |
expires_in | integer | 아니요 | 청구서가 만료되기까지의 초, 2678400(31일)까지. 안 넣거나 0이면 만료 없음 |
rate_lock_seconds | integer | 아니요 | 확장 기능, fiat 모드 전용. 만들 때의 환산 시세를 이 시간 동안 묶어 둬요(아래 참고) |
결과는 — 그리고 invoice_paid webhook의 페이로드도 — 청구서 객체예요.
주요 필드는 이래요.
- 식별자와 상태:
invoice_id,hash(pay_url안에 들어가는 공개 id),status(active/paid/expired),pay_url— 내는 사람에게 보내는t.me링크예요(bot_invoice_url,mini_app_invoice_url,web_app_invoice_url은 같은 값의 별칭이에요). - 금액:
amount(액면 금액 — fiat 청구서면 법정화폐 단위, 아니면 코인 단위예요. 열린 금액 청구서가 아직 결제 전이면null),amount_minor, 그리고 결제된 fiat 청구서에는paid_asset/paid_amount/paid_fiat_rate— 실제로 청구한 코인과 쓰인 시세예요. - 수수료:
fee_asset/fee_amount. 결제할 때 찍히고, 장부에 쓸 값은 이거예요(fee와usd_rate는 이제 쓰지 않는 Crypto Bot 호환 별칭이에요). 수수료와 한도를 보세요. - 결제한 사람:
paid_by_user_id(익명을 골랐으면null),paid_anonymously,comment. - 환불(확장 기능): 누적으로 쌓이는
refunded_amount/refunded_minor, 그리고 전액 환불되면 찍히는refunded_at. - 시세 고정(확장 기능):
rate_lock_until과rate_lock_rates— 자산별로 찍어 둔 시세예요. 고정을 요청하지 않았으면null이에요. - 만들 때 넣은 값:
description,hidden_message,payload,paid_btn_name/paid_btn_url,expiration_date(expires_at은 별칭), 교환 관련 필드(swap_to,is_swapped,swapped_to,swapped_rate,swapped_output, …).
오류: 400 invalid_currency(asset과 fiat을 섞어 쓴 경우),
400 invalid_amount, 404 unknown_asset, 400 unsupported_fiat,
400 paid_btn_url_required. 시세 고정을 요청했다면
400 rate_lock_fiat_only, 409 ratelock_disabled,
409 rate_unavailable(받기로 한 자산의 최신 시세가 없을 때 — 다시
시도하세요)도 있어요.
청구서는 이렇게 결제돼요
내는 사람은 미니 앱에서 지갑 잔액으로 결제해요. 바로 끝나고 네트워크
수수료도 없어요. 외부 지갑에서 입금해 결제할 수도 있어요. 앱이 입금
주소를 보여 주고, 그 사람 지갑으로 입금이 들어오면 청구서가 알아서 결제돼요.
어느 쪽이든 내 쪽에서 보이는 건 똑같아요. 평범한 paid 청구서와
invoice_paid webhook뿐이고, 따로 처리할 파라미터나 필드는 없어요.
fiat 청구서는 결제할 때 코인 금액을 계산하고, 받는 쪽에 유리하게 올림해요. 그래서 법정화폐 액면가보다 적게 받는 일은 없어요. 최신 시세를 가져오지 못하면 오래된 시세로 처리하는 대신, 내는 쪽에서 결제가 실패해요.
fiat 청구서의 시세 고정하기
rate_lock_seconds를 넘기면 받기로 한 모든 자산의 현재 시세를 만들 때
찍어 둬요. 고정이 살아 있는 동안 내는 사람은 찍힌 금액 그대로 보고, 결제도 그
시세로 환산돼요. 그 시간 동안의 시세 변동은 내가 떠안아요. 서버는 이 구간을
60초부터 플랫폼 최대치(지금은 15분)까지로 맞춰요.
고정이 풀려도 청구서는 그대로 결제할 수 있고, 조용히 결제 시점 환산으로
돌아가요. 견적과 함께 청구서도 끝나길 원한다면 expires_in에 같은 값을 넣어
주세요.
getInvoices
GET /pay/api/getInvoices — read 권한. 필터: asset, fiat,
invoice_ids(쉼표로 구분), status(active / paid / expired —
expired는 확장 기능이고, active에는 기한이 지난 청구서가 빠져요), 그리고
offset / count. {"items": [invoice, …]}를 최신순으로 돌려줘요.
deleteInvoice
POST /pay/api/deleteInvoice — invoices 권한. 파라미터는 invoice_id
하나예요. 아직 결제되지 않은 청구서를 취소하고 true를 돌려줘요. 오류:
404 invoice_not_found, 409 invoice_already_paid — 이미 결제된 청구서는 돈이
움직인 뒤라 지울 수 없어요.
refundInvoice
POST /pay/api/refundInvoice — refunds 권한, 분당 30회. Crypto Bot에는 없는
확장 기능이에요. 결제된 청구서의 액면 금액을, 또는 그중 일부를 앱 잔액에서
결제한 사람에게 돌려줘요. 익명으로 결제한 사람에게도 보낼 수 있고, 누구인지는
드러나지 않아요.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
invoice_id | integer | 예 | 결제된 청구서 |
amount | string | 아니요 | 돌려줄 금액, 청구서의 자산 단위예요. 비워 두면 아직 환불하지 않은 나머지 전부. 부분 환불은 액면 금액까지 쌓여요 |
spend_id | string | 아니요 | 멱등키 — 응답이 늦어 다시 보내도 두 번 환불되지 않게 하나 넣어 주세요 |
결과는 누적 refunded_amount / refunded_minor가 반영된 청구서 객체예요.
전액 환불되면 refunded_at이 찍혀요. 상태는 paid 그대로예요. 서비스
수수료는 돌려주지 않아요. 환불할 때마다 원할 때만 켜는 refund_completed
webhook이 발생해요.
오류: 404 invoice_not_found, 409 invoice_not_paid,
409 already_refunded(더 환불할 게 없을 때), 409 amount_too_big(남은
금액보다 클 때), 409 insufficient_funds, 400 invalid_amount, 그리고
spend_id 짝인 409 idempotency_conflict / 409 idempotency_in_progress.
이 문서가 도움이 됐나요?
의견 고마워요.