facebook twitter hatena line email

「Php/アプリストア連携/返金API/GooglePlayStore」の版間の差分

提供: 初心者エンジニアの簡易メモ
移動: 案内検索
(phpサンプルコード)
行1: 行1:
==準備==
+
[[Php/アプリストア連携/返金API/GooglePlayStore/voided-purchases]]
[[Php/GooglePlayApi]] [ショートカット]
+
 
+
上記ページの以下処理を行う。
+
#gcpでサービスアカウントを作成し鍵を作る
+
#Google Play Android Developer APIを有効に
+
#PlayConsoleにサービスアカウントの権限を追加して、売上を見れるように。
+
 
+
==GooglePlayAPIのvoided-purchasesを使用==
+
このAPIは、自サーバからdevelopers.googleのAPIへ、問い合わせして、返金情報をリストで取得するもの
+
 
+
https://developers.google.com/android-publisher/voided-purchases?hl=ja
+
 
+
https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.voidedpurchases/list?hl=ja
+
 
+
https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.voidedpurchases?hl=ja#VoidedPurchase
+
 
+
<pre>
+
voidedSource:取り消し済みの購入の開始者。有効な値は 0 です。ユーザー 1.デベロッパー 2.Google
+
voidedReason:購入が取り消された理由。有効な値は 0 です。その他 1.購入者都合 2.Not_received 3.欠陥 4.Accidental_purchase 5.不正行為 6.フレンドリーな不正行為 7.チャージバック 8.Unacknowledged_purchase
+
</pre>
+
 
+
===phpサンプルコード===
+
本体
+
<pre>
+
class GooglePlayRefundChecker {
+
    private $serviceAccountFile;
+
    private $packageName;
+
    private $accessToken;
+
 
+
    public function __construct($serviceAccountFile, $packageName) {
+
        $this->serviceAccountFile = $serviceAccountFile;
+
        $this->packageName = $packageName;
+
    }
+
 
+
    // アクセストークンを取得
+
    public function authenticate() {
+
        $credentials = json_decode(file_get_contents($this->serviceAccountFile), true);
+
       
+
        $jwtHeader = base64_encode(json_encode([
+
            'alg' => 'RS256',
+
            'typ' => 'JWT'
+
        ]));
+
       
+
        $now = time();
+
        $jwtClaimSet = base64_encode(json_encode([
+
            'iss' => $credentials['client_email'],
+
            'scope' => 'https://www.googleapis.com/auth/androidpublisher',
+
            'aud' => 'https://oauth2.googleapis.com/token',
+
            'iat' => $now,
+
            'exp' => $now + 3600
+
        ]));
+
       
+
        $signatureInput = "$jwtHeader.$jwtClaimSet";
+
        openssl_sign($signatureInput, $signature, $credentials['private_key'], 'SHA256');
+
        $jwtSignature = base64_encode($signature);
+
       
+
        $jwt = "$signatureInput.$jwtSignature";
+
       
+
        $response = $this->httpPost('https://oauth2.googleapis.com/token', [
+
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
+
            'assertion' => $jwt
+
        ]);
+
       
+
        $this->accessToken = $response['access_token'];
+
        return $this->accessToken;
+
    }
+
 
+
    // 返金情報を取得
+
    public function getVoidedPurchases($startTime = null) {
+
        if (!$this->accessToken) {
+
            throw new Exception('Access token not available. Call authenticate() first.');
+
        }
+
       
+
        $url = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{$this->packageName}/purchases/voidedpurchases";
+
        $params = [
+
            'type' => 1, // 0: アプリ内購入, 1: 定期購入を含む
+
        ];
+
       
+
        if ($startTime) {
+
            $params['startTime'] = $startTime;
+
        }
+
       
+
        $allVoidedPurchases = [];
+
        $nextPageToken = null;
+
       
+
        do {
+
            if ($nextPageToken) {
+
                $params['token'] = $nextPageToken;
+
            }
+
           
+
            $response = $this->httpGet($url, $params);
+
            $data = json_decode($response, true);
+
           
+
            if (isset($data['voidedPurchases'])) {
+
                $allVoidedPurchases = array_merge($allVoidedPurchases, $data['voidedPurchases']);
+
            }
+
           
+
            $nextPageToken = $data['tokenPagination']['nextPageToken'] ?? null;
+
           
+
        } while ($nextPageToken);
+
       
+
        return $allVoidedPurchases;
+
    }
+
 
+
    // 返金処理を実行
+
    public function processRefunds($voidedPurchases) {
+
        foreach ($voidedPurchases as $purchase) {
+
            try {
+
                $orderId = $purchase['orderId'];
+
                $purchaseToken = $purchase['purchaseToken'];
+
                $voidedTime = date('Y-m-d H:i:s', $purchase['voidedTimeMillis'] / 1000);
+
               
+
                // ここでデータベース更新やサービス停止処理を実装
+
                $this->updateOrderStatus($orderId, 'REFUNDED');
+
                $this->revokeUserAccess($purchaseToken);
+
               
+
                echo "Processed refund for order: $orderId (voided at: $voidedTime)\n";
+
            } catch (Exception $e) {
+
                error_log("Error processing refund for order {$orderId}: " . $e->getMessage());
+
            }
+
        }
+
    }
+
 
+
    private function httpPost($url, $data) {
+
        $ch = curl_init($url);
+
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
        curl_setopt($ch, CURLOPT_POST, true);
+
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
+
        $response = curl_exec($ch);
+
        curl_close($ch);
+
        return json_decode($response, true);
+
    }
+
 
+
    private function httpGet($url, $params = []) {
+
        $query = http_build_query($params);
+
        $fullUrl = $url . ($query ? "?{$query}" : '');
+
       
+
        $ch = curl_init();
+
        curl_setopt($ch, CURLOPT_URL, $fullUrl);
+
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
+
            'Authorization: Bearer ' . $this->accessToken,
+
            'Content-Type: application/json'
+
        ]);
