npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

payment-backend

v4.0.0

Published

Payment Backend Server

Downloads

2

Readme

Payment Backend

1. Outline

GitHub license npm version Downloads Build Status

payment-backend 는 통합 결제 서버를 구현한 프로젝트이다.

여기서 말하는 통합 결제란, 아임포트토스 페이먼츠 등, 여러 PG 사들을 일괄 관리할 수 있다는 뜻이다. 더하여 payment-backend 는 MSA (Micro Service Architecture) 를 고려하여 설계된 프로젝트로써, 귀하의 서비스 중 결제 부문만을 따로이 분리하여 관리할 수 있다.

또한 payment-backend 가 연동하게 되는 결제 PG 사들은 본디 프론트 어플리케이션과 연동한 수기 테스트가 필요하다. 이 때문에 이들 결제 PG 사들과 연동해야 하는 결제 서버들은, 테스트 자동화 프로그램을 작성할 수 없기에, 필연적으로 테스트 커버리지가 낮아 매우 불안정해진다. 하지만 payment-backend 는 결제 PG 사들의 API 를 흉내낸 가짜 PG 서버들을 구현, 이들을 통하여 테스트 자동화 프로그램을 구성함으로써 안정성을 담보한다.

더불어 payment-backendpayment-api 라 하여, 통합 결제 서버와 연동할 수 있는 SDK 라이브러리를 제공한다. 귀하는 이 payment-api 를 통하여, 통합 결제 서버와 매우 손쉽게 연동할 수 있고, 이를 통하여 결제 부문에 관련된 MSA (Micro Service Architecture) 를 매우 안전하게 구성할 수 있다.

그리고 만일 귀하가 payment-backend 와의 연동을, 제공되는 SDK 를 활용하는 것이 아닌 API 스펙을 보고 직접 구현하고자 한다면, 반드시 알아두어야 할 것이 하나 있다. 그것은 바로 payment-backend 가 모든 request 및 response body 에 적재하는 JSON 데이터를, 보안 강화를 위하여 AES 알고리즘으로 암호화한다는 것이다.

import { TestValidator } from "@nestia/e2e";
import api from "payment-api";
import { IPaymentHistory } from "payment-api/lib/structures/payments/IPaymentHistory";
import { IPaymentWebhookHistory } from "payment-api/lib/structures/payments/IPaymentWebhookHistory";
import toss from "toss-payments-server-api";
import { ITossPayment } from "toss-payments-server-api/lib/structures/ITossPayment";
import { sleep_for } from "tstl/thread/global";
import typia from "typia";
import { v4 } from "uuid";

import { PaymentConfiguration } from "../../../src";
import { FakePaymentStorage } from "../../../src/providers/payments/FakePaymentStorage";
import { TossAsset } from "../../../src/services/toss/TossAsset";

