> For the complete documentation index, see [llms.txt](https://ofin.gitbook.io/document/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ofin.gitbook.io/document/payment/front.md).

# 결제창 연동하기

결제 프로세스와 결제창 연동 예시 코드를 제공합니다.

#### 결제 프로세스

<figure><img src="https://1090249943-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQ6iXOjoyMqHLAAFiE4vY%2Fuploads%2FQaxcFrHzYTMNpBxeQYc0%2F%E1%84%80%E1%85%A7%E1%86%AF%E1%84%8C%E1%85%A6%20flow.png?alt=media&amp;token=d9a4ef9d-0564-4ac3-a16b-b7040a546879" alt=""><figcaption></figcaption></figure>

### 연동 예시 코드

권장 화면 규격은 **가로: 520px(최소 320px 이상), 세로 640px** 입니다.

<details>

<summary>JavaScript</summary>

```javascript
<button id="paymentButton">
  후불결제하기
</button>
<div id="popup-message" style="color: red; margin-top: 10px;"></div>

<script>
  // -- 설정 변수 --
  const API_ENDPOINT = 'https://api.ofin.co.kr/api/v1/user/payment';
  const ERROR_PAGE_URL = '/error-page';
  
  /**
   * 결제 생성에 필요한 데이터(API_KEY 등) 받아오는 함수
   */
  async function getInitialPaymentData() {
    // 결제 생성에 필요한 데이터를 받아오는 로직을 추가해주세요.
  }
   
  // -- 이벤트 리스너 --
  document.getElementById('paymentButton').addEventListener('click', async () => { // 1. async 추가
    try {
      // getInitialPaymentData 함수를 호출하고 결과를 initialData에 저장합니다.
      const initialData = await getInitialPaymentData();

      // initialData와 다른 필요한 데이터를 합쳐서 최종 requestData를 구성합니다.
      const requestData = {
        ...initialData,
      };

      // 완성된 requestData로 결제 요청 함수를 호출합니다.
      initiatePaymentCheckout(requestData);

    } catch (error) {
      console.error('초기 데이터를 가져오는 데 실패했습니다:', error);
      // 사용자에게 에러를 알리는 처리
      document.getElementById('popup-message').textContent = '데이터를 불러오는 중 오류가 발생했습니다.';
    }
  });


  /**
   * API 요청 실패를 처리하는 헬퍼 함수
   */
  function handleApiError(error, message) {
    console.error(message, error);
    const messageElement = document.getElementById('popup-message');
    if (messageElement) {
      messageElement.textContent = '오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
    }
    // 에러 페이지로 리디렉션
    window.location.href = ERROR_PAGE_URL;
  }
  
  /**
   * 팝업창을 여는 헬퍼 함수
   */
  function openCenteredPopup(url) {
    const popupSize = { width: 520, height: 640 };
    const left = (window.screen.width / 2) - (popupSize.width / 2);
    const top = (window.screen.height / 2) - (popupSize.height / 2);
    const popupOptions = `width=${popupSize.width},height=${popupSize.height},left=${left},top=${top},resizable=no,scrollbars=no,status=no,toolbar=no,menubar=no,location=no`;
    
    const popup = window.open(url, 'payment-popup', popupOptions);

    if (popup) {
      popup.focus();
      setTimeout(() => popup.moveTo(left, top), 100);
    } else {
      const messageElement = document.getElementById('popup-message');
      if (messageElement) {
        messageElement.textContent = '팝업이 차단되었습니다. 팝업 차단을 해제하고 다시 시도해주세요.';
      }
    }
  }

  /**
   * 결제를 생성하고 결제 팝업을 띄우는 함수
   * @param {object} requestData - 결제 생성에 필요한 데이터
   */
  async function initiatePaymentCheckout(requestData) {
    try {
      const response = await fetch(API_ENDPOINT, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(requestData),
      });

      if (!response.ok) {
        // 서버가 에러를 응답한 경우
        const errorData = await response.json().catch(() => ({}));
        throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.message || 'Unknown error'}`);
      }

      const result = await response.json();
      const checkoutPageUrl = result.data?.checkoutPage;

      if (checkoutPageUrl) {
        openCenteredPopup(checkoutPageUrl);
      } else {
        throw new Error('Checkout page URL not found in API response');
      }
    } catch (error) {
      handleApiError(error, 'Failed to request payment:');
    }
  }
</script>
```

</details>

<details>

<summary>React</summary>

```javascript
import { useState } from 'react';

// -- 설정 변수 --
  const API_ENDPOINT = 'https://api.ofin.co.kr/api/v1/user/payment';
  const ERROR_PAGE_URL = '/error-page';
  
/**
 * 팝업창을 여는 헬퍼 함수
 */
  function openCenteredPopup(url: string) {
    const popupSize = { width: 520, height: 640 };
    const left = (window.screen.width / 2) - (popupSize.width / 2);
    const top = (window.screen.height / 2) - (popupSize.height / 2);
    const popupOptions = `width=${popupSize.width},height=${popupSize.height},left=${left},top=${top},resizable=no,scrollbars=no,status=no,toolbar=no,menubar=no,location=no`;
  
    const popup = window.open(url, 'payment-popup', popupOptions);

    if (popup) {
      popup.focus();
      setTimeout(() => popup.moveTo(left, top), 100);
    } else {
      return false;
    }
    return true;
  }

/**
 * 결제 생성에 필요한 데이터(API_KEY 등) 받아오는 함수
 */
  async function getInitialPaymentData() {
    // 결제 생성에 필요한 데이터를 받아오는 로직을 추가해주세요.
  }

/**
 * 결제를 생성하고 결제 팝업을 띄우는 커스텀 훅
 */
  export function usePayment() {
  
  const [isLoading, setIsLoading] = useState(false);
  const [errorMessage, setErrorMessage] = useState<string | null>(null);

  const handleInitiateCheckout = async () => {
    if (isLoading) return;
    
    setIsLoading(true);
    setErrorMessage(null);

    try {
      const initialData = await getInitialPaymentData();
      
      // initialData와 다른 필요한 데이터를 합쳐서 최종 requestData를 구성합니다.
      const requestData = {
        ...initialData,
      };

      const response = await fetch(API_ENDPOINT, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(requestData),
      });

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(`${response.status}: ${errorData.message || 'Unknown error'}`);
      }

      const result = await response.json();
      const checkoutPageUrl = result.data?.checkoutPage;
      
      if (!checkoutPageUrl) {
        throw new Error('Checkout page URL not found');
      }

      if (!openCenteredPopup(checkoutPageUrl)) {
        setErrorMessage(ERROR_MESSAGES.POPUP_BLOCKED);
        return;
      }

    } catch (error: any) {
      console.error('Payment request failed:', error);
      
      const errorMsg = ERROR_MESSAGES.GENERIC_ERROR;
      setErrorMessage(errorMsg);
      window.location.href = ERROR_PAGE_URL;
      
    } finally {
      setIsLoading(false);
    }
  };

  return { handleInitiateCheckout, isLoading, errorMessage };
}

/**
 * 결제 생성 창을 띄우는 버튼
 */
 export function PaymentButton() {
   const { handleInitiateCheckout, isLoading, errorMessage } = usePayment()
   return (
      <div>
        <button onClick={handleInitiateCheckout} disabled={isLoading}>
          {isLoading ? '로딩 중...' : '후불결제하기'}
        </button>
      
        {errorMessage && (
          <div style={{ color: 'red', marginTop: '10px' }}>
            {errorMessage}
          </div>
        )}
      </div>
    );
 }
```

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://ofin.gitbook.io/document/payment/front.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
