資料はこちら
NIKKEI COMPASSのStripe決済フローと決済高速化の方法 #決済LT

株式会社overflow 共同創業者CTO
大谷 旅人経路探索エンジンの研究開発後、2010年に株式会社サイバーエージェント入社。Ameba事業本部でシステム開発・運用責任者、事業部ボードとして組織運営などを務める。2013年株式会社メタップス入社、決済、AI分析プラットフォームの基盤開発やシステム開発責任者として従事、2015年に上場を経験。株式会社overflowの共同創業者CTOで設立に参画。
日経COMPASSの技術構成・設計方針
日本経済新聞社は、実はBtoB 向けに企業や記事の情報を売るサービスをたくさん展開しております。本日はそんなサービスの1つである、日経COMPASSの事例紹介をしていきます。
https://www.nikkei.com/が 日経電子版で、その配下のcompassで展開されているサービスになり、記事や企業情報を検索して読めます。

次に構成技術、採用技術についてです。
フロントエンドはNext.jsを使っていて、最近は、App Routerとかも出てきていますが、Pages Routerの方で構築されています。バックエンドには、GraphQLサーバーのApolloサーバーを置いて、画面との通信はGraphQLを使っています。

画面に表示する情報は、日経の内外含めてほぼCOMPASS以外のAPIから取得しています。 記事や企業情報はもちろん、商品マスタや決済履歴についても、StripeのAPIを利用します。
アプリケーションが受け取った外部のAPIレスポンスをアプリケーションキャッシュとしてRedisに保存させています。画面の方は決済のためにStripeとやりとりしていて、 バックエンドではWebhookで完了通知を受け取っています。基本的に表示する情報は他のAPIに依存するような形です。
一応COMPASS内にもDBはありますが、セッション程度の情報しか持たないように意図的にしています。 決済履歴や商品マスタなどは自前のDBに置くサービスがほとんどですが、COMPASSではStripeのAPI越しに取得します。こうすることで、他のAPIと同じように扱えるのと同時に、提供する情報に一貫性を持たせています。

外部決済サービス「Stripe」を活用した決済フローの全体像
決済フローの全体像はこんな感じです。

まずは、ユーザーがコンテンツの購入ページを開くと決済モーダルが表示されます。ここで、COMPASSのバックエンドにPaymentIntent(Stripeにおける支払いを表すオブジェクト)を作成していて、支払いに関係するものは基本的にPaymentIntentに紐づくようにしています。

COMPASSでは、支払い対象のコンテンツの記事タイトルやコンテンツIDなどの情報をメタデータとしてPaymentIntentに持たせています。 ここではまだ決済が実行されていないので、Stripeのダッシュボードで見ると未完了として記録されています。

そして画面の方で、PaymentIntentの内容を基にStripe側で直接決済が実行されます。決済が完了すると、コールバックがStripeから返ってきて、決済完了モーダルが表示されるようになっています。
この後ユーザーはコンテンツ表示ページに遷移しますが、ここでCOMPASSのバックエンドは一度StripeにPaymentIntentの状態を問い合わせます。 決済完了であると判定できれば、コンテンツを返します。
以上がCOMPASSの決済の全体像です。

COMPASS上では購入履歴データを持っていません。Stripe APIで決済完了したPaymentIntentを検索することで、そのまま決済履歴として使えるようにしています。
支払い履歴にあるコンテンツ名や書誌情報などは決済履歴として表示しないといけない情報です。なので、あらかじめPaymentIntentのメタデータとして保持しておいて、支払い後にコンテンツが参照不可・期限切れとなった場合、コンテンツ自体は見れなくなるものの決済情報自体は消えない状態で購入履歴は表示させ続けることが可能です。
外部決済サービスによる受け入れテストにおける問題点
しかし、実際に受入テストをしていると問題が報告されました。
ユーザーが決済完了してからコンテンツの閲覧ページに自動遷移までに30秒程度かかっている、という状況でした。
決済完了画面のモーダル表示について、Stripeから決済完了のコールバックが来て表示されているので問題ありません。そのため、サーバー側の購入判定が遅いのでは、という感じになりました。
COMPASSでは、買ってから半年以上経つと閲覧不可になるというコンテンツの閲覧期限があります。この閲覧期限がある関係上、決済時刻が重要になってきます。StripeではChargeというオブジェクトが司っていて、支払いの度に新しいChargeが作成されるような形になっています。
通常PaymentIntentにChargeは1つだけ紐づくので、バックエンドではPaymentIntentにChargeが紐づいたことで決済完了と判定していました。 画面の決済完了コールバックはすぐ返ってきましたが、PaymentIntentにChargeが紐づくまで30秒ほどかかり、決済完了判定にも時間がかかっていました。
この紐付けにかかる時間をずっと待っていたので、決済完了判定にも時間がかかってしまっていました。

