logoONESOFT

결제 완료 화면이 떴는데 주문은 왜 ‘미결제’로 남을까요?

결제 완료 화면이 떴는데 주문은 왜 ‘미결제’로 남을까요?

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

사용자는 결제를 정상적으로 완료했고 화면에도 ‘결제가 완료되었습니다’라는 안내가 표시되었습니다.

그런데 관리자 페이지에서 주문을 확인해보면 상태가 여전히 ‘미결제’ 또는 ‘결제 대기’로 남아 있습니다.

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

이런 문제는 결제창 자체보다 결제 결과를 우리 서비스의 주문 상태와 어떻게 연결하는지에서 발생하는 경우가 많습니다.

결제 시스템은 단순히 결제 화면을 띄우고 성공 페이지로 이동시키는 기능으로 끝나지 않습니다.

결제사에서 실제로 승인된 상태를 확인하고, 그 결과를 내부 주문 데이터에 반영하며, 취소와 환불까지 동일한 기준으로 관리해야 합니다.

특히 사용자의 화면 전환만으로 결제 성공 여부를 판단하면 네트워크 오류나 브라우저 종료 같은 상황에서 결제 상태가 서로 어긋날 수 있습니다.

따라서 결제 기능을 개발할 때는 화면보다 상태 관리와 동기화 구조를 먼저 설계하는 것이 중요합니다.


1. 결제 성공 화면은 결제 상태 그 자체가 아닙니다

사용자가 결제 버튼을 누르면 일반적으로 결제사 화면을 거쳐 결제를 진행하게 됩니다.

이후 결제가 정상적으로 끝나면 성공 화면으로 이동합니다.

화면만 보면 다음과 같은 단순한 흐름으로 보일 수 있습니다.

주문 생성 → 결제 → 성공 화면 → 주문 완료

하지만 실제 시스템에서는 결제사 승인 결과와 우리 서비스 내부 주문 상태가 별도로 존재합니다.

결제사에서는 이미 결제가 승인됐는데 우리 서버에서 주문 상태를 아직 변경하지 못했을 수도 있습니다.

반대로 사용자가 성공 화면에 도착했다고 해서 실제 승인 상태를 서버에서 확인하지 않은 채 주문을 완료 처리하는 것도 위험할 수 있습니다.

따라서 ‘결제 완료 화면을 봤다’와 ‘실제 결제가 승인됐다’는 개념을 구분해서 보는 것이 좋습니다.


2. 결제 직후 사용자가 화면을 닫을 수도 있습니다

실제 사용자는 개발자가 예상한 순서대로만 행동하지 않습니다.

결제가 끝난 직후 브라우저를 닫거나 앱을 종료할 수도 있습니다.

모바일 환경에서는 결제 앱에서 원래 앱으로 돌아오는 과정이 끊길 수도 있고, 순간적으로 네트워크가 불안정해질 수도 있습니다.

예를 들어 결제사에서는 카드 승인이 완료됐지만 사용자가 성공 페이지로 돌아오기 전에 앱을 종료했다고 생각해보겠습니다.

내부 시스템이 성공 페이지에 들어왔을 때만 주문 상태를 ‘결제 완료’로 바꾸는 구조라면 해당 주문은 계속 미결제로 남을 수 있습니다.

실제 금액은 결제됐지만 서비스 데이터는 결제 전 상태인 상황입니다.

이런 문제를 줄이려면 사용자의 화면 이동과 별개로 서버가 결제 상태를 확인할 수 있는 구조가 필요합니다.


3. 결제 상태는 서버에서 확인하는 것이 중요합니다

결제 완료 여부를 브라우저에서 전달받은 값만으로 판단하는 것은 적절하지 않을 수 있습니다.

결제와 금액은 중요한 데이터이기 때문에 서버가 결제사의 실제 승인 결과를 확인하는 과정이 필요합니다.

예를 들어 결제사에서 전달한 결제 키나 거래번호를 이용해 승인 결과를 확인하고, 주문번호와 금액이 정상적으로 일치하는지 검증한 뒤 내부 결제 상태를 변경할 수 있습니다.

이 과정에서 다음과 같은 정보를 확인할 수 있습니다.

  • 실제 결제 승인 여부
  • 주문번호
  • 결제 금액
  • 결제 수단
  • 결제사 거래번호
  • 결제 처리 시각

이렇게 하면 단순히 성공 페이지에 도착했다는 이유만으로 주문을 완료 처리하는 문제를 줄일 수 있습니다.


4. 결제사 웹훅을 함께 사용하는 이유

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

