본문 바로가기
개발 기타

Adyen 문서로 살펴보는 결제 API 의 멱등성(Idempotency)

by RWriter 2021. 8. 5.
반응형

먼저 멱등성의 개념을 알아볼 필요가 있다.

 

https://developer.mozilla.org/ko/docs/Glossary/Idempotent

 

멱등성 - 용어 사전 | MDN

동일한 요청을 한 번 보내는 것과 여러 번 연속으로 보내는 것이 같은 효과를 지니고, 서버의 상태도 동일하게 남을 때, 해당 HTTP 메서드가 멱등성을 가졌다고 말합니다. 다른 말로는, 멱등성 메

developer.mozilla.org

 

발췌

 

동일한 요청을 한 번 보내는 것과 여러 번 연속으로 보내는 것이 같은 효과를 지니고, 서버의 상태도 동일하게 남을 때, 해당 HTTP 메서드가 멱등성을 가졌다고 말합니다. 

 

보통 Http 메서드의 GET, POST, PUT, HEAD, DELETE 중

 

POST를 제외한 GET, HEAD, PUT, DELETE 는 멱등성을 가진다고 말한다.

 

왜냐하면 POST는 리소스의 생성을 의미하기 때문에 요청이 반복되면 리소스가 계속 생성이 되는 것이고,

 

GET, PUT, DELETE 등은 여러번 요청해도 서버 상태가 변하지 않기 때문이다.

(PUT, DELETE 는 한번 수정, 삭제가 되면 여러번 요청해도 같은 상태를 유지하는게 원칙이다.)

 

 

그런데.. 개인적으로 Http method 의 멱등성을 원칙대로 고수하기 어려운 경우가 많다고 생각한다.

 

옛날처럼 리소스가 어떤 Html 파일이나, 단순한 것(게시글 등..) 을 의미하지도 않고, 시스템이 복잡해지면서 각종 요구사항을 수행하게 되기 때문이다.

 

한 트랜잭션 안에서 여러 api 를 호출할 수도 있고, 그쪽에서도 멱등성 응답을 준다는 보장도 없고 등등..

 

(global convention 을 지키려는 노력은 필요하지만...)

 

 

아무튼! 멱등성이란 그러한 것인데,

 

멱등성을 제공하는 API 를 어떻게 만들어야 할 지에 대한 가이드를 글로벌 결제서비스 기업인 Adyen 의 문서로부터 살펴본다.

 

https://docs.adyen.com/development-resources/api-idempotency

 

API idempotency | Adyen Docs

API idempotency Learn how to safely resend API requests without performing the same operation multiple times.

docs.adyen.com

 

결제, 결제 취소 API 의 경우 설계를 안전하게 하지 않을 경우 고객의 금전과 연관이 있으므로 이슈에 굉장히 크리티컬하다.. 

 

멱등성을 제공하는 이유

- 언제든 여러번 호출 할 수 있습니다. 실패 및 재시도 시 원치 않는 중복을 방지하는 데 도움이 됩니다. 예를 들어 타임아웃 오류가 발생한 경우 결제 내역이 한 번만 실행된다는 보장으로 동일한 API 결제 호출을 여러 번 안전하게 재시도할 수 있습니다.

 

POST 요청에 대한 멱등성

- API 요청에서 멱등성을 활성화합니다. Adyen API는 POST 요청에서 멱등성을 지원합니다. (GET, DELETE 및 PUT과 같은 다른 요청 유형은 정의에 따라 (당연히) 멱등합니다.)

 

멱등성 요청 방법

- 요청 헤더에 Idempotency-Key:<Key> 추가 (UUID를 권장)

- 응답을 받지 못한 경우 (예: 타임 아웃), 동일한 헤더로 안전하게 여러번 요청 가능

- 이미 요청을 처리한 경우 첫째번 건에 대한 응답이 반환됨

- 요청이 멱등적으로 처리되었는지는 응답 헤더의 Idempotency-Key 확인

