課金の仕組み — Stripe が代行していること・ Webhook で権利を付ける

この記事は、プログラミングやバイブコーディングをする上で最低限身につけたほうがよい IT の実践的な知識を 1 から身につける「IT 基礎知識講座の一部」です。
カードの情報は決済代行サービスで止まり、自分のサーバーを通りません。有料の機能を出す根拠が Webhook の通知になる理由を図解で確かめます。

この記事は、課金の仕組みを扱います。

カードの情報を自分のサーバーに届かせない仕組みと、支払いの結果を自分のアプリに反映する仕組みの 2 つに分かれます。

カードの情報が通る場所と、権利を付ける根拠
利用者がカードを入力決済代行サービスが受け取る支払いの結果が通知で届くカード番号はここで止まるmy-app が権利を記録する
上の枝がカードの情報で、決済代行サービスの中で止まります。下の枝が支払いの結果で、my-app はこちらだけを見て有料の機能を出します。

カード番号は上の枝で止まり、my-app には届きません。

有料の機能を出す根拠になるのは、下の枝で届く通知だけです。

カード番号の受け取りもカード会社とのやり取りも決済代行サービスの側で行われ、my-app に残るのは結果を記録に書く部分だけです。

カードの情報を受け取るのは決済代行サービスで、自分のサーバーではない

支払いの処理には、カードの番号・有効期限・セキュリティコードが要ります。

カード情報を扱う事業者が守る基準がPCI DSS(Payment Card Industry Data Security Standard)、カード決済を代行する外部サービスが決済代行サービス(payment service provider)です。

決済代行サービスは Stripe や PayPal が代表です。

入力欄をどこに置くかで、カード番号が通る場所が変わります。

入力欄の置き場所カード番号が通る場所PCI DSS の対象
my-app で受け取って保存my-app と決済代行サービスmy-app も対象
my-app で受け取って転送my-app と決済代行サービスmy-app も対象
決済代行サービスの画面決済代行サービスだけmy-app の範囲は最小

3 行目だけが、カード番号を my-app のサーバーに通していません。

このとき my-app に残るのは支払いを見分ける値だけで、上の 2 行ではカード番号そのものや通信の記録が残ります。

保存せず転送するだけでも値は自分のサーバーを通り、通った時点で PCI DSS の対象範囲に my-app も入ります

my-app 側で守る範囲をいちばん小さくするため、入力欄ごと決済代行サービスに預けます。

1 回の支払いで、どの情報がどこまで届くか
支払い 1 回分(pay_88)
決済代行サービスの中だけに残る
  • カードの番号・有効期限・セキュリティコード
  • カード会社とのやり取りの記録
my-app に届く
  • 支払いを見分ける値 pay_88
  • 金額と、支払いが済んだかどうか
  • どの利用者の支払いか(u_1024)
外枠が pay_88 という 1 回の支払い。カードの情報は上の枠の中だけに残る。my-app が持つのは、支払いが済んだという事実と、それを見分ける値だけ。

カード番号は my-app を通らない

カード番号を自分で受け取らなければ、それを守る責任も自分の側には来ません。

保存しても、すぐ転送しても自分のサーバーを通せば同じ責任がかかるので、入力欄ごと決済代行サービスに預けて、my-app に届くのを支払いが済んだという事実だけにします。

支払いは、決済代行サービスのドメインにある決済ページで終わる

カードの情報を自分のサーバーに通さないよう、入力する画面そのものを別の場所に置いたものがホスト型の決済ページ(hosted checkout page。決済代行サービスが用意し、そのドメインで表示される支払いの画面)です。

別のドメインの画面へ利用者を移す動きがリダイレクト(redirect)です。

2 段階目で渡すのは金額と戻り先で、決済代行サービスはその支払い専用の決済ページの URL を返します。

この受け渡しは Web API を呼んで行い、API キーを添えます。

支払いの間、ブラウザが開いている 2 つのドメイン
利用者のブラウザ
my-app のドメイン
  • 有料プランの申し込み画面
  • 支払いのあとに開く /thanks
決済代行サービスのドメイン
  • カード番号を入力する決済ページ
  • この画面の中身は my-app からは見えない
外枠が利用者のブラウザ。上の枠が my-app の画面、下の枠が決済代行サービスの画面で、リダイレクトで上から下へ移り、また上へ戻る。

決済ページは、my-app の画面へ枠として置く形でも提供されます。

見え方は変わりますが、入力された値の行き先は同じです。

2 つの置き方で、変わるところと変わらないところ
別の画面へ移る形URL は決済代行のドメイン支払いのあとは/thanks へ戻る画面に枠を置く形URL は my-app のドメイン支払いのあとも同じ画面のままどちらの形でもカード番号の行き先は決済代行サービスmy-app に届くのは支払いの事実だけ
上の 2 行が置き方ごとの違い、いちばん下の行がどちらの形でも変わらないことです。左から右へ、置き方・ブラウザに出る URL・支払いのあとの順に並びます。

決済ページは決済代行サービスが用意する

カード番号を入力する画面は、my-app ではなく決済代行サービスが持っています。

利用者はリダイレクトでその画面へ移り、支払いが終わると /thanks に戻りますが、画面に枠として置く形にしても、入力された値の行き先は変わりません。