export async function test_api_toss_vbank_payment(
    connection: api.IConnection,
): Promise<IPaymentHistory> {
    //----
    // 결제의 원천이 되는 주문 정보
    //----
    /**
     * 귀하의 백엔드 서버가 발행한 주문 ID.
     */
    const yourOrderId: string = v4();

    /**
     * 주문 금액.
     */
    const yourOrderPrice: number = 19_900;

    /* -----------------------------------------------------------
        결제 내역 등록
    ----------------------------------------------------------- */
    /**
     * 토스 페이먼츠 시뮬레이션
     *
     * 고객이 프론트 어플리케이션에서, 토스 페이먼츠가 제공하는 팝업 창을 이용, 카드 결제를
     * 하는 상황을 시뮬레이션 한다. 고객이 카드 결제를 마치거든, 프론트 어플리케이션에
     * {@link ITossPayment.paymentKey} 가 전달된다.
     *
     * 이 {@link ITossPayment.paymentKey} 와 귀하의 백엔드에서 직접 생성한
     * {@link ITossPayment.orderId yourOrderId} 를 잘 기억해두었다가, 이를 다음 단계인
     * {@link IPaymentHistory} 등록에 사용하도록 하자.
     */
    const payment: ITossPayment =
        await toss.functional.v1.virtual_accounts.store(
            await TossAsset.connection("test-toss-payments-store-id"),
            {
                // 가상 계좌 정보
                method: "virtual-account",
                bank: "신한",
                customerName: "Samchon",

                // 주문 정보
                orderId: yourOrderId,
                orderName: "something",
                amount: yourOrderPrice,

                // 고의 미승인 처리
                __approved: false,
            },
        );
    typia.assert(payment);

    /**
     * 웹훅 URL 설정하기.
     *
     * 웹훅 URL 을 테스트용 API 주소, internal.webhook 으로 설정.
     */
    const webhook_url: string = `http://127.0.0.1:${PaymentConfiguration.API_PORT()}${
        api.functional.payments.internal.webhook.METADATA.path
    }`;

    /**
     * 결제 이력 등록하기.
     *
     * 앞서 토스 페이먼츠의 팝업 창을 이용하여 가상 계좌 결제를 진행하고 발급받은
     * {@link ITossPayment.paymentKey}, 그리고 귀하의 백엔드에서 직접 생성한
     * {@link ITossPayment.orderId yourOrderId} 를 각각 {@link IPaymentVendor.uid} 와
     * {@link IPaymentSource.id} 로 할당하여 {@link IPaymentReservation} 레코드를
     * 발행한다.
     *
     * 참고로 결제 이력을 등록할 때 반드시 비밀번호를 설정해야 하는데, 향후 결제 이력을
     * 조회할 때 필요하니, 이를 반드시 귀하의 백엔드 서버에 저장해두도록 한다.
     */
    const history: IPaymentHistory =
        await api.functional.payments.histories.store(connection, {
            vendor: {
                code: "toss.payments",
                store_id: "test-toss-payments-store-id",
                uid: payment.paymentKey,
            },
            source: {
                schema: "some-schema",
                table: "some-table",
                id: yourOrderId,
            },
            webhook_url, // 테스트용 웹훅 URL
            price: yourOrderPrice,
            password: "some-password",
        });
    typia.assert(history);

    /* -----------------------------------------------------------
        웹훅 이벤트 리스닝
    ----------------------------------------------------------- */
    /**
     * 입금 시뮬레이션하기.
     *
     * 고객이 자신 앞을 발급된 계좌에, 결제 금액을 입금하는 상황 시뮬레이션.
     */
    await toss.functional.internal.deposit(
        await TossAsset.connection("test-toss-payments-store-id"),
        payment.paymentKey,
    );

    // 웹훅 이벤트가 귀하의 백엔드 서버로 전달되기를 기다림.
    await sleep_for(1_000);

    /**
     * 웹흑 리스닝 시뮬레이션.
     *
     * 귀하의 백엔드 서버가 웹훅 이벤트를 수신한 상황을 가정한다.
     */
    const webhook: IPaymentWebhookHistory | undefined =
        FakePaymentStorage.webhooks.back();

    // 이하 웹훅 데이터를 통한 입금 여부 검증
    TestValidator.equals("webhook")(!!webhook)(true);
    TestValidator.equals("history.id")(history.id)(webhook?.current.id);
    TestValidator.equals("paid_at")(!!webhook?.previous.paid_at)(false);
    TestValidator.equals("paid_at")(!!webhook?.current.paid_at)(true);

    // 웹훅 데이터 삭제
    FakePaymentStorage.webhooks.pop_back();

    return history;
}

2. Installation

2.1. NodeJS

본 서버 프로그램은 TypeScript 로 만들어졌으며, NodeJS 에서 구동된다.

고로 제일 먼저 할 일은, NodeJS 를 설치하는 것이다. 아래 링크를 열어, NodeJS 프로그램을 다운로드 받은 후 즉각 설치하기 바란다. 참고로 NodeJS 버전은 어지간히 낮은 옛 시대의 버전만 아니면 되니, 구태여 latest 버전을 설치할 필요는 없으며, stable 버전만으로도 충분하다.

  • https://nodejs.org/en/

2.2. PostgreSQL

본 서버는 PostgreSQL 을 사용하고 있다.

따라서 로컬에서 DB 서버를 개발하고 구동하려거든, PostgreSQL 이 반드시 설치되어있어야 한다. 아래 링크를 참조하여, PostgreSQL 14 버전을 설치할 것. 단, PostgreSQL 을 설치하면서, StackBuilder 와 PostGIS 도 함께 설치해줘야 한다.

  • https://www.enterprisedb.com/downloads/postgres-postgresql-downloads
  • https://postgis.net/workshops/postgis-intro/installation.html

