1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutterアプリへの決済機能導入ガイド(2025年版)

背景:スマホ新法と決済手段の多様化促進

2025年12月18日に日本で施行された「スマートフォンソフトウェア競争促進法(通称:スマホ新法)」により、AppleやGoogleによるアプリストア運営(課金導線・手数料・決済手段の制約など)に大きな変更が入りました。

この流れの中で、開発者が取りうる課金導線は大きく次のように整理できます。

  • 公式ストアの課金システム(IAP)
    従来通り App Store / Google Play の決済を使う方式
  • 代替の決済プロバイダをアプリ内に組み込む
    サードパーティ決済SDK等をアプリ内で直接利用する方式
  • 外部Webサイトへの決済誘導
    アプリから外部ブラウザでWeb上の支払いページに遷移させる方式
  • 代替アプリストアでの配信と決済
    他社ストア経由でアプリ配布・決済を行う方式

重要:ただし、iOS/Androidともに「地域」「カテゴリ」「年齢」などの条件で要件が変わる可能性があるため、実務では“出し分け前提”で設計するのが安全です。

本記事では Flutter を使った決済機能の導入方法について、外部決済(Web決済やカード決済SDKの組み込み)ストア内課金(IAP) のパターンを整理します。主要な Flutter 向け決済パッケージの比較や、実装のポイント・注意点を中級〜上級者向けに解説します。


IAP vs 外部決済:まず方式を選ぶ

最初のステップは、自分のアプリの課金形態に応じて 「IAP」「外部決済」 のどちらを採用するか(または併用するか)を決めることです。

アプリ内課金(IAP)が基本になるケース

  • アプリ内で消費される デジタルコンテンツ(追加機能、ゲーム内アイテム、サブスク等)
  • 公式SDKの恩恵(レシート検証・購入復元・返金導線など)を活かしたい
  • 実装をシンプルにしたい / 審査観点の不確実性を減らしたい
  • 海外展開などで地域差に追随しづらい

外部決済(Web課金/決済SDK)が適するケース

  • 物理的サービス / 物理商品の購入(IAPの対象外)
  • Webにもサービスがあり Web課金した権利をアプリでも有効化したい
  • 手数料削減・決済手段の多様化(コンビニ払い・QR・銀行振込等)がユーザーメリットになる
  • 既存の決済基盤(PayPal/Braintree等)を流用したい

デメリット:実装/運用/セキュリティ(不正、チャージバック、二重決済、税務処理)の負担が増えやすい。UX面でも外部ブラウザ遷移は離脱要因になり得ます。

💡ハイブリッド運用も現実解

  • iOSはIAP中心、Androidは独自決済も併用
  • 日本向けのみ外部決済を出して、他地域はIAPのみ
  • Remote Config などで課金ボタン自体を出し分け

Flutter なら Platform.isIOS だけでなく、地域/年齢/審査状況に応じた出し分けも比較的作りやすいです。


Flutter主要決済パッケージの比較

代表的なものをまとめます。

パッケージ名 主な対応サービス・機能 対応プラットフォーム 実装方式 主な用途・特徴
flutter_stripe Stripe(カード、Apple Pay/Google Pay 等) iOS / Android(※Webは別実装) ネイティブSDK連携 外部決済の本命。汎用性が高い
pay Apple Pay / Google Pay iOS / Android ネイティブAPI連携 “ウォレットボタンだけ”追加したい時に最適
payjp_flutter PAY.JP(国内カード決済 + Apple Pay) iOS / Android ネイティブSDK連携 国内向け。日本語情報も多い
flutter_braintree Braintree(カード、PayPal等) iOS / Android ネイティブSDKラッパー Braintree運用中なら候補
flutter_paypal_payment PayPal単独 iOS / Android / Web / Desktop WebView + APIラッパー 早くPayPalを入れたい時の簡易解
paypay_uo(非公式) PayPay(Open API) Dartのみ(UI自前) REST APIクライアント 日本向けQR決済。運用/審査/保守は要注意
in_app_purchase App Store / Play課金 iOS / Android 公式ラッパー 単発購入 / サブスクを直接実装(設計は自前)
purchases_flutter RevenueCat(IAP管理) iOS / Android IAP抽象化 + バックエンド サブスク管理の王道。Entitlements一元化
purchases_ui_flutter RevenueCat Paywalls iOS / Android 課金UIコンポーネント 課金画面を“丸ごと”導入したい時に便利

外部決済の実装パターン

Flutterで外部決済を導入する方法は大きく 「Webベースの決済フロー」「ネイティブ決済SDK組み込み」 の2つです。


パターン1:Web決済フロー(外部ブラウザ / WebView)

アプリ → Webで決済 → アプリに戻って同期 という流れです。
決済UIやセキュリティをWeb側に寄せられる一方、決済完了をアプリ側にどう反映するかが要点になります。

典型フロー:

  1. アプリから購入URLを開いてWebで決済
  2. 決済成功データを決済基盤(Stripe等)で確定
  3. アプリ復帰時に購入状態を再取得して機能を解放

RevenueCat Web Purchase Links の例(url_launcher)

// RevenueCat購入リンクを組み立て
Uri buildWebPurchaseLink({
  required String token,      // RevenueCat発行のリンクトークン
  required String appUserId,  // 紐付けるユーザーID
  String? packageId,          // (任意)特定プラン指定
}) {
  final base = Uri.parse(
    "https://pay.rev.cat/$token/${Uri.encodeComponent(appUserId)}",
  );

  final params = <String, String>{
    "utm_source": "app",
    if (packageId != null) "package_id": packageId,
  };

  return base.replace(queryParameters: params);
}

