Toss Payments 정리 2

Toss Payments 정리 2 - 자동결제(빌링) 결제창 연동하기

7분 소요

자동 결제 (빌링) 결제창 연동하기

sequenceDiagram
    participant User
    participant Client
    participant Server
    participant TossPayments
 
    User->>Client: 카드 등록 요청
    Client->>TossPayments: 카드 등록창 호출
    TossPayments-->>User: 카드 등록창 띄우기
    User->>TossPayments: 카드 정보 입력 및 본인 인증
    TossPayments-->>Client: SuccessUrl로 이동
    Client-->>Server:
    Server->>TossPayments: 빌링키 발급 API 요청
    TossPayments->>Server: 빌링키 발급 결과 응답
    Server-->>Client: 
    Client-->>User: 카드 등록 완료 안내
    
    Server->>TossPayments: 빌링 결제 승인 요청
    TossPayments->>Server: 빌링 결제 승인 결과 응답
    Server-->>Client: 
    Client-->>User: 결제 결과 안내

1. 구매자 카드 등록하기

클라이언트 쪽에 토스페이먼츠 SDK를 설치, 클라이언트 키로 SDK를 초기화 -> payment() 메서드로 결제창 인스턴스를 생성

requestBillingAuth() 메서드 호출 -> 결제창 띄우기 -> 메서드 파라미터로 주문번호, 결제금액, SuccessUrl, failUrl 전달 -> 주문서의 카드 등록하기 버튼에 자동결제 요청 메서드를 이벤트 등록

2. 리다이렉트 URL로 이동하기

결제창에서 구매자의 결제수단 인증 -> 인증 결과 리다이렉트 URL로 확인

  • 인증 성공시

    • 성공 리다이렉트 URL (successUrl)의 쿼리 파라미터로 결제 정보를 확인하고 검증
    /success?customerKey={CUSTOMER_KEY}&authKey={AUTH_KEY}
    
    • customerKey : 구매자 키
    • authKey : 빌링키를 발급할 때 필요한 일회성 인증 키 최대길이 300자
  • 인증 실패시

    • 실패 리다이렉트 URL (failUrl)로 이동, 쿼리 파라미터로 에러 코드 확인
    /fail?code={ERROR_CODE}&message={ERROR_MESSAGE}
    

3. 빌링키 발급하기

빌링키: 카드번호, 유효기간, CVC 등 카드 정보를 암호화하여 토스페이먼츠 서버에 저장한 후 발급되는 영문+숫자 조합

구매자의 카드 정보 대신 빌링키를 사용해 구독 결제 시점에 결제를 내는 원리

시크릿 키와 :을 base64로 인코딩해서 Basic 인증 헤더를 아래와 같이 만들어 준다. 비밀번호가 없다는 것을 알리기 위해 시크릿 키 뒤에 :을 붙여 인코딩한다.

Basic base64("{API_SECRET_KEY}:")

시크릿 키 인코딩 방법

const base64SecretKey = btoa("{API_SECRET_KEY}:");

인코딩된 값을 Basic 인증 헤더에 넣고 요청 본문도 추가한다. 앞 단계에서 리다이렉트 URL로 전달받은 customerKeyauthKey를 카드 자동결제 빌링키 발급 요청 API의 요청 본문으로 보낸다.

curl --request POST \
  --url https://api.tosspayments.com/v1/billing/authorizations/issue \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --data '{"authKey":"5pBoBAPfQr7R1coCx9WSr","customerKey":"qLKNEQdVbLshykFfzorKz"}'
const fetch = require('node-fetch');
 
const url = 'https://api.tosspayments.com/v1/billing/authorizations/issue';
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==',
    'Content-Type': 'application/json'
  },
  body: '{"authKey":"5pBoBAPfQr7R1coCx9WSr","customerKey":"qLKNEQdVbLshykFfzorKz"}'
};
 
try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}

API 호출 결과로 HTTP 200 OK 응답을 받으면 빌링키 발급 성공

빌링키를 구매자를 특정하는 customerKey와 함께 서버에 저장한다. 한 번 발급된 빌링키는 다시 조회할 수 없다. 앞으로 해당 구매자의 billingKey, customerKey가 있으면 결제를 낼 수 있다.

빌링키가 노출되어도 customerKey가 없으면 결제할 수 없다.

빌링키의 유효기간은 빌링키와 연결된 카드 유효기간과 같다.

발급된 빌링키가 더 이상 필요하지 않으면 사용하지 않거나 빌링키 삭제 API로 삭제한다.

응답