PostgreSQL installer

StackBuilder installer

그리고 만일 개발 환경이 윈도우라면, 환경변수 PATH 에 PostgreSQL 이 설치된 경로의 bin 폴더를 추가해준다. 아마도 그 경로는 C:\Program Files\PostgreSQL\14\bin 일 것이다. 맥북의 경우에는 /Applications/Postgres.app/Contents/MacOS/bin 이다.

이후 PostgreSQL 터미널로 접속, samchon DB 와 payments 스키마를 각각 생성해준다. 그리고 samchonsamchon_r 계정을 생성하여, 각각 쓰기 및 읽기 권한을 부여한다.

만일 로컬 PostgreSQL 의 계정을 postgres, 그리고 비밀번호를 root 로 설정하였다면, npm run schema 명령어로 아래 SQL 스크립트를 대체할 수 있다.

-- CREATE USER
CREATE ROLE samchon_w WITH ENCRYPTED PASSWORD 'https://github.com/samchon';
GRANT samchon_w TO postgres;

-- CREATE DB & SCHEMA
CREATE DATABASE samchon OWNER samchon_w;
CREATE SCHEMA payments AUTHORIZATION samchon_w;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA payments TO samchon;

-- READABLE ACCOUNT
CREATE USER samchon_r WITH ENCRYPTED PASSWORD 'https://github.com/samchon';
GRANT CONNECT ON DATABASE samchon TO samchon_r;
GRANT USAGE ON SCHEMA payments TO samchon_r;
GRANT SELECT ON ALL TABLES IN SCHEMA payments TO samchon_r;

2.3. Server

NodeJS 및 PostgreSQL 의 설치가 끝났다면, 바로 payment-backend 구동을 시작할 수 있다.

제일 먼저 git clone 을 통하여, 결제 서버 프로젝트를 로컬 저장소에 복사하도록 한다. 그리고 해당 폴더로 이동하여 npm install 명령어를 실행함으로써, 통합 결제 서버를 구동하는 데 필요한 라이브러리들을 다운로드 한다. 그리고 npm run build 명령어를 입력하여, 결제 서버의 소스 코드를 컴파일한다. 마지막으로 npm run start 명령어를 실행해주면, 결제 서버가 구동된다.

다만 payment-backend 를 구동하기 전, 각각 PaymentConfigurationPaymentGlobal 클래스에 어떠한 속성들이 있는지 꼼꼼히 읽어보고, 귀하의 서비스에 알맞는 설정을 해 주도록 한다.

# CLONE REPOSITORY
git clone https://github.com/samchon/payments
cd packages/payment-backend/payments

# INSTALLATION & COMPILATION
npm install
npm run build
npm run schema

# START SERVER & STOP SERVER
npm run start
npm run stop

npm version Downloads

더하여 payment-backend 는 npm 모듈로 설치하여 import 할 수 있다.

이러한 방식은 payment-backend 와 연동하는 백엔드 서버를 개발할 때 특히 유용하다. 해당 백엔드 서버의 안정성을 상시 보증하기 위하여 테스트 자동화 프로그램을 개발할 때, 테스트 자동화 프로그램에서 paments-server 의 설정과 개설 및 폐쇄를 완전히 통제할 수 있기 때문이다.

따라서 귀하의 백엔드 서버가 TypeScript 내지 JavaScript 를 사용한다면, 테스트 자동화 프로그램을 구성함에 있어 github 저장소를 clone 하고 payment-backend 를 별도 구동하기보다, 귀하의 백엔드 서버 테스트 프로그램에서 payment-backend 모듈을 import 후 그것의 개설과 폐쇄를 직접 통제하는 것을 권장한다.

그리고 이렇게 테스트 자동화 프로그램으로 payment-backendimport 하여 사용할 때 역시, 각각 PaymentConfigurationPaymentGlobal 클래스에 어떠한 속성들이 있는지 꼼꼼히 읽어보고, 귀하의 서비스에 알맞는 설정을 해 주도록 한다.

// npm install --save-dev payment-backend
import payments from "payment-backend";