// 外部ブラウザでリンクを開く
Future<void> openExternalCheckout(Uri url) async {
  await launchUrl(url, mode: LaunchMode.externalApplication);
}

💡実務TIP(反映ラグ対策)

購入直後は entitlement 反映に数秒ラグが出ることがあります。

  • resumed でまず1回同期
  • 反映が無ければ数秒おきに2〜3回再試行
  • それでもダメなら「購入済みの方はこちら(復元)」ボタンを用意

Stripe(自前)を使う場合の要点

  • Web側の決済成功は Webhook(例:checkout.session.completed)で受けてDB更新
  • アプリは復帰時に「購入状態」APIを叩いて同期(ポーリング / pushでも可)
  • 二重決済対策として idempotency_key / サーバ側の購入済みチェックは必須

パターン2:ネイティブ決済SDKを組み込む(アプリ内完結)

例:flutter_stripe で PaymentSheet / CardField を出し、その場で決済確定まで行うパターンです。
UXは良い一方、ストア規約・審査の扱いが難所になりがちです。

代表的な手順

  1. バックエンドで Intent / Token を作る(カード情報を直接サーバに送らない)
  2. クライアントで支払いUIを表示して確定
  3. 成功/失敗を処理し、必要ならサーバにも結果保存

注意:iOSでデジタル商品を代替決済で売る場合、表示文言・導線・要件が変わる可能性があるため、最初から出し分けできる設計が重要です。


RevenueCatを用いた外部決済連携(Web Purchase Links)

RevenueCat はIAP管理サービスですが、近年「Web課金 → Entitlement付与」の形で外部課金を扱う運用が可能になっています。

基本はこうです

  • RevenueCat ダッシュボードで Web課金(Stripe等)を設定
  • Web Purchase Link を発行
  • アプリからリンクを開かせて購入
  • アプリ復帰時に Purchases.getCustomerInfo() で entitlement 同期

Redemption Links(引き換えリンク)

ログイン前ユーザーでも Webで購入 → 後からアプリに購入権利を紐付け できる仕組み。

  • Webで購入(App User IDなし)
  • 決済完了後に一回限りの Redeem URL が発行される
  • ユーザーが踏むとアプリ起動 → SDKが購入を現在のユーザーIDへ紐付け
  • getCustomerInfo() で entitlement が active になっていれば有効化完了

制約例:リンクが1回限り / 失効時間あり、など運用設計が必要。


ストア規約とプラットフォームごとの注意点

外部決済導入時の最大難所はここです。特に デジタル商品 は要注意。

iOS(Apple)

  • 年齢やカテゴリにより外部決済リンクが制限される可能性
  • UI上の表記(「Appleの決済ではない」等)や、導線要件が変わり得る
  • 地域別に外部決済ボタンを出し分けるのが現実解になりやすい

Android(Google)

  • iOSより柔軟なケースが多いが、ユーザーに混乱を与えないUIが重要
  • Google決済と並列で選ばせるUIなど、設計パターンが増える

共通の注意点

  • 外部決済を選ぶなら「なぜ外部なのか」を説明できる設計・文言が必要
  • 価格表示・ボタン文言・リンク先の扱いは審査で突っ込まれやすい
  • 最新のガイドライン/事例に追随する前提で、設計を固定化しない

UX面での考慮事項

  • 事前案内:外部ブラウザへ遷移するなら「ブラウザで決済を続けます」など説明を出す
  • 復帰後の同期:ローディング表示・自動同期・再試行・復元ボタンを用意
  • エラー処理:通信不良/拒否/キャンセルの分岐を定義し、リカバリ導線を作る
  • 重複購入防止:購入済み表示、ボタン無効化、サーバ側の二重購入防止
  • 端末移行/再ログイン:IAPは復元、外部はDB照会、RevenueCatは同期設計を整理
  • テスト:成功/失敗/キャンセル/遅延/復帰などシナリオ網羅。Deep Link/Universal Linksも検証

日本国内での実装上の課題とヒント

PayPayなど国内スマホ決済

  • 公式Flutter SDKが無いケースが多く、REST API連携+UI自前になりやすい
  • Webhookで結果を受ける設計が必要(支払い作成 → 支払い完了通知 → 反映)
  • 非公式ライブラリ採用は保守・審査・仕様変更リスクを織り込む

国内向けカード決済サービス

  • Stripeは強力だが、国内事業者向けにPAY.JP等の選択肢もある
  • SDKが無い決済代行は Web決済(WebView/外部ブラウザ)が現実解になりやすい

法規制・年齢制限(未成年対応)

  • 独自決済を導入すると、親権者同意やUI設計を自前で持つ必要が出やすい
  • トラブル回避のため、利用規約・同意フロー・記録(ログ)も含めて設計

手数料と会計処理

  • Apple売上とStripe売上で入金サイクル/手数料/税務が変わる
  • 購入経路(IAP / Web / SDK)をイベント・ログで区別しておくと後々楽

まとめ

  • 実装はまず IAP vs 外部決済 を整理すると迷いが減る
  • 外部決済は Webフロー(外部ブラウザ + 復帰時同期)が導入しやすい
  • サブスク運用なら RevenueCat の Web Purchase Links + getCustomerInfo()同期 が最短ルート
  • ただし、デジタル商品を外部へ誘導する場合は規約/地域差が絡むため、最初から出し分け設計を入れるのが安全

参考リンク(出典メモ)

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?