Webhook 없이 API로 결제를 확인하는 방법

API를 이용한 서버 측 인보이스 확인: 결제 상태, 제한된 폴링, 오류 처리, 주문의 단 한 번 이행과 재시작 후 복구.

수정일:

Webhook 없이도 애플리케이션 서버에서 결제를 확인할 수 있습니다. Cryptanio API로 인보이스를 주기적으로 조회하고 주문과 대조한 뒤, 현재 상태가 paid일 때 주문을 이행하면 됩니다. 이러한 주기적 조회를 폴링이라고 합니다. 확인 대기열, 오류 처리, 중복 이행 방지가 필요하며, 성공한 HTTP 응답 하나만으로는 결제가 확인되지 않습니다.

애플리케이션에서 백그라운드 작업을 실행할 수 있지만 수신 알림을 받기는 아직 불편한 경우에 적합합니다. 변경 사항은 다음 조회에서 발견되므로 폴링은 즉각적인 알림을 제공하지 않습니다.

필요한 사항

자체 데이터베이스에 주문과 인보이스의 연결 정보를 저장하세요. 인보이스의 id, 프로젝트, 자체 reference, 합의한 가격, 운영 모드를 보관합니다. 식별자는 문자열로 저장하세요. 확인 대상은 해당 주문의 인보이스이며, 금액이 비슷하다는 이유로 찾은 트랜잭션이 아닙니다.

invoices 읽기 권한, payments 서비스 및 필요한 프로젝트에 대한 접근 권한이 있는 서버용 API 키를 준비하세요. 실제 주문에는 그에 맞는 실제 운영 모드가 필요합니다. 키를 브라우저, 구매자에게 보내는 링크, 공개 로그에 넣지 마세요. 별도의 대조 프로세스에는 읽기 전용 권한이면 충분합니다.

1단계. 현재 상태 가져오기

이 예제는 인보이스 하나를 조회합니다. 서버에 CRYPTANIO_API_KEY와 CRYPTANIO_RESOURCE_ID를 설정하세요. 두 번째 값은 저장한 인보이스의 id이며 주문 번호나 TXID가 아닙니다.

#!/usr/bin/env bash
set -euo pipefail
: "${CRYPTANIO_RESOURCE_ID:?Set CRYPTANIO_RESOURCE_ID}"
: "${CRYPTANIO_API_KEY:?Set CRYPTANIO_API_KEY}"
urlencode() {
  local LC_ALL=C text="$1" char index
  for ((index=0; index<${#text}; index++)); do
    char="${text:index:1}"
    case "$char" in [a-zA-Z0-9.~_-]) printf '%s' "$char" ;; *) printf '%%%02X' "'$char" ;; esac
  done
}
response_file="$(mktemp)"
trap 'rm -f "$response_file"' EXIT
transfer_status=0
http_status="$(curl \
  --silent --show-error --max-time 30 --request GET \
  --header "Authorization: Bearer $CRYPTANIO_API_KEY" \
  --header 'Accept: application/json' \
  "https://api.cryptanio.com/v1/invoices/$(urlencode "$CRYPTANIO_RESOURCE_ID")" \
  --output "$response_file" --write-out '%{http_code}')" || transfer_status=$?
if [[ "$http_status" == 2?? ]]; then
  cat "$response_file"
  exit "$transfer_status"
fi
printf 'HTTP %s\n' "$http_status" >&2
cat "$response_file" >&2
exit 1

예제 다운로드

HTTP 200을 받으면 JSON을 파싱하고 id, project_id, reference, sandbox를 자체 기록과 대조하세요. 실제 주문은 sandbox=false여야 합니다. invoice_currency와 invoice_amount에 가격을 저장했다면 이 필드도 확인하세요. 다른 주문의 인보이스나 다른 모드의 기록으로 주문이 변경되어서는 안 됩니다.

GET의 checkout_url 링크는 조회용입니다. 인보이스 생성 응답에서 저장한 대화형 링크를 이 링크로 바꾸지 마세요. 상태 조회는 새 결제를 만들거나 새로운 대화형 checkout을 발급하지 않습니다.

2단계. 대기와 결제 완료 구분하기

인보이스의 status를 기준으로 판단하세요.

  • pending — 결제를 기다리는 상태입니다. seen — 송금은 발견했지만 결제가 아직 완료되지 않았습니다.
  • partial — 금액 일부가 확인되었으며 결제 규칙에 따라 대기를 계속합니다.
  • underpaid, overpaid, late — 검토가 필요합니다. needs_decision과 판매자의 결정을 고려하세요.
  • paid — 인보이스가 결제된 것으로 기록되었습니다. 주문과의 연결을 확인한 뒤 이행을 준비할 수 있습니다.
  • expired, canceled — 구매자의 현재 결제 대기는 종료하되 이후 대조를 위해 기록은 보관하세요.