PaymentIntentt< >Chargeの紐付き(決済完了)判定の高速化に向けた取り組み
この判定をどう高速化したかについて紹介します。このシーケンス図については決済周りだけを抜粋したものです。
まずは、画面のコールバックとほぼ同時に、決済完了Webhookも呼ばれていることが分かったので、Webhookで届いたPaymentIntentをRedisに60秒程度の短命で保存するようにしました。
実際に決済判定を行う際には、Stripeの履歴を確認すると同時にRedisにも保存されたPaymentIntentが無いかを調べて、 StripeまたはRedisからPaymentIntentが取得できた場合は決済完了とする形になっています。
こうすることで今まで30秒ほどかかっていた処理が1秒未満に短縮され、ユーザーも支払い完了後にすぐにコンテンツを閲覧できるようになりました。

決済完了後にPaymentIntentにChargeが紐づくのにはある程度の時間がかかるため、決済完了のWebhookで受け取ったPaymentIntentを一時的に保存して使うことで、決済判定を高速化したという形になります。
AI 時代のエンジニア転職は Offers で
あなたのキャリアに合った求人やスカウトが届く、エンジニア向け転職サービスです。
無料ではじめる正社員の求人を探す
Offers Jobs で正社員の求人を探せます。
求人を見る関連記事
イベントレポートtoBサービスで外部決済サービスを導入し、うまくいったこといかなかったこと #決済LT
Offers と Offers MGR の決済に Stripe を組み込んだ経緯と、トライアルの導入がうまくいった点、複数サービスで共通の決済基盤を作れなかった点を共有した #決済LT の登壇レポートです。
株式会社overflow
磯崎 慶太 他1名
イベントレポート【イベントレポート】人間のレビューなしで"そのまま出せる"デザインは作れるか こぎそさんに聞く デザインハーネスの現在地
AIに画面を作らせても、人のレビューは要るのか。デザインハーネスを提唱するこぎそ様が、4層モデルとデモ、レビューコメントから判断基準を育てる実践、人が判断を残すべき領域を語りました
株式会社Lumilinks 代表取締役 / デザイナー
こぎそ 他1名
イベントレポート【イベントレポート】AIでコード書く前に、要求・要件定義足りてる?ミノ駆動さんと改めて考える ドメイン駆動設計
設計・実装がAIに移った今、人間に残る要求・要件定義をどう考えるか。『良いコード/悪いコードで学ぶ設計入門』著者のミノ駆動様が、文脈・未知の未知・アウトカム・仮説推論の4つの思考と、現場の問いへの答えを語りました
合同会社DMM.com
ミノ駆動 他1名
イベントレポート【イベントレポート】【Claude Code】世界のスタートアップが必死に求める「GTMエンジニア」とは
#GTMLab主宰のなかむらまこと氏とLayerXの飯沼広基氏が登壇。GTMエンジニアは何をエンジニアリングするのか、なぜエンジニア出身が向いているのかを、効率化ではなく売上を伸ばす開発の実践とともに語りました。
#GTMLab 主宰
なかむら まこと 他2名
イベントレポート【イベントレポート】AI時代 Drizzle?Kysely?Prisma? kennさん・cojiさんと語るORM選定
AIがコードを書く時代にORMをどう選ぶか。KyselyとD1でDBを2回吹き飛ばした溝口浩二(coji)様と、DrizzleとAtlasでスキーマだけを見るKenn Ejima様が、型・マイグレーション・DB選定で人が担う判断を語りました
Dot Product AI, Inc. Founder
Kenn Ejima 他2名
イベントレポート【後編】MIT、GPL、BSL?何をしたら違反になる?ケースから学ぶ OSSライセンス講座 #DeepDive
【前編はこちら】 Contents1 ディスカッション・質疑応答1.1 【OSS
バーチャルテックポエマー
紅月/koduki 他1名