연동하기
결제 링크 연동
결제 생성 → 링크 전달 → 결제 완료 콜백 → 조회로 확정. 서버 API 4개로 끝나는 표준 흐름입니다.
표준 결제 시퀀스
- 귀사 서버가 금액을 결정합니다(상품 카탈로그 기준). 클라이언트가 보낸 금액을 그대로 믿지 않습니다.
POST /api/requestPayment— 거래번호·금액·고객 휴대폰·CONFIRM_URL·서명을 보내고REDIRECT_URL을 받습니다. 주문을PENDING으로 저장합니다.- 고객에게 링크를 전달합니다 — 바로 이동(패턴 A) 또는 문자·알림톡 발송(패턴 B).
- 고객이 결제 페이지에서 카드·카카오페이로 결제합니다.
- 카리페이가
CONFIRM_URL로 결제 완료 콜백을 POST 합니다. - 귀사 서버는
POST /api/searchPayment로APPROVE_STATUS == "APPROVE_COMPLETE"와 승인금액을 확인한 뒤 주문을PAID로 바꾸고 서비스를 제공합니다.
1. 결제 생성
import { CariPay, newTransSeqno } from "@caripay/sdk";
const pay = CariPay.fromEnv();
const transSeqno = newTransSeqno("svc"); // svc + 시각14자리 + 난수
const { redirectUrl } = await pay.createPayment({
transSeqno,
amount: 128000, // 서버 카탈로그에서 결정
mobileNo: "01012345678",
payerName: "홍길동",
reason: "9월 이용료",
confirmUrl: `https://api.example.com/caripay/callback/${transSeqno}`,
returnUrl: "https://example.com/orders/complete", // 선택: 결제 후 고객을 돌려보낼 곳
tempValue: "order-8812", // 선택: 콜백·리턴에 그대로 돌아오는 값
});
await orders.create({ transSeqno, amount: 128000, status: "PENDING" });필드 전체는 결제 생성 API를 보세요.
2. 링크 전달 — 두 가지 UX 패턴
패턴 A · 바로 이동 (모바일 권장)
REDIRECT_URL로 사용자를 이동시킵니다. returnUrl을 지정하면 결제 완료 후 결제 페이지가 returnUrl?RESULT_CODE=0000&TRANS_SEQNO=...&APPROVAL_AMOUNT=...&TEMP_VALUE=...로 고객을 돌려보냅니다. 돌아온 화면에서 승인으로 처리하지 말고, 서버가 조회 API로 확정한 결과를 보여 주세요.
패턴 B · 링크 발송 + 완료 대기 (데스크톱·원격 청구 권장)
링크를 문자·알림톡으로 보내고 화면에 "휴대폰으로 결제 링크를 보냈어요" 대기 화면을 띄운 뒤 3초 간격으로 주문 상태를 폴링합니다. 데스크톱에서 카드번호를 입력하는 이탈 구간이 사라집니다.
// 프런트 대기 화면이 3초마다 부르는 조회 엔드포인트 (귀사 서버)
app.get("/orders/:transSeqno", async (req, res) => {
await settle(req.params.transSeqno); // 아래 4. 참고
res.json(await orders.get(req.params.transSeqno));
});3. 콜백 수신
app.post("/caripay/callback/:transSeqno", async (req, res) => {
res.sendStatus(200); // 먼저 200 — 처리는 비동기로
await settle(req.params.transSeqno).catch(console.error);
});콜백 본문 필드와 재전송 규칙은 콜백 구현에서 자세히 다룹니다. 요점은 세 가지입니다 — HTTPS, 본문을 믿지 말고 조회로 확정, 중복 도달을 전제로 멱등 처리.
4. 승인 확정 — 상태 전이는 한 곳에서
async function settle(transSeqno) {
const order = await orders.get(transSeqno);
if (!order || order.status !== "PENDING") return; // 멱등: 이미 처리됨
const p = await pay.confirmCallback(transSeqno); // = searchPayment
if (p.paid) {
if (p.amount !== order.amount) { await orders.update(transSeqno, { status: "AMOUNT_MISMATCH" }); return; }
await orders.update(transSeqno, { status: "PAID", approvalNumber: p.approvalNumber });
await grantService(order); // 이용권 해금·주문 확정 — 딱 1회
} else if (p.canceled) {
await orders.update(transSeqno, { status: "CANCELED" });
}
}콜백과 폴링 어느 쪽이 먼저 와도 같은 결과가 되도록 settle() 하나로 모읍니다.
권장 주문 상태 모델
PENDING(생성) ─▶ PAID(승인 확인) ─▶ CANCELED(취소)
└▶ DELETED(미결제 삭제) └▶ AMOUNT_MISMATCH(대사 불일치 · 수동 확인)5. 취소·삭제
- 승인된 결제의 환불:
cancelPayment({ transSeqno })— 승인금액 전액만 취소됩니다. - 미결제 청구 닫기:
deleteBill({ transSeqno, amount, mobileNo })— 상태가STORE_DELETE가 되고 링크로 결제할 수 없습니다.
자세한 규칙은 취소·삭제를 보세요.
SHOP 주문 (상품 결제)
교재·굿즈처럼 품목이 있는 주문은 orderType: "SHOP"과 items[]를 함께 보냅니다. 결제 페이지에 품목 표가 표시되고 합계·부가세는 품목 기준으로 계산됩니다.
await pay.createPayment({
amount: 77000, mobileNo: "01012345678", payerName: "홍길동", reason: "교재 구매",
confirmUrl: "https://api.example.com/caripay/callback/...",
orderType: "SHOP",
items: [
{ name: "교재 A", unitPrice: 11000, qty: 3, publisher: "미래출판사", imageUrl: "https://cdn.example.com/a.png" },
{ name: "교재 B", unitPrice: 22000, qty: 2 },
],
});amount는 품목 합계(Σ unitPrice × qty, 할인 반영)와 같아야 합니다.
문서에 없는 내용이나 오류는 bellight@goatheaven.com 또는 지원 문의로 알려주세요.