쇼핑몰에 카드 결제를 붙이려는데, 신한·국민·삼성·현대 같은 카드사마다 따로 계약하고 따로 연동해야 한다면 몇 달이 걸린다.
고객은 카드 말고도 카카오페이·네이버페이·토스페이, 계좌이체, 가상계좌, 휴대폰 결제까지 원한다.
AI에게 “결제 붙여줘”라고만 했더니, 어느 회사와 어떤 방식으로 연동할지 몰라 엉뚱한 예제 코드가 나왔다.
결제 연동 예제에 있던 비밀 키를 프런트엔드 코드에 그대로 넣었다.
용어
merchant(가맹점, 상점) — 카드 결제를 받는 쪽, 즉 내 쇼핑몰. 카드사와 “우리 가게에서 이 카드를 받겠다”는 계약을 맺은 곳.
카드사(card issuer, 카드 발급사) — 고객에게 카드를 내준 회사. 결제를 허락할지(한도·도난 여부) 판단하고, 나중에 고객에게 청구한다.
PG(Payment Gateway, 결제대행사) — 쇼핑몰과 카드사·은행·간편결제사 사이에서 결제를 대신 처리해 주는 회사(진단 Q2). 토스페이먼츠, KG이니시스, 나이스페이 등.
왜 거치나 — PG가 여러 카드사와 미리 계약된 대표 가맹점 역할을 하고, 내 쇼핑몰은 PG 아래의 하위 가맹점으로 들어간다. 카드사마다 계약·심사·연동할 필요 없이 PG 한 곳과 계약 + 연동 한 번으로 카드·간편결제·계좌이체·가상계좌를 모두 받는다.
대가 — 결제 금액의 일정 비율을 PG 수수료로 떼고, 돈을 바로 주지 않고 며칠 뒤 모아서 정산해 준다(다음 레슨).
비유: 택배 대행사. 쇼핑몰이 CJ·한진·롯데와 각각 계약하는 대신 대행사 한 곳에 맡기면 알아서 보내 주는 것과 같다. 대신 수수료를 낸다.
payment method(결제수단) — 카드, 간편결제(카카오페이·네이버페이·토스페이처럼 카드·계좌를 미리 등록해 두고 비밀번호·생체 인증으로 결제), 계좌이체, 가상계좌(주문마다 발급되는 임시 입금 계좌, 무통장입금), 휴대폰 결제. PG 하나로 대부분을 묶어서 쓴다.
결제창(결제위젯) — PG가 제공하는 결제 화면. 카드번호 입력·카드사 인증이 PG 화면 안에서 이뤄져서, 내 서버는 카드번호를 만지지 않는다. 카드번호를 직접 받아 저장하면 보안 의무가 크게 늘어나므로 결제창을 쓰는 게 기본.
MID(Merchant ID, 상점 아이디) — PG가 내 가맹점에 붙여 주는 식별 번호. PG 관리자 화면·정산 내역·문의할 때 쓰인다.
API 키 두 종류 — 대부분의 PG가 나눠서 준다.
client key(클라이언트 키) — 브라우저에서 결제창을 띄울 때 쓰는 공개해도 되는 키.
secret key(시크릿 키) — 서버에서 결제를 확정·취소할 때 쓰는 비밀 키. 프런트엔드에 넣으면 누구나 내 가게 이름으로 결제를 취소할 수 있다(상황 4, 웹·앱 보안 용어의 비밀 관리와 같은 원칙). 반드시 서버 환경 변수에.
test key / live key — 실제 돈이 오가지 않는 테스트 키로 먼저 개발하고, 가맹점 심사 통과 후 라이브 키로 바꾼다.
지시 예문
“토스페이먼츠 결제위젯으로 카드·간편결제를 붙여줘. 결제창은 PG 화면을 쓰고, 우리 서버는 카드번호를 받거나 저장하지 않게 해줘.”
“PG 클라이언트 키는 프런트엔드에, 시크릿 키는 서버 환경 변수에만 두고, 시크릿 키가 클라이언트 번들에 섞이지 않았는지 확인해줘.”
“지금은 테스트 키로 개발하고, 라이브 키로 바꿀 때 바꿔야 할 설정 목록을 정리해줘.”
돈이 움직이는 순서: 인증 · 승인 · 매입 · 정산 · 매입 전 취소
본인 확인·승인·청구·판매자 입금은 서로 다른 단계입니다.
상황
고객이 결제 버튼을 눌렀는데, 돈이 판매자 통장에 들어오기까지 무슨 일이 일어나는지 모른다(진단 Q1).
고객 카드 앱에는 “승인” 문자가 왔는데, 내 통장에는 며칠째 돈이 안 들어온다.
같은 “결제 취소”인데 어떤 건 고객 카드에서 바로 사라지고, 어떤 건 환불까지 며칠이 걸린다.
결제 연동 문서에 “결제 요청 → 인증 → 승인”이라고 적혀 있어서 인증과 승인을 같은 말로 알았다.
용어
전체 흐름: 요청 → 인증 → 승인 → 매입 → 정산. 앞의 셋은 결제 버튼을 누른 그 순간 몇 초 안에, 뒤의 둘은 며칠에 걸쳐 일어난다.
결제 요청 — 쇼핑몰이 PG에 “이 주문, 이 금액으로 결제창 띄워 줘”라고 보내는 것.
authentication(인증) — 결제창에서 고객이 본인임을 확인하는 단계(카드 앱·비밀번호·생체 인증). 아직 돈은 확정되지 않았다. PG가 쇼핑몰로 “인증 성공” 결과를 돌려준다.
authorization(승인) — 쇼핑몰 서버가 PG에 승인 요청을 보내고, 카드사가 “이 카드로 이 금액 결제 가능”(한도·정지 여부)을 확정하는 단계. 이때 고객 카드에 “승인” 문자가 온다(상황 2). 인증과 승인은 다른 단계라서, 인증만 되고 승인을 안 보내면 결제가 완료되지 않는다(상황 4).
매입(capture, 매출 확정) — PG가 승인된 거래 데이터를 모아 카드사에 넘겨 실제 대금 청구를 시작하는 단계. 보통 결제 다음 영업일에 자동으로 처리된다. 이때부터 매출이 확정된다.
매입사(acquirer) — 가맹점을 대신해 카드 대금을 사들이는 쪽. 고객 카드를 발급한 발급사(issuer, 1번 레슨 카드사)와 짝이 되는 말.
정산(settlement) — 매입된 금액에서 PG 수수료를 뗀 돈이 가맹점 통장에 들어오는 단계. 결제 당일이 아니라 며칠 뒤(PG·계약·업종마다 주기가 다름)라서 “승인됐는데 돈이 없다”(상황 2)가 정상이다. 이 주기를 D+N(결제일 Day 기준 N영업일 뒤)으로 부른다.
취소 시점에 따른 차이(상황 3)
매입 전 취소(승인 취소) — 매입되기 전, 보통 결제 당일에 취소. 카드사로 청구가 넘어가기 전이라 바로 사라진다.
매입 후 취소 — 이미 매출이 확정된 뒤라 카드사가 다시 취소 매입 절차를 밟아야 해서, 고객 카드에 반영되기까지 며칠 걸린다.
고객 문의 “취소했는데 왜 아직 안 빠졌죠?”의 대부분이 이 차이다.
비유: 식당 외상 장부. 승인은 주인이 “이 손님 외상 돼요” 확인하는 것, 매입은 장부를 정리해 본사에 청구서를 넘기는 것, 정산은 본사가 수수료를 떼고 돈을 보내 주는 것. 청구서 넘기기 전에 지우면(매입 전 취소) 깔끔하지만, 넘긴 뒤에는 정정 서류(취소 매입)가 다시 오가야 한다.
지시 예문
“결제 흐름을 요청 → 인증 → 승인 순서로 구현해줘. 결제창 인증 성공 후 우리 서버에서 PG 승인 API를 호출해야 주문을 ‘결제 완료’로 바꿔줘.”
“주문 관리 화면에 결제 상태(승인·매입·취소)와 정산 예정일을 함께 보여줘.”
“고객이 결제를 취소하면 매입 전/후에 따라 ‘즉시 취소’ 또는 ‘카드사 반영까지 영업일 기준 며칠 소요’ 안내 문구를 다르게 보여줘.”
결제 검증과 결과 받기: 금액 위변조 · 서버 검증 · successUrl · webhook · 결제 조회
상황
5만 원짜리 주문인데, 누군가 브라우저 개발자 도구에서 결제 요청 금액을 100원으로 바꿔 결제했고, 서버는 그대로 “결제 완료” 처리했다.
고객이 결제 직후 브라우저를 닫아 버려서 성공 페이지에 도착하지 못했다. 카드는 승인됐는데 주문은 “결제 대기”로 남았다.
고객이 가상계좌를 발급받고 다음 날 입금했는데, 우리 서버는 입금 사실을 알 방법이 없었다.
용어
결제 금액 위변조(tampering) — 결제 요청은 브라우저(클라이언트)에서 시작되므로 금액·상품 정보를 사용자가 마음대로 바꿀 수 있다(상황 1). 웹·앱 보안 용어의 “클라이언트는 믿지 않는다” 원칙이 결제에서 가장 비싸게 적용되는 곳.
서버 금액 검증 — 막는 방법은 하나다.
결제 요청 전에 서버가 주문번호(orderId)와 서버가 계산한 금액을 DB에 저장한다.
결제 후 돌아온 금액을 저장해 둔 금액과 대조한다.
일치할 때만 서버에서 시크릿 키로 승인 API(2번 레슨 승인)를 호출한다.
승인 응답의 상태·금액을 한 번 더 확인하고 주문을 갱신한다.
금액은 항상 서버가 상품 가격·쿠폰·배송비로 다시 계산한다. 프런트에서 넘어온 합계를 그대로 쓰지 않는다.
successUrl / failUrl(성공·실패 리다이렉트 주소) — 결제창 인증이 끝나면 PG가 브라우저를 보내는 주소. 성공 시 결제 키(paymentKey)·주문번호·금액이 주소 뒤에 붙어 온다. 브라우저를 거치는 경로라서 고객이 창을 닫거나 네트워크가 끊기면 도착하지 않는다(상황 2).
webhook(웹훅) — PG가 결제 상태가 바뀔 때 우리 서버로 직접 보내 주는 알림 요청. 브라우저를 거치지 않으므로 창을 닫아도 온다. 가상계좌 입금 완료처럼 고객이 나중에 행동하는 결제는 웹훅 없이는 알 수 없다(상황 3).
비유: successUrl은 손님이 영수증을 들고 직접 와서 보여 주는 것, 웹훅은 카드사가 가게로 직접 전화해 알려 주는 것. 손님은 안 올 수도 있으니 전화를 받을 준비가 필요하다.
웹훅도 그대로 믿지 않는다 — 누구나 우리 웹훅 주소로 가짜 요청을 보낼 수 있으므로, 받으면 PG 결제 조회 API로 실제 상태를 다시 확인한 뒤 반영한다.
같은 웹훅이 여러 번 올 수 있다 — PG는 응답을 못 받으면 재전송한다. 같은 알림을 두 번 처리해도 결과가 같게 만들어야 한다(다음 레슨 중복 결제·멱등성).
결제 조회 API — 결제 키나 주문번호로 PG에 “이 결제 지금 어떤 상태야?”를 묻는 API. 웹훅 재확인, 상태가 애매한 주문을 주기적으로 맞추는 대사(reconciliation, 우리 DB와 PG 기록 맞춰 보기)에 쓴다.
지시 예문
“결제 요청 전에 서버에서 주문 금액을 다시 계산해 DB에 저장하고, 승인 API를 호출하기 전에 돌아온 금액과 일치하는지 검증해줘. 다르면 승인하지 말고 에러 로그를 남겨줘.”
“PG 웹훅 엔드포인트를 만들어줘. 받은 내용을 그대로 믿지 말고 결제 조회 API로 상태를 다시 확인한 뒤 주문 상태를 갱신하고, 같은 웹훅이 여러 번 와도 한 번만 반영되게 해줘.”
"’결제 대기’로 30분 넘게 남아 있는 주문을 PG 조회 API로 확인해서 실제 상태와 맞추는 작업을 주기적으로 돌려줘.”
중복 결제 막기: double charge · timeout ≠ 실패 · idempotency key · 주문번호 UNIQUE · 조건부 상태 변경
같은 키의 재요청이 중복 승인 대신 같은 결과를 받도록 처리합니다.
상황
결제 버튼이 느려서 고객이 두 번 눌렀더니 카드가 두 번 승인됐다.
서버가 PG 승인 API를 호출했는데 응답이 오기 전에 타임아웃이 났다. “실패”로 보고 다시 호출했더니, 사실 첫 번째도 성공해서 이중 결제가 됐다.
successUrl 처리와 웹훅(3번 레슨)이 거의 동시에 도착해서, 같은 주문의 포인트가 두 번 적립되고 확인 문자도 두 번 나갔다.
용어
double charge(중복 결제, 이중 결제) — 한 번 사려던 주문에 돈이 두 번 빠져나가는 것. 고객 신뢰를 가장 빨리 잃는 결제 사고. 원인은 거의 항상 “같은 요청이 두 번 처리됨”이고, 해법은 웹 용어 27번 멱등성(idempotency)이다.
timeout ≠ 실패 — 응답을 못 받은 것은 실패가 아니라 “모름”이다(상황 2). 요청은 PG에 도착해 성공했는데 돌아오는 길에 끊겼을 수 있다. 그래서 타임아웃이 나면 바로 다시 결제하지 말고, 결제 조회 API(3번 레슨)로 실제 상태를 먼저 확인한다.
idempotency key(멱등 키) — 결제 요청마다 고유한 키를 만들어 헤더에 붙이면, PG가 같은 키로 온 요청을 다시 처리하지 않고 첫 결과를 돌려준다. 그래서 타임아웃 후 같은 키로 재시도하면 안전하다. 토스페이먼츠·Stripe 같은 PG가 Idempotency-Key 헤더로 지원한다. 핵심은 재시도할 때 새 키를 만들지 않는 것.
주문번호(orderId) 고유성 — 주문마다 한 번만 쓰는 주문번호를 서버가 만들고, DB에 UNIQUE 제약(데이터베이스 용어 6번)을 건다. PG도 이미 승인된 주문번호로 다시 승인하면 거절한다. 버튼 연타(상황 1)는 앞단의 버튼 잠금 + 이 고유성이 함께 막는다.
조건부 상태 변경(atomic update) — successUrl과 웹훅이 동시에 와도 한쪽만 후속 처리(포인트 적립·재고 차감·문자 발송)를 하게 만든다(상황 3). 데이터베이스 용어 8번의 atomic update를 그대로 쓴다.
UPDATE orders SET status = 'PAID' WHERE id = ? AND status = 'PENDING' → 바뀐 행이 1개일 때만 후속 처리. 두 번째로 온 쪽은 0개가 바뀌므로 아무것도 안 한다.
비유: 번호표 기계. 같은 번호표로는 창구에서 한 번만 업무를 본다. 두 사람이 같은 번호표를 들고 와도 먼저 온 사람만 처리되고, 다음 사람은 “이미 처리됐어요”를 듣는다.
방어를 겹겹이 — 버튼 잠금(화면) → 주문번호 UNIQUE(DB) → 멱등 키(PG 호출) → 조건부 상태 변경(후속 처리). 한 층만으로는 새는 곳이 생긴다.
지시 예문
“PG 승인 API 호출에 멱등 키를 붙이고, 타임아웃이 나면 같은 키로 재시도하거나 결제 조회 API로 상태를 먼저 확인하게 해줘. 타임아웃을 실패로 처리하지 마.”
“주문번호는 서버에서 생성하고 DB에 UNIQUE 제약을 걸어줘.”
“successUrl과 웹훅이 동시에 와도 포인트 적립·재고 차감·알림이 한 번만 일어나도록, ‘PENDING일 때만 PAID로 바꾸기’ 조건부 업데이트로 처리해줘.”
주문 상태 머신: order status · state machine · 허용 전이 · 구매 확정 · 상태 이력
상황
이미 배송 중인 주문을 고객이 “주문 취소” 버튼으로 취소했고, 환불까지 나갔는데 물건도 도착했다.
주문 테이블에 is_paid, is_shipped, is_canceled 같은 참/거짓 칸이 여러 개 있어서, “취소됐는데 배송 완료” 같은 말이 안 되는 주문이 생겼다.
고객이 “언제 취소했죠?”라고 물었는데, 지금 상태만 저장돼 있어서 언제 누가 바꿨는지 알 수 없었다.
용어
order status(주문 상태) — 주문이 지금 어느 단계에 있는지를 나타내는 하나의 값. 흔한 흐름:
state machine(상태 머신) — “상태 목록 + 어느 상태에서 어느 상태로만 갈 수 있는지(전이, transition) 규칙”을 한곳에 정해 두는 설계. 버튼·API·관리자 화면이 각자 상태를 바꾸지 않고, 이 규칙을 통과해야만 바꾼다.
비유: 보드게임 말판. 말은 화살표가 그려진 칸으로만 움직인다. “배송 중” 칸에서 “취소” 칸으로 가는 화살표가 없으면 그 이동은 불가능하다(상황 1).
허용 전이(allowed transitions) — 예: 취소는 PAID·PREPARING에서만, 출고 후에는 반품으로만. 이 표를 먼저 정하면 “이 버튼을 언제 보여 줄까”도 자동으로 정해진다(상태별 가능한 행동만 노출).
참/거짓 칸 여러 개 대신 상태 하나 — is_paid·is_shipped·is_canceled처럼 쪼개면 칸끼리 모순되는 조합이 생긴다(상황 2). 상태를 하나의 enum 값으로 두고, DB에 허용 값만 들어가게 CHECK 제약(데이터베이스 용어 6번)을 건다.
주문 상태 ≠ 결제 상태 ≠ 배송 상태 — 결제(승인·매입·취소, 2번 레슨)와 배송(택배사 추적)은 각자 상태가 따로 있고, 주문 상태는 이들을 바탕으로 고객 관점의 단계를 보여 준다. 섞어 쓰면 “결제 취소됐는데 주문은 배송 중” 같은 혼란이 생긴다.
구매 확정(purchase confirmation) — 고객이 “물건 잘 받았다”고 확정하거나, 배송 완료 후 일정 기간이 지나면 자동으로 확정되는 단계. 보통 이 시점부터 반품 불가·리뷰 작성·포인트 적립, 오픈마켓에서는 판매자 정산이 진행된다. 기간은 쇼핑몰마다 정한다.
상태 이력(status history) — 상태가 바뀔 때마다 이전 상태·새 상태·시각·바꾼 사람(고객/관리자/시스템)·사유를 별도 테이블에 한 줄씩 쌓는다(상황 3). CS(Customer Service, 고객 응대)와 분쟁 대응의 근거가 된다.
지시 예문
“주문 상태를 enum 하나로 정의하고, 허용 전이 표(어느 상태에서 어느 상태로 갈 수 있는지)를 코드 한곳에 만들어줘. 상태 변경은 모두 이 함수를 거치게 하고, 허용되지 않은 전이는 에러로 막아줘.”
“주문 상세 화면에서 현재 상태에서 가능한 행동(취소·반품 신청·구매 확정)만 버튼으로 보여줘.”
“주문 상태가 바뀔 때마다 이전/이후 상태, 시각, 변경 주체, 사유를 order_status_history 테이블에 기록해줘.”
재고: SKU · 옵션 · 재고 차감 시점 · 재고 예약 · overselling
상황
티셔츠 상품의 재고를 “100개”로 하나만 관리했더니, 빨강 M은 이미 0개인데 계속 팔렸다.
재고가 1개 남은 상품을 두 명이 동시에 결제했고, 둘 다 결제 완료가 됐다. 한 명에게는 사과하고 환불해야 했다.
장바구니에 담을 때 재고를 깎았더니, 담아만 두고 안 사는 사람들 때문에 실제로는 남아 있는데 품절로 보였다.
결제가 실패했는데 깎인 재고가 돌아오지 않아서 재고가 점점 줄었다.
용어
product(상품)와 SKU(Stock Keeping Unit, 재고 관리 단위) — 상품은 고객이 보는 “티셔츠” 한 장의 페이지, SKU는 재고를 세는 실제 단위인 “티셔츠 / 빨강 / M”. 재고·가격·바코드는 SKU에 붙는다(상황 1). 상품 1개 : SKU 여러 개(데이터베이스 용어의 1:N 관계).
option(옵션) — 색상·사이즈처럼 SKU를 나누는 기준. 옵션 2개(색 3 × 사이즈 4)면 SKU 12개. 추가 옵션(선물 포장 등)은 재고와 무관하게 가격만 더하는 경우가 많아 SKU와 구분한다.
재고 차감 시점 — 언제 재고를 깎느냐가 핵심 결정이다.
장바구니 담을 때 — 거의 안 쓴다. 안 살 사람이 재고를 묶어 둔다(상황 3).
주문 생성(결제 시작) 때 — 결제 중에 다른 사람이 못 사게 막는다. 대신 결제 실패·이탈 시 되돌려야 한다(상황 4).
결제 완료 때 — 단순하지만, 결제하는 몇십 초 사이 다른 사람도 결제할 수 있어 초과 판매 위험이 있다.
흔한 절충: 주문 생성 때 예약하고 결제 완료 때 확정.
재고 예약(reservation, hold) — 결제 시작 시 재고를 잠시 잡아 두고, 정해진 시간(예: 10~30분) 안에 결제가 안 되면 자동으로 풀어 주는 방식. 비유: 식당 예약석. 시간 안에 안 오면 다른 손님에게 넘어간다.
그래서 재고를 두 숫자로 본다: 실재고(on-hand, 창고에 실제 있는 수)와 가용 재고(available, 실재고 − 예약 중). 화면의 “남은 수량”은 가용 재고.
overselling(초과 판매) — 가진 것보다 많이 판 것(상황 2). 원인은 데이터베이스 용어 8번의 lost update: 두 요청이 동시에 “재고 1”을 읽고 각자 “0으로” 쓴다.
막는 법: 읽고 계산하지 말고 조건부 원자적 차감(atomic update) — UPDATE skus SET stock = stock - 1 WHERE id = ? AND stock >= 1 → 바뀐 행이 0개면 품절 처리. 4번 레슨의 “PENDING일 때만 PAID” 조건부 변경과 같은 원리.
재고 복원 — 결제 실패·예약 만료·주문 취소·반품 시 재고를 되돌리는 규칙을 차감 규칙과 짝으로 만든다. 반품은 검수 후 다시 팔 수 있을 때만 복원(불량품은 제외).
품절(sold out)·일시품절 — 가용 재고 0이면 구매 버튼을 막고, 입고 예정이면 재입고 알림 신청을 받는다.
지시 예문
“상품과 SKU를 분리해줘. 상품 하나에 색상·사이즈 옵션 조합별 SKU가 여러 개 있고, 재고·가격은 SKU에 둬.”
“주문 생성 시 재고를 15분 동안 예약하고, 결제 완료 시 확정, 결제 실패나 시간 만료 시 자동으로 풀어줘. 화면에는 가용 재고(실재고 − 예약)를 보여줘.”
“재고 차감은 ‘stock >= 수량’일 때만 빼는 조건부 UPDATE 한 문장으로 처리하고, 바뀐 행이 0개면 품절 에러를 돌려줘. 동시에 두 명이 마지막 1개를 결제하는 테스트도 만들어줘.”
할인과 부분 환불: 쿠폰 종류 · 포인트 · 안분 · 부분 취소 · 끝전 처리
상황
1만 원짜리 상품 2개를 사면서 3천 원 쿠폰을 써서 1만 7천 원을 냈다. 1개만 반품하면 얼마를 돌려줘야 할지 몰랐다(진단 Q3).
반품 1개에 정가 1만 원을 그대로 환불했더니, 고객은 쿠폰 할인을 받고도 남은 상품을 7천 원에 산 셈이 됐다.
“2만 원 이상 구매 시” 쿠폰이었는데, 반품 후 남은 주문은 1만 원이라 쿠폰 조건이 깨졌다.
카드 + 포인트로 섞어 낸 주문을 부분 환불하는데, 카드로 돌려줄지 포인트로 돌려줄지 정해 둔 게 없었다.
용어
쿠폰 종류
정액 할인 — “3,000원 할인”. 정률 할인 — “10% 할인”, 보통 최대 할인 금액 상한을 같이 둔다.
상품 쿠폰 — 특정 상품 하나에만 붙는 할인. 주문(장바구니) 쿠폰 — 주문 전체 합계에서 빼는 할인. 배송비 쿠폰.
사용 조건 — 최소 주문 금액, 대상 상품·카테고리, 유효 기간, 1인 1회, 중복 사용 가능 여부.
포인트(적립금) — 쇼핑몰이 자체 발행하는 현금처럼 쓰는 금액. PG를 거치지 않으므로 결제 수단이 카드 + 포인트처럼 나뉜다(상황 4).
안분(按分, proration) — 주문 쿠폰처럼 주문 전체에 걸린 할인을 상품별로 나눠 배정하는 것. 보통 금액 비율대로 나눈다.
Q3 답: 1만 원 : 1만 원 = 1 : 1이니 3천 원 할인을 1,500원씩 배정 → 상품별 실결제액 8,500원 → 1개 반품 시 8,500원 환불. 정가 1만 원을 돌려주면 회사가 1,500원 손해(상황 2).
결제 시점에 상품별 할인액·실결제액을 저장해 둔다. 나중에 다시 계산하면 쿠폰 정책이 바뀌었을 때 금액이 어긋난다.
조건 깨짐 처리 — 반품 후 남은 주문이 쿠폰 조건(최소 금액)을 못 채우면(상황 3) 정책을 정해야 한다.
쿠폰 유지 — 그냥 8,500원 환불. 고객 친화적, 단순.
할인 회수 — 남은 상품은 할인 없이 1만 원짜리였어야 하므로, 낸 돈 1만 7천 원 − 1만 원 = 7,000원 환불. 정확하지만 고객 불만이 생길 수 있어 구매 전에 고지해야 한다.
환불 수단 순서 — 카드 1만 5천 원 + 포인트 2천 원으로 낸 주문에서 8,500원 환불 시
포인트 먼저 — 포인트 2천 원 + 카드 6,500원. 회사 입장에서 현금 유출이 줄어든다.
카드 먼저 — 카드 8,500원. 고객 입장에서 선호.
어느 쪽이든 정해서 고지하고 코드에 한 규칙으로 둔다.
부분 취소(partial cancel) — PG에 전체가 아니라 일부 금액만 취소 요청하는 것. 취소 API에 취소 금액을 넣는다. 일부 선불·기프트카드처럼 부분 취소가 안 되는 결제수단도 있어서, 그때는 전체 취소 후 재결제하거나 다른 수단으로 돌려준다(2번 레슨 매입 후 취소와 함께 처리 기간도 안내).
끝전 처리(rounding) — 1천 원 할인을 3개에 나누면 333.33…원. 원 단위로 내림한 뒤 남는 끝전을 마지막 상품에 몰아준다(333 / 333 / 334). 그래야 상품별 합계가 할인 총액과 정확히 맞는다. 돈 계산은 소수점 없는 정수(원 단위)로 한다.
포인트 적립 회수 — 반품된 상품으로 적립된 포인트는 같이 회수한다. 이미 써 버렸으면 환불액에서 빼는 등 규칙이 필요하다.
지시 예문
“주문 쿠폰 할인을 상품별 금액 비율로 안분하고, 원 단위 끝전은 마지막 상품에 붙여줘. 결제 시점에 상품별 할인액과 실결제액을 order_items에 저장해줘.”
“부분 반품 시 환불액은 저장된 상품별 실결제액으로 계산하고, 반품 후 쿠폰 최소 금액 조건이 깨지면 할인을 회수하는 정책으로 처리해줘. 이 정책을 주문서에 미리 고지해줘.”
“카드+포인트 혼합 결제의 환불은 포인트 먼저 돌려주고 나머지를 PG 부분 취소로 처리해줘. 반품 상품으로 적립된 포인트도 회수해줘.”
정기결제: 빌링키 · customerKey · 결제 주기 · 결제 실패 재시도 · 일할 계산 · 유료 전환 고지
상황
월 구독 서비스를 만드는데, 매달 고객에게 결제창을 다시 띄울 수는 없다. 그렇다고 카드번호를 우리 DB에 저장할 수도 없다(1번 레슨 결제창 원칙).
매달 1일 자동 결제가 카드 한도 초과·카드 만료로 실패했는데, 아무 처리 없이 서비스가 계속 열려 있었다.
월 1만 원 플랜 쓰던 고객이 달 중간에 2만 원 플랜으로 올렸는데, 얼마를 더 받아야 할지 몰랐다.
무료 체험이 끝나면 말없이 유료로 넘어가는 구조였다가 고객 항의를 받았다.
용어
정기결제(recurring payment, 자동결제, 빌링) — 처음 한 번 카드를 등록하면 이후에는 고객이 결제창을 거치지 않고 정해진 주기마다 서버가 결제하는 방식. 일반 결제와 별도로 PG에 빌링 서비스를 신청·계약해야 쓸 수 있는 경우가 많다.
빌링키(billing key) — 카드 등록 시 PG가 발급해 주는 카드 대신 쓰는 열쇠. 우리 서버는 카드번호 대신 이 키만 저장하고, 결제할 때 “이 빌링키로 이 금액 결제”를 PG에 요청한다.
비유: 호텔 카드키. 진짜 열쇠(카드번호)는 호텔(PG)이 보관하고, 손님(우리 서버)은 그 방에만 쓸 수 있는 카드키를 받는다. 잃어버리면 그 카드키만 무효화하면 된다.
빌링키도 비밀 정보다. 시크릿 키와 함께 있으면 결제가 되므로 서버에만 두고, 로그·프런트에 노출하지 않는다.
customerKey(고객 식별키) — 빌링키를 어느 고객 것으로 묶을지 정하는 값. 남이 추측할 수 있는 자동 증가 번호(1, 2, 3…) 대신 UUID 같은 무작위 값을 쓴다(웹·앱 보안 용어의 IDOR와 같은 원리).
billing cycle(결제 주기)·다음 결제일 — 월간·연간 등. “1월 31일 가입자의 2월 결제일”처럼 말일 처리 규칙을 미리 정한다(보통 그달 마지막 날로).
결제 실패와 재시도(dunning, 미납 독촉 절차) — 자동 결제는 한도 초과·카드 만료·정지로 실패할 수 있다고 가정한다(상황 2).
재시도 일정 — 예: 실패 후 1일·3일·7일 뒤 다시 시도.
알림 — 실패 즉시 “결제 수단을 확인해 주세요” 안내, 카드 만료 전 미리 안내.
유예 기간(grace period) — 재시도 동안은 서비스를 유지하고, 끝까지 실패하면 이용 정지·해지 상태로(5번 레슨 상태 머신으로 구독 상태도 관리: ACTIVE → PAST_DUE(미납) → CANCELED).
매달 결제도 멱등 키(4번 레슨)로 같은 달 이중 청구를 막는다.
일할 계산(proration) — 7번 레슨 안분을 기간에 적용한 것. 달 중간에 플랜을 바꾸면 남은 기간 비율만큼 차액을 받거나 돌려준다(상황 3).
예: 30일 중 15일 남은 시점에 1만 원 → 2만 원 플랜 변경 → 차액 1만 원 × 15/30 = 5,000원 추가 결제.
다운그레이드는 보통 다음 결제일부터 적용해 환불 계산을 피한다.
유료 전환·가격 인상 고지 — 한국 전자상거래법(2025년 2월 14일 시행 개정)은 정기결제 대금을 올리거나 무료에서 유료로 전환할 때 소비자에게 별도 동의와 고지 절차를 거치도록 한다(상황 4). 숨은 자동 갱신·해지 방해 같은 다크패턴(dark pattern, 사용자를 속이는 화면 설계)도 규제 대상. 해지 버튼은 가입만큼 찾기 쉽게 둔다.
지시 예문
“토스페이먼츠 빌링으로 월 구독 결제를 만들어줘. 카드번호는 저장하지 말고 빌링키만 서버 DB에 암호화해 저장하고, customerKey는 UUID로 만들어줘.”
“자동 결제가 실패하면 1·3·7일 뒤 재시도하고, 실패 즉시 결제 수단 변경 안내 메일을 보내줘. 구독 상태는 ACTIVE → PAST_DUE → CANCELED 상태 머신으로 관리해줘.”
“플랜 업그레이드는 남은 일수 비율로 차액을 즉시 결제하고, 다운그레이드는 다음 결제일부터 적용해줘.”
“무료 체험 종료 전에 유료 전환 안내와 동의를 받는 화면을 만들고, 해지 버튼을 설정 화면 첫 단계에 둬줘.”
상품 가격 1만 원을 “부가세 별도”로 저장했다가, 결제 화면에서 1만 1천 원이 찍혀 고객이 놀랐다.
관리자 화면 매출이 1만 원짜리 주문을 “부가세 1,000원”으로 계산했는데, 세무사가 909원이 맞다고 했다.
책(면세)과 문구(과세)를 같이 파는데, 전부 부가세 10%로 신고해서 세금을 더 냈다.
계좌이체로 15만 원어치를 산 고객이 현금영수증을 요청하지 않아 그냥 넘어갔다.
카드로 결제한 사업자 고객이 세금계산서도 발급해 달라고 했다.
용어
부가세(VAT(Value Added Tax), 부가가치세) — 물건·서비스 값에 붙는 세금, 한국은 10%. 소비자에게 보여 주는 가격은 부가세 포함 가격이 원칙이다(상황 1). 8번 레슨에서 배운 다크패턴 규제처럼, 반드시 내야 하는 돈은 처음부터 총액에 넣는다.
공급가액(supply value) — 부가세를 뺀 금액. 합계 = 공급가액 + 부가세.
1만 1천 원 → 공급가액 10,000 + 부가세 1,000.
1만 원 → 공급가액 9,091 + 부가세 909(상황 2). 1만 원의 10%가 아니라 합계 ÷ 1.1로 공급가액을 구하고, 부가세는 합계 − 공급가액으로 구해 끝전 차이가 없게 한다(7번 레슨 끝전 처리와 같은 원리).
과세 / 면세(tax-free) — 대부분 상품은 과세(부가세 있음), 가공하지 않은 농·수산물, 도서 등 일부는 면세(부가세 없음). 한 주문에 섞이면 과세 금액과 면세 금액을 나눠 저장하고, PG 결제 요청에도 면세 금액을 따로 넘긴다(상황 3). 쿠폰을 쓴 주문은 할인을 과세·면세 상품에 어떻게 안분했는지에 따라 금액이 달라진다.
증빙(증명 서류) — 거래가 있었다는 세무상 영수증. 결제수단에 따라 다르다.
카드 매출전표 — 카드 결제 시 자동 발급. 이것 자체가 증빙이라 따로 영수증이 필요 없고, 같은 거래에 세금계산서를 또 발급할 수 없다(상황 5, 중복 증빙).
현금영수증 — 계좌이체·가상계좌 같은 현금성 결제의 증빙. 고객이 휴대폰 번호(소득공제용)나 사업자번호(지출증빙용)로 받는다. 온라인 쇼핑몰(전자상거래 소매업)은 현금영수증 의무발행업종이라, 건당 10만 원 이상 현금 거래는 고객이 요청하지 않아도 발급해야 한다. 고객 정보를 모르면 국세청 지정 번호로 자진 발급한다(상황 4).
세금계산서 — 사업자끼리 거래(B2B(Business to Business, 기업 간 거래))에서 쓰는 증빙. 사업자 고객이 현금성 결제 후 요청하면 발급한다. 지금은 대부분 전자세금계산서.
PG와 증빙 — 많은 PG가 가상계좌·계좌이체 결제 시 현금영수증 자동 발급 옵션을 제공한다. 결제 연동할 때 같이 켜 두면 상황 4를 막을 수 있다.
세율·의무 기준·업종 범위는 바뀔 수 있으므로, 실제 신고와 정책은 국세청 안내나 세무사로 최종 확인한다.
지시 예문
“상품 가격은 부가세 포함 금액으로 저장하고, 주문마다 공급가액(합계 ÷ 1.1, 원 단위 반올림)과 부가세(합계 − 공급가액)를 따로 저장해줘.”
“상품에 과세/면세 구분 필드를 추가하고, 주문마다 과세 금액과 면세 금액을 나눠 저장해서 PG 결제 요청에 면세 금액을 넘겨줘.”
“가상계좌·계좌이체 결제는 PG의 현금영수증 자동 발급을 켜고, 고객이 번호를 입력하지 않으면 국세청 자진 발급 번호로 발급해줘.”
“카드 결제 주문에는 세금계산서 발급 버튼을 숨기고, 현금성 결제 주문에서 사업자 고객만 세금계산서를 신청할 수 있게 해줘.”
배송과 반품: 출고 · 송장번호 · 배송 추적 · 부분 출고 · 배송비 정책 · 청약철회 · 반품 회수
상황
주문 3개 중 2개는 오늘, 1개는 다음 주 입고라 따로 보냈는데, 주문 하나에 송장 칸이 하나뿐이라 두 번째 송장을 넣을 곳이 없었다.
단순 변심 반품인데 반품 배송비를 판매자가 냈고, 무료배송 주문이라 처음 보낸 배송비까지 손해를 봤다.
용어
출고(fulfillment, 주문 처리·발송) — 창고에서 상품을 꺼내 포장하고 택배사에 넘기는 과정. 5번 레슨 상태로는 PREPARING → SHIPPED.
풀필먼트 서비스 — 보관·포장·출고를 통째로 맡기는 외부 물류 대행. 이때는 재고 수량도 그 회사 시스템과 맞춰야 한다(6번 레슨 재고).
송장번호(운송장 번호, tracking number) — 택배 한 상자마다 붙는 추적 번호. 택배사 코드 + 송장번호 한 쌍으로 저장해야 조회가 된다(같은 번호가 다른 택배사에도 있을 수 있음).
배송 추적(tracking) — 송장번호로 배송 단계(집하 → 이동 중 → 배송 출발 → 배송 완료)를 가져오는 것. 택배사·배송 조회 API를 연동해 주기적으로 조회(polling)하거나 상태 변경 알림(웹훅, 3번 레슨)을 받아 주문 상태를 자동으로 DELIVERED까지 바꾼다(상황 2). 배송 완료 시점은 5번 레슨 구매 확정 자동 기간의 시작점이 된다.
부분 출고(split shipment)와 묶음 배송(합포장) — 한 주문을 여러 상자로 나눠 보내거나, 여러 주문을 한 상자로 합쳐 보내는 것. 그래서 주문과 배송은 1:1이 아니다(상황 1). shipment(배송 건) 테이블을 따로 두고, 어떤 주문 상품이 어느 상자에 들어갔는지 연결한다.
배송비 정책
무료배송 기준 — “3만 원 이상 무료배송”. 7번 레슨처럼 반품 후 기준 아래로 떨어지면 처음 배송비를 환불액에서 빼는지 정해 둔다.
도서산간 추가 배송비 — 제주·섬 지역은 우편번호로 판별해 추가 운임을 받는다(상황 3).
반품 배송비 / 교환 배송비 — 보통 왕복 기준으로 정해 상품 페이지에 미리 고지.
청약철회(withdrawal of offer, 소비자의 반품 권리) — 한국 전자상거래법상 온라인 구매자는 상품을 받은 날 등으로부터 7일 이내 이유 없이(단순 변심) 반품할 수 있다. 상품이 광고·계약 내용과 다르면 받은 날부터 3개월, 그 사실을 안 날부터 30일 이내.
비용 부담 — 단순 변심 반품의 반송 비용은 소비자 부담, 광고·계약과 다른 경우는 판매자 부담(상황 4). 반품을 이유로 위약금은 받을 수 없다.
반품이 제한되는 경우 — 소비자 잘못으로 훼손, 사용으로 가치가 크게 떨어진 경우 등. 단, 내용 확인을 위해 포장을 뜯은 것만으로는 막을 수 없다.
반품 회수(return pickup) — 고객 집으로 택배 기사를 보내 상품을 가져오는 것. 회수 송장번호를 따로 저장하고, 입고 후 검수 → 환불(7번 레슨 부분 환불) → 재고 복원(6번 레슨) 순으로 처리한다. 상태로는 RETURN_REQUESTED → 회수 중 → 입고·검수 → RETURNED.
반품 기간·비용 기준은 법 개정이나 상품 종류에 따라 달라질 수 있으니, 정책 문구는 공정거래위원회·한국소비자원 안내로 확인한다.
“배송 조회 API로 하루 몇 번 배송 상태를 확인해서 배송 완료가 되면 주문 상태를 DELIVERED로 바꾸고, 그 시점부터 구매 확정 자동 기간을 세줘.”
“우편번호로 제주·도서산간을 판별해서 추가 배송비를 받아줘.”
“반품 신청 시 사유가 단순 변심이면 왕복 배송비를 환불액에서 빼고, 상품 불량·오배송이면 전액 환불하고 회수비는 우리가 부담하게 해줘. 신청 가능 기간은 배송 완료 후 7일로 하고 이 정책을 상품 페이지에 고지해줘.”
결제 분쟁과 부정 결제: chargeback · 카드 도용 · card testing · FDS · 3DS · PCI DSS
상황
배송까지 끝난 주문인데, 몇 주 뒤 PG에서 “고객이 카드사에 부정 사용 이의 제기를 해서 매출이 취소될 수 있다”는 연락이 왔다.
밤사이 1,000원짜리 결제 시도가 수백 건, 매번 다른 카드번호로 들어왔다.
상품권·게임 아이템처럼 바로 현금화할 수 있는 상품만 도용 카드로 대량 주문됐다.
“결제를 빠르게 하려고” 카드번호와 CVC(Card Verification Code, 카드 뒷면 보안 코드)를 우리 DB에 저장하자는 의견이 나왔다.
용어
chargeback(차지백, 지불 거절) — 고객이 가맹점이 아니라 카드사에 “내가 한 결제가 아니다 / 물건을 못 받았다”고 이의를 제기해, 카드사가 이미 정산된 매출을 강제로 되돌리는 것(상황 1). 2번 레슨의 정산이 끝났어도 뒤집힐 수 있다. 해외 카드에서 특히 흔하다.
대응 — 가맹점은 증빙(배송 완료 기록·수령 서명, 접속 IP·기기, 주문·로그인 이력, 고객과의 대화)을 제출해 반박한다. 그래서 10번 레슨 배송 추적과 5번 레슨 상태 이력이 돈을 지키는 근거가 된다.
friendly fraud(우호적 사기) — 본인이 사 놓고 “안 샀다”고 차지백을 거는 경우. 증빙이 없으면 판매자가 진다.
카드 도용(card fraud) — 훔친 카드 정보로 결제하는 것. 피해 카드 주인이 차지백을 걸면 판매자가 물건과 돈을 둘 다 잃는다. 상품권·게임 재화·전자기기처럼 되팔기 쉬운 상품이 주 표적(상황 3).
card testing(카드 테스팅 공격) — 유출된 카드번호가 살아 있는지 확인하려고 작은 금액 결제를 대량으로 시도하는 공격(상황 2). 웹·앱 보안 용어의 credential stuffing과 같은 구조. PG 수수료·승인 실패 기록이 쌓이고 PG 계약이 위험해질 수 있다.
방어: 결제 시도에 rate limit(횟수 제한), 같은 IP·기기·계정의 연속 실패 차단, 의심 시 CAPTCHA, 로그인한 사용자만 결제.
FDS(Fraud Detection System, 이상거래탐지시스템) — 결제 패턴(짧은 시간 다수 결제, 낯선 해외 IP, 배송지·결제자 불일치, 고액 환금성 상품)을 보고 의심 거래를 막거나 보류하는 시스템. PG가 기본 제공하는 경우가 많고, 우리 쪽에는 “의심 주문은 자동 출고 보류 → 사람 확인” 규칙을 둔다.
3DS(3-D Secure, 카드 본인 인증) — 해외 카드 결제 때 카드사가 추가 본인 인증(앱 승인·OTP)을 요구하는 표준. 인증을 통과한 거래는 도용 차지백 책임이 카드사 쪽으로 넘어가는 경우가 많아 판매자를 보호한다. 국내 카드는 2번 레슨의 결제창 인증 단계가 비슷한 역할.
PCI DSS(Payment Card Industry Data Security Standard, 카드 정보 보안 표준) — 카드번호를 저장·처리·전송하는 회사가 지켜야 하는 국제 보안 기준. 요구 사항이 무겁다(암호화, 접근 통제, 정기 점검 등).
그래서 결론은 1번 레슨과 같다: 카드번호는 PG 결제창에서만 입력받고, 우리는 만지지 않는다. 반복 결제는 8번 레슨 빌링키로(상황 4).
CVC는 승인 후 어떤 경우에도 저장하면 안 되는 민감 인증 정보다.
화면·로그에는 마스킹된 카드번호(1234-56**-****-7890)와 카드사 이름 정도만 남긴다.
지시 예문
“결제 API에 사용자·IP별 rate limit을 걸고, 같은 IP에서 결제 실패가 연속 5번 나면 30분간 막아줘. card testing 공격 방어용이야.”
“상품권처럼 환금성 높은 상품, 첫 주문 고액, 배송지와 결제자 정보가 크게 다른 주문은 자동 출고하지 말고 ‘검토 대기’ 상태로 보내줘.”
“차지백 대응용으로 주문마다 결제 시점의 IP·기기 정보, 로그인 이력, 배송 완료 기록을 함께 조회할 수 있는 화면을 만들어줘.”
“코드베이스에서 카드번호·CVC를 받거나 저장하거나 로그로 남기는 곳이 있는지 점검해줘. 화면과 로그에는 마스킹된 번호만 남겨줘.”
커머스 지표와 정산 대사: GMV · 순매출 · AOV · 취소·반품률 · 매출 인식 시점 · 정산 대사
상황
대시보드 “이번 달 매출 1,100만 원”을 보고 좋아했는데, 실제 통장에 들어온 돈은 800만 원대였다.
같은 “매출”인데 마케팅팀은 결제 금액, 회계팀은 구매 확정 금액을 말해서 숫자가 안 맞았다.
PG 정산 금액이 우리 주문 DB 합계와 몇만 원 차이 났는데, 어느 주문 때문인지 찾을 수 없었다.
용어
GMV(Gross Merchandise Volume, 총거래액) — 일정 기간 주문된 상품의 할인·취소를 빼기 전 총액. 규모를 보여 주지만 회사가 번 돈이 아니다. 회사마다 정의(할인 전/후, 취소 포함 여부)가 달라 정의를 문서로 고정해야 한다(상황 2).
결제액 → 순매출(net revenue) — 숫자는 단계별로 줄어든다(예시, 주문 200건).
GMV(정가 합계) 1,100만 원 − 쿠폰·포인트 할인 100만 원 = 결제액 1,000만 원
결제액 − 취소·반품 환불 150만 원 = 순매출 850만 원
순매출 − PG 수수료(예시 3%) 25만 5천 원 = 정산 예상액 약 824만 원(취소분 수수료는 돌려받는다고 가정한 단순 예시)
상황 1의 “1,100만 원”은 GMV, 통장의 “800만 원대”는 정산액이었다.
AOV(Average Order Value, 객단가·평균 주문 금액) — 결제액 ÷ 주문 수. 예시는 1,000만 원 ÷ 200건 = 5만 원. 무료배송 기준(10번 레슨)이나 “함께 사면 할인” 같은 장치로 올린다. 디지털 마케팅 용어의 CVR(Conversion Rate, 전환율)과 곱하면 방문자당 매출이 나온다.
취소율·반품률 — 금액 기준 예시 150만 ÷ 1,000만 = 15%. 상품·사유별로 쪼개 봐야 원인(사이즈 안내 부족, 불량, 오배송)이 보인다. 그래서 10번 레슨 반품 사유를 선택지로 받아 저장한다.
매출 인식 시점 — “언제 매출로 칠 것인가”. 결제 완료 시점(빠르지만 이후 취소로 줄어듦) vs 구매 확정 시점(5번 레슨, 늦지만 거의 확정). 대시보드에는 어느 기준인지 라벨로 표시한다(상황 2). 세무·회계 기준은 회계사와 정한다.
정산 대사(settlement reconciliation) — 3번 레슨의 대사를 돈에 적용한 것. 세 장부를 건별로 맞춰 본다(상황 3).
우리 주문 DB — 주문번호·결제 키·금액·취소 금액
PG 정산 내역 — 건별 거래액·수수료·정산 예정일(PG 관리자 화면·파일·API)
통장 입금 내역 — 실제 들어온 돈
합계만 비교하면 원인을 못 찾으므로 결제 키(주문번호) 기준으로 건별 매칭하고, 어긋난 건만 목록으로 뽑는다. 흔한 원인: 웹훅 누락으로 우리 DB만 “결제 대기”(3번), 부분 취소 금액 불일치(7번), 차지백(11번), 정산 보류.
비유: 가계부와 카드 명세서 맞춰 보기. 총액이 다르면 한 줄씩 대조해야 빠진 항목을 찾는다.
지표는 원본에서 계산 — 대시보드 숫자는 따로 쌓은 집계표가 아니라 주문·결제·환불 원본 기록에서 다시 계산할 수 있어야 한다. 7번 레슨에서 상품별 실결제액을 저장해 둔 이유가 여기서도 쓰인다.
지시 예문
“관리자 대시보드에 GMV, 결제액, 환불액, 순매출, PG 수수료, 정산 예상액을 단계별로 보여주고, 각 숫자의 정의를 툴팁으로 달아줘. 매출 기준(결제 완료/구매 확정)은 토글로 바꿀 수 있게 해줘.”
“AOV와 금액 기준 취소·반품률을 주간 추이로 보여주고, 반품률은 상품·사유별로 쪼개 볼 수 있게 해줘.”
“PG 정산 파일을 올리면 결제 키 기준으로 우리 주문 DB와 건별 대사해서, 금액이 다르거나 한쪽에만 있는 건만 목록으로 보여줘.”