IAP API 連携ガイド
Unveily IAP APIを使ったサーバーサイドアプリ内課金検証の設定 — SDK不要。
ひと目でわかる
Unveily IAP APIはお客様のバックエンドサーバーからGoogle PlayおよびApp Storeの購入レシートを検証します。 アプリはGoogle Play Billing / StoreKit 2で決済を処理し、サーバーが当社APIを呼び出してレシートを検証します。
Android アプリ ──purchaseToken──▶ お客様サーバー ──googleAccessToken──▶ Unveily API ──▶ Google Play
iOS アプリ ──signedTransaction(JWS)──▶ お客様サーバー ─────────────────────▶ Unveily API ──▶ App Store| 項目 | 必要条件 |
|---|---|
| Unveilyプラン | IAP API サブスクリプション |
| Google Play | デベロッパーアカウント + 支払いプロファイル設定済み |
| インフラ | バックエンドサーバー(言語・クラウド不問) |
はじめる前に
サービスアカウントキーのセキュリティ
サービスアカウントJSONキーはマスター認証情報です
このキーはGoogle Play Consoleの全注文データへの読み取り権限を持ちます。 パスワードと同等に扱ってください — ソースコードへの含有や平文での共有は絶対に避けてください。
インフラ環境に合ったシークレット管理ツールにキーを保管してください:
| インフラ環境 | 推奨保管方法 |
|---|---|
| AWS | AWS Secrets Manager または Systems Manager Parameter Store (SecureString) |
| Google Cloud | GCP Secret Manager |
| Azure | Azure Key Vault |
| 自社サーバー (オンプレミス) | HashiCorp Vault 推奨 — 難しい場合はOSの環境変数で隔離保管 |
| 共通禁止事項 | ソースコードへのハードコード、Gitへのコミット、本番環境での平文 .env 使用は絶対禁止 |
ミッション 1 — Google Cloudサービスアカウントの作成
1-1. Google Play Android Developer APIの有効化
- Google Cloud Console を開く → Playアカウントに紐付けられたプロジェクトを選択
- APIとサービス → APIとサービスの有効化
- Google Play Android Developer API を検索 → 有効にする
1-2. サービスアカウントの作成
- IAMと管理 → サービスアカウント → サービスアカウントを作成
- 名前を入力(例:
unveily-iap-verifier)→ 作成して続行 - ロールの付与ステップはスキップ → 完了
1-3. JSONキーのダウンロード
- 作成したサービスアカウントをクリック → キー タブ
- 鍵を追加 → 新しい鍵を作成 → JSON → 作成
- JSONファイルが自動的にダウンロードされます — すぐにシークレット管理ツールに保管してください
ダウンロードは1回限り
Googleはキー作成時のみダウンロードを許可します。 紛失した場合は、そのキーを削除して新たに作成する必要があります。
ミッション 2 — Google Play Consoleへの権限付与
2-1. アクセス権限の付与
- Google Play Console → 設定 → APIアクセス
- Google Cloudプロジェクトとリンク(未リンクの場合)
- サービスアカウント一覧から作成したアカウントを見つける → アクセス権を付与
2-2. 最小権限の設定
最小権限の原則
購入検証に必要な権限のみを付与してください — それ以上は不要です。
| 権限 | 必要 | 理由 |
|---|---|---|
| 財務データの閲覧 | 必須 | 購入・サブスクリプション状態の読み取り |
| 注文とサブスクリプションの管理 | 必須 | purchases APIの呼び出しに必要 |
| その他すべての権限 | 不要 | 有効化しないでください |
- 保存 → 権限の反映まで最大24時間かかります
ミッション 3 — サーバーでのAccess Token発行
サーバーはシークレット管理ツールからJSONキーを読み込み、短期有効なGoogle OAuth2 Access Token(有効時間1時間)を生成します。JSONキーではなく、このトークンのみをUnveilyに渡します。
キーではなくトークンを渡す理由
JSONキーは有効期限のない永続的な認証情報です。Access Tokenは1時間後に期限切れになります。 HTTPSで送信中に万が一漏洩しても、被害時間が厳しく制限されます。 Unveilyはトークンを保存しません — リクエストごとに1回使用して廃棄します。
Node.js
import { GoogleAuth } from 'google-auth-library';
const auth = new GoogleAuth({
// シークレット管理ツールから読み込む — 絶対にハードコードしないこと
credentials: JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_JSON),
scopes: ['https://www.googleapis.com/auth/androidpublisher'],
});
async function getGoogleAccessToken() {
const client = await auth.getClient();
const tokenResponse = await client.getAccessToken();
return tokenResponse.token; // "ya29.xxxx..."
}npm install google-auth-libraryPython
from google.oauth2 import service_account
import google.auth.transport.requests
import json, os
def get_google_access_token() -> str:
key_data = json.loads(os.environ["GOOGLE_SERVICE_ACCOUNT_JSON"])
credentials = service_account.Credentials.from_service_account_info(
key_data,
scopes=["https://www.googleapis.com/auth/androidpublisher"],
)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
return credentials.token # "ya29.xxxx..."pip install google-authJava
import com.google.auth.oauth2.GoogleCredentials;
import java.io.ByteArrayInputStream;
import java.util.Collections;
public String getGoogleAccessToken() throws Exception {
String keyJson = System.getenv("GOOGLE_SERVICE_ACCOUNT_JSON");
GoogleCredentials credentials = GoogleCredentials
.fromStream(new ByteArrayInputStream(keyJson.getBytes()))
.createScoped(Collections.singletonList(
"https://www.googleapis.com/auth/androidpublisher"
));
credentials.refreshIfExpired();
return credentials.getAccessToken().getTokenValue(); // "ya29.xxxx..."
}<!-- pom.xml -->
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.23.0</version>
</dependency>.NET (C#)
using Google.Apis.Auth.OAuth2;
public async Task<string> GetGoogleAccessTokenAsync()
{
var keyJson = Environment.GetEnvironmentVariable("GOOGLE_SERVICE_ACCOUNT_JSON")
?? throw new InvalidOperationException("GOOGLE_SERVICE_ACCOUNT_JSON が設定されていません。");
var credential = GoogleCredential
.FromJson(keyJson)
.CreateScoped("https://www.googleapis.com/auth/androidpublisher");
return await credential.UnderlyingCredential
.GetAccessTokenForRequestAsync(); // "ya29.xxxx..."
}dotnet add package Google.Apis.Authミッション 4 — 検証APIの呼び出し
Access Tokenを発行したら、サーバーからUnveily IAP APIを呼び出します:
POST https://api.theunveily.com/api/iap/verify
Authorization: Bearer {UNVEILY_LICENSE_KEY}
Content-Type: application/json
{
"googleAccessToken": "ya29.xxxx...",
"purchaseToken": "AO-J1OxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxBxxxxxx",
"packageName": "com.example.myapp",
"productId": "premium_monthly",
"productType": "subs",
"platform": "android"
}| フィールド | 必須 | 説明 |
|---|---|---|
Authorization ヘッダー | 必須 | Bearer {UNVEILY_LICENSE_KEY} |
googleAccessToken | 必須 | サービスアカウントから発行した短期OAuth2トークン |
purchaseToken | 必須 | デバイスでGoogle Play Billingが返したトークン |
packageName | 必須 | アプリのパッケージ名(例: com.example.myapp) |
productId | 必須 | Play Consoleに登録された商品ID |
productType | 必須 | inapp または subs |
platform | 任意 | デフォルト android |
成功レスポンス
{
"success": true,
"data": {
"success": true,
"platform": "android",
"productId": "premium_monthly",
"orderId": "GPA.1234-5678-9012-34567",
"purchaseTime": "2025-05-01T09:23:11Z",
"purchaseState": 0,
"isAcknowledged": false,
"storeApiVerified": true
}
}エラーレスポンス
| HTTP | エラー | 対処 |
|---|---|---|
401 | Authorizationヘッダーなしまたは形式エラー | Bearer {licenseKey} 形式を確認 |
400 | googleAccessToken なし | 呼び出し前にトークンを発行してください |
400 | Play API検証失敗 | トークン期限切れ、またはサービスアカウントのPlay Console権限未設定 |
400 | 検証済みレシート | 同じ purchaseToken の重複処理 — 無視して安全 |
400 | 無効なライセンスキー | ダッシュボードでライセンスキーとサブスクリプション状態を確認 |
iOS / App Store バリアント
iOSはGoogleサービスアカウントも googleAccessToken も不要です。StoreKit 2が返したApple署名JWS(signedTransaction)をそのまま渡すと、UnveilyサーバーがAppleの公開鍵で署名を検証します。platform を ios に設定してください。
POST https://api.theunveily.com/api/iap/verify
Authorization: Bearer {UNVEILY_LICENSE_KEY}
Content-Type: application/json
{
"platform": "ios",
"signedTransaction": "eyJhbGciOiJFUzI1NiIsIng1YyI6...",
"productId": "premium_monthly",
"productType": "subs"
}| フィールド | 必須 | 説明 |
|---|---|---|
Authorization ヘッダー | 必須 | Bearer {UNVEILY_LICENSE_KEY} |
platform | 必須 | iOSは必ず ios |
signedTransaction | 必須 | StoreKit 2が返したApple署名JWS(purchaseToken + googleAccessToken を代替) |
productId | 必須 | App Store Connectに登録された商品ID |
productType | 必須 | inapp または subs |
iOSは googleAccessToken・packageName 不要
JWS自体に署名されたバンドルIDと商品情報が含まれるため、iOSリクエストでは googleAccessToken と packageName を送信しません。UnveilyがAppleの公開鍵で署名を直接検証します。
iOS 成功レスポンス
{
"success": true,
"data": {
"success": true,
"platform": "ios",
"productId": "premium_monthly",
"transactionId": "2000000012345678",
"purchaseTime": "2025-05-01T09:23:11Z",
"storeApiVerified": true
}
}ミッション 5 — 全体フローの実装例
// Node.js / Express の例
import axios from 'axios';
import { getGoogleAccessToken } from './googleAuth';
app.post('/purchase/verify', async (req, res) => {
const { purchaseToken, productId, productType } = req.body;
// 1. 新しいGoogle Access Tokenを発行
const googleAccessToken = await getGoogleAccessToken();
// 2. Unveily IAP APIを呼び出し
const response = await axios.post(
'https://api.theunveily.com/api/iap/verify',
{
googleAccessToken,
purchaseToken,
packageName: 'com.example.myapp',
productId,
productType,
platform: 'android',
},
{
headers: {
Authorization: `Bearer ${process.env.UNVEILY_LICENSE_KEY}`,
'Content-Type': 'application/json',
},
}
);
if (response.data.success) {
// 3. 購入した機能を有効化
await activateFeature(req.user.id, productId);
res.json({ success: true });
} else {
res.status(400).json({ success: false, error: response.data.error });
}
});完了前の確認
- サービスアカウントJSONキーをシークレット管理ツールに保管(コード・
.envファイルを除く) - Play Console権限: 財務データの閲覧 + 注文とサブスクリプションの管理 のみ有効
- Access Tokenは検証呼び出し直前にサーバーで発行
- すべてのリクエストに
Authorization: Bearer {licenseKey}ヘッダーを含める - デバイス → サーバー間の
purchaseToken送信はHTTPSのみ - レスポンスの
storeApiVerified: trueを確認(Google Play検証完了) - 重複レシートエラー(
400 検証済みレシート)を適切に処理
次の旅へ
- IAP Bridge API — Proプラン SDKベースのIAP
- ライセンス設定 — ライセンスキーの設定