iOS内购(IAP)自动续订订阅类型服务端总结

  • IOS 后台需注意
    • iOS 的 App 内购类型有四种:
    • App 专用共享密钥
    • 订阅状态 URL
  • 内购流程
    • 流程简述
  • 服务端验证
    • 自动续费
    • 调用函数方法

IOS 后台需注意

iOS 的 App 内购类型有四种:

  • 消耗型商品:只可使用一次的产品,使用之后即失效,必须再次购买。
    示例:钓鱼 App 中的鱼食。
  • 非消耗型商品:只需购买一次,不会过期或随着使用而减少的产品。
    示例:游戏 App 的赛道。
  • 自动续期订阅:允许用户在固定时间段内购买动态内容的产品。除非用户选择取消,否则此类订阅会自动续期。
    示例:每月订阅提供流媒体服务的 App。
  • 非续期订阅:允许用户购买有时限性服务的产品。此 App 内购买项目的内容可以是静态的。此类订阅不会自动续期。
    示例:为期一年的已归档文章目录订阅。

App 专用共享密钥

需要创建一个 “App 专用共享密钥”,它是用于接收此 App 自动续订订阅收据的唯一代码。这个秘钥用来想苹果服务器进行校验票据 receipt,不仅需要传 receipt,还需要传这个秘钥。
如果您需要将此 App 转让给其他开发人员,或者需要将主共享密钥设置为专用,可能需要使用 App 专用共享密钥。

订阅状态 URL

内购流程

流程简述

先来看一下iOS内购的通用流程

用户向苹果服务器发起购买请求,收到购买完成的回调(购买完成后会把钱打给申请内购的银行卡内)
购买成功流程结束后, 向服务器发起验证凭证(app端自己也可以不依靠服务器自行验证)
自己的服务器工作分 4 步:

  • 1、接收 iOS 端发过来的购买凭证。
  • 2、判断凭证是否已经存在或验证过,然后存储该凭证。
  • 3、将该凭证发送到苹果的服务器(区分沙盒环境还是正式环境)验证,并将验证结果返回给客户端。
    sandbox 开发环境:https://sandbox.itunes.apple.com/verifyReceipt
    prod 生产环境:https://buy.itunes.apple.com/verifyReceipt
  • 4、修改用户相应的会员权限或发放虚拟物品。

简单来说就是将该购买凭证用 Base64 编码,然后 POST 给苹果的验证服务器,苹果将验证结果以 JSON 形式返回。

服务端验证

ios客户端发送给服务端的数据