+
        $response = curl_exec($ch);
+
        curl_close($ch);
+
        return $response;
+
    }
+
 
+
    private function updateOrderStatus($orderId, $status) {
+
        // データベース更新処理を実装
+
        // 例: DB::table('orders')->where('order_id', $orderId)->update(['status' => $status]);
+
    }
+
 
+
    private function revokeUserAccess($purchaseToken) {
+
        // ユーザーアクセス無効化処理を実装
+
        // 例: $user = User::findByPurchaseToken($purchaseToken); $user->revokePremiumAccess();
+
    }
+
}
+
</pre>
+
呼び出し
+
<pre>
+
try {
+
    $checker = new GooglePlayRefundChecker(
+
        '/path/to/service-account.json',
+
        'com.your.app.package'
+
    );
+
   
+
    // 認証
+
    $accessToken = $checker->authenticate();
+
    echo "Access Token: $accessToken\n";
+
   
+
    // 返金情報取得(前回処理から1時間前までのデータを取得)
+
    $lastCheckTime = strtotime('-1 hour') * 1000; // ミリ秒単位
+
    $voidedPurchases = $checker->getVoidedPurchases($lastCheckTime);
+
   
+
    // 返金処理実行
+
    $checker->processRefunds($voidedPurchases);
+
   
+
    echo "Refund processing completed. Total processed: " . count($voidedPurchases) . "\n";
+
   
+
} catch (Exception $e) {
+
    die("Error: " . $e->getMessage());
+
}
+
</pre>
+
 
+
GCPのサービスアカウントのキーをDLしたjson
+
service-account.json 例
+
<pre>
+
{
+
  "project_id": "your-gcp-project",
+
  "private_key": "-----BEGIN PRIVATE KEY-----...",
+
  "client_email": "play-api-client@your-project.iam.gserviceaccount.com",
+
}
+
</pre>
+
 
+
===正常レスポンス===
+
<pre>
+
{
+
  "voidedPurchases": [
+
    {
+
      "kind": "androidpublisher#voidedPurchase",
+
      "purchaseToken": "abcdef123456",
+
      "orderId": "GPA.1234-5678-9012-34567",
+
      "voidedTimeMillis": "1610000000000",
+
      "voidedSource": 1,
+
      "voidedReason": 2
+
    }
+
  ],
+
  "tokenPagination": {
+
    "nextPageToken": "next_page_token_123"
+
  }
+
}
+
</pre>
+

2025年6月18日 (水) 16:33時点における版

Php/アプリストア連携/返金API/GooglePlayStore/voided-purchases