FlutterでPAY.JPを導入してみた(カードフォームでトークン化 → サーバーで支払い作成まで)
mizoiです。
今回はPAY.JP の Flutter プラグイン(payjp_flutter)を使って、Flutterサンプルアプリにクレジットカード決済を組み込むところまでを一通りやってみました。
PAY.JP の Flutter 連携は「アプリ側でカード情報を直接扱わず、SDKでトークン化して、トークンだけをサーバーへ送る」という設計になっていて、最短距離で“決済っぽい体験”を作れます。 (PAY.JP)
この記事でやること
- Flutter に
payjp_flutterを入れる - カードフォーム → トークン作成まで(アプリ側)
- 受け取った トークンをサーバーへ送信
- サーバー側で 支払い(charge)を作成してテスト決済完了
重要:Secret Key(秘密鍵)はアプリに絶対入れない(サーバー側だけで使用) (PAY.JP)
全体の流れ(ざっくり)
Flutterアプリ
↓(カードフォームで入力)
PAY.JP Mobile SDK(payjp_flutter)
↓(Token発行)
Flutterアプリ
↓(token.id を自サーバーへPOST)
自サーバー
↓(Secret Keyで /v1/charges)
PAY.JP API
↓
決済完了(テスト)
PAY.JP の Flutter プラグインは、カードフォームを表示してトークンを作る機能(+ iOS は Apple Pay)を提供しています。 (PAY.JP)
前提
-
PAY.JP のアカウント作成(テスト環境OK)
-
管理画面から APIキー取得
- 公開鍵(Public Key):アプリ(SDK初期化)に入れる
- 秘密鍵(Secret Key):サーバーでのみ使用 (PAY.JP)
1. Flutter側:payjp_flutter を追加
pubspec.yaml
dependencies:
flutter:
sdk: flutter
payjp_flutter:
公式の Flutter 利用ガイドでも、このプラグインを使う手順が案内されています。 (PAY.JP)
私は公式の手順を見ながら作業しました。
2. Flutter側:SDK初期化(公開鍵)
import 'package:payjp_flutter/payjp_flutter.dart';
Future<void> initPayjp() async {
await Payjp.init(publicKey: 'pk_test_xxxxxxxxxxxxxxxxxxxxxxxx');
}
Payjp.init(publicKey: ...) で公開鍵をセットします。 (PAY.JP)
3. Flutter側:カードフォームを開く → Tokenを受け取る
最小はこれです。
Future<void> startCardForm() async {
await Payjp.startCardForm(
cardFormType: CardFormType.cardDisplay,
onCardFormProducedTokenCallback: onToken,
);
}
Future<CallbackResult> onToken(Token token) async {
try {
// token.id を自サーバーへ送る
await sendTokenToServer(token.id);
return CallbackResultOk();
} on ApiException catch (e) {
// ここで返すとフォーム内にエラー表示される
return CallbackResultError(e.message);
}
}
-
Payjp.startCardForm()でカードフォームを起動 (PAY.JP) -
onCardFormProducedTokenCallbackで Token を受け取ってサーバー送信 (PAY.JP) - 成功時は
CallbackResultOk()、失敗時はCallbackResultError()を返すと、フォーム側のUXが自然です (PAY.JP)
4. iOSの注意:カード読み取りを使うならカメラ許可
iOS ではカード読み取り機能のために Info.plist に NSCameraUsageDescription が必要です(Android側ではFlutter経由のカード読み取りは現在利用不可の旨も明記されています)。 (PAY.JP)
5. サーバー側:Tokenで支払い(charge)を作成
PAY.JP では、トークン作成ができたら次はサーバー側で charges を作って支払いを行います。 (PAY.JP)
また、トークンは1回しか使えない点も要注意です。 (PAY.JP)
例:Node.js(Express)で最小実装
import express from "express";
const app = express();
app.use(express.json());
const PAYJP_SECRET_KEY = process.env.PAYJP_SECRET_KEY; // sk_test_...
app.post("/pay/charge", async (req, res) => {
const { tokenId, amount } = req.body;
// ここでは最小構成でfetchを使う(SDKを使ってもOK)
const params = new URLSearchParams();
params.append("amount", String(amount));
params.append("currency", "jpy");
params.append("card", tokenId);
const resp = await fetch("https://api.pay.jp/v1/charges", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
// Basic認証: username=SecretKey, passwordは空
Authorization:
"Basic " + Buffer.from(`${PAYJP_SECRET_KEY}:`).toString("base64"),
},
body: params.toString(),
});
const data = await resp.json();
if (!resp.ok) {
return res.status(resp.status).json({ error: data });
}
return res.json({ charge: data });
});
app.listen(3000, () => console.log("listening on 3000"));
PAY.JP の API は Secret Key を使った認証(Basic認証)で操作でき、Secret Key は全API操作が可能な重要情報なので取り扱い注意、という前提が公式に書かれています。 (PAY.JP)
(なので アプリに Secret Key を入れないのが絶対条件)
Flutter側は /pay/charge に tokenId と amount をPOSTするだけにすると安全です。
6. 3Dセキュアを使う場合(必要なときだけ読む)
payjp_flutter は Payjp.startCardForm() の引数で useThreeDSecure を指定できます(デフォルトは false)。 (PAY.JP)
モバイルSDKで3Dセキュアを使う場合、管理画面で リダイレクトURL を設定し、認証後にアプリへ戻るための導線(Universal Links / App Links / カスタムスキーム等)を準備します。 (PAY.JP)
また、管理画面側で「トークン作成時の3Dセキュアを常に必須」にするモードもあり、その場合はSDK側の指定に関わらず実施される挙動も明記されています。 (PAY.JP)
まずは「テストで動かす」なら、3DSなしでトークン → チャージ作成まで通してから、必要に応じて3DSを足すのが早いです。
7. 実装してみて良かったところ / ハマりどころ
良かった
- Flutterからカードフォームを呼び出すだけでトークン化まで完了する(自前UI・自前バリデーション地獄になりにくい) (PAY.JP)
- コールバックの成否でフォーム側が自然に完了/エラー表示してくれる(
CallbackResultOk/Error) (PAY.JP) - スマホ新法で今後も使うことが多そうだと感じた。
ハマりどころ
- 「決済」はサーバー側が必須(Secret Keyを安全に扱うため)
- 3Dセキュアを入れるなら、リダイレクトURLやアプリ復帰の導線づくりが必要 (PAY.JP)
- トークンは使い捨て(再利用前提の設計にすると詰む) (PAY.JP)
- リポジトリの注意として、Android Emulatorで3DS画面が出ないケース(Chrome初期設定が原因のことがある)も言及があります (GitHub)
まとめ
PAY.JP × Flutter は、
- アプリ:カード入力 → トークン発行
- サーバー:トークンで支払い作成
という王道パターンを、payjp_flutter のカードフォームでかなり短い実装で作れました。 (PAY.JP)
今後も決済でぜひ使っていきたいサービスだと思いました。
いいねやコメント待ってます!!