{
  "mId": "tosspayments",
  "customerKey": "qLKNEQdVbLshykFfzorKz",
  "authenticatedAt": "2021-01-01T10:00:00+09:00",
  "method": "카드",
  "billingKey": "Z_t5vOvQxrj4499PeiJcjen28-V2RyqgYTwN44Rdzk0=",
  "card": {
    "issuerCode": "61",
    "acquirerCode": "31",
    "number": "43301234****123*",
    "cardType": "신용",
    "ownerType": "개인"
  },
  "cardCompany": "현대",
  "cardNumber": "43301234****123*",
  "transfers": null,
  "easyPay": null
}

4. 빌링키로 자동결제 승인하기

원하는 결제 주기에 따라 카드 자동결제 승인 API를 호출한다.

토스페이먼츠에서는 자체적으로 스케줄링 기능을 제공하지 않으므로 직접 스케줄링 기능을 구현하여 원하는 주기, 시점에 자동결제 승인 API를 호출해야 한다.

발급한 billingKey를 카드 자동결제 승인 API의 Path 파라미터로 추가한다. 요청 본문에는 주문 정보와 함께 customerKey를 넣어 준다.

curl --request POST \
  --url https://api.tosspayments.com/v1/billing/{billingKey} \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --data '{"customerKey":"qLKNEQdVbLshykFfzorKz","amount":4900,"orderId":"ZbiAf8QsWA979_j-TEp_N","orderName":"토스 프라임 구독","customerEmail":"customer@email.com","customerName":"박토스","taxFreeAmount":0}'
const fetch = require('node-fetch');
 
const url = 'https://api.tosspayments.com/v1/billing/{billingKey}';
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==',
    'Content-Type': 'application/json'
  },
  body: '{"customerKey":"qLKNEQdVbLshykFfzorKz","amount":4900,"orderId":"ZbiAf8QsWA979_j-TEp_N","orderName":"토스 프라임 구독","customerEmail":"customer@email.com","customerName":"박토스","taxFreeAmount":0}'
};
 
try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}

5. 결제 완료 후 응답 확인

API 호출 결과로 HTTP 200 OK 응답을 받으면 결제 성공.

Payment 객체가 응답으로 돌아오며 자동 결제는 card 필드가 포함되어 있어야 한다.

응답으로 받은 Payment 객체가 아래 예시와 다르다면 API 버전을 확인할 것.

개발자 센터의 API 키 메뉴에서 설정된 API 버전을 확인하고 변경할 수 있다.

응답

{
  "mId": "tosspayments",
  "version": "2024-06-01",
  "paymentKey": "G5ZkmSdMiKbD20aLWfkgj",
  "status": "DONE",
  "lastTransactionKey": "V4YHFbLcupFWh5wmrkl57",
  "orderId": "0idqIcrzThutspevszn7f",
  "orderName": "토스 프라임 구독",
  "requestedAt": "2022-06-08T15:40:09+09:00",
  "approvedAt": "2022-06-08T15:40:49+09:00",
  "useEscrow": false,
  "cultureExpense": false,
  "card": {
    "issuerCode": "61",
    "acquirerCode": "31",
    "number": "43301234****123*",
    "installmentPlanMonths": 0,
    "isInterestFree": false,
    "interestPayer": null,
    "approveNo": "00000000",
    "useCardPoint": false,
    "cardType": "신용",
    "ownerType": "개인",
    "acquireStatus": "READY",
    "amount": 4900
  },
  "virtualAccount": null,
  "transfer": null,
  "mobilePhone": null,
  "giftCertificate": null,
  "cashReceipt": null,
  "cashReceipts": null,
  "discount": null,
  "cancels": null,
  "secret": null,
  "type": "BILLING",
  "easyPay": null,
  "country": "KR",
  "failure": null,
  "isPartialCancelable": true,
  "receipt": {
    "url": "https://dashboard.tosspayments.com/sales-slip?transactionId=KAgfjGxIqVVXDxOiSW1wUnRWBS1dszn3DKcuhpm7mQlKP0iOdgPCKmwEdYglIHX&ref=PX"
  },
  "checkout": {
    "url": "https://api.tosspayments.com/v1/payments/G5ZkmSdMiKbD20aLWfkgj/checkout"
  },
  "currency": "KRW",
  "totalAmount": 4900,
  "balanceAmount": 4900,
  "suppliedAmount": 4455,
  "vat": 455,
  "taxFreeAmount": 0,
  "metadata": null,
  "taxExemptionAmount": 0,
  "method": "카드"
}