0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

QRコード決済のアーキテクチャをPayPayの公開情報から設計してみる

0
Posted at

はじめに

「PayPayってどういう仕組みで動いてるんだろう」と考え始めたのがきっかけだった。
決済系のシステムはいくつか自作したことがあるものの、7000万人規模のユーザーを捌く実サービスとなると、教科書的な設計だけでは足りない部分が絶対にある。
まずは一般的なQRコード決済システムとしてゼロから設計を組み立て、そのあとPayPayが公開している技術情報と突き合わせてみることにした。

この記事では設計の中身と、PayPayの実例、QRコードそのものの成り立ちに加えて、AWSサービスへのマッピングまでをまとめる。
実際に手を動かしてデプロイする話は次回に回すこととした。
今回はまず「何をどう作るべきか」を固める回である。

QRコード決済、思ったより化け物じみたスケールだった

設計に入る前に、そもそもの市場規模を確認しておく。
数字で見ると想像以上だった。

日本国内のQRコード決済取引数は2025年に435億件に達している。
2018年時点では162億件だったので、7年で2.7倍に増えた計算になる(Statista調べ)。経済産業省が公表しているキャッシュレス決済比率は2025年で58%(METI発表)。そしてPayPayは登録ユーザー約7200万〜7300万人、月間利用者約4000万人を抱え、QRコード決済の取引量ベースで国内シェア約65%を握っている(Statista調べ)。

つまり日本のQRコード決済は「PayPayとその他」という構図にかなり近い。
設計を考えるうえでPayPayを軸に据えるのは妥当な選択だと思う。

そもそもQRコードは誰のものか

決済の話に入る前に、QRコードそのものの話をしておきたい。
今回調べていて一番面白かった部分だ。

QRコードは1994年にデンソー(現デンソーウェーブ)が開発し、同年3月14日に特許出願、1999年6月11日に登録されている(デンソーウェーブ公式発表)。ここまでは普通の企業発明の話だが、デンソーウェーブは開発当初から「規格化されたQRコードに対しては特許権を行使しない」と明言していた。
つまり誰でも無償でQRコードを生成・利用できる(特許庁の紹介記事)。

一方で、QRコードを読み取るリーダー側の技術には別途特許を取得し、そちらでしっかり収益化している。
さらに「QRコード」という名称自体は登録商標として保持していて、QRコードが十分に普及したタイミングを見計らって
「QRコードは株式会社デンソーウェーブの登録商標です、と表記してください」
と各企業に依頼するという順番で商標を守った。
普及前にこれをやっていたら企業側が名称の使用を敬遠していたかもしれない、というのが後年の分析だ(PatentRevenueの分析記事)。

規格そのものは無償公開、読み取り機で稼ぐ、商標は普及後に静かに固める。
特許を手放すことと事業として儲けることが両立している設計で、今のQRコード決済がここまで普及した土台には、この「特許を無償公開する」という判断がある。
PayPayを含むすべてのQRコード決済サービスは、この上に乗っている。

一般的なQRコード決済システムを設計する

ここからが本題。
PayPayの内部実装がすべて公開されているわけではないので、まずは一般的な決済系システムのプラクティスとして設計を組み立てる。
PayPayの実例は次のセクションで別途扱う。

決済方式は2種類ある

QRコード決済には大きく2つの方式がある。

  • ユーザースキャン型(MPM: Merchant-Presented Mode): 店舗が固定QRまたは動的QRを提示し、利用者がスキャンして金額を入力する
  • 店舗スキャン型(CPM: Consumer-Presented Mode): 利用者がアプリにバーコード/QRを表示し、店員が専用端末でスキャンする

コンビニのレジで見かけるのは大体CPM、個人店の壁に貼ってあるQRはMPMだ。

アーキテクチャの全体像

                         ┌─────────────┐
                         │   Client    │ (ユーザーアプリ / 加盟店端末)
                         └──────┬──────┘
                                │ HTTPS(mTLS)
                         ┌──────▼──────┐
                         │  API Gateway │ ─ 認証(OAuth2/JWT)・レート制限
                         └──────┬──────┘
              ┌─────────────────┼─────────────────┐
      ┌───────▼──────┐  ┌───────▼───────┐  ┌───────▼───────┐
      │ Payment Svc   │  │  Wallet Svc   │  │  Merchant Svc  │
      │ (決済オーケストレータ)│  │ (残高管理)     │  │ (加盟店/QR管理) │
      └───────┬──────┘  └───────┬───────┘  └───────┬───────┘
              │                 │                   │
      ┌───────▼──────┐  ┌───────▼───────┐  ┌───────▼───────┐
      │ Ledger Svc    │  │ Risk/Fraud    │  │ Notification   │
      │ (元帳/複式簿記) │  │ Engine        │  │ Svc(Push/SMS)  │
      └───────┬──────┘  └───────────────┘  └───────────────┘
              │
      ┌───────▼──────────────────────────┐
      │  外部連携: 銀行API/カードブランド/    │
      │  資金移動業者間精算/AML KYC基盤      │
      └───────────────────────────────────┘

