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

PHP实现苹果(IOS)内购(IAP)相关推荐

  1. IOS 内购IAP 自动订阅收据验证文档服务端翻译

    将收据数据发送到App Store: 提交此JSON对象作为HTTP POST请求的有效负载. 中文文档:https://help.apple.com/app-store-connect/#/dev7 ...

  2. iOS 内购IAP(In-App Purchases)代码实现(上)

    iOS 内购IAP(In-App Purchases)代码实现(上) iOS 内购,也叫内支付,是在iOS应用内部,向苹果服务器发起购买请求的过程.我们在这边来讲一讲代码的实现过程.还有,在做内购的时 ...

  3. ios内购二次验证安全性问题_苹果IOS内购二次验证返回state为21002的坑

    项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...

  4. 苹果 iOS 内购三步曲:App 内退款、历史订单查询、绑定用户防掉单

    ????????关注后回复 "进群" ,拉你进程序员交流群???????? 转自:掘金 37手游iOS技术运营团队 https://juejin.cn/post/697473339 ...

  5. 苹果iOS内购三步曲:App内退款、历史订单查询、绑定用户防掉单!--- WWDC21

    一.前言 如果大家的 App 有使用 IAP 功能,那么可能会遇到用户反馈苹果充值成功,但是服务没有到账的情况,用户一般会提供这样的苹果收据: 用户反馈时提供的苹果收据中,有一个字段中 ORDER I ...

  6. iOS内购(IAP,In App Purchases-在APP内部支付),设置及使用

    项目中使用到了中间货币(金币)的形式来进行功能使用,模式是使用RMB换成-金币比如:(1RMB = 10金币),所以会集成第三方的支付平台,使用了微信和支付宝的第三方平台过后,发现审核失败,被苹果拒绝 ...

  7. 苹果IOS内购验单完整流程,in_app购买收据处理

    // 收据格式处理val data = JsonObject()data.addProperty("receipt-data", receipt.replace(" &q ...

  8. IOS内购IAP(IN-APP-PURCACHE)收据

    将receipt_data发送到App Store获得app内商品的ios收据详情 请求方式:向appstore发送HTTP POST请求 在测试环境中,URL为 https://sandbox.it ...

  9. iOS内购IAP(In App Purchases)入门

    成为ios开发者最大的好处就是,你编写的应用程序会有很多方式可以赚钱.比如,收费版,免费挂广告版,还有就是程序内置购买. 程序内置购买会让你爱不释手,主要有以下原因: 相比程序本身的下载收费以,你还可 ...

最新文章

  1. python学习干货教程(5):#!/usr/bin/python
  2. POJ 3342 Party at Hali-Bula ——(树型DP)
  3. Eigen教程1 - 基础
  4. 运维利器1-supervisor
  5. 轧机用弹性阻尼体反力计算_三个典型的阻尼模型及相关理论
  6. vim配置及插件安装笔记
  7. xajax中的中文乱码问题
  8. By Elevator or Stairs? CodeForces - 1249E(动态规划)
  9. 数据结构铁轨问题_每天5分钟用C#学习数据结构(20)图 Part 1
  10. Spring整合Quartz定时任务 在集群、分布式系统中的应用(Mysql数据库环境)
  11. 阿里联合学界开源大型3D场景数据集,6813个户型,几何与纹理细节丰富
  12. Atitit 界面接口技术 cli gui nui cui管理 attilax总结 1. NUI 1 1.1. 问:什么是“自然用户界面”? 1 2. Cli到gui到nui CUI 2 2.1.
  13. xampp错误: mysql 非正常关闭._mysql数据库DBA实用技巧--为你的数据库开启Innodb监控...
  14. “微音乐”微信小程序实战开发过程
  15. sun java认证真题,sun scjp考试心得_java认证
  16. 思科路由器如何导出配置文件_在思科路由器上保存超大的配置文件
  17. Editplus 使用技巧大全
  18. 一文获取36个Python开源项目,平均Star 1667,精选自5000个项目
  19. 苹果mp3软件_M4R如何转为MP3?音频转换的高效方法
  20. 学习PPT,这些制作设计技巧需先掌握

热门文章

  1. 贴吧楼中楼回复机器人_GitHub - fiepi/TieBaRobot: 百度贴吧机器人,可回复@和楼中楼对话...
  2. Antv X6画布缩放
  3. 我的2021年总结——学生到初入职场
  4. TZC 初中信息技术(python)题单————题解
  5. 进军日本,滴滴会成下一个“炮灰”吗?
  6. 小米盒子3-MiBOX3-MDZ16AA-安卓5.1.1-当贝纯净桌面-线刷固件包
  7. 定长图文验证码模型训练
  8. 父亲节python代码半个心_父亲节文案来了,太走心了!
  9. 无法显示隐藏文件和文件夹
  10. wordpress建立自己博客网站,建站前必看(开端篇)