diff --git a/src/content/docs/_redir.yaml b/src/content/docs/_redir.yaml
index 9f8109618..1e82f2a61 100644
--- a/src/content/docs/_redir.yaml
+++ b/src/content/docs/_redir.yaml
@@ -247,6 +247,27 @@
- old: /ko/ready/3-v2
new: /ko/ready/readme?v=v2#integration-identifiers
+- old: /ko/auth/guide/def
+ new: /ko/authpay/guide#definition
+- old: /ko/auth/guide/1
+ new: /ko/authpay/guide?v=v1#sdk-installation
+- old: /ko/auth/guide/2
+ new: /ko/authpay/guide?v=v1#sdk-initialization
+- old: /ko/auth/guide/3
+ new: /ko/authpay/guide?v=v1#request-payment
+- old: /ko/auth/guide/4/readme
+ new: /ko/authpay/guide?v=v1#handle-result
+- old: /ko/auth/guide/4/iframe
+ new: /ko/authpay/guide?v=v1#handle-callback
+- old: /ko/auth/guide/4/redirect
+ new: /ko/authpay/guide?v=v1#handle-redirect
+- old: /ko/auth/guide/5/readme
+ new: /ko/authpay/guide?v=v1#complete
+- old: /ko/auth/guide/5/post
+ new: /ko/authpay/guide?v=v1#complete
+- old: /ko/v2-payment/authpay
+ new: /ko/authpay/guide?v=v2
+
# 신규 API 문서
- old: /ko/api/api
new: https://developers.portone.io/api/rest-v1
diff --git a/src/content/docs/ko/_nav.yaml b/src/content/docs/ko/_nav.yaml
index b086398a2..c12029229 100644
--- a/src/content/docs/ko/_nav.yaml
+++ b/src/content/docs/ko/_nav.yaml
@@ -6,21 +6,7 @@
- label: 결제 연동
systemVersion: v1
items:
- - slug: /ko/auth/guide/readme
- items:
- - /ko/auth/guide/def
- - /ko/auth/guide/1
- - /ko/auth/guide/2
- - /ko/auth/guide/3
- - slug: /ko/auth/guide/4/readme
- items:
- - /ko/auth/guide/4/iframe
- - /ko/auth/guide/4/redirect
- - slug: /ko/auth/guide/5/readme
- items:
- - /ko/auth/guide/5/pre
- - /ko/auth/guide/5/post
- - /ko/auth/guide/6
+ - slug: /ko/authpay/guide
- slug: /ko/auth/guide-1/readme
items:
- slug: /ko/auth/guide-1/bill/readme
@@ -303,7 +289,7 @@
systemVersion: v2
items:
- /ko/v2-payment/v2
- - /ko/v2-payment/authpay
+ - /ko/authpay/guide
- /ko/v2-payment/key-in
- slug: /ko/v2-payment/billing-key/readme
items:
diff --git a/src/content/docs/ko/authpay/_assets/authpay-example.png b/src/content/docs/ko/authpay/_assets/authpay-example.png
new file mode 100644
index 000000000..20912b465
Binary files /dev/null and b/src/content/docs/ko/authpay/_assets/authpay-example.png differ
diff --git a/src/content/docs/ko/authpay/_assets/authpay-flow.png b/src/content/docs/ko/authpay/_assets/authpay-flow.png
new file mode 100644
index 000000000..63d3db63b
Binary files /dev/null and b/src/content/docs/ko/authpay/_assets/authpay-flow.png differ
diff --git a/src/content/docs/ko/authpay/_components/v2-sdk-installation.mdx b/src/content/docs/ko/authpay/_components/v2-sdk-installation.mdx
new file mode 100644
index 000000000..6dc106a06
--- /dev/null
+++ b/src/content/docs/ko/authpay/_components/v2-sdk-installation.mdx
@@ -0,0 +1,61 @@
+import Tab from "~/components/gitbook/tabs/Tab.astro";
+import Tabs from "~/components/gitbook/tabs/Tabs.astro";
+import Hint from "~/components/Hint.astro";
+import * as prose from '~/components/prose';
+
+export const components = prose;
+
+포트원 V2 SDK는 npm 레지스트리와 CDN을 통해 배포되고 있습니다.
+
+- npm, yarn 등 패키지 매니저를 사용한다면 의존
+ 대상으로 [@portone/browser-sdk](https://www.npmjs.com/package/@portone/browser-sdk)를 추가하세요.
+
+- 패키지 매니저를 사용하지 않는다면 `
+ ```
+
+ `
+ ```
+
+
+
+
+
+
+
+### 2. 결제 요청하기
+
+
+
+ #### SDK 초기화하기
+
+ 포트원 SDK를 사용하여 결제창을 호출하려면, 먼저 포트원 SDK를 초기화하여야 합니다.
+
+ 먼저, 관리자 콘솔의 결제 연동 페이지에서 **고객사 식별코드**를 확인해 주세요.
+
+ 그리고 결제창을 호출할 페이지에서 다음과 같이 포트원 SDK를 초기화합니다.
+
+
+ 아래 초기화 함수를 2회 이상 중복 호출하지 않도록 주의해 주세요.
+
+
+ ```ts
+ IMP.init("고객사 식별코드"); // 예: 'imp00000000'
+ ```
+
+ ##### 하위 상점에서 SDK 초기화하기
+
+ 하위 상점에서 SDK를 초기화하려면, `IMP.init()` 함수 대신 `IMP.agency()` 함수를 사용합니다.
+
+ ```ts
+ IMP.agency("고객사 식별코드", "티어코드"); // 예: 'imp00000000', '123'
+ ```
+
+
+ #### 결제창 불러오기
+
+
+ SDK의 `IMP.request_pay()` 함수를 호출하여 결제 수단에 따른 결제창을 열 수 있습니다.
+
+ 아래와 같이 [결제 요청 파라미터](../sdk/javascript-sdk/payrq)를 `request_pay()` 함수의
+ 첫 인자로 설정하여 호출합니다.
+
+ ```ts
+ IMP.request_pay(
+ {
+ pg: "{PG사 코드}.{상점 ID}",
+ pay_method: "card",
+ merchant_uid: `payment-${crypto.randomUUID()}`, // 주문 고유 번호
+ name: "노르웨이 회전 의자",
+ amount: 64900,
+ buyer_email: "gildong@gmail.com",
+ buyer_name: "홍길동",
+ buyer_tel: "010-4242-4242",
+ buyer_addr: "서울특별시 강남구 신사동",
+ buyer_postcode: "01181",
+ },
+ function (response) {
+ // 결제 종료 시 호출되는 콜백 함수
+ // response.imp_uid 값으로 결제 단건조회 API를 호출하여 결제 결과를 확인하고,
+ // 결제 결과를 처리하는 로직을 작성합니다.
+ },
+ );
+ ```
+
+
+
+
+
+ SDK의 `PortOne.requestPayment()` 함수를 호출하여 결제 수단에 따른 결제창을 열 수 있습니다.
+
+ 먼저, 관리자 콘솔의 결제 연동 페이지에서 **Store ID**와 사용할 채널의 **채널 키**를 확인해 주세요.
+
+ 그리고 아래와 같이 [결제 요청 파라미터](../v2-payment/v2-sdk/payment-request)를
+ `requestPayment()` 함수의 첫 인자로 설정하여 호출합니다.
+
+ ```ts
+ const response = await PortOne.requestPayment({
+ // Store ID 설정
+ storeId: "store-4ff4af41-85e3-4559-8eb8-0d08a2c6ceec",
+ // 채널 키 설정
+ channelKey: "channel-key-893597d6-e62d-410f-83f9-119f530b4b11",
+ paymentId: `payment-${crypto.randomUUID()}`,
+ orderName: "나이키 와플 트레이너 2 SD",
+ totalAmount: 1000,
+ currency: "CURRENCY_KRW",
+ payMethod: "CARD",
+ });
+ ```
+
+
+
+
+
+
+ **주문 고유 번호(`merchant_uid`) 관련 유의사항**
+
+
+
+ **주문 고유 번호(`paymentId`) 관련 유의사항**
+
+
+
+ - 주문 고유 번호는 개별 결제 요청을 구분하기 위해 사용되는 문자열입니다.
+
+ - 따라서 주문 고유 번호는 결제 요청 시 항상 **고유한 값**으로 채번되어야 하며,
+ 결제 완료 이후 결제 기록 조회나 위변조 대사 작업 시 사용되기 때문에
+ 고객사 **DB 상에 별도로 저장**해야 합니다.
+
+
+### 3. 결제 결과 처리하기
+
+결제창이 활성화되는 방식에 따라 결제 결과를 획득하는 방법이 상이합니다.
+
+일반적으로 PC 환경에서는 iframe 또는 팝업 방식으로 페이지 이동 없이 결제창이 활성화되며,
+따라서 SDK의 반환값을 통해서 결제 결과를 받아 볼 수 있습니다.
+반면, 모바일 환경에서는 일반적으로 새로운 페이지로 리다이렉트되는 방식으로 결제창이 활성화되고,
+SDK의 반환값 대신 URL의 [쿼리 문자열](https://en.wikipedia.org/wiki/Query_string) 형태로
+결제 결과를 받아볼 수 있습니다.
+
+
+ 결제창이 활성화되는 방식은 `windowType` 파라미터를 통해 명시적으로 설정할 수 있습니다.
+
+
+#### SDK 반환값으로 처리하기
+
+
+
+ **`request_pay()`** 함수의 두 번째 인자인 **callback** 함수를 통해 결제 결과를 확인할 수 있습니다.
+
+ ```ts
+ IMP.request_pay(
+ {
+ /* 파라미터 생략 */
+ },
+ async (response) => {
+ if (response.error_code != null) {
+ return alert(`결제에 실패하였습니다. 에러 내용: ${response.error_msg}`);
+ }
+
+ // 고객사 서버에서 /payment/complete 엔드포인트를 구현해야 합니다.
+ // (다음 목차에서 설명합니다)
+ const notified = await fetch(`${SERVER_BASE_URL}/payment/complete`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ // imp_uid와 merchant_uid, 주문 정보를 서버에 전달합니다
+ body: JSON.stringify({
+ imp_uid: response.imp_uid,
+ merchant_uid: response.merchant_uid,
+ // 주문 정보...
+ }),
+ });
+ },
+ );
+ ```
+
+ 결제가 완료되면 반환되는 응답 객체([response](../sdk/javascript-sdk-old/readme))의
+ 에러 여부에 따라 처리 로직을 콜백 함수에 작성합니다.
+ 요청이 성공했을 경우에 결제번호(`imp_uid`)와 주문번호(`merchant_uid`)를
+ 서버에 전달하는 로직을 위와 같이 작성합니다.
+
+
+ 최종 결제 결과 처리는 반드시 [웹훅](../result/webhook)을 이용하여
+ 안정적으로 처리해 주셔야 합니다.
+
+ 웹훅 연동을 생략하시는 경우 결제 결과를 정상적으로 수신받지 못하는 상황이 발생합니다.
+
+
+
+
+ `PortOne.requestPayment()` 함수의 반환값을 통해 결제 요청의 결과를 확인할 수 있습니다.
+
+ `code != null`이면 결제 과정에서 오류가 발생한 것이므로 적절히 처리하여야 합니다.
+
+ 결제가 성공한 경우 `paymentId`를 서버에 전달하여 서버 측에서 결제 완료 처리를 진행하도록 합니다.
+ (가상 계좌 결제의 경우 결제가 아직 완료되지 않은 상태일 수 있습니다)
+
+ ```ts
+ async function requestPayment() {
+ const response = await PortOne.requestPayment({
+ /* 파라미터 생략 */
+ });
+
+ if (response.code != null) {
+ // 오류 발생
+ return alert(response.message);
+ }
+
+ // 고객사 서버에서 /payment/complete 엔드포인트를 구현해야 합니다.
+ // (다음 목차에서 설명합니다)
+ const notified = await fetch(`${SERVER_BASE_URL}/payment/complete`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ // paymentId와 주문 정보를 서버에 전달합니다
+ body: JSON.stringify({
+ paymentId: paymentId,
+ // 주문 정보...
+ }),
+ });
+ }
+ ```
+
+ 결과값에 들어 있는 필드는 다음과 같습니다.
+
+ |필드명 |설명 |비고 |
+ |-----------|----------|------------|
+ |`paymentId`|결제 건 ID|공통 |
+ |`code` |오류 코드 |실패 시 포함|
+ |`message` |오류 문구 |실패 시 포함|
+
+
+
+#### URL 쿼리 문자열로 처리하기
+
+모바일 환경에서의 결제는 대부분 리다이렉트 방식으로 이루어집니다.
+리다이렉트 방식에서는 브라우저가 결제창으로 리다이렉트되었다가,
+결제창에서의 작업이 끝나면 지정한
+`m_redirect_url``redirectUrl`로
+다시 리다이렉트됩니다.
+이 경우에는 함수 호출 결과를 이용할 수 없고,
+결제 성공 여부 등은 [쿼리 문자열](https://en.wikipedia.org/wiki/Query_string)로 전달받게 됩니다.
+
+
+
+ ```ts
+ IMP.request_pay({
+ /* 기타 파라미터 생략 */
+ m_redirect_url: `${BASE_URL}/payment-redirect`,
+ }); // 리다이렉트 방식의 경우 콜백은 실행되지 않습니다.
+ ```
+
+
+
+ ```ts
+ PortOne.requestPayment({
+ /* 기타 파라미터 생략 */
+ redirectUrl: `${BASE_URL}/payment-redirect`,
+ });
+ ```
+
+
+
+쿼리 문자열로 전달되는 내용은 다음과 같습니다.
+
+
+
+ |키 |설명 |비고 |
+ |--------------|---------------------|------------|
+ |`imp_uid` |포트원 결제 ID |공통 |
+ |`merchant_uid`|고객사 주문 고유 번호|공통 |
+ |`error_code` |오류 코드 |실패 시 포함|
+ |`error_msg` |오류 문구 |실패 시 포함|
+
+ 예를 들어 `merchant_uid`가 `payment-39ecfa97`, `m_redirect_url`이 `https://example.com/payment-redirect`인 경우,
+ 결제 성공 시에 `https://example.com/payment-redirect?merchant_uid=payment-39ecfa97`로 리다이렉트됩니다.
+
+
+
+ |키 |설명 |비고 |
+ |------------|----------|------------|
+ |`payment_id`|결제 건 ID|공통 |
+ |`code` |오류 코드 |실패 시 포함|
+ |`message` |오류 문구 |실패 시 포함|
+
+ 예를 들어 `paymentId`가 `payment-39ecfa97`, `redirectUrl`이 `https://example.com/payment-redirect`인 경우,
+ 결제 성공 시에 `https://example.com/payment-redirect?payment_id=payment-39ecfa97`로 리다이렉트됩니다.
+
+
+
+### 4. 결제 완료 처리하기
+
+
+
+ `imp_uid`와 `merchant_uid`를 서버에 전달하면, 서버는 포트원의 [결제 조회 API](/api/rest-v1/payment#get%20%2Fpayments%2F%7Bimp_uid%7D)를
+ 호출하여 해당 결제 건의 상태를 확인하고 결제 완료 처리를 진행하여야 합니다.
+
+
+
+ `paymentId`를 서버에 전달하면, 서버는 포트원의 [결제 조회 API](/api/rest-v2/payment#get%20%2Fpayments%2F%7BpaymentId%7D)를
+ 호출하여 해당 결제 건의 상태를 확인하고 결제 완료 처리를 진행하여야 합니다.
+
+
+
+
+ **결제 검증 필수**
+
+ 인증 결제의 흐름상 결제 금액 등 정보가 고객의 브라우저 측에서 처리되므로,
+ 의도한 결제 내용이 맞는지 서버 측에서 꼭 확인하여야 위변조를 막을 수 있습니다.
+
+
+예시로, 위에서 사용했던 `/payment/complete` 엔드포인트를 다음과 같이 구현할 수 있습니다.
+
+
+
+ ```ts title="Express"
+ // JSON 요청을 처리하기 위해 body-parser 미들웨어 세팅
+ app.use(bodyParser.json());
+
+ // POST 요청을 받는 /payments/complete
+ app.post("/payment/complete", async (req, res) => {
+ try {
+ // 요청의 body로 imp_uid와 merchant_uid가 전달되기를 기대합니다.
+ const { imp_uid, merchant_uid } = req.body;
+
+ // 1. 포트원 API 엑세스 토큰 발급
+ const tokenResponse = await fetch("https://api.iamport.kr/users/getToken", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ imp_key: "imp_apikey", // REST API 키
+ imp_secret: "ekKoeW8RyKuT0zgaZsUtXXTLQ4AhPFW", // REST API Secret
+ }),
+ });
+ if (!tokenResponse.ok)
+ throw new Error(`tokenResponse: ${tokenResponse.statusText}`);
+ const { access_token } = await tokenResponse.json();
+
+ // 2. 포트원 결제내역 단건조회 API 호출
+ const paymentResponse = await fetch(
+ `https://api.iamport.kr/payments/${imp_uid}`,
+ { headers: { Authorization: access_token } },
+ );
+ if (!paymentResponse.ok)
+ throw new Error(`paymentResponse: ${paymentResponse.statusText}`);
+ const payment = await paymentResponse.json();
+
+ // 3. 고객사 내부 주문 데이터의 가격과 실제 지불된 금액을 비교합니다.
+ const order = await OrderService.findById(merchant_uid);
+ if (order.amount === payment.amount) {
+ switch (payment.status) {
+ case "ready": {
+ // 가상 계좌가 발급된 상태입니다.
+ // 계좌 정보를 이용해 원하는 로직을 구성하세요.
+ break;
+ }
+ case "paid": {
+ // 모든 금액을 지불했습니다! 완료 시 원하는 로직을 구성하세요.
+ break;
+ }
+ }
+ } else {
+ // 결제 금액이 불일치하여 위/변조 시도가 의심됩니다.
+ }
+ } catch (e) {
+ // 결제 검증에 실패했습니다.
+ res.status(400).send(e);
+ }
+ });
+ ```
+
+
+
+ [PORTONE\_API\_SECRET](/docs/ko/ready/readme?v=v2#4-2-v2-api-secret-%EB%B0%9C%EA%B8%89%ED%95%98%EA%B8%B0)
+ 은 V2 전용 시크릿으로, 포트원 콘솔 내 결제연동 탭에서 발급받을 수 있습니다.
+
+ ```ts title="Express"
+ // JSON 요청을 처리하기 위해 body-parser 미들웨어 세팅
+ app.use(bodyParser.json());
+
+ // POST 요청을 받는 /payments/complete
+ app.post("/payment/complete", async (req, res) => {
+ try {
+ // 요청의 body로 paymentId가 전달되기를 기대합니다.
+ const { paymentId, orderId } = req.body;
+
+ // 1. 포트원 결제내역 단건조회 API 호출
+ const paymentResponse = await fetch(
+ `https://api.portone.io/payments/${paymentId}`,
+ { headers: { Authorization: `PortOne ${PORTONE_API_SECRET}` } },
+ );
+ if (!paymentResponse.ok)
+ throw new Error(`paymentResponse: ${paymentResponse.statusText}`);
+ const payment = await paymentResponse.json();
+
+ // 2. 고객사 내부 주문 데이터의 가격과 실제 지불된 금액을 비교합니다.
+ const order = await OrderService.findById(orderId);
+ if (order.amount === payment.amount.total) {
+ switch (payment.status) {
+ case "VIRTUAL_ACCOUNT_ISSUED": {
+ // 가상 계좌가 발급된 상태입니다.
+ // 계좌 정보를 이용해 원하는 로직을 구성하세요.
+ break;
+ }
+ case "PAID": {
+ // 모든 금액을 지불했습니다! 완료 시 원하는 로직을 구성하세요.
+ break;
+ }
+ }
+ } else {
+ // 결제 금액이 불일치하여 위/변조 시도가 의심됩니다.
+ }
+ } catch (e) {
+ // 결제 검증에 실패했습니다.
+ res.status(400).send(e);
+ }
+ });
+ ```
+
+