웹훅은 사용자의 브라우저와 별개로 서버 간에 결제 상태 변경을 전달하는 방식입니다.

사용자가 결제 후 화면을 닫더라도 결제사에서 우리 서버로 결제 완료 이벤트를 전달할 수 있습니다.

우리 서버는 해당 이벤트를 받고 실제 결제 상태를 다시 확인한 뒤 주문을 결제 완료로 변경할 수 있습니다.

따라서 결제 완료 처리에서는 다음과 같은 흐름을 고려할 수 있습니다.

사용자 결제 → 결제사 승인 → 서버 검증 → 웹훅 수신 → 주문 상태 동기화

이 구조를 사용하면 사용자의 화면 상태에만 의존하지 않고 결제사와 내부 주문 데이터를 맞출 수 있습니다.


5. 웹훅도 무조건 한 번만 들어온다고 가정하면 안 됩니다

웹훅을 연동했다고 해서 모든 문제가 끝나는 것은 아닙니다.

결제사에서 동일한 이벤트를 여러 번 전달할 수도 있기 때문입니다.

예를 들어 서버 응답이 늦거나 네트워크 문제가 발생하면 결제사가 같은 결제 완료 이벤트를 다시 보낼 수 있습니다.

이때 웹훅을 받을 때마다 동일한 주문 완료 로직을 반복 실행하면 문제가 생길 수 있습니다.

결제 완료와 함께 다음 작업이 실행된다고 생각해보겠습니다.

  • 재고 차감
  • 포인트 지급
  • 쿠폰 사용 처리
  • 예약 확정
  • 알림톡 발송
  • 이메일 발송

동일한 결제 이벤트가 여러 번 처리되면 재고가 두 번 줄거나 포인트가 중복 지급되는 문제가 발생할 수 있습니다.

따라서 결제 시스템에서는 같은 이벤트가 여러 번 들어와도 실제 업무 처리는 한 번만 실행되도록 중복 방지 구조를 설계하는 것이 중요합니다.


6. 주문 상태와 결제 상태는 따로 관리하는 것이 좋습니다

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

하지만 실제로는 두 상태를 분리해서 보는 것이 더 자연스러운 경우가 많습니다.

주문 상태는 다음과 같을 수 있습니다.

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

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

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

결제가 완료됐다고 해서 바로 배송이 시작되는 것은 아닙니다.

반대로 주문이 취소됐더라도 실제 카드 환불은 아직 처리되지 않았을 수도 있습니다.

따라서 주문 상태와 결제 상태를 각각 관리하면 실제 업무 상황을 더 정확하게 표현할 수 있습니다.


7. 결제 금액도 반드시 확인해야 합니다

결제 승인 여부만 확인한다고 끝나는 것은 아닙니다.

실제 승인된 금액이 우리 서버가 계산한 주문 금액과 동일한지도 확인하는 것이 중요합니다.

예를 들어 서버에서 계산한 주문 금액이 120,000원인데 결제사에서 승인된 금액이 12,000원이라면 정상 주문으로 처리해서는 안 됩니다.

상품 가격, 쿠폰 할인, 배송비, 포인트 사용 등을 적용한 최종 결제 금액은 가능한 한 서버 기준으로 계산하고 실제 승인 금액과 비교하는 것이 좋습니다.

특히 브라우저에서 전달된 결제 금액만 그대로 신뢰하는 방식은 피하는 것이 좋습니다.


8. 결제번호와 주문번호를 명확하게 연결해야 합니다

결제사에서는 자체 거래번호를 발급하고 우리 서비스에서도 별도의 주문번호를 사용합니다.

이 두 값이 정확하게 연결되어 있어야 문제가 발생했을 때 어느 주문의 결제인지 확인할 수 있습니다.

예를 들어 고객이 “결제는 됐는데 주문이 안 보여요”라고 문의했을 때 운영자는 결제사 거래번호를 기준으로 내부 주문을 찾아야 할 수도 있습니다.

반대로 주문번호만 알고 있을 때 실제 결제사 거래 건을 확인해야 하는 경우도 있습니다.

따라서 다음 정보들을 연결해서 저장하는 것이 좋습니다.

  • 내부 주문번호
  • 내부 결제번호
  • 결제사 거래번호
  • 결제 금액
  • 결제 상태
  • 승인 시각

이런 구조가 있으면 고객 문의와 장애 대응이 훨씬 쉬워집니다.


9. 취소도 ‘주문 취소 버튼’만 눌렀다고 끝나지 않습니다

결제 완료 이후에는 취소가 발생할 수 있습니다.

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

