facebook twitter hatena line email

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

提供: 初心者エンジニアの簡易メモ
移動: 案内検索

pub/subを使ってエンドポイント作成

  1. https://console.cloud.google.com/
  2. 上部の検索バーで「Pub/Sub」と入力し、「Cloud Pub/Sub」を選択
  3. 左側メニューで「トピック」をクリック
  4. 画面上部の「+トピックを作成」ボタンをクリック
  5. トピックIDに「play-refund-notifications」と入力
  6. 「作成」ボタンをクリック
  7. 例:projects/your-project-id/topics/play-refund-notifications のようなトピックが生成されることを確認。

pub/subのサブスクリプション作成

  1. Google Cloud ConsoleのPub/Subページに戻る
  2. 左メニューで「サブスクリプション」をクリック
  3. 画面上部の「+サブスクリプションを作成」ボタンをクリック
  4. サブスクリプション設定:
サブスクリプションID: play-refund-notifications-sub
トピック名: play-refund-notifications (ドロップダウンから選択)
配信タイプ: 「Push」を選択
エンドポイントURL: https://yourdomain.com/notifications # 後で修正可能
確認期限: 60 (秒)
認証 (オプションですが推奨):
「Push認証を構成」を展開
「サービスアカウント」で適切なアカウントを選択
  1. 「作成」ボタンをクリック

Google Play Consoleでの設定

  1. Google Play Consoleにログイン
  2. GooglePlayで収益化する/収益化のセットアップを開く
  3. リアルタイム デベロッパー通知の、セクションで通知を受け取るエンドポイントのトピック名(例:projects/your-project-id/topics/play-refund-notifications)を設定

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();