Payment・Wallet・Ledgerを別サービスに分けているのには理由がある。Ledgerを複式簿記の元帳として厳密なACID境界に閉じ込めておきたいからだ。決済フロー制御(Payment)や残高の読み書き(Wallet)は多少複雑になっても、資金移動の記録そのもの(Ledger)だけは何があっても揺るがない場所に置く。

データモデルの肝は「追記のみの元帳」

ledger_entries(
  entry_id, transaction_id, account_id,
  debit_credit,      -- DEBIT/CREDIT
  amount, currency,
  balance_after,      -- 監査用スナップショット
  created_at
)
-- 1トランザクションにつき必ず貸借が一致する複数レコード(複式簿記)

transactions(
  transaction_id,
  idempotency_key,    -- クライアント発行のキー(UNIQUE制約)
  type[payment|refund|transfer|charge],
  status[pending|authorized|captured|failed|reversed],
  payer_id, payee_id, amount, currency,
  created_at, updated_at
)

ledger_entries はUPDATE/DELETEを一切行わず追記専用のテーブルにする。
訂正が必要な場合は逆仕訳を新規レコードとして追加する。
こうしておけば監査証跡が原理的に改ざん不能になる。
wallets.balance はLedgerから再計算できるキャッシュとして持たせるだけで、真実源はあくまでLedger側、という役割分担にする。

二重決済を防ぐ、これが一番大事

決済系の設計で一番神経を使うのがここだ。
ネットワークが不安定な店頭環境では、リクエストがタイムアウトしたときに「サーバでは処理済みだがクライアントは失敗と誤認する」というケースが必ず起きる。

対策は冪等キー(idempotency key)をクライアント側でリクエストごとに発行し、transactions.idempotency_key にUNIQUE制約を張ること。
同じキーでの再送は既存レコードをそのまま返すだけにして、二重処理を機械的に弾く。
ここで守るべき鉄則は「タイムアウトしたら同じキーで再送する、新しいキーでの再送は絶対にしない」という一点に尽きる。
新しいキーで再送すると、それはサーバ側から見ればただの「別の正当な決済」になってしまう。

残高更新自体も、SELECT ... FOR UPDATE などの行ロックか楽観ロック(version列)で排他制御する。Wallet ServiceとLedger Serviceが別マイクロサービスに分かれている場合は、Sagaパターン+補償トランザクションで結果整合性を担保しつつ、ユーザー体験上は同期的に見せる作り込みが必要になる。

スケールと障害への備え

QRセッション(店舗が提示する一時的なQR情報)は生存期間が短く読み書き頻度が高いので、インメモリストア(Redis等)に寄せて低レイテンシ化する。
逆に特定の巨大加盟店(コンビニチェーン全店など)への入金が集中するケースでは、加盟店側のアカウントをシャーディングし、集計は非同期バッチで合算する設計にしないと、そこがホットスポットになる。

障害対策としては、Ledger書き込みが失敗したら自動ロールバックして明確に失敗を返すことである。
「処理中」のような曖昧な状態をユーザーに長時間見せ続けないのが重要だ。監査ログはWORM(Write Once Read Many)ストレージに別途永続化し、Ledgerとの突合を日次バッチで回して不整合を早期に見つける。

PayPayは実際どう作られているか

ここからは公開情報ベースの話。
PayPay社内の詳細な内部設計がすべて公開されているわけではないが、公式ブログや技術記事から確認できる事実はいくつかある。

PayPayはマイクロサービスアーキテクチャを採用していて、少人数のチームで約200のマイクロサービスを運用している(PayPay公式ブログ)。
基盤はKubernetesで統一されており、CI/CDはほぼ完全自動化。Terraform・Ansible・Helmといったツールを組み合わせ、監視にはPrometheusとDatadogを使っている。

