logoONESOFT

결제 완료인데 주문은 결제 대기? 결제 연동에서 놓치기 쉬운 구조

결제 완료인데 주문은 결제 대기? 결제 연동에서 놓치기 쉬운 구조

온라인 쇼핑몰이나 예약 서비스, 구독형 서비스처럼 결제가 포함된 웹·앱 서비스를 운영하다 보면 이런 상황이 발생할 수 있습니다.

사용자는 카드 결제를 정상적으로 완료했습니다.

카드사 또는 결제사에서는 결제가 승인되었고 실제로 금액도 정상적으로 처리됐습니다.

그런데 서비스 안에서는 주문 상태가 여전히 ‘결제 대기’로 남아 있습니다.

사용자 입장에서는 이미 돈을 냈는데 주문이 정상적으로 접수된 것인지 불안할 수 있고, 운영자는 결제사 관리자 화면과 서비스 주문 화면을 번갈아 확인해야 할 수 있습니다.

이런 문제는 결제 화면 자체보다 결제 결과를 우리 시스템의 주문 상태와 어떻게 연결하느냐에서 발생하는 경우가 많습니다.

결제 기능을 개발할 때는 단순히 사용자가 결제 버튼을 누르고 성공 화면을 보는 것만으로 끝나지 않습니다.

실제 서비스에서는 결제 성공, 실패, 취소, 부분 취소, 환불, 중복 알림, 네트워크 오류 등 여러 상황을 고려해야 합니다.

특히 결제 완료 여부를 사용자의 브라우저 화면만 기준으로 처리하면 결제사와 서비스 내부 데이터가 서로 달라질 가능성이 있습니다.

따라서 안정적인 결제 시스템을 만들기 위해서는 주문 데이터와 실제 결제 상태를 계속 일치시키는 구조를 설계하는 것이 중요합니다.


1. 결제 성공 화면이 떴다고 해서 주문 상태까지 정상 반영됐다는 뜻은 아닙니다

온라인 결제의 흐름을 단순하게 보면 다음과 같습니다.

상품 선택 → 주문 생성 → 결제 요청 → 결제 성공 → 주문 완료

얼핏 보면 사용자가 결제 성공 페이지까지 도착하면 모든 처리가 완료된 것처럼 보입니다.

하지만 실제로는 결제사에서 결제가 승인된 시점과 우리 서비스가 그 결과를 저장하는 시점이 서로 다를 수 있습니다.

예를 들어 결제사에서 결제가 정상 승인된 직후 사용자가 브라우저를 닫았다고 생각해보겠습니다.

결제사에는 결제 성공 기록이 남았지만 우리 서비스의 결제 완료 처리 화면까지 도달하지 못했다면 주문 상태는 여전히 ‘결제 대기’로 남을 수 있습니다.

모바일 앱에서도 비슷한 상황이 발생할 수 있습니다.

결제를 완료한 직후 앱이 종료되거나, 네트워크가 순간적으로 끊기거나, 결제 앱에서 원래 앱으로 돌아오는 과정에서 문제가 발생할 수 있습니다.

따라서 사용자의 화면 이동만 믿고 결제 상태를 확정하는 구조는 안정적이지 않을 수 있습니다.


2. 결제 완료 처리를 사용자의 브라우저에만 맡기면 문제가 생길 수 있습니다

결제 연동을 처음 구현할 때 자주 사용하는 방식 중 하나는 결제가 끝난 뒤 성공 URL로 사용자를 이동시키고, 그 화면에서 주문 상태를 변경하는 것입니다.

정상적인 상황에서는 잘 동작할 수 있습니다.

하지만 실제 사용 환경에서는 여러 예외가 발생합니다.

  • 결제 직후 사용자가 브라우저를 닫은 경우
  • 결제 완료 후 네트워크가 끊긴 경우
  • 모바일 결제 앱에서 서비스 앱으로 정상 복귀하지 못한 경우
  • 성공 페이지 요청이 서버까지 전달되지 않은 경우
  • 페이지 로딩 중 오류가 발생한 경우

이런 상황에서는 결제사에는 이미 결제 완료로 기록됐지만 우리 서비스에는 결제 결과가 반영되지 않을 수 있습니다.

반대 상황도 생각해야 합니다.

사용자가 성공 화면을 봤다고 해서 서버가 실제 결제 승인을 확인하지 않은 상태에서 주문을 무조건 완료 처리하는 것도 위험할 수 있습니다.

결제 상태는 화면 표시가 아니라 서버에서 실제 결제사 데이터를 확인한 결과를 기준으로 처리하는 것이 중요합니다.


