PHP实现苹果(IOS)内购(IAP)

反反复复经过多次重写(内部需要),发现苹果使用PHP来验证苹果内购数据是否正确并不是一件很难的事情。我把我的一些心得写出来,以供以后有这方面需求的小伙伴参考,以PHP语言为例,谁让PHP是最好的语言呢!

首先要知道苹果内购分沙箱环境和正式环境,两者区别就是名字不同,请求是需要携带的参数是相同的。最重要的就一个数据,叫receipt-data,这个一般是APP传递给你的,你拿来组装成苹果系统认识的数据就可以了,里面的数据不要改动,这个数据大概由4000-5000个字符(大写字母、小写字母、数字和"=")组成。其他,看你的业务需要,传递必要的参数,如:账户、详情等。

本例的内购项目类型是:消耗型。

一、由于苹果内的付款价格都是死的(苹果内定好的),我们不能随意修改只能选择(这也是分沙箱环境和正式环境的原因吧),不像微信支付在测试的时候可以填0.01元,苹果内想要在正式环境测试,最低价格:6元。下面是各个内购项目(消耗性和非消耗性项目都是这个价)的价格列表,以供参考:

等级1~等级87(RMB):6 - 12 - 18 - 25 - 30 - 40 - 45 - 50 - 60 - 68 - 73 - 78 - 88 - 93 - 98 - 108 - 113 - 118 - 123 - 128 - 138 - 148 - 153 - 158 - 163 - 168 - 178 - 188 - 193 - 198 - 208 - 218 - 223 - 228 - 233 - 238 - 243 - 248 - 253 - 258 - 263 - 268 - 273 - 278 - 283 - 288 - 298 - 308 - 318 - 328 - 348 - 388 - 418 - 448 - 488 - 518 - 548 - 588 - 618 - 648 - 698 - 798 - 818 - 848 - 898 - 998 - 1048 - 1098 - 1148 - 1198 - 1248 - 1298 - 1398 - 1448 - 1498 - 1598 - 1648 - 1998 - 2298 - 2598 - 2998 - 3298 - 3998 - 4498 - 4998 - 5898 - 6498

二、错误码的说明很简洁也很好找的,这里我再次贴出来,如下:

* 21000 App Store不能读取你提供的JSON对象  
* 21002 receipt-data域的数据有问题  
* 21003 receipt无法通过验证  
* 21004 提供的shared secret不匹配你账号中的shared secret  
* 21005 receipt服务器当前不可用  
* 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送  
* 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务  
* 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务 

三、沙箱环境数据请求正式环境url,返回的数据如下:

{"status":21007}

 没错,就一个错误码,其他什么都没有。

四、沙箱环境数据请求沙箱环境url,返回数据如下:

{
    "receipt": {
        "original_purchase_date_pst": "2019-07-07 18:28:28 America\/Los_Angeles",
        "purchase_date_ms": "1562549444613",
        "unique_identifier": "fe841fbd6cdc0e5b2fd645db904a9368e6444613",
        "original_transaction_id": "1000000544444613",
        "bvrs": "31",
        "transaction_id": "1000000544360754",
        "quantity": "1",
        "unique_vendor_identifier": "EF34A882-AF4E-45CC-BFBB-A3EB40096235",
        "item_id": "1435427810",
        "version_external_identifier": "0",
        "bid": "com.time.LL0814",
        "is_in_intro_offer_period": "false",
        "product_id": "BuySomething",
        "purchase_date": "2019-07-08 01:28:28 Etc\/GMT",
        "is_trial_period": "false",
        "purchase_date_pst": "2019-07-07 18:28:28 America\/Los_Angeles",
        "original_purchase_date": "2019-07-08 01:28:28 Etc\/GMT",
        "original_purchase_date_ms": "1562549308899"
    },
    "status": 0,
}

注意,沙箱环境中返回的status的值也是:0

五、正式环境数据请求正式环境url,返回数据如下:

{
    "receipt": {
        "original_purchase_date_pst": "2019-08-11 21:06:09 America\/Los_Angeles",
        "unique_identifier": "00008020-000824490A444613",
        "original_transaction_id": "320000570444613",
        "bvrs": "38",
        "app_item_id": "1332442547",//沙箱数据中不存在此值
        "transaction_id": "320000570369329",
        "quantity": "1",
        "unique_vendor_identifier": "C9991FBF-6526-4768-B139-BA437D288931",
        "product_id": "BuySomething",
        "item_id": "1435427810",
        "version_external_identifier": "832084373",
        "bid": "com.Playtime.CC0107",
        "is_in_intro_offer_period": "false",
        "purchase_date_ms": "1565582769000",
        "purchase_date": "2019-08-12 04:06:09 Etc\/GMT",
        "is_trial_period": "false",
        "purchase_date_pst": "2019-08-11 21:06:09 America\/Los_Angeles",
        "original_purchase_date": "2019-08-12 04:06:09 Etc\/GMT",
        "original_purchase_date_ms": "1565582769000"
    },
    "status": 0,
}

 注意,正式环境和沙箱环境返回的数据有一个不同,已在返回数据中标明,二者还有的不同就是字段的排序不一样。

