Unity接入IAP内购(Android,IOS)最新流程,第四篇:后台对内购退单问题的处理(IOS欺诈检测以及欺诈信息反馈)


欢迎进入Unity内购系列

你好! 这将是一个系列的文章
第一篇 介绍客户端里支付的调起以及购买。
第二篇 介绍后台对购买结果的验证以及发货(IOS)。
第三篇 介绍后台对购买结果的验证以及发货(Android)。
第四篇 介绍后台对内购退单问题的处理(IOS欺诈检测以及欺诈信息反馈)。

第四篇:后台对内购退单问题的处理(IOS欺诈检测以及欺诈信息反馈)

本篇介绍PHP后台对玩家退单问题处理,简称防欺诈。
这是纯后端内容,不限于Unity项目,其他项目同样可用。

重要提示:
1、本人不会PHP,这是同事提供的代码,是项目实战代码,代码里有些遗漏的还请自行添加,万一有无法解决的可以留言,我再请教同事。
2、代码中涉及到sql和db相关的代码为数据库操作,需要根据自己的项目实现

一、应用场景

玩家在游戏内充值成功之后,服务器会给玩家发送相应的道具,玩家将道具消耗,或者卖给其他人,然后在AppStore申请退款,此时如果我们不实现防欺诈接口,那么退款结果完全由苹果说了算,如果我们实现了防欺诈接口,那么玩家申请退单的时候,苹果会发送一条退款的通知到我们的服务器,我的服务器根据通知内容,查找对应的玩家,将玩家在游戏内的各种行为指标上报给苹果,然后苹果根据玩家行为决定是否退款。

二、基本流程

1、玩家申请退款
2、苹果发送退款通知给服务器
3、服务器解析通知数据
4、服务器根据解析出的数据,查询游戏中对应玩家的数据
5、服务器将玩家相关的数据通过接口上报给苹果(包括:玩家年龄,玩家消费频率,是否是新玩家,玩家是否获得物品,玩家的游戏账号状态等等)

三、AppStore后台设置