3. 이런 문제를 줄이기 위해 사용하는 것이 웹훅입니다

결제사에서는 결제 상태가 변경됐을 때 우리 서버에 직접 알려주는 기능을 제공하는 경우가 많습니다.

이런 방식을 일반적으로 웹훅(Webhook)이라고 합니다.

예를 들어 사용자가 결제를 완료하면 결제사는 사용자 브라우저와 별개로 우리 서버에 다음과 같은 정보를 전달할 수 있습니다.

  • 결제 성공
  • 결제 실패
  • 결제 취소
  • 부분 취소
  • 환불 완료

우리 서버는 이 알림을 받아 실제 결제 상태를 확인한 뒤 주문 상태를 변경할 수 있습니다.

예를 들어 사용자가 결제 직후 브라우저를 닫더라도 결제사에서 서버로 전달한 웹훅을 정상적으로 받았다면 주문을 ‘결제 완료’로 변경할 수 있습니다.

즉 결제 완료 처리를 사용자의 화면 이동에만 의존하지 않고 결제사와 서버 간 통신으로 한 번 더 확인하는 것입니다.


4. 웹훅을 연결했다고 끝나는 것은 아닙니다

웹훅을 연동하면 결제 상태 동기화가 훨씬 안정적으로 바뀔 수 있습니다.

하지만 단순히 웹훅 URL을 하나 만들어두는 것만으로 결제 시스템이 완성되는 것은 아닙니다.

실제 운영에서는 웹훅이 한 번만 도착한다고 가정하면 안 됩니다.

네트워크 문제나 결제사 재시도 정책에 따라 같은 결제 결과가 여러 번 전달될 수 있습니다.

예를 들어 하나의 결제 완료 웹훅이 세 번 들어왔다고 생각해보겠습니다.

웹훅을 받을 때마다 단순히 주문 완료 로직을 실행하면 재고가 여러 번 차감되거나 포인트가 중복 지급되거나 알림 메시지가 반복 발송되는 문제가 생길 수 있습니다.

따라서 웹훅 처리에서는 같은 이벤트가 여러 번 들어오더라도 실제 업무 처리는 한 번만 실행되도록 설계하는 것이 중요합니다.


5. 중복 처리를 막는 구조가 필요합니다

결제 시스템에서는 동일한 요청이나 이벤트가 반복 전달될 가능성을 항상 고려하는 것이 좋습니다.

이를 위해 결제번호, 주문번호, 이벤트 ID 등 고유한 값을 이용해 이미 처리한 이벤트인지 확인할 수 있습니다.

예를 들어 결제 완료 웹훅이 들어왔을 때 이미 해당 결제번호가 ‘완료’ 상태라면 주문 상태를 다시 변경하지 않고 정상 응답만 반환할 수 있습니다.

이런 구조는 흔히 멱등성 또는 중복 방지 처리라고 표현합니다.

사용자에게는 잘 보이지 않는 부분이지만 결제처럼 실제 금액과 연결되는 시스템에서는 매우 중요한 설계입니다.

특히 다음과 같은 작업이 결제 완료와 함께 실행된다면 중복 여부를 더욱 주의해서 관리해야 합니다.

  • 재고 차감
  • 포인트 지급
  • 쿠폰 사용 처리
  • 주문 확정
  • 예약 확정
  • 알림톡 또는 이메일 발송
  • 제휴 시스템 데이터 전송

같은 결제 이벤트가 두 번 들어왔다고 해서 이런 업무가 두 번 실행되어서는 안 됩니다.


6. 주문 상태와 결제 상태는 구분해서 관리하는 것이 좋습니다

결제 시스템을 기획할 때 주문 상태와 결제 상태를 하나의 값으로 관리하려는 경우가 있습니다.

하지만 주문과 결제는 서로 연결되어 있으면서도 실제로는 다른 개념입니다.

예를 들어 주문 상태는 다음과 같을 수 있습니다.

  • 주문 접수
  • 상품 준비 중
  • 배송 중
  • 배송 완료
  • 주문 취소

반면 결제 상태는 다음과 같이 구분할 수 있습니다.

  • 결제 대기
  • 결제 완료
  • 결제 실패
  • 부분 취소
  • 전체 취소
  • 환불 완료

이 둘을 분리해 관리하면 실제 업무 상태를 더 정확하게 표현할 수 있습니다.

예를 들어 결제는 완료됐지만 아직 상품 준비가 시작되지 않은 주문도 있을 수 있고, 주문은 취소됐지만 환불 처리는 아직 진행 중일 수도 있습니다.