権利を付ける根拠は、戻ってきた画面ではなく Webhook の通知

有料の機能を出してよいかを決めるのは決済代行サービスではなく my-app で、その記録が権利(entitlement。支払いを済ませた利用者が使える機能の範囲を記録したもの)です。

利用者の画面が /thanks に戻ってきただけでは、my-app は支払いが済んだかを知りません。

支払いのあとに起きることを、2 つの道に分けて見てください。

支払いが終わったあと、my-app へ向かう 2 つの道
ブラウザが/thanks へ移る画面に完了の表示が出る閉じられると何も届かない決済ページで支払いが終わるサーバーが/webhook を呼ぶ署名つきの通知が届く決められた期間再送される
1 回の支払いから 2 つの道が分かれます。上はブラウザが通る道で途中で止まることがあり、下はサーバー同士の道で成功を返すまで続きます。

上の道は、利用者が支払いの直後にブラウザを閉じれば、そこで止まります。

下の道はサーバー同士なので、届かなければ決められた回数と期間のあいだ通知がやり直されます。

どちらを根拠にするかで、同じ 3 人でも権利が付く相手が変わります。

その利用者に起きたこと/thanks で判断すると/webhook で判断すると
支払って /thanks も開いた権利が付く権利が付く
支払ったあとすぐ閉じた権利が付かない権利が付く
支払わずに /thanks を開いた権利が付く権利が付かない

/thanks で判断すると、閉じた人には付かず、支払っていない人には付いてしまいます。

権利を付ける根拠にするのは、下の道の通知だけです。

決済では、同じ通知を 2 回反映すると、使える期限が二重に延びてしまいます。

通知を見分ける値を記録しておき、2 回目の通知では権利を変えません。

/webhook に届いたものを、my-app がどう仕分けるか
署名が合わない通知初めて届いたevt_7再送されたevt_7/webhook が受け取る捨てる。権利は変わらない権利を付けてevt_7 を記録記録にあるので何もしない
左の 3 つはどれも同じ /webhook に届きます。真ん中でまとめて受け取り、署名と通知を見分ける値で右の 3 つに分かれます。

署名が合わない通知は、権利を変えずに捨てます。

残る 2 つの違いは、その通知をすでに反映したかどうかだけです。

my-app のデータベースに置く 3 種類の記録
my-app のデータベース
利用者の記録
  • 利用者を見分ける値 u_1024
  • パスワードのハッシュ
権利の記録
  • プランの種類(paid)
  • 使える期限
  • どの利用者のものか(u_1024)
受け取った通知の記録
  • 通知を見分ける値 evt_7
  • 反映した日時
外枠が my-app のデータベース。真ん中の権利の記録が、プランの種類と使える期限を持つ。下の通知の記録があるので、再送された同じ通知を 2 回反映せずに済む。

画面で有料のボタンを隠しても、そのコードは利用者の端末に届きます。

サーバー側で毎回権利を確かめるのは、ログイン機能の仕組みで扱った認可と同じです。

カード番号は真ん中の囲みの中で止まり、右の囲みには入りません。

権利が付くのは矢印 2 の通知が届いたときで、利用者のブラウザが戻ってきたときではありません。

支払いを知らせるのはサーバーからの通知

支払いが済んだことを知らせるのは、利用者が戻ってきた画面ではなく、決済代行サービスのサーバーから届く通知です。

届いたものは署名を照合し、すでに反映した通知なら権利を変えず、その結果を自分のデータベースに書いて、有料の機能を出すかどうかを決めます。

サブスクリプションでは、期間ごとに通知が届いて権利が変わる

月ごとや年ごとに料金がかかる契約がサブスクリプション(subscription。決められた期間ごとに支払いが自動で繰り返される契約)です。

1 回きりの支払いとサブスクリプションで、変わるのは通知の回数
支払いの形1 回きりの支払い通知は 1 回だけ権利の期限は変わらないサブスクリプション期間ごとに何度も届く次の支払いまで期限が延びる
左が支払いの形です。上の道が 1 回きり、下の道がサブスクリプション。どちらも権利を付ける根拠は Webhook の通知で、違うのは届く回数と期限です。

繰り返される支払いが、毎回成功するとは限りません。

失敗の行では、決済代行サービスが日を置いて請求をやり直します。

その間も使える状態を残すか、すぐ止めるかは my-app が決めます。

公開する前に確かめられるのがテストモード(test mode。実際の請求を起こさずに動作を確かめるための、決済代行サービスが用意した状態)です。

テスト用と本番用で API キーが分かれているので、環境変数の値で切り替えます。

権利を外す通知まで決めてから公開する

サブスクリプションでは期間が来るたびに支払いが行われ、そのたびに通知が届きます。

付ける通知だけを処理すると支払いが止まった利用者が使えるまま残るので、付ける・延ばす・外すの 3 つを届く通知ごとに決め、公開の前にテストモードで一通り通しておきます。

QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1決済代行サービスを使って支払いを受けるとき、カードの情報はどこを通りますか。

Q2支払いが済んだと判断して利用者に権利を付ける根拠に使うのはどれですか。

Q3継続課金で支払いの失敗を知らせる通知が届いたとき、自分のアプリが行うのはどれですか。