stripeって? webhookって?
web決済を使うAPIとして知っている人は知っている有名なものがstripeです。
このAPIで決済を行う際に、webhookという機能をつかえます。
簡単にいうと、stripe側で発生したイベントをこちらのシステムのエンドポイントに伝達してくれます。
これによりたとえば、決済画面でユーザーが決済途中で画面がきりかわるまえに画面を消したとしても、決済が完了したことをこちらのシステムで確認できるようになります。
決済機能という高次元の機能だからこそ、細かいですがこういうところは大切です。
今回は、そのwebhookでこちら側でイベントをうけとって関数内で処理する際に注意すべきことを自分の備忘録もかねて書いときます。
環境
webhook通知を受け取るRoute
まず、laravelでrouteを設定します。webhook通知を受け取るメソッドは、POSTです。
また、Csrfの適応を除外するために、VerifyCsrfToken.phpにも除外対応のURLを記入しときましょう。
webhook通知を受け取るContollerのメソッド
はじめに、うけとるための呪文的な書き方あります。
//後々説明↓
$current_api_key = $this->getCurrentStripeApiKey();
if ($current_api_key == null) {
$this->setApi();
}
//後々説明↑
//↓呪文---------------------------------------------------------
/** @var string $payload */
$payload = @file_get_contents('php://input');
/** @var string $sig_header */
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
/** @var string $endpoint_secret */
$endpoint_secret = config('services.stripe.end_point_secret');
try {
/** @var \Stripe\Event $event */
$event = \Stripe\Webhook::constructEvent(
$payload,
$sig_header,
$endpoint_secret
);
} catch (\UnexpectedValueException $e) {
// Invalid payload
return response()->json(['message' => 'Stripe Webhook error catch'], 400);
}
// ↓★webhookに成功したあとに流れ
http_response_code(200);
//↑呪文---------------------------------------------------------
その中で、クライアントをビルドする書き方が自分のしっている方法で2種類ありますが、
public function setApi(): void
{
Stripe::setApiKey(config('services.stripe.secret_key')); //シークレットキー
return;
}
public function setApi2(): \Stripe\StripeClient
{
$stripe = new \Stripe\StripeClient(config('services.stripe.secret_key'));
return $stripe;
}
上のほうは、グローバルにstripeAPI関連のメソッドを1つなぎの処理が完了するまで使えます。
下のほうはスコープが決まっていますが、引数として$stripeを渡していけば他のメソッド内でも使えます。
1つ目の注意ポイント
1つなぎの処理内で上記の処理を複数回行うと、stripe側では、「HTTP ステータスコード タイムアウト 」
をだしてしまいます。全然重い処理でも、長く時間がかかる処理でなくてもそうなってしまう可能性が高いです。
なので、1つなぎの処理内で複数回クライアントをビルドしないように、下記のような関数を利用することを推奨します。
function getCurrentStripeApiKey(): null|string
{
if (method_exists(\Stripe\Stripe::class, 'getApiKey')) {
return \Stripe\Stripe::getApiKey();
}
return null;
}
自分がグローバルstripeのAPIの処理を使うほうのクライアントをビルドするコードを利用していること前提ですが、この関数の戻り値が、stringなら、すでにクライアントをビルド済みで、nullならクライアントをビルドしてないことが判断できます。
2つめの注意ポイント
決済完了直後のイベント(payment_intent.succeeded)をwebhook通知で取得している場合にですが、
返金の処理を直後に行うと「HTTP ステータスコード タイムアウト 」になります。
返金の処理とはたとえば↓のような処理です。
/** @var \Stripe\Refund $refund */
$refund = \Stripe\Refund::create([
'payment_intent' => $payment_intent_id,
'amount' => $refund_amount,
]);
これは、おそらくどこにも書いて無く、この法則に気づくのに多少時間を要しました。
対応策は簡単で、1秒でも時間をあけると「HTTP ステータスコード タイムアウト 」は起きなくなります。たとえば↓のような感じです。
//↓こんだけ
sleep(1);
//↑こんだけ
/** @var \Stripe\Refund $refund */
$refund = \Stripe\Refund::create([
'payment_intent' => $payment_intent_id,
'amount' => $refund_amount,
]);
盲点ですよね、タイムアウトだから時間を短くすることを考えていたのに解決策は時間を長くすることです。
もちろん、言い忘れていましたが、関数の初めのほうで、
http_response_code(200);はおそらく必須です。
ただ、その後続の処理でsleep(10);とかだけしてもタイムアウトにならないです。
あとは、普通に後続の処理で何かしらの凡ミスやエラー(関数を定義してないや、型にあわない 等)のときも「HTTP ステータスコード タイムアウト 」となります。
以上です。
返信がありません