本文へスキップ
Unveilydocs

IAP 連携ガイド

Google Play と App Store のアプリ内課金を、最初から最後まで設定します。

はじめる前に

項目必要条件
Unveily プランPro(IAP 機能含む)
AndroidGoogle Play デベロッパーアカウント+お支払いプロファイル設定済み
iOSApple Developer Program アカウント+App Store Connect にアプリ登録済み
アプリビルドAndroid: リリース署名済み AAB を1回以上アップロード済み

IAP 有効化ゲート — coming_soon

IAP 機能は、次の 2 つの条件が両方 満たされたときにのみ動作します。

  1. ライセンスに iap 機能 が含まれていること(Pro プラン)。
  2. 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 アップロード

  1. Google Play Consoleアプリを作成
  2. アプリ名、言語、種類を入力して作成
  3. リリース署名済み AAB を内部テストトラックにアップロード
  4. ストアの掲載情報、コンテンツのレーティング、ポリシーを完了 → 審査に提出

1-2. お支払いプロファイルの設定

  1. Play Console → 設定お支払いプロファイル → マーチャントアカウントを作成
  2. ビジネス情報、銀行口座、税務情報を入力
  3. 承認まで最大1〜2営業日かかります

ミッション 2 — アプリ内商品の登録(Android)

サブスクリプション

  1. Play Console → アプリ選択 → 収益化定期購入
  2. 定期購入を作成 をクリック
  3. 必要事項を入力:
項目説明
商品 IDmonthly_proコードで使用する ID(後で変更不可)
名前Pro 月額プランユーザーに表示される名前
請求期間1か月更新周期
価格¥980国別価格設定も可能
  1. 有効化 をクリック → ステータスが 有効 に変わることを確認

ミッション 3 — テストアカウントの設定(Android)

  1. Play Console → アプリ → 内部テストテスター タブ → Gmail アドレスを追加
  2. 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 サービスアカウントの作成

  1. Google Cloud Console → プロジェクト選択(Play Console と同じ Google アカウント)
  2. API とサービス有効な APIGoogle Play Android Developer API を有効化
  3. IAM と管理サービスアカウントサービスアカウントを作成
  4. 作成完了後 キー タブ → 鍵を追加JSON → ダウンロード

4-2. Google Play Console との連携

  1. Google Play Console設定API アクセス
  2. Google Cloud プロジェクトを連携
  3. 作成したサービスアカウントを確認 → アクセス権を付与
  4. 権限: 財務データの表示注文の管理 にチェックして保存
  5. 権限の反映まで最大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 機能を追加

  1. Xcode → プロジェクト選択 → Signing & Capabilities タブ
  2. + Capability をクリック → In-App Purchase を追加

5-2. App Store Connect でアプリ内商品を登録

  1. App Store Connect → アプリ選択 → 収益化アプリ内課金
  2. + をクリック → 商品タイプを選択(自動更新サブスクリプション、消耗型、非消耗型)
  3. 必要事項を入力:
項目説明
参照名Pro Monthly内部管理用の名前
商品 IDmonthly_proコードで使用する ID(Android と同じ値に設定可能)
価格ティア10(¥1,200)App Store 価格ティアを選択
  1. ローカライズ(名前・説明)を追加 → 保存提出準備完了 状態を確認

5-3. サブスクリプショングループの設定

自動更新サブスクリプションは必ずサブスクリプショングループに属している必要があります。

  1. サブスクリプショングループサブスクリプショングループを作成
  2. グループ名を入力(例: "Pro Plan")
  3. 作成したサブスクリプション商品をグループに追加

5-4. サンドボックステスターの設定

  1. App Store Connect → ユーザーとアクセスサンドボックステスター
  2. + をクリック → テスト用 Apple ID を作成(実際の Apple ID ではなくテスト専用アカウント)
  3. 実機: 設定から 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: truetransactionId 含む)を確認
  • acknowledgePurchase なしで機能が有効化されることを確認
  • アプリ再起動後にサブスクリプションが復元されることを確認

次の旅へ

On this page