/*** 订单同步*/
public function verify_order(){$eventSystem = new \Freeios\Event\SystemEvent();$request_uri = addslashes($_SERVER['REQUEST_URI']);$resp_str = file_get_contents( "php://input");$eventSystem->add_error('苹果端回调-input',$request_uri,$resp_str);$resp_str = stripslashes($resp_str);$resp_data = json_decode($resp_str,true);//苹果内购的验证收据,可以根据需要传递订单或者用户信息过来$receipt_data = $resp_data['apple_receipt'];$uid = $this->uid;if (!$uid){return_json_data(-99,'请先登录');}$eventSystem->add_error('苹果端回调-apple_receipt',$request_uri,$receipt_data);// 验证支付状态$result=$this->validate_apple_pay($receipt_data);if(!$result['status']){ // 凭据验证不通过$eventSystem->add_error('苹果端回调-result',$request_uri,'凭据验证不通过');return_json_data(0,'Credential verification failed');}$notify = $result['data'];$transId = $notify['transaction_id'];   // 交易的标识$originalTransId = $notify['original_transaction_id'];  // 原始交易ID$transTime = $this->toTimeZone($notify['purchase_date']);  // 购买时间$transResult = $this->check_apple_trans($notify,$transId,$originalTransId,$transTime,$receipt_data);if($transResult['status']<=0){$eventSystem->add_error('苹果端回调-result',$request_uri,'交易号已经出现过了');return_json_data(0,'交易号已经出现过了');}// 处理订单数据$buyerInfo = $result['sandbox'];  // 1 沙盒数据 0 正式$productId = $notify['product_id']; // 订单类型$is_trial_period = $notify['is_trial_period'] == 'false' ? 0 : 1; //是否首次购买$purchaseDate = str_replace(' America/Los_Angeles','',$notify['purchase_date_pst']);$pay_detail = $this->pay_detail[$reward]; // 购买畅读卡$products = array_column($pay_detail,null,'expend_identifier');$products = $products[$productId];$total_fee = $products['pay']*100; // 分$type = 3; // 苹果内购支付if($buyerInfo == 1){$type = 6;//沙盒模式}// 写入订单(这个其实可以在IOS发起支付的时候请求服务端,先生成订单,并返回订单号)$orderId = 'ios_a'.$this->uid.date("mdHis").rand(2000,8000);if(!$orderId ){$eventSystem->add_error('苹果端回调-result',$request_uri,'订单处理出错');return_json_data(0,'写入订单失败');}// 处理订单 $rs = 1;if(!$rs){$eventSystem->add_error('苹果端回调-result',$request_uri,'更新数据错误失败');return_json_data(0,'更新数据错误失败');}$eventSystem->add_error('苹果端回调-result',$request_uri,'订单处理成功');return_json_data(1,'ok');}

自动续费


/*
* 自动续费订阅回调
* password 秘钥: 43f37f26****adc66a1be
* */
public function renew(){$resp_str = file_get_contents( "php://input");if(empty($resp_str)){$inputArr = I('','trim','');$resp_str = '';foreach($inputArr as $key=>$value){$resp_str.=$key."=".$value."&";}}$eventSystem = new \Freeios\Event\SystemEvent();$eventSystem->add_error('renew','AppleAutoPay',$resp_str);$data = json_decode($resp_str,true);if(!empty($resp_str)) {//有时候苹果那边会传空数据调用// notification_type 几种状态// NOTIFICATION_TYPE  描述// INITIAL_BUY  初次购买订阅。latest_receipt通过在App Store中验证,可以随时将您的服务器存储在服务器上以验证用户的订阅状态。// CANCEL  Apple客户支持取消了订阅。检查Cancellation Date以了解订阅取消的日期和时间。// RENEWAL  已过期订阅的自动续订成功。检查Subscription Expiration Date以确定下一个续订日期和时间。// INTERACTIVE_RENEWAL  客户通过使用应用程序界面或在App Store中的App Store中以交互方式续订订阅。服务立即可用。// DID_CHANGE_RENEWAL_PREF  客户更改了在下次续订时生效的计划。当前的有效计划不受影响。$notification_type = $data['notification_type'];//通知类型$password = $data['password']; // 共享秘钥if ($password == "43f37f26****c66a1be") {$receipt = isset($data['latest_receipt_info']) ? $data['latest_receipt_info'] : $data['latest_expired_receipt_info']; //latest_expired_receipt_info 好像只有更改续订状态才有$product_id = $receipt['product_id'];   // //商品的标识$original_transaction_id = $receipt['original_transaction_id'];  // //原始交易ID$transaction_id = $receipt['transaction_id'];  //  //交易的标识$purchaseDate = str_replace(' America/Los_Angeles','',$receipt['purchase_date_pst']);//查询出该apple ID最后充值过的用户$userid = 0; // 去数据库查询是否充值过if ($notification_type == 'CANCEL') { //取消订阅,做个记录if ($userid > 0) {$eventSystem->add_error('renew','AppleAutoPay','用户订阅取消记录成功');}} else {//自动续订,给用户加时间//排除几种状态不用处理,1,表示订阅续订状态的更改 2,表示客户对其订阅计划进行了更改 3,在最初购买订阅时发生//if ($notification_type != "DID_CHANGE_RENEWAL_PREF" && $notification_type != "DID_CHANGE_RENEWAL_STATUS" && $notification_type != "INITIAL_BUY") {if ($notification_type == "INTERACTIVE_RENEWAL" || $notification_type == "RENEWAL") {$transTime = $this->toTimeZone($receipt['purchase_date']);//查询数据库,该订单是否已经处理过了$appleTransCnt = 1; // 去数据库查看该订单是否处理过if ($appleTransCnt == 0) { //没有使用过,继续走$order_type = $this->products[$product_id];$order_money = $this->product_money[$order_type];$eventSystem->add_error('renew','AppleAutoPay','续订成功');} else {$eventSystem->add_error('renew','AppleAutoPay','此次支付订单已处理过');}} else {$eventSystem->add_error('renew','AppleAutoPay','该类型通知不予处理--notification_type:' . $notification_type);}}} else {$eventSystem->add_error('renew','AppleAutoPay','该通知传递的密码不正确--password:' . $password);}}
}

调用函数方法


/*** 验证这个交易号是否存在过了* @param $notify* @param $transId* @param $totalAmount* @param $tradeId* @param $receipt_data*/
public function check_apple_trans($notify,$transId,$originalTransId,$transTime,$receipt_data){$eventOrder = new \Freeios\Event\OrderEvent();$where = ['trade_no'=>$transId, ];$appleTransCnt = $eventOrder->get_order_count($where);if($appleTransCnt>0){return ['status'=>-1,'appleTransCnt'=>$appleTransCnt];}else{$eventOrder->add_order_log_apple(['trans_id'=>$transId,'original_trans_id'=>$originalTransId,'content'=>json_encode(['appleTransCnt'=>$appleTransCnt,'notify'=>$notify,'receipt_data'=>$receipt_data]),]);return ['status'=>1];}
}private function toTimeZone($src, $from_tz = 'Etc/GMT', $to_tz = 'Asia/Shanghai', $fm = 'Y-m-d H:i:s') {$datetime = new \DateTime($src, new \DateTimeZone($from_tz));$datetime->setTimezone(new \DateTimeZone($to_tz));return $datetime->format($fm);
}/*** 根据语言获取当前地区时间* 以本地服务器时间为准* 比美国纽约快12小时* 比泰国,印尼快1小时* 比葡萄牙里本斯快7小时* @param $language*/
private function format_time_zone($language,$is_format=true){if($language == 1){$f_time = strtotime('-12 hours');}else if($language == 2 || $language == 3){$f_time = strtotime('-1 hours');}else{//葡萄牙语$f_time = strtotime('-7 hours');}if($is_format){$f_time = date('Y-m-d H:i:s',$f_time);}return $f_time;
}private function format_to_time_zone($time_zone){date_default_timezone_set($time_zone);//设置时区$f_time = date('Y-m-d H:i:s');date_default_timezone_set('Asia/Shanghai');//设置回默认的return $f_time;
}/*** 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环境的验证服务*/
private function acurl($receipt_data, $sandbox=0){//小票信息$POSTFIELDS = array("receipt-data" => $receipt_data,"password"=>"43f37f26****c66a1be");$POSTFIELDS = json_encode($POSTFIELDS);//正式购买地址 沙盒购买地址$url_buy     = "https://buy.itunes.apple.com/verifyReceipt";$url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";$url = $sandbox ? $url_sandbox : $url_buy;//简单的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_VERIFYPEER, 0);  //这两行一定要加,不加会报SSL 错误curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);$result = curl_exec($ch);curl_close($ch);return $result;
}/*** 验证AppStore内付* @param  string $receipt_data 付款后凭证* @return array                验证是否成功*/
private function validate_apple_pay($receipt_data){// 验证参数if (strlen($receipt_data)<20){$result=array('status'=>false,'message'=>' Illegal param');return $result;}// 请求验证$html = $this->acurl($receipt_data);$data = json_decode($html,true);$data['sandbox'] = '0';// 如果是沙盒数据 则验证沙盒模式if($data['status']=='21007'){$html = $this->acurl($receipt_data, 1);$data = json_decode($html,true);$data['sandbox'] = '1';}$eventSystem = new \Freeios\Event\SystemEvent();$eventSystem->add_error('苹果验证','validate_apple_pay',json_encode($data));// 判断是否购买成功if(intval($data['status'])===0){ // 成功$receipts = $data['latest_receipt_info']; // 自动续订的订阅项 时才会有if(!isset($data['latest_receipt_info'])){$receipts = $data['receipt']['in_app']; // 消费类型}if(count($receipts)>0){$maxDate = '0';  //最新的日期,时间戳$appData = null; //最新的那组数组foreach($receipts as $k=>$app){if($maxDate<$app['purchase_date_ms']){$appData = $app;$maxDate = $app['purchase_date_ms'];}}$result=array('status'=>true,'message'=>'Purchase success','data'=>$appData,'sandbox'=>$data['sandbox'],);}else{$result=array('status'=>false,'message'=>'No data status:'.$data['status']);}}else{ // 失败$result=array('status'=>false,'message'=>'Failed purchase status:'.$data['status']);}return $result;
}

iOS内购(IAP)自动续订订阅类型服务端总结相关推荐

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

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

  2. iOS内购:自动续期订阅总结

    前言:内购类型有四种:消耗型商品,非消耗型商品,非续期订阅,自动续期订阅. 顾名思义,从中最有难度的就是自动续期订阅的实现,开通自动续期订阅后,订阅会员的处理将会遇到如下问题:自动订阅的到期继续自动订 ...

  3. php++内购续期订阅,iOS内购:自动续期订阅总结

    前言:内购类型有四种:消耗型商品,非消耗型商品,非续期订阅,自动续期订阅. 顾名思义,从中最有难度的就是自动续期订阅的实现,开通自动续期订阅后,订阅会员的处理将会遇到如下问题:自动订阅的到期继续自动订 ...

  4. IOS 内购IAP 自动订阅收据验证返回结构体

    exclude-old-transactions 参数等于true时. 数据通过json_decode()转换后 1.首次订阅支付通过票据进行验单的返回结构体: Array ([status] =&g ...

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

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

  6. iOS内购测试自动订阅型产品

    最近产品需要涉及到订阅型的内购创建,上一次使用自动订阅内购还是好几年的事情了,很多内容大都忘得差不多了,加上苹果设计的内购又增加了一些新功能和改变,不得不重新来审视下自动订阅型内购,而官方又没有中文文 ...

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

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

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

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

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

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

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

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

最新文章

  1. react-webpack-express
  2. C#发现之旅第一讲 C#-XML开发
  3. 2018.10.17考试
  4. HighNewTech:支付宝全球首发5G手机,四大逆天功能(防骗三折叠气味识别自由飞)!
  5. snipaste滚动截图方法_百度终于良心一回,出了一款ios长截图神器!
  6. 如何实时主动监控你的网站接口是否挂掉并及时报警
  7. Android实战:手把手实现“捧腹网”APP(三)-----UI实现,逻辑实现
  8. 【转】2.2 SharePoint服务器端对象模型 之 访问网站和列表数据(Part 2)
  9. 不加载执行js_前端性能优化:preload 预加载页面资源
  10. mysql 删除表中 id不等于XXX的 并且XXX字段的重复记录
  11. synchronize原理以及和CAS的浅层比较
  12. Ueditor编辑器任意文件上传漏洞
  13. ArcGIS Server 发布 REST 服务
  14. 怕公司的新同事超过你怎么办
  15. 不带电脑看-吃货联盟集合
  16. Java使用Graphics2D添加文字水印碰见的坑(给透明底图片加半透明水印)
  17. 新媒体运营都包括哪些方面
  18. html中size是设置什么意思,CSS的font-size-adjust属性有什么用?
  19. 音视频播放器关键技术探讨
  20. 文件(图片,音频)上传OSS并返回上传地址

热门文章

  1. 用java开发编译器之Thompson构造:正则表达式的词法解析
  2. nofollow是什么意思,nofollow标签的作用是什么?
  3. 几何算法合集(3D)
  4. 数学建模——相关系数
  5. case when 多条件查询
  6. unity项目之太空大战(2)
  7. HCIA H12-211题库解析
  8. 2017年总结,2018新开始
  9. 除了压缩包加密,还有这些更好的文件加密方案
  10. mysql .idb_mysql.idb