特に興味深いのが決済コアDBの移行だ。
最重要マイクロサービスであるPayment(決済処理そのもの)のデータベースを、Auroraから分散SQLデータベースのTiDBに移行している。移行後はスループットが従来比3倍になったという(PayPay Inside-Out Tech Talks vol.26)。
当時すでに4200万人以上のユーザーにサービス提供していた状態での移行で、日本国内にTiDBの知見を持つ技術者が少なく、日本語の情報も乏しい中での判断だったと語られている。

これは前セクションで組み立てた設計と照らし合わせると腑に落ちる部分がある。
Ledgerに相当する決済コアのデータベースだけを、可用性とスループットを最優先して分散SQLに切り替えるという判断は、「決済の中核だけを別格で扱う」という設計思想そのものだ。
単一のRDBMSで全部を賄おうとすると、ユーザー数がある規模を超えた瞬間にそこが詰まる。

paypay-ja.jpg

もう一つ、公開情報という意味では特許情報も面白い切り口になる。
PayPayはユーザースキャン方式で金額を入力したあと、店員が確認しやすいように支払い画面を自動で180度回転させる機能を2020年9月16日から提供している。
無人販売や金額固定のQRコードでは回転しない。
この機能、実は特許を取得している。
特許第7105337号「情報処理装置、表示制御方法および表示制御プログラム」がPayPay株式会社名義で登録されている(Google Patents)。
出願時は「金額表示画面の回転」という広めの書き方だったが、拒絶理由通知を受けて「画面を拡大して回転させる→支払ボタンを表示させる→反転を元に戻すアイコンを表示させる」という一連の処理フローに絞り込まれて登録されている(黒川弁理士事務所の解説Toreru Media)。
さらに意匠登録(意匠2021-004638)も併せて取得していて、特許と意匠の二重保護になっている。
PayPayは2021年時点で特許出願数がメガバンク3行合計の2.5倍という報道もあり(日本経済新聞)、こういう細かいUIの工夫まで丁寧に権利化しているらしい。

AWS環境のみで実装できるか

ここまでの設計をAWSサービスにマッピングしたらどうなるかについて机上で整理しておく。
実際に手を動かしてデプロイするのは次回に回すが、どのサービスをどこに当てるかという「設計図」の部分は今回のうちに固めておきたい。

結論から言うと、決済コアのアーキテクチャはAWSサービスだけで組める。
ただし「AWSのみで完結する」とは言い切れない領域が一つある。
銀行API・カードブランドネットワーク・KYC/AMLベンダーとの接続は、AWS外の実システムとの連携が本質的に必須で、これはAWSのマネージドサービスとしては提供されていない。

コンポーネント別マッピング

設計要素 AWSサービス 備考
API Gateway Amazon API Gateway + AWS WAF mTLS/JWT検証はLambda AuthorizerまたはCognitoで行う
認証・認可 Amazon Cognito ユーザー用・加盟店用でUser Poolを分離する
Payment/Wallet/Merchant Svc Amazon ECS on Fargate 決済コアは常時起動・低レイテンシ要件が強いので、コールドスタートのあるLambdaよりFargate向き
QRセッション(短命・高頻度) Amazon ElastiCache for Redis TTL管理・低レイテンシ読み書きに最適
Ledger Svc(元帳) Amazon Aurora PostgreSQL(Multi-AZ) 下で詳しく検討する
Risk/Fraudルールベース判定 AWS Lambda 同期パスの軽量判定
Risk/Fraud MLスコアリング Amazon SageMaker AI(非同期推論) 重い推論は決済の同期パスから外す
イベントバス Amazon MSKまたはAmazon EventBridge Ledger更新から通知・集計への伝播。順序保証が必要ならMSK
通知 Amazon SNS Push/SMS通知
監査ログ(WORM) Amazon S3 + Object Lock(Compliance mode) 改ざん不能な追記ログの保管先
分散トレーシング AWS X-Ray サービスを跨いだレイテンシ分析
シークレット管理 AWS Secrets Manager DB認証情報・外部API鍵
カード情報の暗号化 AWS Payment Cryptography PCI DSS要件に対応したHSMベースの鍵管理

構成図はAWS公式アイコンを使って別途まとめてある。

aws-ja.jpg

Ledgerの整合性、Aurora PostgreSQLかAurora DSQLか

