이 포스트는 토스비즈니스피드에서도 읽을 수 있습니다.

구독 결제 서비스 구현 순서는 다음과 같다고 설명했었는데요. 지난 포스트에서 빌링키를 발급하고 저장하는 부분까지 함께 해봤어요.

Step 1. 고객의 결제 수단 정보를 대체하는 빌링키를 발급받은 뒤
Step 2. 빌링키와 고객 정보, 구독 정보를 데이터베이스에서 관리하고
Step 3. 그 정보에 따라 동적으로 스케줄링된 결제를 실행하고 결과 확인

이번 포스트에서는 3단계인 구독 결제 시스템의 핵심인 스케줄링을 구현하고 마무리 해볼게요.

3. 스케줄링으로 결제 실행하기

스케줄링은 구독 정보에 따라 맞게 반복해서 결제를 실행하는 로직이에요. 빌링키와 고객 정보가 연결된 걸 확인했으니, 이제 정기적으로 결제할 수 있도록 스케줄링 로직을 넣을 차례에요. index.jsnode-cron 패키지를 추가해서 구현해볼게요.

월간 플랜을 매달 1일에 실행한다고 가정했어요.

// 매달 1일에 실행
cron.schedule('0 0 1 * *', function () {
  // 데이터베이스에서 billingKey와 customerKey를 가져오는 로직
  db.get(
    'SELECT customerKey, billingKey FROM user LIMIT 1',
    [],
    (error, row) => {
      if (error || !row) {
        console.error('Database error or no data: ', error);
        return;
      }

      const { customerKey, billingKey } = row;
      
      // 결제 실행하기
      got
        .post('https://api.tosspayments.com/v1/billing/' + billingKey, {
          headers: {
            Authorization:
             'Basic ' + Buffer.from(secretKey + ':').toString('base64'),
             'Content-Type': 'application/json',
          },
          json: {
            customerKey,
            amount: 4900,
            orderId: uuid(),
            orderName: '스트리밍 서비스 구독',
            customerEmail: 'customer@email.com',
            customerName: '박토스',
            taxFreeAmount: 0,
          },
          responseType: 'json',
        })
        .then(function (response) {
          console.log(response.body);
        })
        .catch(function (error) {
          console.log('Error:', error);
        });
    }
  );
});

테스트를 위에 맨 처음 cron job 설정을 매 분 실행하는 걸로 설정해 볼게요.

cron.schedule('* * * * *', function () {
  // ...
})

매 분마다 결제가 실행되는 것을 서버 콘솔에서 확인한 뒤, 다시 매달 1일에 실행하는 cron 표현식으로 바꿔주세요.

cron 표현식 이해하기

cron.schedule의 첫 번째 인자는 cron 표현식입니다. 각각 분 시 일 월 요일을 뜻하고 공백으로 구분합니다. *는 와일드 카드로 '매 번'을 의미합니다.
예제에서 사용한 cron 표현식 '0 0 1 * *'을 예시로 cron 표현식의 의미를 알아볼게요.

  • 0: 0분
  • 0: 0시 (자정)
  • 1: 매월 1일
  • *: 매월
  • *: 매 요일

node-cron은 초 설정이 생략되면 기본적으로 0초로 간주합니다. 더 자세한 내용은 cron 위키피디아에서 살펴보세요.

결제 결과 및 기록 확인하기

이제 결제 결과를 확인해볼게요.

