# 2. 포트원

## 포트원(PortOne, 구 import) 사용하여 결제 API 연동하기

포트원 API를 사용하여 결제 기능을 구현하기 위해 먼저 포트원에 가입을 진행한다.

### 결제에 필요한 정보

포트원 API를 호출하기 위해서 몇 가지 필요한 정보가 있다.

#### 1. 고객사 식별코드

가입하면 자동으로 부여받는다.

왼쪽 메뉴의 **상점 ・ 계정 관리** > 상단의 **내 식별코드 ・ API Keys** 에서 확인할 수 있다.

![고객사 식별코드](/files/wvzPk3VhQekUM6mw9qHl)

#### 2. 상점 아이디

채널을 추가해야 한다.

![채널만들기1](/files/Qo8KuBD9qmCx3Mi0AgwW)

연동모드를 테스트로 하고, 결제 대행사 및 결제 모듈을 선택한다. 여기서는 KG이니시스, 일반/정기결제 V1 을 선택하였다. 이 때, 하단의 PG Provider를 복사해둔다.

나중에 코드에서 **PG Provider 와 상점 아이디**를 붙여서 body 에 세팅해줘야한다.

글을 내려보면 두 값을 붙인 값을 환경변수에 세팅해줬는데, 환경변수를 따로 만들어서 코드에서 붙이는 작업을 하는 것이 더 좋겠다.

> PG Provider는 결제 대행사마다 다르다.

![채널만들기2](/files/6oc921DzpNctaxKzIUCN)

* 이제 채널에 대한 구체적이 정보를 적어준다.
* PG상점 아이디(MID)는 선택할 수 있는데, 테스트 연동이라면 INIBillTst 와 INIPayTest 를 선택할 수 있다. 여기서는 그냥 INIPayTest 를 선택한다.
* 과세구분 (부가세 신고 참고 자료용)은 어차피 테스트이기 때문에 미설정으로 하고 저장 버튼 클릭

![채널만들기3](/files/AddIr3nZhAiVnI5SP4QX)

채널 생성 확인

![채널만들기4](/files/gxDsuaMVmM9sKcAD03SP)

***

## 프로젝트에 세팅하기

### 1. 아래와 같이 index.html에 import.js 모듈을 로드할 수 있도록 세팅하자.

```js
<body>
  <div id='root'></div>
  <script src='https://cdn.iamport.kr/v1/iamport.js'></script>
  <script type='module' src='./src/main.tsx'></script>
</body>
```

### 2. main.tsx 에서 가맹점 식별코드 세팅

```tsx
function main() {
  // PortOne initialization
  Reflect.get(window, 'IMP').init(process.env.PORTONE_IMP);

  // ...
}
```

환경 변수는 아래와 같은 형태로 설정

```.env
# 고객사 식별코드
PORTONE_IMP=imp999999

# PG사 코드 + 상점 아이디
PORTONE_PG_CODE=html5_inicis.INIpayTest
```

### 3. PaymentService 를 만들고 portone 문서를 참고하여 결제 API 요청 코드를 만들 수 있다

```ts
@singleton()
export default class PaymentService {
  private instance = Reflect.get(window, 'IMP');

  async requestPayment({
    merchantId,
    product,
    buyer,
  }: {
    merchantId: string;
    product: Product;
    buyer?: Buyer;
  }): Promise<{
    merchantId: string;
    transactionId: string;
  }> {
    return new Promise((resolve, reject) => {
      this.instance.request_pay(
        {
          pg: PG_CODE,
          pay_method: 'card',
          merchant_uid: merchantId,
          name: product.name,
          amount: product.price,
          buyer_email: buyer?.email,
          buyer_name: buyer?.name,
          buyer_tel: buyer?.phoneNumber,
          buyer_addr: buyer?.address,
          buyer_postcode: buyer?.postalCode,
        },
        (response: PaymentResponse) => {
          if (response.success) {
            resolve({
              merchantId: response.merchant_uid,
              transactionId: response.imp_uid ?? '',
            });
          } else {
            reject(Error(response.error_msg));
          }
        },
      );
    });
  }
}

export const paymentService = container.resolve(PaymentService);
```

눈여겨 봐야 할 부분은 request\_pay의 객체로 들어가는 부분이다.

* merchant\_uid: portone 에 전달해야 할 유니크한 값이고, 사용하는 쪽에서 만들어줘야 한다. 유니크한 키를 생성한 후에 DB에 저장하는 것이 좋다.
* pay\_method: 기본적으로 card 로 진행. 카카오페이 간편결제 기능도 card 로 가능하다.

> PortOne에서는 프론트에서 결제 요청을 보낸 뒤 금액 위변조 체크를 권장하고 있다.
>
> 이것은 백엔드에서 수행하는 것이 좋다.
>
> 이 때 merchant\_uid 값을 사용하여 결제 테이블에서 금액을 가져와서 비교할 수 있고, 해당 값으로 portone 에서 검색도 가능하다.
>
> 결제 요청을 하기 전에 결제 정보를 DB에 저장해도 되고, 요청이 성공한 후에 DB에 저장해도 된다. 필요에 따라서 달라질 수 있다.
>
> 결제 API가 REST API로 제공해준다면, 훨씬 더 쉬울 수 있는데, 일단 V1에서는 존재하지 않는다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://pm1100tm.gitbook.io/devnote/week11/undefined-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