따라서 주문 상태와 결제 상태를 각각 관리한 뒤 필요한 시점에 서로 연결하는 구조를 고려하는 것이 좋습니다.


7. 결제 완료 이후 취소와 환불도 동기화해야 합니다

결제 상태 동기화는 결제 성공 시점에서 끝나지 않습니다.

실제 서비스에서는 결제 후 취소나 환불이 발생합니다.

사용자가 주문을 취소하거나 운영자가 관리자 페이지에서 환불을 처리할 수도 있습니다.

이때 결제사에서는 이미 결제가 취소됐는데 우리 서비스에는 여전히 ‘결제 완료’로 남아 있다면 데이터가 서로 어긋나게 됩니다.

반대로 서비스 화면에서 주문을 취소했지만 실제 결제 취소 요청이 실패했다면 사용자에게는 취소된 것처럼 보이지만 카드 결제는 그대로 유지될 수도 있습니다.

따라서 취소와 환불 역시 결제사의 실제 처리 결과를 확인한 뒤 내부 상태를 변경하는 것이 좋습니다.

특히 결제 취소 API를 호출했다고 해서 곧바로 성공했다고 가정해서는 안 됩니다.

취소 요청 → 결제사 결과 확인 → 내부 결제 상태 변경 → 주문 상태 반영의 흐름을 명확하게 설계할 필요가 있습니다.


8. 부분 취소가 있는 서비스라면 구조가 더 복잡해집니다

여러 상품을 한 번에 주문할 수 있는 커머스 서비스에서는 주문 전체가 아니라 일부 상품만 취소할 수 있습니다.

예를 들어 10만원짜리 주문에서 3만원짜리 상품 하나만 취소했다고 생각해보겠습니다.

이런 경우 결제 상태를 단순히 ‘완료’ 또는 ‘취소’ 두 가지로만 관리하기 어렵습니다.

원래 결제 금액, 취소 금액, 남아 있는 결제 금액을 함께 관리해야 할 수 있습니다.

또한 상품별 취소 상태와 결제사의 부분 취소 결과가 서로 연결되어야 합니다.

따라서 부분 취소가 필요한 서비스라면 초기 데이터 구조부터 이를 고려하는 것이 좋습니다.

나중에 부분 환불 기능을 추가하면서 기존의 단순한 결제 테이블을 크게 변경하면 예상보다 개발 범위가 커질 수 있습니다.


9. 결제 실패도 기록해야 합니다

정상적으로 완료된 결제만 데이터베이스에 저장하면 문제가 발생했을 때 원인을 확인하기 어렵습니다.

결제 과정에서는 다양한 실패가 발생할 수 있습니다.

  • 카드 한도 초과
  • 결제 인증 실패
  • 사용자 결제 취소
  • 결제사 API 오류
  • 네트워크 오류
  • 결제 승인 이후 내부 서버 처리 실패

이런 실패 기록을 일정 수준 남겨두면 사용자 문의가 발생했을 때 원인을 파악하기 쉬워집니다.

예를 들어 사용자가 “돈은 나갔는데 주문이 없습니다”라고 문의했을 때 결제 로그와 주문 로그를 비교하면 어느 단계에서 문제가 발생했는지 확인할 수 있습니다.

또한 특정 오류가 반복적으로 발생하고 있는지도 파악할 수 있습니다.

따라서 결제 시스템에서는 성공 데이터뿐 아니라 실패한 시도와 오류 원인도 추적할 수 있도록 설계하는 것이 좋습니다.


10. 결제 상태가 맞지 않는 주문을 운영자가 확인할 수 있어야 합니다

아무리 안정적으로 시스템을 설계해도 모든 예외 상황을 완전히 없애기는 어렵습니다.

외부 결제사의 일시적인 장애나 네트워크 오류 등 우리 시스템 밖의 문제가 발생할 수도 있기 때문입니다.

따라서 자동 처리뿐 아니라 운영자가 이상 상태를 확인할 수 있는 관리자 기능도 중요합니다.

예를 들어 다음과 같은 주문을 별도로 조회할 수 있습니다.

  • 결제 승인됐지만 주문이 완료되지 않은 건
  • 주문 취소됐지만 결제가 취소되지 않은 건
  • 환불 요청 후 처리가 완료되지 않은 건
  • 웹훅 처리에 실패한 건
  • 결제사 상태와 내부 상태가 다른 건

이런 데이터를 운영자가 확인할 수 있으면 문제가 생겼을 때 개발자가 데이터베이스를 직접 확인하지 않아도 기본적인 운영 대응이 가능합니다.


11. 웹훅 처리 실패 기록과 재처리 기능도 중요합니다