Ledgerは「同一アカウントへの同時書き込みをどう捌くか」が設計の肝になる。
ここは2つの選択肢で悩ましい。

  • Aurora PostgreSQL(行ロック、SELECT ... FOR UPDATE): 悲観ロックなので、同時書き込みが競合しても待機して確実に処理できる。
    決済のように「失敗よりリトライの方がユーザー体験を損なう」用途に向く
  • Aurora DSQL(OCC: 楽観的並行性制御): 書き込み競合時は失敗してアプリ側でのリトライが前提になる。以前、限定ドロップECの在庫管理をAurora DSQLで実装したとき(DROPZERO)、在庫のような「同一レコードへの高頻度競合書き込み」ではOCCの特性上リトライ設計が結果を大きく左右することを実機で確認した。人気加盟店(コンビニチェーンなど)への入金が集中するケースでは、同じ種類の競合が起きうる

初期実装としてはAurora PostgreSQLの行ロック方式のほうが無難だと考えている。
DSQLはグローバル分散・マルチリージョンでの強整合性が効くユースケース(単一リージョン中心の決済コアでは恩恵が薄い)で改めて検討する位置づけにしたい。

AWSだけで完結しない領域

  • 銀行口座チャージ・出金: 全銀ネット/銀行APIへの接続が必須で、AWS上のアプリからは外部APIとしてしか呼べない
  • カードブランド決済: Visa/Mastercard等のネットワークへの接続はAWS外
  • KYC/AML本人確認: 運転免許証OCR等はAmazon Rekognitionで一部代替できるが、与信・反社データベースとの照合は外部サービスが必須
  • 資金決済法上の供託・保全: 制度上の要件であり、供託先金融機関との連携が発生する

決済コア(Payment/Wallet/Ledger/Risk/通知/監査ログ)はAWSネイティブで十分構築できる一方、システム全体(銀行接続・カードネットワーク・KYC含む)としては「AWSのみで完結」とは言えない。これは実装力の問題ではなく、金融インフラの外部接続点が制度的に存在するからである。

この画面回転、AWSで作るなら何が代わりを担うか

ここで一つ寄り道をしておきたい。
上の表はバックエンドのコンポーネントを一通りマッピングしたが、さっき紹介した画面回転機能だけは、この表に素直には収まらない。というのも、これは「AWSサービスの選定」という話の外側にある機能だからだ。

画面がユーザーの手の動きを検知して回転する部分は、iOSならCoreMotion、AndroidならSensorManagerといった、スマホOSが提供する加速度センサーAPIの領域になる。「金額表示を拡大して回転させ、支払ボタンを表示し、反転を戻すアイコンを出す」という一連のUI状態遷移も、クライアントアプリ内のステートマシンの話であって、サーバーサイドの処理ではない。
つまりこの機能の中核は、AWSのどのサービスにも対応しない。バックエンドをどれだけ精巧に組んでも、ここは再現できない部分だ。

「代用する」というよりは「バックエンドが担うべき部分だけ」を切り出すとすれば、次の2つになる。

  • 加盟店ごとに「この画面を回転させてよいか」を判定するフラグの管理。
    無人販売や金額固定のQRコードでは回転させない、という業態ごとの出し分けが必要なので、これはMerchant Serviceが持つ加盟店プロファイル(業態区分・QR種別)をAurora PostgreSQL(あるいはDynamoDB)に持たせておき、クライアントが決済開始時にAPI経由で取得する設計になる
  • 機能自体のロールアウト制御。
    新しいUI挙動を全ユーザーに一斉公開せず、特定の加盟店セグメントや地域から段階的に出したい場合は、AWS AppConfigのフィーチャーフラグで制御するのが定石

つまりAWS側が持つのは「回転させてよいかの判断材料」までで、「回転そのもの」はクライアントの仕事のまま残る。
PayPayを差別化しているのは、実はこの「バックエンドでは代替できない部分」まで丁寧に特許を取っているところなんじゃないか、というのが今回一番腑に落ちた発見だった。

QRコードは特許を手放したことで世界中に広まった。
PayPayはその上で、画面が回転するというごく細かいUXまで特許で固めている。
同じ「特許」という道具を、片方は手放す方向に、片方は固める方向に使っていて、この対比だけで一本記事が書けそうな気がしている。
決済インフラの設計パターン自体は、こうして公開情報から組み立て直せるくらいには開かれている。実際にAWS上にデプロイして動かすところは、また次回書く。

最後まで読んでいただきありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?