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を追加できます。