1、添加接受消息的Url
打开App Store connect,在 应用 > App信息 > 综合信息 > App Store 服务器通知网址 (URL)中填入自己后台提供的地址,用于接受苹果推送的退款通知(例如:https://www.aaa.com/notify/AppleOrderRefund,其中必须要支持https)
在这里插入图片描述

2、获取相关的密钥以及证书,提供给PHP使用
(1)、private_key
1、选择 “用户和访问”,然后选择 “密钥” 子标签页 >
2、在 “密钥类型” 下选择 “App内购买项目” >
3、单击 “生成API内购买项目密钥”(如果之前创建过,则点击 “添加(+)” 按钮新增。) >
4、输入密钥的名称。名字可以随便取,名字不作为密钥的一部分 >
5、单击 “生成”
6、列表右边有 下载 App 内购买项目密钥 按钮,将密钥下载下来,这个就是private_key
在这里插入图片描述

(2)、pub_key
1、 公钥可以官网下载,这个公钥是所有人都一样的
苹果官网AppleRootCA-G3.cer下载
2、 将这个cer格式的公钥转为pem格式,转换方法自行百度

(3)、kid
1、 第一步生成内购买项目密钥时,密钥列表中间有个密钥 ID,把它拷贝出来,这个就是kid

(4)、bundle_id
1、 包名,不用多说

(5)、iss
1、选择 “用户和访问”,然后选择 “密钥” 子标签页 >
2、在 “密钥类型” 下选择 “App Store Connect API” >
3、单击 “生成API密钥” >
4、输入密钥的名称。名字可以随便取,名字不作为密钥的一部分 >
5、单击 “生成”
6、上方有个issuer ID,将这个id拷贝出来,这个就是iss
在这里插入图片描述

四、PHP代码实现

重要提示:PHP需要安装JWT环境,安装命令如下

    // 安装JWT
    composer require firebase/php-jwt

校验签名并解码返回数据:

    // 校验签名并解码返回数据
    use \Firebase\JWT\JWT;
	use \Firebase\JWT\Key;
	
    public function decodePayNotifyV2($jwt)
    {
        list($header, $payload, $sign) = explode('.', $jwt);
        $header_decode = base64_decode($header);
        $header_json = json_decode($header_decode, true);

        if (!isset($header_json['x5c'])) {
            // 解析失败

            return false;
        }

        //这一步是将公钥转成对应的格式
        $pubkey = "-----BEGIN CERTIFICATE-----\n" . $header_json['x5c'][0] . "\n-----END CERTIFICATE-----";
        try {
            $decoded = Firebase\JWT\JWT::decode($jwt, new Firebase\JWT\Key($pubkey,  $header_json['alg']));
            // 判断返回的证书链是否等于苹果公钥证书
            $pem = $this->pub_key;
            $str = str_replace("\r\n", "", $pem);
            $pubPem = "-----BEGIN CERTIFICATE-----" . $header_json['x5c'][2] . "-----END CERTIFICATE-----";
            if ($str != $pubPem) {
                // 返回的公钥证书链错误

            }
        } catch (\Exception $e) {
            // 签名校验失败

            return false;
        }
        return json_decode(json_encode($decoded), true);
    }

生成 JWT token:

    //获取Authorization: Bearer
    private $private_key = null;
    private $pub_key = null;
    private $header = null;
    private $algorithm = "ES256";
    private $payload = null;
    private $kid = null;
    private $bundle_id=null;
    private $iss=null;


    private function authorbeaer()
    {
        $this->payload  = [
            'iss' => $this->iss, //iss值
            'iat' => intval(time()),
            'exp' => intval(time() + 3600),
            'aud' => 'appstoreconnect-v1',  //固定值 https://appleid.apple.com
            'bid' => $this->bundle_id, //应用bundle_id
        ];
        //print_r($this->payload);

        $this->header = [
            "alg" => "ES256",
            "kid" => $this->kid,
            "typ" => "JWT"
        ];
        //print_r($this->header);

        $token = Firebase\JWT\JWT::encode($this->payload, $this->private_key, $this->algorithm, $this->kid, $this->header);


        return $token;
    }

提交欺诈信息:

	private $private_key = null;
    private $pub_key = null;
    private $header = null;
    private $algorithm = "ES256";
    private $payload = null;
    private $kid = null;
    private $bundle_id=null;
    private $iss=null;



    public function AppleOrderRefund()
    {

        $data = array_merge($_POST,$_GET);

        $content = file_get_contents("php://input");

        $content = json_decode($content,true);

        $jwt = $content['signedPayload'];

        //需要从AppStore准备的5个数据
        $this->pub_key = file_get_contents('AppleRootCA-G3.pem');

        $this->private_key = file_get_contents('SubscriptionKey_9DY6HVVYLM.p8'); //密钥

        $this->kid = '9DY6HVVYLM'; //kid

        $this->bundle_id = 'com.abc.def'; //APP包名

        $this->iss = 'c2072b74-65b7-4b12-9320-8699803627fa';

        $payload_json = $this->decodePayNotifyV2($jwt);

        if (empty($payload_json) || !isset($payload_json['data'])) 
        {
            return;
        }

        $t_payload_json = $this->decodePayNotifyV2($payload_json['data']['signedTransactionInfo']);

        if (empty($t_payload_json) || empty($t_payload_json['originalTransactionId'])) 
        {
            return;
        }



        /*

         *  lifetimeDollarsPurchased 与 lifetimeDollarsRefunded

         *      0. 终身购买金额未申报。

                1. 终身购买金额为0美元。

                2. 终身购买金额在0.01至49.99美元之间。

                3. 终身购买金额在50-99.99美元之间。

                4. 终身购买金额在100-499.99美元之间。

                5. 终身购买金额在500–999.99美元之间。

                6. 终身购买金额在1000–1999.99美元之间。

                7. 终身购买金额超过2000美元。

         * */



        $originalTransactionId = $t_payload_json['originalTransactionId'];

        $url = 'https://api.storekit.itunes.apple.com/inApps/v1/transactions/consumption/'.$originalTransactionId;

        $player_order_DB = new IModel('player_order');

        $player_order_info = $player_order_DB->getObj("o_no=".$originalTransactionId);

        if(empty($player_order_info))
        {
            return;
        }



        if($payload_json['notificationType'] != 'CONSUMPTION_REQUEST'){

            $AppleOrderRefund_log_DB = new IModel('AppleOrderRefund_log');

            $time = time();

            $sql = "update g_player_order set final_result='{$payload_json['notificationType']}',update_time=$time where o_no='{$originalTransactionId}' limit 1";

            $AppleOrderRefund_log_DB->dbquery($sql);

            $sql = "update g_AppleOrderRefund_log set final_result='{$payload_json['notificationType']}',update_time=$time where originalTransactionId='{$originalTransactionId}' limit 1";

            $AppleOrderRefund_log_DB->dbquery($sql);

            return;

        }



        $o_pid = $player_order_info['o_pid'];

        $sql = "select sum(o_money) as o_money_total from g_player_order where o_pid={$o_pid} and o_pay_status=1";

        $money_total_res = $player_order_DB->dbquery($sql);

        $o_money_total = isset($money_total_res[0]) ? $money_total_res[0]['o_money_total'] : 0;

        $o_money_total_USD = $o_money_total/300;//换算成美元

        if($o_money_total_USD==0){

            $lifetimeDollarsPurchased = 1;

        }elseif($o_money_total_USD >= 0.01 && $o_money_total_USD < 49.99){

            $lifetimeDollarsPurchased = 2;

        }elseif($o_money_total_USD >= 50 && $o_money_total_USD < 99.99){

            $lifetimeDollarsPurchased = 3;

        }elseif($o_money_total_USD >= 100 && $o_money_total_USD < 499.99){

            $lifetimeDollarsPurchased = 4;

        }elseif($o_money_total_USD >= 500 && $o_money_total_USD < 999.99){

            $lifetimeDollarsPurchased = 5;

        }elseif($o_money_total_USD >= 1000 && $o_money_total_USD < 1999.99){

            $lifetimeDollarsPurchased = 6;

        }elseif($o_money_total_USD >= 2000){

            $lifetimeDollarsPurchased = 7;

        }



        $request_data = [

            'accountTenure' => '1',//用户账户的年龄

            'appAccountToken' => $payload_json['notificationUUID'],

            'consumptionStatus' => '3',//消耗状态 0:不确定;1:没消耗;2:部分消耗;3:完全消耗

            'customerConsented' => true,//用户是否同意提供消费数据 true:同意

            'deliveryStatus' => 0,//应用是否成功交付了正常运行的应用内购买

            'lifetimeDollarsPurchased' => $lifetimeDollarsPurchased,//计算用户所有充值的总金额

            'lifetimeDollarsRefunded' => '1',//计算用户所有退款的总金额

            'platform' => 1,

            'playTime' => 1,

            'sampleContentProvided' => false,

            'userStatus' => 1,

        ];

        $ch = curl_init($url);

        $data_query = json_encode($request_data);

        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");

        curl_setopt($ch, CURLOPT_HEADER, true);

        $apple_jwt_token = $this->authorbeaer();

        $token = $apple_jwt_token;

        if(!$token)
        {
            return;
        }

        $header = [

            "Authorization: Bearer $token",

            "Content-Type: application/json",

        ];

        curl_setopt($ch, CURLOPT_HTTPHEADER,$header);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        curl_setopt($ch, CURLOPT_POSTFIELDS, $data_query);

        curl_setopt($ch, CURLOPT_TIMEOUT, 3);



        $response = curl_exec($ch);



        if ($response === false){

        }

        curl_close($ch);





        $_db = new IQuery('AppleOrderRefund_log');

        $request_data['originalTransactionId'] = $originalTransactionId;

        $request_data['result'] = print_r($response,true);

        $request_data['add_time'] = time();

        $request_data['final_result'] = $payload_json['notificationType'];

        $request_data['update_time'] = time();

        $_db->setData($request_data);

        $_db->add();



        $time = time();

        $sql = "update g_player_order set final_result='{$payload_json['notificationType']}',update_time=$time where o_no='{$originalTransactionId}' limit 1";

        $_db->dbquery($sql);

    }

五、参考文档

苹果官方文档:App Store Server API
WWDC21 - App Store Server API 实践总结
PHP App Store Server API 苹果API退款 查询订单 历史订单 PHP校验签名解码
AppStore服务端通知(订阅/退款回调通知)

总结

1、防欺诈不需要客户端参与
2、PHP实现接口处理AppStore通知的数据
3、防欺诈只能协助Apple处理退款请求,而不能100%决定退款结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值