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

2023-11-08

反反复复经过多次重写(内部需要),发现苹果使用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框架】如下:


<?php
/**
 * Created by PhpStorm.
 * User: lsk
 * Date: 2019/08/12
 * Time: 14:00
 * QQ:1113277581
 */

namespace app\iap\model;

use think\Controller;
use app\iap\model\User;
use app\common\exception\ParamErrorException;//自定义的异常处理类,你可直接return其中的数组
use think\Log;


class Iap extends Controller
{
    //字符串长度
    const STRING_LENGTH = 1000;

    /**
     * 本段代码的逻辑:先向正式环境的url发起验证本次支付信息是否有误的请求,若本次请求的数据是沙箱环境中的数据,则只返回 status = 21007,
     * 那么会再向沙箱环境url发送一次请求。无论哪种请求,只要支付信息消息验证无误,status = 0 就会将本次返回的数据都会(json)写入到数据库中(iap表)
     */
    public function validateApplePay($receiptData, $phone, $payProject)
    {
        // 验证参数
        if (strlen($receiptData) < self::STRING_LENGTH) {
            return;
        }
        // 请求验证【默认向真实环境发请求】
        $html = $this->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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PHP实现苹果(IOS)内购(IAP) 的相关文章

  • 如何根据另一个下拉列表中的选择动态填充下拉列表中的选项?

    我有一个表 其中包含类别信息 例如产品 我已将它们列在下拉菜单中 现在 我需要做的是 在下一个下拉菜单中列出所选类别的子类别 我希望 javascript 是必需的 但我对 javascript 还不太熟悉 将非常感谢您的帮助 你应该使用
  • PHPunit - 错误

    当 PHPunit 框架不希望发生的错误发生时 测试会停止 PHP 会抛出错误 但 PHPunit 不会记录这是一个错误 我如何确保 PHPunit 将其记录为错误 免责声明 我是 PHPUnit 的新手 我也试图弄清楚 发生错误时会发生什
  • 准备好的语句需要 0 个参数,给定 1 个参数..,使用 php 手册示例 [重复]

    这个问题在这里已经有答案了 我直接从 php 手册示例中获取了这个 它几乎与我需要的相同 但我仍然收到此错误 有人可以告诉我我错过了什么吗 stmt link gt prepare SELECT obitBody Photo FROM tn
  • __callStatic():从静态上下文实例化对象?

    我对 PHP 中的 静态 和 动态 函数和对象如何协同工作感到困惑 特别是在 callStatic 方面 callStatic 的工作原理 您可以有一个普通的班级 MyClass 在班级内您可以 放置一个名为 callStatic 的静态函
  • PHP 文件上传帮助

    div align center div 这是我的代码
  • 从 FilterControllerEvent 监听器重定向到另一个 Symfony 路由

    我正在尝试设置一个 kernel controller 侦听器 以便在函数返回 true 时重定向到另一个路由 我有可用的路线 但无法使用此路线设置控制器 event gt setController 我收到以下错误 FilterContr
  • PHP解析xml文件错误

    我正在尝试使用 simpleXML 来获取数据http rates fxcm com RatesXML http rates fxcm com RatesXML Using simplexml load file 我有时会遇到错误 因为这个
  • cURL 错误 77:设置证书验证位置时出错:CAfile

    我正在使用 Firebase php SDKlink https firebase php readthedocs io en latest index html并在 Windows 10 上的 XAMPP 服务器上使用 laravel 最
  • 如何以编程方式获取 WooCommerce 中的所有产品?

    我想获取 WooCommerce 中的所有产品数据 产品 sku 名称 价格 库存数量 可用性等 我可以使用 wp query 来做到这一点吗 这样你就可以通过 wp query 获取所有产品 global wpdb all product
  • AWS S3 上传的图像已损坏

    我正在 AWS ec2 ubuntu 机器上工作 我的代码在 cakephp 中 当我尝试将任何图像上传到 AWS S3 时 它都会损坏 虽然它在核心 php 代码中运行良好 这是我的控制器代码 if this gt User gt sav
  • Laravel 按动态 ID 数组对集合进行排序 [重复]

    这个问题在这里已经有答案了 我有以下 people array 5 2 9 6 11 people collection People find people 但当我倾倒并死去时 people collection集合按 ID ASC 排序
  • CakePHP Xml 实用程序库触发 DOMDocument 警告

    我正在使用 CakePHP 在视图中生成 XMLXML核心库 http book cakephp org 2 0 en core utility libraries xml html xml Xml build data array ret
  • 未捕获的异常“Google_IO_Exception”,消息为“HTTP 错误:无法连接”

    我有一个任务 查询 运行一些从 gmail 邮箱检索数据的 php 代码 直到上周五 2015 年 4 月 10 日 它一直工作正常 现在我收到以下错误日志 E 11 58 26 094 2015 04 15 200 3 38 KB 14
  • PHP中如何识别服务器IP地址

    PHP中如何识别服务器IP地址 对于服务器 ip 来说是这样的 SERVER SERVER ADDR 这是港口的 SERVER SERVER PORT
  • 如何在没有引用的情况下复制对象?

    PHP5 OOP 有据可查对象通过引用传递 http php net manual en language oop5 references php默认情况下 如果这是默认的 在我看来 有一种非默认的方式可以在没有参考的情况下进行复制 如何
  • 将数组拆分为特定数量的块

    我知道array chunk 允许将数组拆分为多个块 但块的数量根据元素的数量而变化 我需要的是始终将数组拆分为特定数量的数组 例如 4 个数组 以下代码将数组分为 3 个块 两个块各有 2 个元素 1 个块有 1 个元素 我想要的是将数组
  • php 表单提交 - Q2

    我对这个虚拟问题感到抱歉 这是我的简单 PHP 表单 其中包含两个 SQL 表和 ADD 提交 按钮 我希望将人员从 Test1 转移到 Test2 很多事情都很好 只有提交按钮不起作用 因此 Test2 表没有反馈 Revised 现在提
  • PHP文件上传

    如果我想在文件名转到服务器的永久位置 而不是临时位置 之前更改文件名 我该如何执行此操作 代码如下
  • php下拉菜单人口

    我正在尝试编写一个 php 脚本 该脚本将根据主下拉菜单的选择填充第二个下拉菜单 我想使用 jquery 来完成所有非页面刷新的事情 但我发现现有的所有东西都很难理解和修改 你知道有什么写得很好且易于理解的东西吗 或者可能是现有的教程 下面
  • 如何在 codeigniter 查询中使用 FIND_IN_SET?

    array array classesID gt 6 this gt db gt select gt from this gt table name gt where array gt order by this gt order by q

随机推荐

  • Java面试题库,极客时间百度云盘百度网盘

    一 基础知识 比较简单的一些基础入门 二 微服务构建 Spring boot 三 服务治理 Spring Cloud Euraka 四 客户端负载均衡 Spring Cloud Ribbon 五 服务器容错保护 Spring Cloud H
  • 全国计算机等级考试题库二级C操作题100套(第80套)

    第80套 给定程序中 函数fun的功能是 将形参n中 各位上为偶数的数取出 并按原来从高位到低位的顺序组成一个新的数 并作为函数值返回 例如 从主函数输入一个整数 27638496 函数返回值为 26846 请在程序的下划线处填入正确的内容
  • BT 种子 tracker 磁链

    磁链 磁链是什么 传统的种子和磁链的区别 https baike baidu com item E7 A3 81 E5 8A 9B E9 93 BE E6 8E A5 5867775 在使用迅雷时 磁链会先下载一个种子文件 然后才开始下载正
  • 执行hexo d部署到github出错

    我的github已经配置了ssh key 并且执行 ssh T git github com能连接到github 但是在我执行hexo d想要将博客部署到github却出错了 利用http localhost 4000 访问本地发现已经部署
  • 服务器安装msyql成功后没有密码无法进入mysql的解决方法

    服务器安装免安装版MySQL成功后遇到的问题 服务器安装msyql成功后没有密码无法进入mysql的解决方法 今天在远程服务器上安装MySQL数据库 本来安装好了 可是登录时一直都是需要密码 在网上也找到相应的方法 在my ini文件中的
  • Linux系统看门狗应用编程

    目录 看门狗应用编程介绍 打开设备 获取设备支持哪些功能 WDIOC GETSUPPORT 获取 设置超时时间 WDIOC GETTIMEOUT WDIOC SETTIMEOUT 开启 关闭看门狗 WDIOC SETOPTIONS 喂狗 W
  • 使用conda安装了cudatoolkit11.7和cudnn8.6, paddle却报错:Cannot load cudnn shared library. Cannot invoke method

    问题来源 近日 使用paddle官方的conda安装命令安装最新版的paddle conda install paddlepaddle gpu 2 4 1 cudatoolkit 11 7 c https mirrors tuna tsin
  • javascript常用排序算法(图文详解)

    文章目录 冒泡排序 原理 时间复杂度 空间复杂度 稳定性 演示效果 代码实现 选择排序 原理 时间复杂度 空间复杂度 稳定性 演示效果 代码实现 插入排序 原理 时间复杂度 空间复杂度 稳定性 演示效果 代码实现 快速排序 原理 时间复杂度
  • 玩转GitHub!7个实用工具,打造完全不同的GitHub

    全文共2395字 预计学习时长9分钟 图源 freebuf GitHub平台是最受欢迎的版本控制存储库之一 拥有不计其数 多种编程语言编写的公共项目 你可以用它分配团队协作工作 也可以从无数软件项目中一些最常使用的开源库中学习 并见机发表见
  • mysql报错error2002_mysql中异常出错ERROR:2002的处理办法分享

    软件安装 装机软件必备包 SQL是Structured Query Language 结构化查询语言 的缩写 SQL是专为数据库而建立的操作命令集 是一种功能齐全的数据库语言 在使用它时 只需要发出 做什么 的命令 怎么做 是不用使用者考虑
  • 实用的集成学习模型调优策略SWA

    集成学习介绍 强力的集成学习算法主要有2种 基于Bagging的算法和基于Boosting的算法 基于Bagging的代表算法有随机森林 而基于Boosting的代表算法则有Adaboost GBDT XGBOOST 集成学习的思想同样适用
  • ES特定场景性能优化

    1 Overview 本文主要介绍一下Elasticsearch 后文简称ES 做相关基准测试的流程 及分享一些我们做过的一些测试结论 简要说明下我们使用情况 宽表的用户画像OLAP分析场景 集群规模200节点 数据量30T左右全热数据 2
  • java设置超链接字体大小_(四十八)Android TextView中文字通过SpannableString来设置超链接、颜色、字体等属性...

    1 程序结构图 2 MainActivity java中的代码 packagecom example setlinkdemo importjava io IOException importorg xmlpull v1 XmlPullPar
  • windows10解决“引用的账户当前已锁定”问题

    背景 多次输入密码之后 提示 引用的账户当前已锁定 可能无法登录 怀疑是多次输入错误的密码导致的 适用场景 多次输错密码之后 想起自己正确的密码 解决方案 按住 shift 的同时 点击关机图标 右下角 后选择 重启 进入 选择一个选项 界
  • wpf基于DevExpress实现折线图的两种方法

    以上为简单实现效果 具体需要什么样式需要自己再去好好调试 WPF实现折线图一般有三种方法 XAML文件 XAML C 代码 C 代码 今天主要结束前二者 方法一 XAML实现
  • python变量与常量

    变量与常量 一 什么是变量 变量就是指可以变化的量 量指的是事物的状态 比如人的年龄 性别 身高 体重 变量有三大组成部分 变量名 赋值符 变量值 变量名 指向赋值符右侧值的内存地址 通过内存地址去访问实际的值 赋值符 将变量值的内存地址绑
  • 二叉树——求两个节点的最近公共祖先

    题目 给定一颗二叉树的头结点 和这颗二叉树中2个节点n1和n2 求这两个节点的最近公共祖先 思路 利用后序遍历实现 对于当前节点cur 如果节点为null或者等于n1或n2中的一个 则直接返回cur 先处理左右子树 左子树返回left 右子
  • python编写一个函数判断一个数是否为偶数_26 python语言编写判断奇数偶数 动态输出菱形 eval函数编写一个控制台版的计算器...

    练习题 判断奇数偶数 1 编写Python程序 实现判断变量x是奇数还是偶数的功能 2 改写第1题 变量x需要从Python控制台输入 第1题 coding utf 8 x 5 a x 2 print a if a 0 print x的值为
  • python yuv转rgb

    注意 yuv 也有很多种格式 cv2 COLOR YUV2BGR NV12 对应的格式 所有格式C https docs opencv org 4 2 0 d8 d01 group imgproc color conversions htm
  • PHP实现苹果(IOS)内购(IAP)

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