Google退款通知 PHP

Voided Purchases API

Google OAuth 2.0 系统支持服务器到服务器的交互

class GP
{
    private $key = "";//JSON里的密钥
    private $client_email = "";//JSON里的client_email
    private $token_uri = "";//JSON里的token_uri
    private $scope = 'https://www.googleapis.com/auth/androidpublisher';//以空格分隔的应用程序请求的权限列表。
    private $package_name = "";//包名

    public $st;//开始时间戳
    public $et;//开始时间戳

    public function __construct($st, $et)
    {
        $this->st = $st * 1000;//开始时间转毫秒
        $this->et = $et * 1000;//开始时间转毫秒
    }

    /**
     * 谷歌退款
     * 获取access_token 接口请求文档 https://developers.google.com/identity/protocols/oauth2/service-account
     * @return mixed
     */
    private function getGPAccessToken()
    {
        //头部 标头包括两个字段,指示签名算法和确认格式。这两个字段都是必需的,每个字段只有一个值。随着其他算法和格式的引入,此标题将相应更改。
        $header = [
            'alg' => 'RS256', //生成signature的算法
            'typ' => 'JWT'    //类型
        ];
        /**
         * JWT 请求集中所需的要求如下所示。它们可以按任何顺序出现在声明集中。

        名称  描述
        iss 服务帐户的电子邮件地址。
        scope   应用程序请求的以空格分隔的权限列表。
        aud 断言的预期目标的描述。发出访问请求时,此值始终为。https://oauth2.googleapis.com/token
        exp 确认的过期时间,以 1970 年 1 月 1 日 00:00:00 UTC 起的秒数指定。此值最长为发出时间后 1 小时。
        iat 确认时间已给出,以 1970 年 1 月 1 日 00:00:00 UTC 起的秒数指定。
         */
        $payload = ['iss' => $this->client_email, 'scope' => $this->scope, 'aud' => $this->token_uri, 'iat' => time(), 'exp' => time() + 1800];
        //  {Base64url encoded JSON header}
        $jwtHeader = $this->base64url_encode(json_encode($header));
        //  {Base64url encoded JSON claim set}
        $jwtClaim = $this->base64url_encode(json_encode($payload));
        //  The base string for the signature: {Base64url encoded JSON header}.{Base64url encoded JSON claim set}
        openssl_sign($jwtHeader . "." . $jwtClaim, $jwtSig, $this->key, "sha256WithRSAEncryption");
        $jwtSign = $this->base64url_encode($jwtSig);
        //  {Base64url encoded JSON header}.{Base64url encoded JSON claim set}.{Base64url encoded signature}
        $jwtAssertion = $jwtHeader . "." . $jwtClaim . "." . $jwtSign;
        
        return $this->requireByCurlQueryFun($this->token_uri, 'POST', urldecode(http_build_query([
                'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                'assertion' => $jwtAssertion,
            ]))
        );
    }

    private function base64url_encode($data)
    {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    public function rawData()
    {
        // 将原始数据直接存入数据库
        try {
            $access_token = json_decode($this->getGPAccessToken(), true);

            if(isset($access_token['access_token'])) {
                $access_token = $access_token['access_token'];
                $data = $this->requireByCurlQueryFun('https://www.googleapis.com/androidpublisher/v3/applications/' .$this->package_name
                    .'/purchases/voidedpurchases?access_token='.$access_token.'&endTime='.$this->et.'&startTime='.$this->st, 'GET');

                $data = json_decode($data, true);
                if (is_null($data) || empty($data)) {
                    Log::info(__CLASS__.';'.__FUNCTION__.' No data');
                } else {

                    if(isset($data['voidedPurchases'])) {
                        do {

                            // TODO 插入到表或其他逻辑
                            if(isset($data['tokenPagination']['nextPageToken']) && !empty($data['tokenPagination']['nextPageToken'])) {
                                $nextPageToken = $data['tokenPagination']['nextPageToken'];
                                $data = $this->requireByCurlQueryFun('https://www.googleapis.com/androidpublisher/v3/applications/'.$this->package_name
                                    .'/purchases/voidedpurchases?access_token='.$access_token.'&endTime='.$this->et.'&startTime='.$this->st
                                    .'&token='.$nextPageToken, 'GET');
                                $data = json_decode($data, true);
                            } else {
                                $nextPageToken = '';
                            }

                        } while(!empty($nextPageToken));
                        Log::info(__CLASS__.';'.__FUNCTION__.'success');
                    } else {
                        Log::error(__CLASS__.';'.__FUNCTION__.'fail:re='.json_encode($data));
                    }

                }
            } else {
                Log::error(__CLASS__.';'.__FUNCTION__.'fail:token is empty; access_token='.json_encode($access_token));
            }

        } catch (\Exception $e) {
            Log::error(__CLASS__.';'.__FUNCTION__.";获取gp退款回调失败,err=".$e->getMessage().';line:'.$e->getLine());
        }
    }

    /**
     * @param string $url           URL地址
     * @param string $mode          GET or POST
     * @param string $params        请求参数
     * @param string $userAgent     是否设置useragent
     * @param string $caFile        是否引用ca加密文件
     * @param bool $needHeader
     * @param int $timeout
     * @return array|bool|string
     */
    private function requireByCurlQueryFun($url, $mode, $params = '', $userAgent = "", $caFile = "", $needHeader = false , $timeout = 10)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        if(!empty($userAgent)){
            curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
        }
        if(!empty($caFile)){
            curl_setopt($ch, CURLOPT_CAINFO, $caFile);
        }

        if ($needHeader) {
            curl_setopt($ch, CURLOPT_HEADER, true);
        }

        if ($mode == 'POST') {
            curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
            curl_setopt($ch, CURLOPT_POST, true);
            if (is_array($params)) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
            } else {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
            }
        } else {
            if (is_array($params)) {
                $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($params);
            } else {
                $url .= (strpos($url, '?') === false ? '?' : '&') . $params;
            }
        }

        curl_setopt($ch, CURLOPT_URL, $url);

        $result = curl_exec($ch);

        if ($needHeader) {
            $tmp = $result;
            $result = array();
            $info = curl_getinfo($ch);
            $result['header'] = substr($tmp, 0, $info['header_size']);
            $result['body'] = trim(substr($tmp, $info['header_size']));  //直接从header之后开始截取,因为 1.body可能为空   2.下载可能不全
            //$info['download_content_length'] > 0 ? substr($tmp, -$info['download_content_length']) : '';
        }

        $errno = curl_errno($ch);
        if ($errno) {
            Log::channel('curl_err_log')->error("curl异常:", [
                'url' => $url,
                'mode' => $mode,
                'params' => $params,
                'errno' => $errno,
            ]);
        }

        curl_close($ch);
        return $result;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容