async function main(): Promise<void>
{
    // UPDATOR SERVER OPENING
    const updator = await payments.PaymentUpdator.master();

    // BACKEND OPENING
    const backend: payments.PaymentBackend = new payments.PaymentBackend();
    await backend.open();

    // CLOSING
    await backend.close();
    await updator.close();
}

2.4. Software Development Kit

npm version Downloads

본 프로젝트 payment-backend 는 연동을 위한 SDK 라이브러리를 제공한다.

귀하는 이 payment-api 를 통하여, 통합 결제 서버와 매우 손쉽게 연동할 수 있고, 이를 통하여 결제 부문에 관련된 MSA (Micro Service Architecture) 를 매우 안전하게 구성할 수 있다.

npm install --save payment-api 명령어를 통하여 통합 결제와의 연동을 위한 SDK 라이브러리를 설치한 후, 아래 매뉴얼 및 예제 코드를 참고하여 귀하의 백엔드 서비스가 필요로 하는 결제 기능을 개발하도록 한다.

import { TestValidator } from "@nestia/e2e";
import imp from "iamport-server-api";
import { IIamportPayment } from "iamport-server-api/lib/structures/IIamportPayment";
import { IIamportResponse } from "iamport-server-api/lib/structures/IIamportResponse";
import PaymentAPI from "payment-api";
import { IPaymentHistory } from "payment-api/lib/structures/payments/IPaymentHistory";
import { IPaymentWebhookHistory } from "payment-api/lib/structures/payments/IPaymentWebhookHistory";
import { sleep_for } from "tstl/thread/global";
import typia from "typia";
import { v4 } from "uuid";

import { PaymentConfiguration } from "../../../src";
import { FakePaymentStorage } from "../../../src/providers/payments/FakePaymentStorage";
import { IamportAsset } from "../../../src/services/iamport/IamportAsset";

export async function test_api_iamport_vbank_payment(
    connection: PaymentAPI.IConnection,
): Promise<IPaymentHistory> {
    //----
    // 결제의 원천이 되는 주문 정보
    //----
    /**
     * 귀하의 백엔드 서버가 발행한 주문 ID.
     */
    const yourOrderId: string = v4();

    /**
     * 주문 금액.
     */
    const yourOrderPrice: number = 19_900;

    /* -----------------------------------------------------------
        결제 내역 등록
    ----------------------------------------------------------- */
    /**
     * 아임포트 시뮬레이션.
     *
     * 고객이 프론트 어플리케이션에서, 아임포트가 제공하는 팝업 창을 이용, 가상 계좌
     * 결제를 하는 상황을 시뮬레이션 한다. 고객이 가상 계좌 발급을 마치거든, 프론트
     * 어플리케이션에 {@link IIamportPayment.imp_uid} 가 전달된다.
     *
     * 이 {@link IIamportPayment.imp_uid} 와 귀하의 백엔드에서 직접 생성한
     * {@link IIamportPayment.merchant_uid yourOrderId} 를 잘 기억해두었다가, 이를
     * 다음 단계인 {@link IPaymentHistory} 등록에 사용하도록 하자.
     */
    const payment: IIamportResponse<IIamportPayment> =
        await imp.functional.vbanks.store(
            await IamportAsset.connection("test-iamport-store-id"),
            {
                merchant_uid: yourOrderId,
                amount: yourOrderPrice,
                vbank_code: "SHINHAN",
                vbank_due: Date.now() / 1_000 + 7 * 24 * 60 * 60,
                vbank_holder: "Samchon",
            },
        );
    typia.assert(payment);

    /**
     * 웹훅 URL 설정하기.
     *
     * 웹훅 URL 을 테스트용 API 주소, internal.webhook 으로 설정.
     */
    const webhook_url: string = `http://127.0.0.1:${PaymentConfiguration.API_PORT()}${
        PaymentAPI.functional.payments.internal.webhook.METADATA.path
    }`;

    /**
     * 결제 이력 등록하기.
     *
     * 앞서 아임포트의 팝업 창을 이용하여 가상 계좌 결제를 진행하고 발급받은
     * {@link IIamportPayment.imp_uid}, 그리고 귀하의 백엔드에서 직접 생성한
     * {@link IIamportPayment.merchant_uid yourOrderId} 를 각각
     * {@link IPaymentVendor.uid} 와 {@link IPaymentSource.id} 로 할당하여
     * {@link IPaymentReservation} 레코드를 발행한다.
     *
     * 참고로 결제 이력을 등록할 때 반드시 비밀번호를 설정해야 하는데, 향후 결제 이력을
     * 조회할 때 필요하니, 이를 반드시 귀하의 백엔드 서버에 저장해두도록 한다.
     */
    const history: IPaymentHistory =
        await PaymentAPI.functional.payments.histories.store(connection, {
            vendor: {
                code: "iamport",
                store_id: "test-iamport-store-id",
                uid: payment.response.imp_uid,
            },
            source: {
                schema: "some-schema",
                table: "some-table",
                id: yourOrderId,
            },
            webhook_url, // 테스트용 웹훅 URL
            price: yourOrderPrice,
            password: "some-password",
        });
    typia.assert(history);

    /* -----------------------------------------------------------
        웹훅 이벤트 리스닝
    ----------------------------------------------------------- */
    /**
     * 입금 시뮬레이션하기.
     *
     * 고객이 자신 앞을 발급된 계좌에, 결제 금액을 입금하는 상황 시뮬레이션.
     */
    await imp.functional.internal.deposit(
        await IamportAsset.connection("test-iamport-store-id"),
        payment.response.imp_uid,
    );

    // 웹훅 이벤트가 귀하의 백엔드 서버로 전달되기를 기다림.
    await sleep_for(1_000);

    /**
     * 웹흑 리스닝 시뮬레이션.
     *
     * 귀하의 백엔드 서버가 웹훅 이벤트를 수신한 상황을 가정한다.
     */
    const webhook: IPaymentWebhookHistory | undefined =
        FakePaymentStorage.webhooks.back();

    // 이하 웹훅 데이터를 통한 입금 여부 검증
    TestValidator.equals("webhook")(!!webhook)(true);
    TestValidator.equals("history.id")(history.id)(webhook?.current.id);
    TestValidator.equals("paid_at")(!!webhook?.previous.paid_at)(false);
    TestValidator.equals("paid_at")(!!webhook?.current.paid_at)(true);

    // 웹훅 데이터 삭제
    FakePaymentStorage.webhooks.pop_back();

    return history;
}