{
  "mId": "tvivarepublica2",
  "lastTransactionKey": "748038ECC457E11C9532BB4A7B7D02E1",
  "paymentKey": "xMljweGQBN5OWRapdA8dPbZN9zYl7X8o1zEqZKLPbmD70vk4",
  "orderId": "b05c8d5b-7414-44af-9bcd-053e5eeec1e1",
  "orderName": "음악 스트리밍 구독",
  "taxExemptionAmount": 0,
  "status": "DONE",
  "requestedAt": "2023-08-08T16:30:01+09:00",
  "approvedAt": "2023-08-08T16:30:01+09:00",
  "useEscrow": false,
  "cultureExpense": false,
  "card": {
    "company": "토스뱅크",
    "issuerCode": "24",
    "acquirerCode": "21",
    "number": "53275010****222*",
    "installmentPlanMonths": 0,
    "isInterestFree": false,
    "interestPayer": null,
    "approveNo": "00000000",
    "useCardPoint": false,
    "cardType": "신용",
    "ownerType": "개인",
    "acquireStatus": "READY",
    "receiptUrl": "https://dashboard.tosspayments.com/receipt/redirection?transactionId=tviva20230808163001X5IR1&ref=PX",
    "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/receipt/redirection?transactionId=tviva20230808163001X5IR1&ref=PX"
  },
  "checkout": {
    "url": "https://api.tosspayments.com/v1/payments/xMljweGQBN5OWRapdA8dPbZN9zYl7X8o1zEqZKLPbmD70vk4/checkout"
  },
  "transactionKey": "748038ECC457E11C9532BB4A7B7D02E1",
  "currency": "KRW",
  "totalAmount": 50000,
  "balanceAmount": 50000,
  "suppliedAmount": 45455,
  "vat": 4545,
  "taxFreeAmount": 0,
  "method": "카드",
  "version": "2022-07-27"
}

이렇게 개발자센터 테스트 결제내역에서도 결제 기록을 확인할 수 있어요.

테스트 결제 내역 이미지

여기까지 간단한 구독 서비스를 구현하는 방법을 익혔어요. 빌링키와 스케줄링 두 가지를 알면 어렵지 않게 구현할 수 있어요. 실제 라이브 환경에서는 추가적인 보안 및 에러 핸들링을 추가적으로 고려해서 서비스를 만들어보세요.

번외: 카드 등록할 때 첫 결제 같이 하기

카드 등록과 동시에 첫 결제를 하고 싶다면 사용자가 카드를 등록할 때 바로 첫 결제를 하는 로직을 추가하면 돼요.

/success 라우터 내에서 billingKey를 데이터베이스에 저장한 후 바로 첫 결제를 진행합니다.

// ... 기존 코드 ...

db.run(
  'INSERT INTO user (customerKey, billingKey) VALUES (?, ?)',
  [customerKey, billingKey],
  (error) => {
    if (error) {
      console.log('Database error: ', error);
      return res.render('fail', {
        isSuccess: false,
        responseJson: error.message,
      });
    }

    // 첫 결제 로직
    got
      .post('https://api.tosspayments.com/v1/billing/' + billingKey, {
        headers: {
          Authorization:
            'Basic ' + Buffer.from(secretKey + ':').toString('base64'),
          'Content-Type': 'application/json',
        },
        json: {
          customerKey,
          amount: 4900,
          orderId: uuid(),
          orderName: '토스 프라임 구독 첫 결제',
          customerEmail: 'customer@email.com',
          customerName: '박토스',
          taxFreeAmount: 0,
        },
        responseType: 'json',
      })
      .then(function (firstPaymentResponse) {
        console.log('First payment success: ', firstPaymentResponse.body);
        res.render('success', { responseJson: response.body });
      })
      .catch(function (firstPaymentError) {
        console.log('First payment error: ', firstPaymentError);
        res.render('fail', {
          isSuccess: false,
          responseJson: firstPaymentError.response.body,
        });
      });
  }
);

// ... 기존 코드 ...

이렇게 하면 첫 결제가 빌링키 발급과 동시에 완료되고 다음 결제일이 설정됩니다.


지금까지 간단한 구독 서비스를 구현해 봤어요. 이 튜토리얼에서는 하나의 플랜을 가지고 정기결제를 내는 로직만 구현하고 있어요. 또, 결제 주기가 매달 1일로 고정되어 있습니다. 실제 서비스 환경에서는 사용자가 원하는 다양한 결제 주기(매주, 매 2주, 매 3개월 등)를 선택할 수 있도록 스케줄링 로직을 확장해서 구현해 보세요.

혹시 구독 서비스처럼 정기적으로 발생하는 결제가 아니라 원하는 시점에 결제를 내고 싶다면 자체 간편결제 서비스인 브랜드페이를 연동하세요.

토스페이먼츠 Twitter를 팔로우하시면 더욱 빠르게 블로그 업데이트 소식을 만나보실 수 있어요.


Writer 한주연

profile
개발자들이 만든, 개발자들을 위한 PG사 토스페이먼츠입니다.

0개의 댓글