이때 내부 주문 상태만 ‘취소’로 바꾸고 실제 결제 취소 요청을 하지 않으면 카드 결제는 그대로 남을 수 있습니다.

반대로 결제사에는 취소가 완료됐는데 내부 상태가 계속 ‘결제 완료’로 남아 있을 수도 있습니다.

따라서 취소 역시 다음과 같은 흐름으로 관리하는 것이 좋습니다.

취소 요청 → 취소 가능 여부 확인 → 결제사 취소 요청 → 처리 결과 확인 → 결제 상태 변경 → 주문 상태 반영

중요한 것은 주문 화면에서 취소 버튼을 눌렀다는 사실이 아니라 실제 결제사에서 취소가 완료됐는지를 기준으로 내부 상태를 변경하는 것입니다.


10. 환불 처리도 별도의 상태가 필요할 수 있습니다

서비스 특성에 따라 결제 취소와 환불이 동일하게 처리되지 않을 수 있습니다.

결제 수단이나 처리 시점에 따라 환불 완료까지 시간이 걸릴 수도 있습니다.

따라서 다음과 같이 상태를 세분화할 수도 있습니다.

  • 환불 요청
  • 환불 처리 중
  • 환불 완료
  • 환불 실패

이렇게 하면 운영자가 현재 환불이 어느 단계인지 확인할 수 있습니다.

사용자에게도 단순히 ‘취소되었습니다’라고 보여주는 것보다 실제 처리 상태에 맞는 안내를 제공할 수 있습니다.


11. 부분 취소가 있다면 처음부터 데이터 구조를 고려해야 합니다

여러 상품을 한 번에 구매할 수 있는 서비스에서는 일부 상품만 취소하는 상황이 생길 수 있습니다.

예를 들어 총 100,000원을 결제했는데 그중 30,000원짜리 상품만 취소할 수 있습니다.

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

원래 결제 금액과 취소 금액, 남아 있는 결제 금액을 구분해야 할 수 있습니다.

상품별 취소 상태도 함께 관리해야 합니다.

서비스에서 부분 취소 가능성이 있다면 초기 설계부터 이를 고려하는 것이 좋습니다.

나중에 기능을 추가하면서 기존 결제 구조를 크게 바꾸면 예상보다 개발 범위가 커질 수 있기 때문입니다.


12. 결제 실패도 정상적인 업무 상태 중 하나입니다

결제 기능에서는 성공한 경우만 생각하기 쉽습니다.

하지만 실제로는 결제 실패도 자주 발생합니다.

예를 들어 다음과 같은 상황이 있습니다.

  • 사용자가 결제를 중간에 취소한 경우
  • 카드 승인이 거절된 경우
  • 결제 인증에 실패한 경우
  • 외부 결제 API 오류가 발생한 경우
  • 네트워크가 끊긴 경우
  • 승인은 됐지만 내부 처리 중 오류가 발생한 경우

이런 상황을 하나의 ‘결제 실패’로만 기록하면 원인을 찾기 어려울 수 있습니다.

가능하다면 실패 시점과 실패 사유, 결제사에서 전달한 오류 정보 등을 일정 범위에서 기록해두는 것이 좋습니다.

그래야 고객 문의가 들어왔을 때 어느 단계에서 문제가 발생했는지 확인하기 쉬워집니다.


13. 결제 상태가 어긋난 주문을 운영자가 찾을 수 있어야 합니다

아무리 잘 설계된 시스템이라도 외부 결제사와 연동하는 이상 모든 예외를 완전히 없애기는 어렵습니다.

외부 서비스 장애나 네트워크 문제, 내부 서버 오류가 발생할 수도 있습니다.

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

예를 들어 관리자 페이지에서 다음과 같은 주문을 별도로 확인할 수 있습니다.

  • 결제사에는 승인됐지만 내부 주문은 미결제인 건
  • 주문은 취소됐지만 결제가 아직 완료 상태인 건
  • 환불 요청 후 완료되지 않은 건
  • 웹훅 처리에 실패한 건
  • 중복 결제 가능성이 있는 건

이렇게 이상 상태를 확인할 수 있으면 운영 중 문제가 발생했을 때 훨씬 빠르게 대응할 수 있습니다.


14. 웹훅 처리 실패도 기록해야 합니다

결제사에서 웹훅을 보냈다고 해서 우리 서버가 항상 정상적으로 처리하는 것은 아닙니다.

웹훅을 받는 순간 서버 오류가 발생하거나 데이터베이스 문제가 생길 수도 있습니다.

이런 경우 결제사는 결제 완료라고 알고 있지만 내부 주문 상태는 갱신되지 않을 수 있습니다.

