本文へスキップ
Unveilydocs

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の全注文データへの読み取り権限を持ちます。 パスワードと同等に扱ってください — ソースコードへの含有や平文での共有は絶対に避けてください。

インフラ環境に合ったシークレット管理ツールにキーを保管してください:

インフラ環境推奨保管方法
AWSAWS Secrets Manager または Systems Manager Parameter Store (SecureString)
Google CloudGCP Secret Manager
AzureAzure Key Vault
自社サーバー (オンプレミス)HashiCorp Vault 推奨 — 難しい場合はOSの環境変数で隔離保管
共通禁止事項ソースコードへのハードコード、Gitへのコミット、本番環境での平文 .env 使用は絶対禁止

ミッション 1 — Google Cloudサービスアカウントの作成

1-1. Google Play Android Developer APIの有効化

  1. Google Cloud Console を開く → Playアカウントに紐付けられたプロジェクトを選択
  2. APIとサービスAPIとサービスの有効化
  3. Google Play Android Developer API を検索 → 有効にする

1-2. サービスアカウントの作成

  1. IAMと管理サービスアカウントサービスアカウントを作成
  2. 名前を入力(例: unveily-iap-verifier)→ 作成して続行
  3. ロールの付与ステップはスキップ → 完了

1-3. JSONキーのダウンロード

  1. 作成したサービスアカウントをクリック → キー タブ
  2. 鍵を追加新しい鍵を作成JSON作成
  3. JSONファイルが自動的にダウンロードされます — すぐにシークレット管理ツールに保管してください

ダウンロードは1回限り

Googleはキー作成時のみダウンロードを許可します。 紛失した場合は、そのキーを削除して新たに作成する必要があります。


ミッション 2 — Google Play Consoleへの権限付与

2-1. アクセス権限の付与

  1. Google Play Console設定APIアクセス
  2. Google Cloudプロジェクトとリンク(未リンクの場合)
  3. サービスアカウント一覧から作成したアカウントを見つける → アクセス権を付与

2-2. 最小権限の設定

最小権限の原則

購入検証に必要な権限のみを付与してください — それ以上は不要です。

権限必要理由
財務データの閲覧必須購入・サブスクリプション状態の読み取り
注文とサブスクリプションの管理必須purchases APIの呼び出しに必要
その他すべての権限不要有効化しないでください
  1. 保存 → 権限の反映まで最大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-library

Python

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

Java

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エラー対処
401Authorizationヘッダーなしまたは形式エラーBearer {licenseKey} 形式を確認
400googleAccessToken なし呼び出し前にトークンを発行してください
400Play API検証失敗トークン期限切れ、またはサービスアカウントのPlay Console権限未設定
400検証済みレシート同じ purchaseToken の重複処理 — 無視して安全
400無効なライセンスキーダッシュボードでライセンスキーとサブスクリプション状態を確認

iOS / App Store バリアント

iOSはGoogleサービスアカウントも googleAccessToken も不要です。StoreKit 2が返したApple署名JWSsignedTransaction)をそのまま渡すと、UnveilyサーバーがAppleの公開鍵で署名を検証します。platformios に設定してください。

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リクエストでは googleAccessTokenpackageName を送信しません。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 検証済みレシート)を適切に処理

次の旅へ

On this page