기한이 지났다고 늦은 입금 가능성이 사라지지는 않습니다. 수동 결정이나 트랜잭션 롤백도 이후 처리를 바꿀 수 있습니다. 어떤 상태를 영원히 확정된 것으로 선언하거나 페이지가 닫혔다는 이유로 주문과 인보이스의 연결을 삭제하지 마세요.

확인된 트랜잭션과 결제된 인보이스는 서로 다릅니다. 확인 횟수만으로 필요한 자산, 금액, 결제 정보가 바로 이 주문에 해당한다는 사실이 증명되지는 않습니다. 차이점은 주문이 미결제 상태일 때의 확인 안내에 자세히 나와 있습니다.

3단계. 금액과 결제 시도 고려하기

결제 방식을 바꾸면 같은 인보이스에 여러 시도가 생길 수 있습니다. 결제된 인보이스에서는 settled_attempt_id와 settled_attempt를 고려하세요. 내역을 검토할 때 attempt_history.has_more와 다음 페이지 커서를 확인하고, 선택한 시도의 송금에는 payment_history를 사용하세요. 첫 페이지에 전체 내역이 모두 들어 있지는 않을 수 있습니다.

amount는 보내야 할 정확한 암호화폐 금액입니다. amount_raw와 paid는 자산의 최소 단위로 표현한 정수 문자열이며, decimals가 소수 자릿수를 정합니다. 부동소수점 수로 비교하거나 일반 문자열처럼 사전순으로 비교하지 마세요. 자체 금액 확인이 인보이스 상태와 프로젝트 규칙을 대신해서도 안 됩니다. 초과 결제는 수락되거나 검토 대상으로 넘어갈 수 있습니다. 조건을 확인하지 않고 부족하게 입금된 금액들을 합산하는 것도 허용해서는 안 됩니다.

4단계. 폴링과 재시도 제한하기

확인 작업을 동시 실행 수가 제한된 서버 대기열에 넣으세요. 구매자가 여러 탭을 열었다고 독립적인 조회 루프가 생겨서는 안 됩니다. 계정의 전체 요청량을 고려하세요. 서로 다른 API 키와 모드가 같은 할당량을 공유합니다. 최신 제한은 API 참고 문서에 나와 있습니다.

자체 프로세스에 맞게 대기 시간을 정하고 다른 요청을 위한 여유를 남기세요. 네트워크 오류나 HTTP 500/503에서는 지연 시간을 늘리고 무작위 편차를 적용해 제한된 횟수만 재시도하세요. HTTP 429에서는 전체 요청량을 줄이고, Retry-After가 있으면 지정된 시간만큼 기다리세요. 헤더가 없다고 즉시 재시도해서는 안 됩니다. 각 요청에는 타임아웃을, 각 재시도 묶음에는 한도를 두고, 한도에 도달한 작업은 이후 검토할 수 있도록 남겨야 합니다.

HTTP 401/403이 발생하면 키와 권한을 확인해야 합니다. 404에서는 ID, 프로젝트, 모드를 확인하세요. 접근 제한으로 인보이스가 보이지 않을 수 있습니다. 이러한 오류가 고객이 아무것도 지급하지 않았다는 뜻은 아닙니다.

5단계. 이행 상태와 복구 정보 저장하기

상태가 paid이면 주문 상태와 이행 작업을 일관되게 저장하세요. 고유한 주문 키와 병렬 처리 방지 장치를 사용하세요. paid를 다시 읽었다고 접근 권한이나 상품을 두 번 제공해서는 안 되며, 전달 후 장애가 발생해도 두 번째 이행이 생성되어서는 안 됩니다.

마지막으로 성공한 확인과 다음 작업을 영속 저장소에 보관하세요. 재시작한 뒤에는 페이지를 열 때만 절차를 시작하지 말고 미완료 확인과 작업을 재개하세요. 현재 결제를 기다리는 시간은 자체 프로세스의 기한으로 제한하되, 별도의 백그라운드 대조에서는 이미 결제된 주문까지 포함해 늦은 입금, 결정, 롤백을 고려해야 합니다. 아직 실행하지 않은 이행 직전에 이행 권한이 여전히 유효한지 다시 확인하고, 이미 부여한 접근 권한은 재검토할 수 있도록 하세요.

준비한 응답으로 반복된 paid, 타임아웃, 재시작, 롤백 이후의 전환을 확인하세요. 주문은 한 번만 이행되고 불확실하거나 분쟁이 있는 결제는 검토 상태로 남아야 올바른 결과입니다. 나중에는 같은 이행 보호 장치를 유지하면서 폴링에 webhook을 추가할 수 있습니다.