五、代码【使用的TP5.0框架】如下:


acurl($receiptData);
        $data = json_decode($html, true);//接收苹果系统返回数据并转换为数组,以便后续处理
        // 如果是沙盒数据 则验证沙盒模式
        if ($data['status'] == '21007') {
            // 请求验证  1代表向沙箱环境url发送验证请求
            $html = $this->acurl($receiptData, 1);
            $data = json_decode($html, true); 
        }
        if (isset($_GET['debug'])) {
            exit(json_encode($data));
        }
        // 判断是否购买成功  【状态码,0为成功(无论是沙箱环境还是正式环境只要数据正确status都会是:0)】
        if (intval($data['status']) === 0) {
            if ($phone != '') {
                $iapData = [
                    'phone' => $phone,
                    'original_purchase_date_pst' => $data['receipt']['original_purchase_date_pst'],//购买时间,太平洋标准时间
                    'purchase_date_ms' => $data['receipt']['purchase_date_ms'],//购买时间毫秒
                    'unique_identifier' => $data['receipt']['unique_identifier'],//唯一标识符
                    'original_transaction_id' => $data['receipt']['original_transaction_id'],//原始交易ID
                    'bvrs' => $data['receipt']['bvrs'],//iPhone程序的版本号
                    'transaction_id' => $data['receipt']['transaction_id'],//交易的标识
                    'quantity' => $data['receipt']['quantity'],//购买商品的数量
                    'unique_vendor_identifier' => $data['receipt']['unique_vendor_identifier'],//开发商交易ID
                    'item_id' => $data['receipt']['item_id'],//App Store用来标识程序的字符串
                    'version_external_identifier' => $data['receipt']['version_external_identifier'],//版本外部的标识,沙箱环境下其值为:0正式环境其值为一个数字,会变,原因未知。是否和修改价格有关?
                    'bid' => $data['receipt']['bid'],//iPhone程序的bundle标识
                   'is_in_intro_offer_period' => $data['receipt']['is_in_intro_offer_period'],//正式环境返回数据中能未找到?考虑删除,目前其值都是false
                    'product_id' => $data['receipt']['product_id'],//商品的标识
                    'purchase_date' => $data['receipt']['purchase_date'],//购买时间
                   'is_trial_period' => $data['receipt']['is_trial_period'],//?沙箱环境中在in_app中找到?正式环境中找得到吗?考虑删除,目前其值都是false
                    'purchase_date_pst' => $data['receipt']['purchase_date_pst'],//太平洋标准时间
                    'original_purchase_date' => $data['receipt']['original_purchase_date'],//原始购买时间
                    'original_purchase_date_ms' => $data['receipt']['original_purchase_date_ms'],//毫秒
                    'status' => $data['status'],
                    'timestamp' => date("Y-m-d H:i:s"),//北京时间(用户真实购买的时间)
                ];
                //插入iap订单表【将苹果返回的所有输数据都插入到数据库中,你可更具需要取舍,这里为了说明方便】
                $this->insert($iapData);
                $user = new User;
                //修改user表中的付款状态【2019-07-02】
                $user->where('phone', $phone)->update(['pay_project' => $payProject, 'create_time' => date('Y-m-d H:i:s', time())]);
                //返回到APP的数据
                $result = array(
                    'status' => 'true',
                    'errorCode' => '购买成功',
                    'pay_project' => $payProject
                );
                Log::record($result, 'toAPP');// 记录到日志
                return $result;
            } else {
                throw new ParamErrorException(['errorCode' => '购买失败,status:' . $data['status'] . ',未填写游戏账户']);
            }
        } else {
            throw new ParamErrorException(['errorCode' => 'receipt参数有误']);
        }
    }
    //curl【模拟http请求】
    public function acurl($receiptData, $sandbox = 0)
    {
        //小票信息
        $POSTFIELDS = array("receipt-data" => $receiptData);
        $POSTFIELDS = json_encode($POSTFIELDS);
        //正式购买地址 沙盒购买地址
        $urlBuy = "https://buy.itunes.apple.com/verifyReceipt";
        $urlSandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
        $url = $sandbox ? $urlSandbox : $urlBuy;//向正式环境url发送请求(默认)
        //简单的curl
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        $result = curl_exec($ch);
        curl_close($ch);
        return $result;
    }
}

以上代码为简单的判断但也已经完全满足需求,如有需要可以做其他删减扩充校验。

本次编辑时间为:2019-08-15 18:25

你可能感兴趣的:(PHP)