Apple和Google退款信息通知

退款的滥用

针对退款,不同国家或地区会有不同的“无条件退款期限”,例如苹果

  • AppStore商店退款政策:

中国/美国/韩国 等大多数国家:90天有条件退款

  • 在中国区AppStore的具体退款政策:一个ID有1次无条件退款条件,一年2次有条件退款,第3次退款会非常难。至于退款到账时间快为36小时,也有7-15个工作日退还。
  • 正是这些“漏洞”,所以出现专业的代充工作室,导致开发者坏账非常严重。特别是火爆的游戏代充和直播行业。

退款的具体手段

  1. 利用淘宝店以代充打折的形式获取玩家的游戏账号,为玩家充值后申请退款。淘宝店获得充值金,玩家获得游戏商品,最终亏损的是游戏厂商。
  2. 收购消费过的App Store ID账号,收到的账号会被用来退款和直播打赏,主播可以自己刷火箭然后申请退款

针对海外支付,退款的方式主要是App Store和Google

App Store

  • AppStore退款****通知参考文档
    苹果会定期通知****退款内容,当用户退款后苹果会调用配置的接口通知我们(被动接收)
  • 配置url
    1. 登录苹果后台 https://appstoreconnect.apple.com/
    2. 选择需要通知的app应用,点击侧边栏的综合->App信息
    3. 在AppStore服务器通知网址(URL)中配置我们的接口地址(必须为Https)
苹果响应信息

responsebody通知参数详细文档
从App Store在服务器通知中返回的是一条JSON数据的退款****通知,例子:

{
    "notification_type": "REFUND",#触发通知的订阅事件。REFUND:退款
    "environment": "PROD", #App Store生成收据的环境,可能是沙箱和生产环境
    "latest_receipt": "",#不推荐使用。自2021年3月10日起,生产和沙箱环境中将不再提供此对象。
    "latest_receipt_info": {},#不推荐使用。自2021年3月10日起,生产和沙箱环境中将不再提供此对象。
    "unified_receipt": {
        "status": 0,
        "environment": "Production",
        "latest_receipt_info": [{#最近100笔退款订单信息
            "quantity": "1",
            "product_id": "com.xxxxxx.xmios.60",
            "transaction_id": "490022793443160", #苹果订单号
            "purchase_date": "2021-04-01 18:04:09 Etc/GMT",
            "purchase_date_ms": "1617300249000",
            "purchase_date_pst": "2021-04-01 11:04:09 America/Los_Angeles",
            "original_purchase_date": "2021-04-01 18:04:09 Etc/GMT",
            "original_purchase_date_ms": "1617300249000",
            "original_purchase_date_pst": "2021-04-01 11:04:09 America/Los_Angeles",
            "is_trial_period": "false",
            "original_transaction_id": "490000793443160",
            "cancellation_date": "2021-04-02 16:24:42 Etc/GMT", #退款时间
            "cancellation_date_ms": "1617380682000", #退款时间 精确到毫秒
            "cancellation_date_pst": "2021-04-02 09:24:42 America/Los_Angeles",
            "cancellation_reason": "1",
            "in_app_ownership_type": "PURCHASED"
        }],
        "latest_receipt": "" #不推荐使用。自2021年3月10日起,生产和沙箱环境中将不再提供此对象。
    },
    "bid": "com.xxxxxx.xmios",
    "bvrs": "2.20"
}

这里我主要关注两个字段

  1. cancellation_date(退款时间)
  2. transaction_id(苹果订单号)
    unified_receipt 存放着最近100笔退款****订单信息,我们可以循环遍历数组,通过数组下的 transaction_id 从数据库中查到订单信息,结合 cancellation_date 保存到退款记录表。
Google
class GoogleRefundCommand extends Command
{
    protected $signature = 'google:refund {startTime?} {endTime?}';

    private $key =  "",//使用JSON里的密钥
 
    private $iss = ''; //服务帐户的电子邮件地址。

    private $sub = ''; //应用程序正在请求委派访问权限的用户的电子邮件地址。

    private $scope = 'https://www.googleapis.com/auth/androidpublisher';//以空格分隔的应用程序请求的权限列表。

    private $aud = 'https://oauth2.googleapis.com/token';//声明的预期目标的描述符。发出访问令牌请求时,此值始终为。https://oauth2.googleapis.com/token

    private $package_name = ['','',''];

    private $getTokenUrl = 'https://www.googleapis.com/androidpublisher/v3/applications/';

    private $getTokenMethod = '/purchases/voidedpurchases';

    private $client = "";


    public function __construct()
    {
        parent::__construct();
        $this->client = HttpAgent::getInstance();
    }


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


    /**
     * 谷歌退款
     * 获取access_token 接口请求文档 https://developers.google.com/identity/protocols/oauth2/service-account
     * @return mixed
     */
    public function handle()
    {
        //只在线上执行
        if (config('app.env') != 'production') {
            Log::info('获取谷歌获取退款脚本执行时间:' . date('Y-m-d H:i:s'));
            return;
        }
        //头部
        $header = [
            'alg' => 'RS256', //生成signature的算法
            'typ' => 'JWT'    //类型
        ];
        $payload = ['iss' => $this->iss, 'sub' => $this->sub, 'scope' => $this->scope, 'aud' => $this->aud, '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;
        
        dd($jwtAssertion);
        // todo...
       $ret = $this->client->request('post', 'https://oauth2.googleapis.com/token', ['query' => [
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion' => $jwtAssertion,
        ]]);
    }
}

这里我们使用postman测试一下
1. 请求 https://oauth2.googleapis.com/token 接口
2.grant_type = urn:ietf:params:oauth:grant-type:jwt-bearer,assertion使用 $jwtAssertion 参数

image.png

保存返回的授权令牌access_token
使用GET请求https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/voidedpurchases

image.png

startTime:您想在响应中看到的最早作废的购买交易的时间。默认情况下,startTime 设为 30 天以前。注意:这里的startTime是毫秒
maxResults:每个响应中出现的已作废购买交易的数量上限。默认情况下,此值为 1000。请注意,此参数的最大值也是 1000。
token: 之前响应中的继续令牌;可让您查看更多结果。

Google响应信息

Google响应信息文档

{
  "tokenPagination": {
    "nextPageToken": "next_page_token"
  },
  "voidedPurchases": [
    {
      "kind": "androidpublisher#voidedPurchase",
      "purchaseToken": "some_purchase_token",
      "purchaseTimeMillis": "1468825200000",
      "voidedTimeMillis": "1469430000000",
      "orderId": "some_order_id",
      "voidedSource": "0",
      "voidedReason": "4"
    },
    {
      "kind": "androidpublisher#voidedPurchase",
      "purchaseToken": "some_other_purchase_token",
      "purchaseTimeMillis": "1468825100000",
      "voidedTimeMillis": "1470034800000",
      "orderId": "some_other_order_id",
      "voidedSource": "2",
      "voidedReason": "5"
    },
  ]
}

这里我主要关注两个字段
voidedTimeMillis(退款时间)
orderId(Google订单号)
voidedPurchases存放着maxResults条退款订单信息,如果结果数量超过了在 maxResults请求参数中指定的数量,响应就会包含一个 nextPageToken值,这里我写了一个递归函数判断 nextPageToken是否为空,非空则将该值传递给后续请求来查看更多结果。

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

推荐阅读更多精彩内容