따라서 다음과 같은 웹훅 처리 정보를 기록하는 방법을 고려할 수 있습니다.

  • 이벤트 수신 시각
  • 결제사 이벤트 ID
  • 이벤트 종류
  • 처리 결과
  • 실패 사유
  • 재처리 여부

실패한 이벤트를 나중에 다시 처리할 수 있도록 구성하면 일시적인 서버 오류가 발생하더라도 주문 상태를 복구할 수 있습니다.


15. 결제 완료 화면을 새로고침해도 중복 처리되지 않아야 합니다

사용자가 결제 완료 페이지에 도착한 뒤 새로고침을 누를 수도 있습니다.

뒤로 가기를 눌렀다가 다시 페이지에 접근할 수도 있습니다.

만약 결제 완료 페이지가 열릴 때마다 주문 완료 로직을 다시 실행하면 문제가 생길 수 있습니다.

재고 차감, 포인트 지급, 쿠폰 사용, 알림 발송 등이 반복될 수 있기 때문입니다.

따라서 결제 완료 처리는 페이지 방문 횟수와 무관하게 한 번만 실행되도록 만드는 것이 좋습니다.

완료 화면은 이미 확정된 주문과 결제 상태를 조회해 사용자에게 보여주는 역할에 가깝게 구성할 수 있습니다.


16. 결제 상태 변경에는 이력을 남기는 것도 좋습니다

결제와 주문 상태가 여러 번 변경될 수 있다면 현재 상태만 저장하는 것보다 변경 이력을 함께 관리하면 운영에 도움이 됩니다.

예를 들어 다음과 같은 이력이 남을 수 있습니다.

14:01 주문 생성 → 14:03 결제 승인 → 14:03 주문 결제 완료 → 15:20 취소 요청 → 15:21 결제 취소 완료

이런 기록이 있으면 고객 문의가 들어왔을 때 어느 시점에 어떤 처리가 이루어졌는지 확인하기 쉽습니다.

특히 관리자에 의한 수동 취소와 자동 웹훅 처리를 구분해서 기록하면 문제를 추적하는 데 도움이 됩니다.


17. 관리자에서 직접 결제 상태를 임의로 변경하는 것은 주의해야 합니다

운영 편의를 위해 관리자 페이지에서 결제 상태를 직접 바꾸는 기능을 만들고 싶을 수 있습니다.

하지만 실제 결제사 상태와 관계없이 내부 값만 변경할 수 있다면 데이터가 쉽게 어긋날 수 있습니다.

예를 들어 실제 카드 결제는 정상 상태인데 관리자가 내부 데이터를 ‘환불 완료’로 직접 변경하면 두 시스템의 상태가 달라집니다.

따라서 중요한 결제 상태는 가능한 한 결제사 처리 결과를 기준으로 변경하고, 수동 변경이 필요한 경우에는 별도의 권한과 사유 기록을 두는 것이 좋습니다.

운영 편의를 위해 만든 관리자 기능이 오히려 결제 데이터 불일치의 원인이 되지 않도록 해야 합니다.


18. 외부 결제 API가 느리거나 응답하지 않는 상황도 생각해야 합니다

결제 기능은 외부 결제사와 연결되기 때문에 우리 서비스가 정상이어도 외부 API가 느려질 수 있습니다.

결제 요청 이후 응답이 늦어지거나 순간적으로 통신이 실패할 수도 있습니다.

이런 상황에서 곧바로 결제 실패로 확정하면 실제로는 결제가 승인된 상태와 어긋날 수 있습니다.

반대로 무한정 결제 처리 중으로 둘 수도 없습니다.

따라서 서비스 특성에 따라 일정 시간 후 결제 상태를 다시 조회하거나 웹훅을 통해 최종 상태를 확인하는 흐름을 설계할 수 있습니다.

외부 API 연동에서는 정상 응답뿐 아니라 응답이 늦거나 확인하기 어려운 상태에서 어떻게 복구할 것인지가 중요합니다.


19. 주문을 먼저 만들지 결제를 먼저 할지도 정해야 합니다

결제 시스템에서는 주문 데이터가 만들어지는 시점도 중요합니다.

일반적으로 결제 전에 주문을 생성하고 ‘결제 대기’ 상태로 저장한 뒤 결제가 완료되면 상태를 변경하는 방식이 사용될 수 있습니다.

이렇게 하면 결제 과정에서 문제가 생겼을 때 어떤 주문에 대한 결제였는지 추적하기 쉽습니다.

