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
voidedSource:取り消し済みの購入の開始者。有効な値は 0 です。ユーザー 1.デベロッパー 2.Google voidedReason:購入が取り消された理由。有効な値は 0 です。その他 1.購入者都合 2.Not_received 3.欠陥 4.Accidental_purchase 5.不正行為 6.フレンドリーな不正行為 7.チャージバック 8.Unacknowledged_purchase
phpサンプルコード
本体
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); // ここでデータベース更新やサービス停止処理を実装 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; } }
呼び出し
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()); }
GCPのサービスアカウントのキーをDLしたjson service-account.json 例
{ "project_id": "your-gcp-project", "private_key": "-----BEGIN PRIVATE KEY-----...", "client_email": "play-api-client@your-project.iam.gserviceaccount.com", }
返品APIデータ例
$voidedPurchases = Array ( [0] => Array ( [purchaseToken] => lljemaxxxxxxx~略~xxxxxxxxxxxxxxxxxxxcjqbs0Tu2x9lL_cZBfZ9XxxxxxxxxxxQ [purchaseTimeMillis] => 1749200918842 [voidedTimeMillis] => 1749261611834 [orderId] => GPA.3326-0823-8279-72022..0 [voidedSource] => 0 [voidedReason] => 4 [kind] => androidpublisher#voidedPurchase ) )
正常レスポンス
{ "voidedPurchases": [ { "kind": "androidpublisher#voidedPurchase", "purchaseToken": "abcdef123456", "orderId": "GPA.1234-5678-9012-34567", "voidedTimeMillis": "1610000000000", "voidedSource": 1, "voidedReason": 2 } ], "tokenPagination": { "nextPageToken": "next_page_token_123" } }