3. Development

3.1. Definition

백엔드 서버에 새 API 를 추가하고 기능을 변경하는 일 따위는 물론, API 컨트롤러, 즉 src/controllers 의 코드를 수정함으로써 이루어진다. 하지만 payment-backend 는 신규 API 가 필요하거나 혹은 기존 API 의 변경 필요할 때, 대뜸 Main Program 의 코드부터 작성하고 보는 것을 매우 지양한다. 그 대신 payment-backend 는 API 의 인터페이스만을 먼저 정의하고, Main Program 의 구현은 나중으로 미루는 것을 지향한다.

따라서 payment-backend 에 새 API 를 추가하려거든, src/controllers 에 새 API 의 인터페이스만을 먼저 정의해준다. 곧이어 npm run build:api 명령어를 통하여, API Library 를 빌드한다. 경우에 따라서는 서비스 서버와의 동시 개발을 위하여, 새로이 빌드된 SDK 를 그대로 npm run package:api 해 버려도 좋다.

이후 로컬에서 새로이 생성된 SDK 와 해당 API 를 이용, 유즈케이스 시나리오를 테스트 자동화 프로그램으로 작성한다. 이후 Main Program 을 제작하며, 앞서 작성해 둔 테스트 자동화 프로그램으로 상시 검증한다. 마지막으로 Main Program 까지 완성되면 이를 배포하면 된다.

이하 payment-backend 의 개략적인 개발 순서를 요약하면 아래와 같다.

  • API Interface Definition
  • API Library (SDK) 빌드
  • Test Automation Program 제작
  • Main Program 제작 및 테스트 자동화 프로그램을 이용한 상시 검증
  • DEV 및 REAL 서버에 배포

3.2. Test Automation Program

Build Status

npm run test

새로이 개발할 API 인터페이스 정의를 마쳤다면, 그 다음에 할 일은 바로 해당 API 에 대한 유즈케이스 시나리오를 세우고 이를 테스트 자동화 프로그램을 만들어, 향후 Main Program 제작시 이를 상시 검증할 수 있는 수단을 구비해두는 것이다 - TDD (Test Driven Development).

