구독 결제 서비스 간단히 구현하기 (2)

domaelist.com / 2023-11-02

구독 결제 서비스는 정기적인 구매를 통해 기업의 매출을 안정시키고, 고객이 우리 서비스를 계속 사용하게 유도할 수 있다는 장점이 있어요. 구독 서비스에 고객이 결제수단을 등록하고 자동으로 결제하도록 하면, 고객이 구매할 때마다 우리 서비스에 재방문하게 만드는 것보다 훨씬 쉽게 결제가 이루어지죠. 고객 입장에서도 장점이 있는데요. 결제 수단을 등록해 두면 매번 결제 과정을 거칠 필요 없이 편리하게 결제할 수 있어요.


이렇게 장점이 많은 구독 서비스, 토스페이먼츠의 결제창 SDK와 빌링키 발급 API를 이용해서 구현할 수 있어요. 토스페이먼츠에서는 결제를 일으킬 수 있는 빌링키 발급까지 제공하고, 그 외 구독 서비스 시스템은 직접 만들어야 하는데요. 이 시리즈에서는 빌링키를 발급한 후 간단한 구독 서비스를 구현하는 방법을 알려드려요.


이해하기

구독 서비스를 구현하려면 다음 세 가지가 꼭 필요해요.


빌링키 (결제 수단 정보)

고객 정보 및 구독 정보 (고객 키, 결제 주기, 결제 금액, 결제 상품 등)

스케줄링 로직 (결제 주기에 따른 결제 실행 로직)

구독 서비스에서 가장 중요한 빌링키를 한 마디로 정의하자면 “암호화된 고객의 결제 정보”예요. 가맹점이 고객 대신 특정 시점에 결제하려면 고객의 결제 정보가 필요하죠. 하지만 결제 수단 정보를 그대로 저장해서 사용할 수 없기 때문에 고객의 카드번호, 유효기간, CVC 등 카드 정보를 암호화한 빌링키를 대신 사용해요. 이 빌링키가 고객을 식별하는 고객키와 매핑되어 어떤 고객의 결제인지 알 수 있어요. 빌링키는 카드사에서 직접 발행하고, 토스페이먼츠는 카드사가 발행한 빌링키를 전달해 줘요.


구독 정보는 월간, 연간 같은 결제 주기 정보와 결제 금액, 결제 상품 등의 정보를 포함해요. 스케줄링은 이 구독 정보에 따라 맞게 반복해서 결제를 실행하는 로직이라고 이해하면 돼요.


즉, 구독 서비스 구현은 다음과 같은 순서로 이루어져요.


Step 1. 고객의 결제 수단 정보를 대체하는 빌링키를 발급받은 뒤


Step 2. 빌링키와 고객 정보, 구독 정보를 데이터베이스에서 관리하고


Step 3. 그 정보에 따라 동적으로 스케줄링된 결제를 실행하고 결과 확인



우리는 매달 1일에 같은 제품을 결제하는 구독 서비스를 가정하고 구현해 볼 건데요, 이 포스트에서는 2번까지 진행하고 다음 포스트에서 3번을 구현해볼게요.



EJS + Express 프로젝트 구조는 다음과 같아요. GitHub에서 샘플 프로젝트로 시작해보세요.



routes/index.js                // 빌링키 발급, 저장, 스케줄링 로직이 있는 서버 코드

client

  ㄴ index.ejs                 // 카드 정보 등록을 위한 페이지

  ㄴ success.ejs               // 카드 정보 등록 성공 페이지    

  ㄴ fail.ejs                  // 카드 정보 등록 실패 페이지      

app.js.                       // 기본 서버 설정

그럼 이제 시작해볼게요!



1. 빌링키 발급하기

먼저 다음과 같은 카드 등록창을 추가할게요. 카드 등록창으로 고객의 카드 정보를 인증하고 등록하면 그 카드 정보를 대신하는 빌링키를 발급할 수 있어요.



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


Step 1. 고객의 결제 수단 정보를 대체하는 빌링키를 발급받은 뒤


Step 2. 빌링키와 고객 정보, 구독 정보를 데이터베이스에서 관리하고


Step 3. 그 정보에 따라 동적으로 스케줄링된 결제를 실행하고 결과 확인


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



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

스케줄링은 구독 정보에 따라 맞게 반복해서 결제를 실행하는 로직이에요. 빌링키와 고객 정보가 연결된 걸 확인했으니, 이제 정기적으로 결제할 수 있도록 스케줄링 로직을 넣을 차례에요. index.js에 node-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 설정을 매 분 실행하는 걸로 설정해 볼게요. 


1

2

3

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개월 등)를 선택할 수 있도록 스케줄링 로직을 확장해서 구현해 보세요.


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




자료출처 : 구독 결제 서비스 간단히 구현하기 (2) (tossbusiness.com)

Document

필독 정보 모음

마케터를 위한 도구