다만 결제하지 않고 이탈한 주문이 계속 쌓일 수 있기 때문에 미결제 주문을 언제까지 보관할지 정책을 정해야 할 수도 있습니다.

서비스에 따라 일정 시간이 지난 미결제 주문을 자동 취소 처리하거나 별도로 구분할 수 있습니다.


20. 결제 완료 후 다음 업무까지 연결되는지도 봐야 합니다

결제 완료는 많은 서비스에서 다음 업무의 시작점입니다.

쇼핑몰이라면 재고 차감과 배송 준비가 시작될 수 있고, 예약 서비스라면 예약이 확정될 수 있습니다.

디지털 서비스라면 이용권이 활성화되거나 콘텐츠 접근 권한이 부여될 수 있습니다.

따라서 결제 상태가 잘못 반영되면 이후 업무도 모두 영향을 받을 수 있습니다.

예를 들어 결제는 완료됐지만 주문이 미결제로 남으면 재고가 차감되지 않거나 서비스 이용권이 활성화되지 않을 수 있습니다.

결제 상태 동기화가 중요한 이유는 단순히 화면에 표시되는 텍스트 때문이 아니라 이후 전체 업무 흐름의 기준이 되기 때문입니다.


21. 결제 기능을 개발할 때 확인하면 좋은 항목

개발 견적서에 ‘PG 결제 연동’이라는 항목이 있다고 해서 모든 결제 시스템의 범위가 같은 것은 아닙니다.

단순히 결제창을 연결하는 것과 주문·결제 상태를 실제 운영 수준으로 관리하는 것은 개발 범위가 다릅니다.

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

  • 승인 확인: 서버에서 실제 결제 승인 여부를 확인하는가?
  • 금액 검증: 주문 금액과 실제 승인 금액을 비교하는가?
  • 웹훅: 결제 상태 변경을 서버에서 받을 수 있는가?
  • 중복 방지: 같은 결제 이벤트가 여러 번 처리되지 않는가?
  • 상태 구분: 주문 상태와 결제 상태를 분리해서 관리하는가?
  • 취소: 주문 취소와 실제 결제 취소가 연결되는가?
  • 부분 취소: 일부 금액만 취소해야 하는 경우가 있는가?
  • 환불: 환불 진행 상태를 관리해야 하는가?
  • 실패 기록: 결제 실패 원인을 확인할 수 있는가?
  • 웹훅 기록: 처리 실패한 이벤트를 추적할 수 있는가?
  • 재처리: 일시적인 오류가 발생했을 때 복구할 수 있는가?
  • 관리자: 결제 상태가 맞지 않는 주문을 확인할 수 있는가?

이런 항목을 미리 정리하면 결제창만 연결된 서비스가 아니라 실제 운영 상황까지 고려한 결제 시스템의 범위를 구체적으로 잡을 수 있습니다.


22. 결제 시스템의 핵심은 화면보다 ‘상태’입니다

사용자에게 보이는 결제 과정은 몇 개의 화면으로 끝날 수 있습니다.

결제 버튼을 누르고 결제창을 거쳐 성공 화면을 확인하는 정도입니다.

하지만 실제 서비스에서는 그 뒤에서 여러 상태가 계속 연결됩니다.

주문이 생성되고, 결제가 승인되고, 승인 결과가 검증되고, 주문 상태가 바뀌며, 이후 취소와 환불까지 이어집니다.

이 중 어느 하나라도 제대로 연결되지 않으면 결제사와 내부 주문 정보가 서로 달라질 수 있습니다.

그래서 결제 기능을 설계할 때는 화면 흐름뿐 아니라 각 단계에서 어떤 상태가 만들어지고 어떤 기준으로 다음 상태로 변경되는지를 함께 정의하는 것이 중요합니다.


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

결제 완료 화면이 정상적으로 표시된다고 해서 결제 시스템 전체가 안정적으로 동작한다고 보기는 어렵습니다.

사용자가 결제 후 브라우저를 닫거나 네트워크가 끊길 수도 있고, 외부 결제 API의 응답이 늦어질 수도 있습니다.

이런 상황에서도 실제 결제 상태와 내부 주문 상태가 최대한 일치하도록 설계해야 합니다.

이를 위해 다음과 같은 전체 흐름을 함께 검토해보는 것이 좋습니다.

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

결국 결제 시스템에서 중요한 것은 사용자가 ‘결제 완료’라는 화면을 보는 것이 아니라 실제 결제사 상태와 우리 서비스의 주문 데이터가 지속적으로 일치하는가입니다.

결제와 주문 상태 관리가 고민이라면 OneSoft와 전체 처리 흐름을 점검해보세요.

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

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

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