그리고 본 프로젝트는 npm run test 라는 명령어를 통하여, 서버 프로그램의 일체 기능 및 정책 등에 대하여 검증할 수 있는, 테스트 자동화 프로그램을 구동해 볼 수 있다. 더불어 테스트 자동화 프로그램은 순수하게 payment-backend 의 메인 서버 프로그램 뿐 아니라, 통합 결제 서버와 연동하는 다양한 외부 PG 사 시스템들도, 가상으로 구동하게 된다.

그리고 만약 새 테스트 로직을 추가하고 싶다면, src/test/features 내 적당한 위치에 새 ts 파일을 하나 만들고, test_ 로 시작하는 함수를 하나 만들어 그 안에 테스트 로직을 작성한 후, 이를 export 심벌을 이용하여 배출해주면 된다. 이에 대한 자세한 내용은 src/test/features 폴더에 들어있는 모든 ts 파일 하나 하나가 다 좋은 예제 격이니, 이를 참고하도록 한다.

import { v4 } from "uuid";
import { sleep_for } from "tstl/thread/global";

import imp from "iamport-server-api";
import payments from "../../../../api";
import { IIamportPayment } from "iamport-server-api/lib/structures/IIamportPayment";
import { IIamportResponse } from "iamport-server-api/lib/structures/IIamportResponse";
import { IPaymentHistory } from "../../../../api/structures/IPaymentHistory";
import { IPaymentWebhook } from "../../../../api/structures/IPaymentWebhook";

import { FakePaymentStorage } from "../../../../providers/FakePaymentStorage";
import { IamportAsset } from "../../../../services/iamport/IamportAsset";
import { PaymentConfiguration } from "../../../../PaymentConfiguration";

export async function test_fake_iamport_payment_webhook
    (connection: payments.IConnection): Promise<void>
{
     const yourOrderId: string = v4(); // 귀하의 서비스가 발행한 주문 ID.
     const yourOrderPrice: number = 19_900; // 주문 금액

    // 아임포트 가상 계좌 결제 시뮬레이션
    const payment: IIamportResponse<IIamportPayment> = 
        await imp.functional.vbanks.store
        (
            await IamportAsset.connection("test-iamport-store-id"),
            {
                merchant_uid: yourOrderId,
                amount: yourOrderPrice,
                vbank_code: "SHINHAN",
                vbank_due: Date.now() / 1_000 + 7 * 24 * 60 * 60,
                vbank_holder: "남정호"
            }
        );

    // 웹훅 URL 설정하기.
    const webhook_url: string = "http://127.0.0.1:"
        + PaymentConfiguration.API_PORT
        + payments.functional.internal.webhook.PATH;
    
    // 결제 이력 등록하기
    const history: IPaymentHistory = await payments.functional.histories.store
    (
        connection,
        {
            vendor: {
                code: "iamport",
                store_id: "test-iamport-store-id",
                uid: payment.response.imp_uid,
            },
            source: {
                schema: "some-schema",
                table: "some-table",
                id: yourOrderId
            },
            webhook_url, // 테스트용 웹훅 URL
            price: yourOrderPrice,
            password: "some-password",
        }
    );
    
    // 가상 계좌 입금 시뮬레이션
    await imp.functional.internal.deposit
    (
        await IamportAsset.connection("test-iamport-store-id"),
        payment.response.imp_uid
    );

    // 웹훅 이벤트가 귀하의 백엔드 서버로 전달되기를 기다림.
    await sleep_for(100);

    // 웹훅 이벤트 리스닝 시뮬레이션
    const webhook: IPaymentWebhook = FakePaymentStorage.webhooks.back();
    if (webhook.current.id !== history.id)
        throw new Error("Bug on PaymentWebhooksController.iamport(): failed to deliver the webhook event.");
    else if (webhook.previous.paid_at !== null)
        throw new Error("Bug on PaymentWebhookProvider.process(): failed to delivery the exact previous data.");
    else if (webhook.current.paid_at === null)
        throw new Error("Bug on PaymentWebhookProvider.process(): failed to delivery the exact current data.");

    // 웹훅 데이터 삭제
    FakePaymentStorage.webhooks.pop_back();
}