curl https://checkout-test.adyen.com/v67/payments \
-H "x-API-key: YOUR_X-API-KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: UNIQUE-ID-dad126b9-d7b3-4d8d-adf0-7c6e324" \  -- key 추가
-d '{
  "amount":{ 
    "currency":"EUR",
    "value":1000
  },
  "reference":"YOUR_ORDER_NUMBER",
  "paymentMethod": {
    "type": "scheme",
    "encryptedCardNumber": "test_4111111111111111",
    "encryptedExpiryMonth": "test_03",
    "encryptedExpiryYear": "test_2030",
    "encryptedSecurityCode": "test_737"
  },
  "returnUrl":"https://your-company.com/checkout?shopperOrder=12xy..",
  "merchantAccount":"YOUR_MERCHANT_ACCOUNT"
}'

 

 

 

에러 핸들링 방식

일시적 에러(Transient errors)

- 일시적으로 에러가 날 수 있지만, Side effect가 없고 다시 시도할 수 있는 일시적인 오류라고 간주.

- 예를 들어, 동일한 키를 가지고 동시에 요청이 오는 경우 (따닥, 코드 버그, 네트워크 패킷 오류 등?) 

- 처리중인 건이 먼저 있으면 다른 하나는 일시적 오류 응답을 준다. 응답 헤더에  Transient-Error: true

- Transient-Error: false 인 경우는 재요청 하지 말 것 (validation 체크 등 클라이언트 오류이지 않을까?)

 

재처리(Retries and exponential backoff)

- 재처리를 하고자 하면 Exponential backoff 전략을 사용할 것 (점점 재처리하는 기간이 늘어나는 식으로..)

 

회복력에 대한 디자인?(Designing for resilience)

- 멱등성을 제공하기 위해서 Adyen 은 각 요청들을 비교할 수 있는 상태 저장소를 사용합니다. (레디스 정도가 아닐지)

- 멱등성 판단은 이 데이터 베이스에 의존합니다. 

- 드물게 데이터 베이스를 사용할 수 없는 경우  

  - 703: "필수 리소스를 일시적으로 사용할 수 없음" 과 함께 HTTP 503 – 서비스를 사용할 수 없음 을 응답합니다.

- 이 경우는 Transient error 로 간주하지만 아래 두 가지 처리를 고려해 주세요.

  1.  처리를 중단하고 나중에 재요청

  2. 멱등요청 헤더 제외하고 요청

 

 

 

일하고 있는 곳에도 결제 API를 멱등성 있게 제공하고자 노력하고 있다.

 

adyen 처럼 세밀하게 제공하지는 않지만 

API를 사용하는 클라이언트 입장을 고려해 응답을 3가지로 분류하여 Unknown 인 경우에 재처리 여부를 결정하도록 하고 있다.

 

성공(Success)

실패(Failure)

모름(Unknown)

 

 

1. 중복 요청, 혹은 특정 범위의 Lock 에 걸려 트랜잭션을 실행할 수 없는 경우

-> transient error 로 간주하지만 Adyen처럼 헤더값이 아닌 unknown 으로 응답을 내려주어 재요청 가능

 

2. 클라이언트 입장에서 TimeOut 을 받았을 경우

-> 클라쪽에서 Unknown 으로 간주했을 것이기 때문에 재요청 예상

 

3. 클라이언트 입장에서 인프라 장애 등 정상처리를 못한 경우

-> 클라쪽에서 Unknown 으로 간주했을 것이기 때문에 재요청 예상

 

4. 우리쪽에서 멱등 key를 저장하는 레디스가 고장났을 경우

-> 이 경우는 결제 서버에서 unknown 응답을 내려주기 때문에 재요청 가능

 

 

 

개인적인 생각에 결제 API 에 멱등성이 중요한 다른 이유는 '성공을 보장' 하기 위함이기도 하지 않을 까 싶다.

 

Unknown 응답을 받았을 때는, 서버쪽이 성공했는지 실패했는지 알 수 없기 때문에 재요청 뿐 아니라 요청에 대한 보상트랜잭션(결제요청건 제거) 으로 돌려놓는 방식도 있을 건데

 

지속적인 재요청을 통해 고객의 결제를 성공시키고자 노력해보자! 라는 의미도 담겨 있는게 아닐까 싶다.

반응형

댓글