결제사에서 웹훅을 정상적으로 보냈더라도 우리 서버가 그 순간 오류 상태라면 처리하지 못할 수 있습니다.

결제사에서 일정 횟수 재전송을 제공하는 경우도 있지만 그것만으로 모든 상황을 해결할 수 있다고 가정하는 것은 위험할 수 있습니다.

시스템 내부에서도 웹훅 수신 시간, 이벤트 종류, 처리 결과, 오류 내용을 기록하는 것이 좋습니다.

처리에 실패한 이벤트가 있다면 관리자가 확인하거나 시스템에서 다시 처리할 수 있도록 구성할 수 있습니다.

예를 들어 결제 완료 웹훅은 정상적으로 수신했지만 내부 데이터베이스 오류로 주문 상태 변경에 실패했다면 이후 해당 이벤트를 다시 처리해 주문 상태를 복구할 수 있습니다.

이런 구조가 없다면 결제사와 내부 주문 상태가 서로 달라진 뒤 문제를 발견하기 전까지 계속 방치될 수 있습니다.


12. 사용자가 결제 완료 화면에서 새로고침해도 문제가 없어야 합니다

결제 완료 화면에서도 중복 처리를 생각해야 합니다.

사용자가 결제 완료 페이지에서 새로고침을 누르거나 뒤로 가기 후 다시 들어올 수 있기 때문입니다.

페이지가 열릴 때마다 결제 완료 로직을 다시 실행하면 쿠폰 사용, 포인트 지급, 재고 차감 등이 반복 실행될 수 있습니다.

따라서 결제 완료 처리는 사용자의 화면 방문 횟수와 관계없이 한 번만 수행되도록 구성하는 것이 좋습니다.

결제 완료 화면 자체는 이미 저장된 주문 상태를 조회해 사용자에게 보여주는 역할에 가깝게 만드는 것이 안정적일 수 있습니다.

즉 중요한 업무 처리를 화면 렌더링과 강하게 묶기보다 서버에서 확정된 데이터 기준으로 동작하게 만드는 것입니다.


13. 결제 금액이 주문 금액과 일치하는지도 확인해야 합니다

결제 결과가 성공이라고 해서 무조건 해당 주문을 완료 처리하면 안 되는 경우도 있습니다.

결제사에서 전달받은 실제 승인 금액과 우리 서버에 저장된 주문 금액이 동일한지 확인하는 과정도 중요합니다.

예를 들어 주문 금액이 100,000원인데 실제 결제 승인 금액이 10,000원으로 전달된다면 주문을 정상 완료 처리하기 전에 문제를 확인해야 합니다.

특히 최종 결제 금액을 브라우저에서 전달받은 값만 기준으로 사용하면 보안상 문제가 될 수 있습니다.

상품 가격, 할인, 쿠폰, 배송비 등을 기준으로 서버에서 계산한 주문 금액과 실제 결제 결과를 비교하는 구조가 필요할 수 있습니다.


14. 결제와 주문의 연결 키를 명확하게 관리해야 합니다

결제 데이터를 내부 주문과 정확하게 연결할 수 있는 식별값도 중요합니다.

일반적으로 주문번호, 결제번호, 결제사 거래번호 등을 각각 관리할 수 있습니다.

이 정보가 명확하게 연결되어 있지 않으면 결제사 관리자 페이지에서 특정 결제를 확인했을 때 우리 시스템의 어떤 주문인지 찾기 어려울 수 있습니다.

반대로 고객의 주문번호만 알고 있을 때 실제 결제사 거래 건을 찾기 어려울 수도 있습니다.

따라서 주문과 결제 정보를 연결할 수 있는 식별값을 데이터 구조에 함께 보관하는 것이 운영과 장애 대응에 도움이 됩니다.


15. 자동 결제나 정기 결제는 더 많은 상태를 고려해야 합니다

구독형 서비스나 정기 결제가 있는 서비스라면 일회성 결제보다 고려해야 할 상황이 더 많습니다.

첫 번째 결제는 성공했지만 다음 달 자동 결제가 실패할 수도 있습니다.

사용자의 카드가 만료되거나 한도 부족으로 결제가 거절될 수 있고 결제 수단이 변경될 수도 있습니다.

따라서 구독 서비스에서는 결제 성공뿐 아니라 결제 실패 이후 재시도, 서비스 이용 제한, 사용자 안내 등의 정책까지 함께 설계할 필요가 있습니다.

예를 들어 결제에 한 번 실패했다고 즉시 구독을 종료할 것인지, 일정 기간 동안 재결제를 시도할 것인지도 서비스 정책에 따라 달라질 수 있습니다.