3.3. Main Program

API 인터페이스를 정의하고 그에 관련된 테스트 자동화 프로그램을 제작하였다면, 마지막으로 남은 일은 바로 서버의 메인 프로그램을 작성, 해당 API 를 완성하는 것이다. 앞서 정의한 API 인터페이스 메서드 내에, 상세 구현 코드를 작성하고, 이를 테스트 자동화 프로그램을 통하여 상시 검증하도록 하자.

단, 모든 소스 코드를 전부 API 컨트롤러의 메서드에 작성하는 우는 범하지 않기를 바란다. API 컨트롤러는 단지 매개체 + a 의 역할만을 해야 할 뿐이며, 주 소스 코드는 src 폴더 내 각 폴더의 분류에 따라 알맞게 나뉘어 작성되어야 한다. 특히, DB 를 통한 데이터 입출력에 관해서는 가급 src/providers 를 경유하도록 할 것.

더하여 통합 결제 서버의 설정 정보는 모두 src/PaymentConfiguration.ts 에 몰아두었으니, 이 설정 정보들을 귀하의 서비스에 알맞게 수정하는 것 또한 잊지 말기 바란다.

3.4. Encryption

모든 데이터는 암호화되어 전송되거나 저장된다.

  • 암호화 방식
    • AES-128/256
    • CBC mode
    • PKCS #5 Padding
    • Base64 Encoding

본 통합 결제 서버 payment-backend 는 보안을 강화하기 위하여, http 프로토콜로 전송되는 모든 body 데이터를 암호화한다 이는 request bodyresponse body 양쪽 모두 해당되는 이야기이며, 설사 http 대신 https 프로토콜을 사용한다 하더라도 예외는 없다.

더하여 payment-backend 는 결제를 비롯한 모든 민감 데이터들을 암호화하여 저장하고 있다. 또한, 각 암호화 항목마다 각기 다른 secret key 및 initialization vector 를 사용함으로써, 보안을 한층 더 강화하고 있다. 그리고 이러한 민감 데이터들은 일괄 조회가 불가능하며, 오직 개별 단위의 조회만 가능하다. 이 개별 단위의 조회조차, 해당 레코드의 비밀번호를 모르면 일절 조회할 수 없다.

payment-backend 에는 이처럼 보안 강화를 위한 강력한 암호화 정책들이 존재한다. 혹여 귀하가 본 payment-backend 를 확장하여 몇 가지 기능을 더 개발한다 하더라도, 이러한 암호화 원칙들은 부디 지켜주었으면 한다.

4. Deploy

4.1. Non-distruptive Update System

만일 귀하가 통합 결제 서버 payment-backend 의 코드를 수정하고 이를 커밋하였다면, 귀하는 이를 기존의 서버 인스턴스를 종료하는 일 없이, 무중단 업데이트를 수행할 수 있다. npm run update 명령어를 입력함으로써, 이러한 무중단 업데이트는 실행된다.

  • Pull new commit
  • Build the new soure code
  • Restart the backend server without distruption

이러한 무중단 업데이트를 달성하기 위해서는, 서버 인스턴스는 메인 백엔드 서버 프로그램을 시작하기 전, 업데이트 프로그램을 실행해 줄 필요가 있다. 만일 귀하의 서버 인스턴스가 ELB (Elastic Loader Balancer) 등을 통하여 여러 대로 구성되어있고, 현재의 인스턴스가 슬레이브라면, npm run start:updator:slave 명령어를 실행해주면 된다.

반면 현재가 마스터 인스턴스라면, npm run start:updator:master 명령어를 실행하도록 한다.

#----
# RUN UPDATOR PROGRAM
#----
# THE INSTANCE IS MASTER
npm run start:updator:master

# THE INSTANCE IS SLAVE
npm run start:updator:slave

#----
# MOUNT THE BACKEND SERVER UP
#----
npm run start real

4.2. Local Server

간혹 로컬에, 테스트 자동화 프로그램이 아닌, payment-backend 그 자체를 구동해야 할 때가 있다. 이럴 때는 아래와 같이 npm run start local 명령어를 입력해주면, 로컬에 payment-backend 서버를 개설할 수 있다. 그리고 실행된 서버를 종료하려거든, npm run stop 명령어를 입력해주면 된다.

