콘텐츠로 이동

@gj-kit/toss-payments 핵심 개념

토스페이먼츠 연동에서 검증 단계를 빠뜨리면 런타임이 아니라 컴파일에서 막힙니다.

토스 연동 사고는 대부분 프로덕션에서야 드러납니다. 저장해 둔 금액과 대조하지 않고 confirm을 호출하거나, 멱등키 없이 cron이 billing approve를 두 번 돌려 이중 과금이 나거나, 서명 없는 PAYMENT_STATUS_CHANGED payload를 그대로 믿고 주문을 이행하거나, 가상계좌 secret 저장을 빠뜨려 입금 webhook이 전부 unknown-order로 거부되는 식입니다. transport 실패로 끝난 confirm이 결제 실패가 아니라는 점도 마찬가지입니다. 뭉뚱그려 실패로 처리하면 돈은 이미 빠져나간 상태에서 고객에게는 실패했다고 안내하게 됩니다.

사용 가능한 flow는 제공한 store를 반영합니다

섹션 제목: “사용 가능한 flow는 제공한 store를 반영합니다”

confirmation, billing, webhook capability는 서버가 대응 store를 제공하기 전에는 나타나지 않아 배선하지 않은 결제 경로를 호출할 수 없습니다.

redirect와 webhook payload는 결제 진실이 아니라 입력입니다. 서버는 확정 전에 주문 금액, raw-body event, trust grade를 검증합니다.

  • 배선한 flow만 타입에 존재 — createTossPayments가 돌려주는 타입에는 BillingKeyStore를 넘기지 않으면 billing 프로퍼티가, OrderStore를 넘기지 않으면 confirm 프로퍼티가 아예 없습니다.
  • 브라우저에 닿지 못하는 secret key — 토스 key 4종이 서로 다른 brand입니다. parseApiSecretKey·parseWidgetSecretKey는 node 조건으로만 해석되는 /server entry에만 있고, loadWidgets는 WidgetClientKey만 받습니다.
  • 검증을 통과해야 confirm 가능 — flow.confirm은 VerifiedCheckout만 받습니다. 이 brand는 flow.verify가 createOrder 시점에 저장한 금액과 callback의 amount를 대조하고 10분 승인 시한까지 확인해야 발급됩니다.
  • 취소에는 지름길이 없음 — paymentKey로 바로 취소하는 API가 없습니다. getPayment → asCancelable → kind narrowing을 거쳐야 하고, 입금 완료 가상계좌는 refundAccount가 필수, 가상계좌가 아닌 결제는 refundAccount?: never로 아예 막힙니다.
  • webhook 신뢰도 3등급 분리 — verify()는 raw body만 받고, 이벤트는 signature / secret / unverified 3등급으로 나뉩니다. 토스가 빌링 승인 webhook을 보내지 않기 때문에 WebhookHandlers에는 onBillingApproved key 자체가 없습니다.

server 키 parser를 브라우저 코드에 import하거나 문서화된 검증 경로 없이 redirect/웹훅을 신뢰하지 마세요. secret과 정확한 audit body는 저장 시 암호화하세요.

다음은 검증된 레시피 두 개입니다.