이 역시 단순히 PG API를 연동하는 것보다 실제 업무 정책을 시스템에 어떻게 반영할지가 중요합니다.


16. 결제 개발 견적에서 확인하면 좋은 항목

개발 견적서에 ‘PG 결제 연동’이라는 항목이 들어 있다고 해서 모든 결제 기능의 범위가 동일한 것은 아닙니다.

단순 카드 결제 창을 연결하는 것과 실제 운영을 위한 결제 상태 관리까지 구현하는 것은 개발 범위가 달라질 수 있습니다.

결제 기능을 검토할 때는 다음 내용을 함께 확인해보는 것이 좋습니다.

  • 결제 승인: 실제 결제 성공 여부를 서버에서 확인하는가?
  • 웹훅: 결제 상태 변경 알림을 서버에서 받을 수 있는가?
  • 중복 처리: 같은 이벤트가 반복돼도 한 번만 처리되는가?
  • 주문 상태: 결제 상태와 주문 상태를 어떻게 연결하는가?
  • 취소: 주문 취소 시 실제 결제도 정상 취소되는가?
  • 부분 취소: 일부 상품이나 일부 금액만 환불할 수 있는가?
  • 실패 기록: 결제 오류와 실패 사유를 확인할 수 있는가?
  • 재처리: 웹훅이나 내부 처리 실패를 복구할 수 있는가?
  • 관리자: 결제와 주문 상태가 맞지 않는 건을 확인할 수 있는가?
  • 정기 결제: 자동 결제가 있다면 실패와 재시도를 어떻게 처리하는가?

이런 항목을 미리 확인하면 ‘결제가 되는 서비스’를 넘어 실제 운영에서 문제를 추적하고 대응할 수 있는 구조인지 판단하기 쉬워집니다.


17. 결제 시스템의 핵심은 화면이 아니라 상태 관리입니다

결제 기능은 사용자 입장에서는 카드 정보를 입력하고 결제 버튼을 누르는 몇 개의 화면으로 보입니다.

하지만 개발 측면에서 중요한 부분은 화면 뒤에 있는 상태 관리입니다.

결제 성공, 실패, 취소, 환불 등의 이벤트가 발생했을 때 주문 상태가 정확하게 변경되어야 하고, 같은 요청이 반복되더라도 데이터가 중복 처리되지 않아야 합니다.

또한 외부 결제 시스템과 내부 주문 데이터가 서로 달라지는 상황을 발견하고 복구할 수 있어야 합니다.

결제가 완료된 사용자가 ‘결제 대기’ 화면을 보거나, 이미 취소한 결제가 서비스 내부에서는 계속 완료 상태로 남는 문제는 사용자 신뢰에도 직접적인 영향을 줄 수 있습니다.

따라서 결제 기능을 개발할 때는 결제창 자체보다 결제 전후의 상태가 어떻게 변화하고 어떤 기준으로 확정되는지를 함께 설계하는 것이 중요합니다.


결제 기능을 개발하고 있다면

결제 기능은 결제창을 연결하고 성공 페이지를 보여주는 것으로 끝나지 않습니다.

사용자가 결제 후 화면을 닫거나 네트워크가 끊기더라도 실제 결제 결과가 주문에 정상 반영될 수 있어야 합니다.

이를 위해서는 서비스 특성에 따라 다음과 같은 흐름을 함께 고려하는 것이 좋습니다.

주문 생성 → 결제 요청 → 결제사 승인 → 서버 검증 → 웹훅 수신 → 중복 확인 → 결제 상태 확정 → 주문 상태 반영 → 취소·환불 상태 동기화 → 실패 기록 및 재처리

특히 결제와 주문은 실제 금액과 사용자 서비스 이용에 직접 연결되는 영역이기 때문에 정상 상황뿐 아니라 실패했을 때 어떻게 복구할지도 중요합니다.

결국 안정적인 결제 시스템의 핵심은 결제가 가능한가가 아니라 실제 결제 상태와 우리 서비스의 주문 상태가 계속 일치하는가입니다.

결제와 주문 상태의 연결 구조가 고민이라면 OneSoft와 기능 범위를 점검해보세요.

개발 문의와 자세한 내용은 OneSoft 홈페이지 에서 확인해보세요.

#결제시스템 #웹훅연동 #주문개발 #플랫폼개발 #PG연동 #결제개발 #쇼핑몰개발 #앱개발 #웹개발 #외주개발 #OneSoft

cta-banner여러분의 아이디어를 현실로,
함께 만드는 기술 파트너