연동하기
청구서 API 연동
청구서를 만들고 카카오 알림톡 또는 문자로 보내기. 발송 수단 선택, 멱등 재시도, 수납 확인까지.
청구서 API는 결제 링크 생성 + 알림톡/문자 발송을 한 번에 처리합니다. 온라인 주문·예약금·방문 서비스·외상처럼 아직 납부하지 않은 금액을 안내할 때 씁니다. 이미 결제된 거래의 영수증 용도로는 쓰지 않습니다.
준비
- 가맹점 계정 · 사업자 등록 승인 · 발송 포인트 — 연동 정보 발급
- 접근 토큰 — SDK
login()또는POST /app/v1/auth/login - 청구 API 주소
https://api.dev.caripay.co.kr— 실제 발송·과금 환경입니다. 연동 환경
발송 수단 고르기
sendChannel | 동작 | 언제 |
|---|---|---|
ALIMTALK (기본) | 카카오 알림톡(카리 채널 승인 템플릿). 접수 실패 시 오류 | 대부분의 고객 |
SMS | 문자(LMS)만. 알림톡을 시도하지 않음 | 카카오톡을 쓰지 않는 고객, 알림톡 거부 고객 |
ALIMTALK_THEN_SMS | 알림톡을 먼저 보내고, 카카오 미가입·차단·접수 거절이면 같은 요청에서 문자로 대체 | 도달률이 중요할 때 |
- 알림톡·문자 모두 같은 링크(
https://caripay.co.kr/pay/{거래번호})를 담습니다. 고객은 청구 내역 화면에서 "결제하기"를 눌러 결제 페이지로 갑니다. - 문자로 대체 발송된 이력은 발송 수단이
SMS로 기록됩니다. - 발송 포인트는 수단과 관계없이 청구서 1건당 1회 차감됩니다(정책이 바뀌면 릴리즈 노트로 안내).
청구서 보내기
import { CariPayBilling } from "@caripay/sdk";
const billing = await CariPayBilling.login({
email: process.env.CARIPAY_BILLING_EMAIL,
password: process.env.CARIPAY_BILLING_PASSWORD,
});
const result = await billing.sendInvoice({
requestId: "order_20260921_0001", // 주문별 고유. 재시도 때 같은 값을 다시 씀
amount: 128000,
recipient: { name: "홍길동", phone: "01012345678" },
reason: "9월 이용료", // 60자 이내
message: "9월 30일까지 납부해 주세요.", // 선택, 200자 이내
channel: "ALIMTALK_THEN_SMS", // ALIMTALK | SMS | ALIMTALK_THEN_SMS
webhookSecret: process.env.CARIPAY_WEBHOOK_SECRET, // 선택: 웹훅 서명 비밀(ASCII 16~128자)
webhookUrl: "https://api.example.com/caripay/invoice-hook", // 선택: 결제 완료·취소 알림
});
// result = { accepted: true, requestId: "order_20260921_0001" }응답 {"result_code":0,"result_msg":"성공","result_data":null}은 접수입니다. 발송은 비동기로 진행되며 수 초 안에 나갑니다.
청구 항목 — 상품명·금액
상품이 여러 개이거나 알림에 상품명을 보여 주고 싶으면 items를 넣습니다. 항목 금액의 합이 청구 금액이 됩니다(SDK는 amount를 생략하면 합계로 채웁니다).
await billing.sendInvoice({
requestId: "order_20260922_0001",
recipient: { name: "홍길동", phone: "01012345678" },
reason: "9월 수강료",
message: "9월 30일까지 납부해 주세요.",
items: [{ name: "수학 특강", price: 120000 }, { name: "교재", price: 15000 }], // amount = 135,000
});고객이 받는 알림톡(문자도 같은 내용):
청구서 발송
홍길동님 안녕하세요.
카리수학에서 납부 안내드립니다.
[청구내역]
▷청구서 발급처: 카리수학학원
▷청구 사유: 9월 수강료
▷청구금액: 135,000원
[청구 항목]
- 수학 특강 120,000원
- 교재 15,000원
9월 30일까지 납부해 주세요.
아래 버튼을 눌러 청구 내역을 확인하고, 바로 납부해 주세요.| 항목 | 규칙 |
|---|---|
상품명 name | 1~20자 |
금액 price | 항목당 100원 이상. 합계가 amount와 같아야 합니다 |
| 알림 표시 | 5개까지 줄마다 표시, 나머지는 외 N건. 결제 화면에는 전부 나옵니다 |
| 가맹점명·발급처 | 요청으로 바꿀 수 없습니다. 가맹점 계정의 등록 정보(가입 때 이름, 사업자 상호)가 들어갑니다 |
멱등 재시도 — requestId
- 같은 가맹점에서 같은
requestId로 다시 보내면 새 청구서를 만들지 않고, 포인트를 다시 차감하지 않으며, 알림도 다시 보내지 않습니다. - 같은
requestId에 다른 금액·사유·수신자를 보내면 거절됩니다. - 통신 오류로 응답을 못 받았을 때 새 ID로 다시 보내지 마세요. 같은 ID로 재시도하면 중복 청구가 생기지 않습니다.
- 규칙: 영숫자·
_·-8~64자, 수신자 1명일 때만 사용.
결제 완료 웹훅
청구서에 webhookUrl을 넣으면 고객이 결제했을 때(bill.paid)와 결제가 취소됐을 때(bill.canceled) 그 주소로 POST(JSON) 합니다.
{
"event": "bill.paid",
"billId": "550e8400-e29b-41d4-a716-446655440000",
"requestId": "order_20260921_0001",
"transSeqNo": "20260921120000AbCdEf",
"status": "DONE",
"amount": 128000,
"reason": "9월 이용료",
"paidAt": "2026-09-21T12:01:35",
"canceledAt": null,
"occurredAt": "2026-09-21T12:01:36"
}| 항목 | 규칙 |
|---|---|
| 헤더 | Content-Type: application/json, X-CariPay-Event: bill.paid 또는 bill.canceled, X-CariPay-Delivery: {전송 ID} |
| 성공 판정 | 귀사 응답 HTTP 2xx |
| 재시도 | 2xx가 아니거나 연결 실패면 1분 → 5분 → 30분 → 2시간 뒤 다시 보내고, 5번째 실패에서 포기합니다 |
| 주소 | https://만. 500자 이하 |
| 서명 | 선택. 청구서에 webhookSecret을 넣으면 X-CariPay-Signature: t=<unix초>,v1=<hex>가 붙습니다. v1 = HMAC-SHA256(secret, "<t>.<본문 원문>"). 아래처럼 검증하고, 검증 후에도 확정은 getInvoice로 하세요 |
| 중복 | 재시도 때문에 같은 이벤트가 두 번 올 수 있습니다. 이미 처리한 billId+event면 200만 응답하세요 |
import { verifyWebhookSignature } from "@caripay/sdk";
app.post("/caripay/invoice-hook", express.raw({ type: "application/json" }), async (req, res) => {
const ok = verifyWebhookSignature({ secret: process.env.CARIPAY_WEBHOOK_SECRET, signature: req.get("X-CariPay-Signature"), body: req.body });
if (!ok) return res.sendStatus(400); // 서명 불일치·5분 초과
res.sendStatus(200); // 먼저 200
const { requestId, billId, event } = JSON.parse(req.body);
const order = await orders.findByRequestId(requestId);
if (!order || order.status !== "PENDING") return; // 멱등
const detail = await billing.getInvoice(billId); // 조회로 확정
if (event === "bill.paid" && detail.bill.status === "DONE") await orders.markPaid(order);
});수납 확인
웹훅을 쓰지 않거나 놓쳤을 때는 조회로 확인합니다.
const list = await billing.listInvoices({ month: "2026-09", page: 1, size: 50 });
const detail = await billing.getInvoice(list.bills[0].uniqueId);
// detail.bill.status: "PENDING" | "DONE" | "CANCELED"
// detail.sendHistories[0]: { sendType, sendResult, sendChannel, sentAt, errorMessage }| 확인할 것 | 위치 |
|---|---|
| 발송이 나갔는가 | sendHistories[].sendResult === "SUCCESS", sendChannel |
| 고객이 결제했는가 | bill.status === "DONE", payment 객체(승인번호·카드사·승인시각) |
| 취소됐는가 | bill.status === "CANCELED" |
권장 폴링: 결제 완료를 빠르게 알아야 하면 1분 간격, 그렇지 않으면 청구 마감일 기준 하루 1~2회. 콘솔의 발송·수납 내역에서도 같은 정보를 봅니다.
재발송·삭제·취소
| 작업 | API | 비고 |
|---|---|---|
| 재발송 | POST /app/v1/sales/bill/resend { "billIds": ["{uniqueId}"] } | 같은 청구서·같은 링크·같은 발송 수단. 포인트 차감 |
| 미납 청구서 삭제 | DELETE /app/v1/sales/bill/{id} | 게이트웨이 청구도 함께 닫힙니다(STORE_DELETE) |
| 결제 완료 건 취소(환불) | PUT /app/v1/sales/bill/{id} | 전액 취소. 카드사 승인취소 |
필드 전체는 청구서 API 레퍼런스를 보세요.
예약·정기 발송
콘솔에서 지원하는 예약(1회)·정기(매월 n일 HH:mm) 발송은 API로도 등록할 수 있습니다 — POST /app/v1/sales/send/bill/schedule. sendRequest에 위와 같은 청구 내용과 sendChannel을 넣습니다. 상세 규격은 키 발급 시 안내합니다.
고객이 보는 화면
- 알림톡 "[CARI PAY] 청구서" — 발급처·청구 사유·금액·청구 항목(있으면)·안내 메시지 + 청구서 확인 버튼 (문자는 같은 내용 + 링크)
caripay.co.kr/pay/{거래번호}— 가맹점명, 금액, 청구일, 품목, 결제 상태- 결제하기 → 카리페이 결제 페이지(신용카드·카카오페이)
- 결제 완료 → 청구서 상태
DONE, 콘솔 수납 내역 반영
문서에 없는 내용이나 오류는 bellight@goatheaven.com 또는 지원 문의로 알려주세요.