npm run start local
npm run stop

또한, 로컬 개발 환경에서의 무중단 업데이트가 얼마나 의미가 있겠냐만은, 어쨋든 payment-backend 는 로컬 환경에서도 무중단 업데이트라는 것을 할 수 있다. 아래와 같이 로컬 서버를 구동하기 전 npm run start updator:master 명령어를 통하여 업데이트 관리자 프로그램을 구동하고, 향후 무중단 업데이트가 필요할 때마다 npm run update local 명령어를 입력해주면 된다.

# START THE LOCAL BACKEND SERVER WITH UPDATOR PROGRAM
npm run start updator:master
npm run start local

# UPDATE THE LOCAL SERVER WITHOUT DISTRUPTION
npm run update local

4.3. Dev Server

Dev 서버를 업데이트하는 것은 매우 간단하다. 그저 소스 코드를 dev 브랜치에 커밋한 후, 로컬 개발환경에서 npm run update dev 명령어를 입력해주면 끝이다. 이로써 Dev 서버의 소스 코드는 가장 최신의 것으로 바뀌며, 동시에 무중단 업데이트가 실행되어 이것이 서버 API 에 적용될 뿐이다.

npm run update dev

다만 dev 서버의 경우, PostgreSQL 이 별도의 RDS 로 구성된 게 아닌, payment-backend 가 설치되고 가동되는 EC2 인스턴스에 함께 설치되기도 한다. 그리고 dev 서버는 로컬 서버와 마찬가지로 테스트 용도를 위하여 개설된 목적인 바, 경우에 따라 DB 를 초기화하고 재 구성해야 하는 경우 또한 생기기 마련이다.

이 경우, 아래와 같이 npm run ssh:dev 명령어를 입력하여 dev 서버로 접속한 후, npm run reset:dev 명령어를 입력해주면 된다. 이 명령어는 dev 서버의 소스코드를 가장 최신의 것으로 변경한 후, payment-backend 의 백엔드 및 업데이트 서버를 종료하고, 테스트 프로그램을 가동함으로써 DB 를 초기화하고 필수 및 샘플 데이터를 재 구성한 후, 종료된 payment-backend 의 백엔드와 업데이트 서버를 재 시작해주는 역할을 한다.

# 다음 두 명령어로 리셋 가능
npm run ssh:dev
npm run reset:dev

# 참고사항 - npm run reset:dev 를 구성하는 명령어 셋
git pull
npm install
npm run build
pm2 stop all
npm run test -- --mode=dev
npm run start:updator:master
npm run start dev

더하여 payment-backend 를 개발하다보면, 문득 현재 가동 중인 payment-backend 서버의 정보가 이리저리 궁금해질 수 있다. 가령 현재 가동 중인 dev 서버가 사용 중인 소스 코드가 무엇인지 알고 싶어, 해당 서버가 사용 중인 소스 코드의 commit 에 대한 hash code 를 알고싶을 수도 있는 법이다.

이 때는 망설이지 말고 바로 아래와 같이, npm run monitor dev 명령을 수행해주면, 바로 현재의 dev 서버에 대한 각종 정보를 취득할 수 있다. 취득할 수 있는 정보는 아래와 같이 대분류 주제로는 두 가지, 그리고 소분류로는 다섯 가지가 있다.

  • 퍼포먼스 정보: IPerformance
    • CPU 사용량
    • 메모리 사용량
    • 리소스 사용량
  • 시스템 정보: ISystem
    • 커밋 정보: 현 서버가 사용 중인 소스 코드의 커밋에 관한 정보
    • 패키지 정보: package.json
    • 기타 서버 개설 일시 정보 등
npm run monitor dev

4.4. Real Server

Real 서버를 업데이트하는 일 또한 dev 서버 때와 마찬가지로 매우 간단하다. 그저 편집한 소스 코드를 master 브랜치에 커밋하고, 로컬 개발 환경에서 npm run update real 명령어를 실행함으로써, 마스터 서버가 스스로 무중단 업데이트를 수행하도록 할 수 있다.

npm run update real

또한 master 서버에 대하여도, 아래 명령어를 통하여, 각종 정보를 취득할 수 있다.

npm run monitor master