IAP 連携ガイド
Google Play と App Store のアプリ内課金を、最初から最後まで設定します。
はじめる前に
| 項目 | 必要条件 |
|---|---|
| Unveily プラン | Pro(IAP 機能含む) |
| Android | Google Play デベロッパーアカウント+お支払いプロファイル設定済み |
| iOS | Apple Developer Program アカウント+App Store Connect にアプリ登録済み |
| アプリビルド | Android: リリース署名済み AAB を1回以上アップロード済み |
IAP 有効化ゲート — coming_soon
IAP 機能は、次の 2 つの条件が両方 満たされたときにのみ動作します。
- ライセンスに
iap機能 が含まれていること(Pro プラン)。 - Unveily サーバー側の
iapEnabledフラグ が有効になっていること。
どちらか一方でも満たされない場合、queryProducts / purchase / restorePurchases は決済シートを表示せず、次を返します。
{ "status": "coming_soon" }すべての結果コールバックで 最初に この状態を確認し、決済 UI を隠すか案内文を表示してください。
function handleIapResult(result) {
if (result.status === "coming_soon") {
// IAP がまだ有効化されていない — 購入ボタンを隠す / 案内を表示
document.getElementById("iap-section").hidden = true;
return false;
}
return true;
}
// 例: 購入結果でゲートを確認
window.onPurchaseResult = async function(result) {
if (!handleIapResult(result)) return; // coming_soon ならここで終了
if (!result.success) return;
// ... サーバー検証へ進む
};Android — Google Play の設定
ミッション 1 — Google Play アプリの準備
1-1. アプリ作成と AAB アップロード
- Google Play Console → アプリを作成
- アプリ名、言語、種類を入力して作成
- リリース署名済み AAB を内部テストトラックにアップロード
- ストアの掲載情報、コンテンツのレーティング、ポリシーを完了 → 審査に提出
1-2. お支払いプロファイルの設定
- Play Console → 設定 → お支払いプロファイル → マーチャントアカウントを作成
- ビジネス情報、銀行口座、税務情報を入力
- 承認まで最大1〜2営業日かかります
ミッション 2 — アプリ内商品の登録(Android)
サブスクリプション
- Play Console → アプリ選択 → 収益化 → 定期購入
- 定期購入を作成 をクリック
- 必要事項を入力:
| 項目 | 例 | 説明 |
|---|---|---|
| 商品 ID | monthly_pro | コードで使用する ID(後で変更不可) |
| 名前 | Pro 月額プラン | ユーザーに表示される名前 |
| 請求期間 | 1か月 | 更新周期 |
| 価格 | ¥980 | 国別価格設定も可能 |
- 有効化 をクリック → ステータスが 有効 に変わることを確認
ミッション 3 — テストアカウントの設定(Android)
- Play Console → アプリ → 内部テスト → テスター タブ → Gmail アドレスを追加
- Play Console → 設定 → ライセンス テスター → 同じ Gmail を追加
- ライセンステスターはサブスクリプションの更新間隔が短縮されます(最大1分)
ミッション 4 — Google Play サービスアカウントの設定(Android リアルタイム検証用)
Android のリアルタイム偽造検証(storeApiVerified: true)を有効にするには Google サービスアカウントが必要です。この認証情報は お客様のWebサーバーにのみ 置き、Unveily へ送信・保存しません。未設定の場合、検証リクエストは storeApiVerified: false(DB 記録のみ)として処理されます。
iOS は Apple 署名(JWS)をサーバーが検証するため、認証情報は不要です。 このミッションは Android 専用です。
4-1. Google Cloud サービスアカウントの作成
- Google Cloud Console → プロジェクト選択(Play Console と同じ Google アカウント)
- API とサービス → 有効な API →
Google Play Android Developer APIを有効化 - IAM と管理 → サービスアカウント → サービスアカウントを作成
- 作成完了後 キー タブ → 鍵を追加 → JSON → ダウンロード
4-2. Google Play Console との連携
- Google Play Console → 設定 → API アクセス
- Google Cloud プロジェクトを連携
- 作成したサービスアカウントを確認 → アクセス権を付与
- 権限: 財務データの表示 + 注文の管理 にチェックして保存
- 権限の反映まで最大24時間かかります
4-3. サービスアカウント JSON を「お客様のWebサーバー」に配置
ダウンロードした JSON は お客様のWebサーバーにのみ 保管します。検証時、Webサーバーがこの JSON を使って ~1時間有効の短期 access token を生成し、Unveily へ渡します。(長期キーである JSON 自体はサーバーの外へ出しません。)
Unveily に認証情報を保存しません
セキュリティのため、Unveily はお客様のサービスアカウントを保存しません。JSON はお客様のWebサーバーに置き、リクエストごとに短期トークンのみ を渡してください。 サーバー側 relay エンドポイントの実装(短期トークン生成 + Unveily 呼び出し)のサンプルは IAP Bridge — サーバー検証 を参照してください。
iOS — App Store の設定
ミッション 5 — App Store Connect アプリの準備
5-1. Xcode で In-App Purchase 機能を追加
- Xcode → プロジェクト選択 → Signing & Capabilities タブ
- + Capability をクリック → In-App Purchase を追加
5-2. App Store Connect でアプリ内商品を登録
- App Store Connect → アプリ選択 → 収益化 → アプリ内課金
- + をクリック → 商品タイプを選択(自動更新サブスクリプション、消耗型、非消耗型)
- 必要事項を入力:
| 項目 | 例 | 説明 |
|---|---|---|
| 参照名 | Pro Monthly | 内部管理用の名前 |
| 商品 ID | monthly_pro | コードで使用する ID(Android と同じ値に設定可能) |
| 価格 | ティア10(¥1,200) | App Store 価格ティアを選択 |
- ローカライズ(名前・説明)を追加 → 保存 → 提出準備完了 状態を確認
5-3. サブスクリプショングループの設定
自動更新サブスクリプションは必ずサブスクリプショングループに属している必要があります。
- サブスクリプショングループ → サブスクリプショングループを作成
- グループ名を入力(例: "Pro Plan")
- 作成したサブスクリプション商品をグループに追加
5-4. サンドボックステスターの設定
- App Store Connect → ユーザーとアクセス → サンドボックス → テスター
- + をクリック → テスト用 Apple ID を作成(実際の Apple ID ではなくテスト専用アカウント)
- 実機: 設定から Apple ID をサインアウト → アプリ起動時にサンドボックスアカウントでサインイン
ミッション 6 — Unveily SDK の実装(iOS)
iOS は acknowledgePurchase 不要
iOS(StoreKit 2)は purchase() 完了時にトランザクションが自動的に finish 処理されます。
acknowledgePurchase の呼び出しなしで、サーバー検証(お客様のWebサーバー経由)の完了後にそのまま機能を有効化してください。
let transactionId = null;
let purchasedProductId = null;
// 1. 商品情報を取得(App Store Connect の商品 ID を使用)
function loadProducts() {
window.unveilyBridge.iap.queryProducts(["monthly_pro"], "subs", "onProductsLoaded");
}
window.onProductsLoaded = function(result) {
if (result.error) return;
document.getElementById("price").textContent = result.products[0].price;
};
// 2. 購入開始
function subscribe() {
window.unveilyBridge.iap.purchase("monthly_pro", "subs", "onPurchaseResult");
}
// 3. 購入完了 → お客様のWebサーバーで検証(iOS は signedTransaction(JWS) を送信)
window.onPurchaseResult = async function(result) {
if (!result.success) return;
const res = await fetch("/api/verify-iap", { // お客様のWebサーバーが Unveily を呼び出す
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({
platform: "ios",
productId: result.productId,
productType: "subs",
signedTransaction: result.signedTransaction, // Apple署名 JWS
}),
});
const verify = await res.json();
if (!verify.success) {
alert("決済の検証に失敗しました。サポートへお問い合わせください。");
return;
}
// Pro 機能を有効化(iOS は acknowledgePurchase 不要)
document.body.classList.add("pro-user");
};
// 5. 起動時に購入を復元(Transaction.currentEntitlements ベース)
// 'load' ではなく 'unveilyGlueReady' を使用してください — ページ読み込み時点では
// グルーがまだ unveilyBridge を注入しておらず、競合(race)が発生し得ます。
window.addEventListener("unveilyGlueReady", () => {
window.unveilyBridge.iap.restorePurchases("subs", "onRestoreResult");
});
window.onRestoreResult = function(r) {
if ((r.purchases || []).some(p => p.transactionId || p.isAcknowledged))
document.body.classList.add("pro-user");
};サーバー検証は必須
サーバー検証なしで機能をすぐに有効化しないでください。検証は お客様のWebサーバー → Unveily 経由で実行します(サーバー検証)。 レシート偽造により、決済なしで Pro 機能にアクセスされる攻撃に脆弱になります。
Android — Unveily SDK の実装
let purchaseToken = null;
let purchasedProductId = null;
// 1. 商品情報を取得
function loadProducts() {
window.unveilyBridge.iap.queryProducts(["monthly_pro"], "subs", "onProductsLoaded");
}
window.onProductsLoaded = function(result) {
if (result.error) return;
document.getElementById("price").textContent = result.products[0].price;
};
// 2. 購入開始
function subscribe() {
window.unveilyBridge.iap.purchase("monthly_pro", "subs", "onPurchaseResult");
}
// 3. 購入完了 → お客様のWebサーバーで検証(Android は purchaseToken を送信)
window.onPurchaseResult = async function(result) {
if (!result.success) return;
const res = await fetch("/api/verify-iap", { // お客様のWebサーバーが Unveily を呼び出す
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({
platform: "android",
productId: result.productId,
productType: "subs",
purchaseToken: result.purchaseToken,
}),
});
const verify = await res.json();
if (!verify.success) {
alert("決済の検証に失敗しました。サポートへお問い合わせください。");
return;
}
// Pro 機能を有効化
document.body.classList.add("pro-user");
// Acknowledge(3日以内に必須 — Android のみ)
window.unveilyBridge.iap.acknowledgePurchase(result.purchaseToken, "onAckResult");
};
window.onAckResult = function(r) { if (r.success) console.log("サブスクリプション有効化完了"); };
// 5. 起動時に復元
// 'load' ではなく 'unveilyGlueReady' を使用してください — ページ読み込み時点では
// グルーがまだ unveilyBridge を注入しておらず、競合(race)が発生し得ます。
window.addEventListener("unveilyGlueReady", () => {
window.unveilyBridge.iap.restorePurchases("subs", "onRestoreResult");
});
window.onRestoreResult = function(r) {
if ((r.purchases || []).some(p => p.isAcknowledged))
document.body.classList.add("pro-user");
};サブスクリプションのアップグレード / ダウングレード
既存の購読者が別のプランへ切り替える際は、purchase のオーバーロードを使い、既存の購入トークンと置換モードを渡します。
window.unveilyBridge.iap.purchase(
"yearly_pro", // 切り替え先の新しい商品 ID
"subs",
{
oldPurchaseToken: currentPurchaseToken, // 既存サブスクの purchaseToken (Android)
replacementMode: 1, // 1=WITH_TIME_PRORATION (既定)
},
"onIAPPurchaseResult"
);replacementMode の値: 1=WITH_TIME_PRORATION(既定)、2=CHARGE_PRORATED_PRICE、3=WITHOUT_PRORATION、5=CHARGE_FULL_PRICE、6=DEFERRED。
iOS はオプションオブジェクトを無視します
iOS(StoreKit 2)では、3 番目のオプションオブジェクト({oldPurchaseToken, replacementMode})は静かに無視されます。App Store は サブスクリプショングループ を通じてアップグレード/ダウングレードを自動処理するため、同じグループ内の別商品を purchase するとプロレーションが適用されます。オプションオブジェクトの使用は Android 専用 です。
完了前の確認
Android
- テスターアカウントで内部テストリンクからアプリをインストール
- 商品照会 → 正しい価格が表示されることを確認
- 購入進行 → Google Play 決済画面が表示されることを確認
- サーバー検証レスポンスで
success: trueを確認 - Acknowledge 完了を確認
- アプリ再起動後にサブスクリプションが復元されることを確認
iOS
- サンドボックステスターアカウントで実機にアプリをインストール
- 商品照会 → App Store Connect の商品情報が表示されることを確認
- 購入進行 → App Store 決済画面が表示されることを確認
- サーバー検証レスポンスで
success: true(transactionId含む)を確認 -
acknowledgePurchaseなしで機能が有効化されることを確認 - アプリ再起動後にサブスクリプションが復元されることを確認
次の旅へ
- IAP Bridge API — 各メソッドの詳細なパラメーターとレスポンス
- アプリ情報 Bridge — 現在のプランを確認
- ライセンス設定 — ライセンスキーの設定方法