facebook twitter hatena line email

Php/アプリストア連携/返金API/GooglePlayStore/RTDN

提供: 初心者エンジニアの簡易メモ
2025年6月19日 (木) 01:03時点におけるAdmin (トーク | 投稿記録)による版 (Google Play Consoleでの設定)

移動: 案内検索

Google Play Consoleでの設定

  1. Google Play Consoleにログイン
  2. GooglePlayで収益化する/収益化のセットアップを開く
  3. リアルタイム デベロッパー通知の、セクションで通知を受け取るエンドポイントURLを設定

php実行準備

Firebase JWTを使用する場合

composer require firebase/php-jwt

phpサンプル

require_once 'vendor/autoload.php';
use phpseclib3\Crypt\PublicKeyLoader;

// Google Playの公開鍵を取得(キャッシュ推奨)
function getGooglePublicKey($keyId) {
    $jwksUrl = 'https://www.googleapis.com/oauth2/v3/certs';
    
    // キャッシュの実装(上記と同じ)
    
    foreach ($jwks['keys'] as $key) {
        if ($key['kid'] === $keyId) {
            return PublicKeyLoader::load($key)->toString('PKCS8');
        }
    }
    return null;
}

// JWTの検証
function verifyJwt($jwt) {
    list($header, $payload, $signature) = explode('.', $jwt);
    
    $header = json_decode(base64_decode($header), true);
    $payload = json_decode(base64_decode($payload), true);
    
    // 公開鍵を取得
    $publicKey = getGooglePublicKey($header['kid']);
    if (!$publicKey) {
        return false;
    }
    
    // 署名検証
    $data = "$header.$payload";
    $signature = base64_decode(str_replace(['-', '_'], ['+', '/'], $signature));
    
    $result = openssl_verify($data, $signature, $publicKey, 'SHA256');
    
    return $result === 1 ? $payload : false;
}

// メイン処理
function handleRtdnNotification() {
    // リクエストボディを取得
    $input = file_get_contents('php://input');
    $data = json_decode($input, true);
    
    if (!$data || !isset($data['message']['data'])) {
        http_response_code(400);
        exit;
    }
    
    // JWTをデコード
    $jwt = base64_decode($data['message']['data']);
    $payload = verifyJwt($jwt);
    
    if (!$payload) {
        http_response_code(401);
        exit;
    }
    
    // 通知タイプに応じて処理
    $notificationType = $payload['eventType'];
    $subscriptionId = $payload['subscriptionNotification']['subscriptionId'];
    $purchaseToken = $payload['subscriptionNotification']['purchaseToken'];
    
    switch ($notificationType) {
        case 'SUBSCRIPTION_CANCELED':
            // 定期購入がキャンセルされた
            handleSubscriptionCanceled($subscriptionId, $purchaseToken);
            break;
            
        case 'SUBSCRIPTION_PURCHASED':
            // 新しい定期購入
            handleSubscriptionPurchased($subscriptionId, $purchaseToken);
            break;
            
        case 'SUBSCRIPTION_RENEWED':
            // 定期購入が更新された
            handleSubscriptionRenewed($subscriptionId, $purchaseToken);
            break;
            
        case 'SUBSCRIPTION_REVOKED':
            // 返金または管理者によるキャンセル
            handleSubscriptionRevoked($subscriptionId, $purchaseToken);
            break;
            
        case 'SUBSCRIPTION_RESTARTED':
            // ユーザーがキャンセル後に再開
            handleSubscriptionRestarted($subscriptionId, $purchaseToken);
            break;
            
        default:
            // 未知の通知タイプ
            http_response_code(400);
            exit;
    }
    
    // 成功レスポンス
    http_response_code(200);
}

// 返金処理(SUBSCRIPTION_REVOKED)
function handleSubscriptionRevoked($subscriptionId, $purchaseToken) {
    // 1. Google Play Developer APIを使用して購入情報を取得
    $purchaseInfo = getSubscriptionPurchaseInfo($subscriptionId, $purchaseToken);
    
    // 2. 返金理由を確認
    $revocationReason = $purchaseInfo['revocationReason'] ?? null;
    
    // 3. ユーザーアカウントを更新(アクセス権限を削除など)
    $userId = getUserIdFromPurchaseToken($purchaseToken);
    revokeUserAccess($userId);
    
    // 4. データベースに返金記録を保存
    logRefund($userId, $subscriptionId, $purchaseToken, $revocationReason);
    
    // 5. 必要に応じてユーザーに通知
    sendRefundNotification($userId);
}

// Google Play Developer APIから購入情報を取得
function getSubscriptionPurchaseInfo($subscriptionId, $purchaseToken) {
    // 実際にはここでGoogle Play Developer APIを呼び出す
    // 必要な認証情報などを設定
    
    // 簡易実装(実際にはAPI呼び出しが必要)
    return [
        'revocationReason' => 1, // 1: 返金, 2: 管理者によるキャンセル
        // その他の購入情報...
    ];
}

// その他のヘルパー関数...

// メイン処理を実行
handleRtdnNotification();