Payios - 副本 - 副本.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. namespace app\api\controller;
  3. use app\common\controller\Api;
  4. use think\Db;
  5. use addons\epay\library\Service;
  6. /**
  7. * 充值配置与充值订单
  8. */
  9. class Payios extends Api
  10. {
  11. /**
  12. * 订单同步
  13. */
  14. public function verify_order(){
  15. $eventSystem = new \Freeios\Event\SystemEvent();
  16. $request_uri = addslashes($_SERVER['REQUEST_URI']);
  17. $resp_str = file_get_contents( "php://input");
  18. $eventSystem->add_error('苹果端回调-input',$request_uri,$resp_str);
  19. $resp_str = stripslashes($resp_str);
  20. $resp_data = json_decode($resp_str,true);
  21. //苹果内购的验证收据,可以根据需要传递订单或者用户信息过来
  22. $receipt_data = $resp_data['apple_receipt'];
  23. $uid = $this->uid;
  24. if (!$uid){
  25. return_json_data(-99,'请先登录');
  26. }
  27. $eventSystem->add_error('苹果端回调-apple_receipt',$request_uri,$receipt_data);
  28. // 验证支付状态
  29. $result=$this->validate_apple_pay($receipt_data);
  30. if(!$result['status']){ // 凭据验证不通过
  31. $eventSystem->add_error('苹果端回调-result',$request_uri,'凭据验证不通过');
  32. return_json_data(0,'Credential verification failed');
  33. }
  34. $notify = $result['data'];
  35. $transId = $notify['transaction_id']; // 交易的标识
  36. $originalTransId = $notify['original_transaction_id']; // 原始交易ID
  37. $transTime = $this->toTimeZone($notify['purchase_date']); // 购买时间
  38. $transResult = $this->check_apple_trans($notify,$transId,$originalTransId,$transTime,$receipt_data);
  39. if($transResult['status']<=0){
  40. $eventSystem->add_error('苹果端回调-result',$request_uri,'交易号已经出现过了');
  41. return_json_data(0,'交易号已经出现过了');
  42. }
  43. // 处理订单数据
  44. $buyerInfo = $result['sandbox']; // 1 沙盒数据 0 正式
  45. $productId = $notify['product_id']; // 订单类型
  46. $is_trial_period = $notify['is_trial_period'] == 'false' ? 0 : 1; //是否首次购买
  47. $purchaseDate = str_replace(' America/Los_Angeles','',$notify['purchase_date_pst']);
  48. $pay_detail = $this->pay_detail[$reward]; // 购买畅读卡
  49. $products = array_column($pay_detail,null,'expend_identifier');
  50. $products = $products[$productId];
  51. $total_fee = $products['pay']*100; // 分
  52. $type = 3; // 苹果内购支付
  53. if($buyerInfo == 1){
  54. $type = 6;//沙盒模式
  55. }
  56. // 写入订单(这个其实可以在IOS发起支付的时候请求服务端,先生成订单,并返回订单号)
  57. $orderId = 'ios_a'.$this->uid.date("mdHis").rand(2000,8000);
  58. if(!$orderId ){
  59. $eventSystem->add_error('苹果端回调-result',$request_uri,'订单处理出错');
  60. return_json_data(0,'写入订单失败');
  61. }
  62. // 处理订单
  63. $rs = 1;
  64. if(!$rs){
  65. $eventSystem->add_error('苹果端回调-result',$request_uri,'更新数据错误失败');
  66. return_json_data(0,'更新数据错误失败');
  67. }
  68. $eventSystem->add_error('苹果端回调-result',$request_uri,'订单处理成功');
  69. return_json_data(1,'ok');
  70. }
  71. /*
  72. * 自动续费订阅回调
  73. * password 秘钥: 43f37f26****adc66a1be
  74. * */
  75. public function renew(){
  76. $resp_str = file_get_contents( "php://input");
  77. if(empty($resp_str)){
  78. $inputArr = I('','trim','');
  79. $resp_str = '';
  80. foreach($inputArr as $key=>$value){
  81. $resp_str.=$key."=".$value."&";
  82. }
  83. }
  84. $eventSystem = new \Freeios\Event\SystemEvent();
  85. $eventSystem->add_error('renew','AppleAutoPay',$resp_str);
  86. $data = json_decode($resp_str,true);
  87. if(!empty($resp_str)) {//有时候苹果那边会传空数据调用
  88. // notification_type 几种状态
  89. // NOTIFICATION_TYPE 描述
  90. // INITIAL_BUY 初次购买订阅。latest_receipt通过在App Store中验证,可以随时将您的服务器存储在服务器上以验证用户的订阅状态。
  91. // CANCEL Apple客户支持取消了订阅。检查Cancellation Date以了解订阅取消的日期和时间。
  92. // RENEWAL 已过期订阅的自动续订成功。检查Subscription Expiration Date以确定下一个续订日期和时间。
  93. // INTERACTIVE_RENEWAL 客户通过使用应用程序界面或在App Store中的App Store中以交互方式续订订阅。服务立即可用。
  94. // DID_CHANGE_RENEWAL_PREF 客户更改了在下次续订时生效的计划。当前的有效计划不受影响。
  95. $notification_type = $data['notification_type'];//通知类型
  96. $password = $data['password']; // 共享秘钥
  97. if ($password == "43f37f26****c66a1be") {
  98. $receipt = isset($data['latest_receipt_info']) ? $data['latest_receipt_info'] : $data['latest_expired_receipt_info']; //latest_expired_receipt_info 好像只有更改续订状态才有
  99. $product_id = $receipt['product_id']; // //商品的标识
  100. $original_transaction_id = $receipt['original_transaction_id']; // //原始交易ID
  101. $transaction_id = $receipt['transaction_id']; // //交易的标识
  102. $purchaseDate = str_replace(' America/Los_Angeles','',$receipt['purchase_date_pst']);
  103. //查询出该apple ID最后充值过的用户
  104. $userid = 0; // 去数据库查询是否充值过
  105. if ($notification_type == 'CANCEL') { //取消订阅,做个记录
  106. if ($userid > 0) {
  107. $eventSystem->add_error('renew','AppleAutoPay','用户订阅取消记录成功');
  108. }
  109. } else {
  110. //自动续订,给用户加时间
  111. //排除几种状态不用处理,1,表示订阅续订状态的更改 2,表示客户对其订阅计划进行了更改 3,在最初购买订阅时发生
  112. //if ($notification_type != "DID_CHANGE_RENEWAL_PREF" && $notification_type != "DID_CHANGE_RENEWAL_STATUS" && $notification_type != "INITIAL_BUY") {
  113. if ($notification_type == "INTERACTIVE_RENEWAL" || $notification_type == "RENEWAL") {
  114. $transTime = $this->toTimeZone($receipt['purchase_date']);
  115. //查询数据库,该订单是否已经处理过了
  116. $appleTransCnt = 1; // 去数据库查看该订单是否处理过
  117. if ($appleTransCnt == 0) { //没有使用过,继续走
  118. $order_type = $this->products[$product_id];
  119. $order_money = $this->product_money[$order_type];
  120. $eventSystem->add_error('renew','AppleAutoPay','续订成功');
  121. } else {
  122. $eventSystem->add_error('renew','AppleAutoPay','此次支付订单已处理过');
  123. }
  124. } else {
  125. $eventSystem->add_error('renew','AppleAutoPay','该类型通知不予处理--notification_type:' . $notification_type);
  126. }
  127. }
  128. } else {
  129. $eventSystem->add_error('renew','AppleAutoPay','该通知传递的密码不正确--password:' . $password);
  130. }
  131. }
  132. }
  133. /**
  134. * 验证这个交易号是否存在过了
  135. * @param $notify
  136. * @param $transId
  137. * @param $totalAmount
  138. * @param $tradeId
  139. * @param $receipt_data
  140. */
  141. public function check_apple_trans($notify,$transId,$originalTransId,$transTime,$receipt_data){
  142. $eventOrder = new \Freeios\Event\OrderEvent();
  143. $where = ['trade_no'=>$transId, ];
  144. $appleTransCnt = $eventOrder->get_order_count($where);
  145. if($appleTransCnt>0){
  146. return ['status'=>-1,'appleTransCnt'=>$appleTransCnt];
  147. }else{
  148. $eventOrder->add_order_log_apple([
  149. 'trans_id'=>$transId,
  150. 'original_trans_id'=>$originalTransId,
  151. 'content'=>json_encode(['appleTransCnt'=>$appleTransCnt,'notify'=>$notify,'receipt_data'=>$receipt_data]),
  152. ]);
  153. return ['status'=>1];
  154. }
  155. }
  156. private function toTimeZone($src, $from_tz = 'Etc/GMT', $to_tz = 'Asia/Shanghai', $fm = 'Y-m-d H:i:s') {
  157. $datetime = new \DateTime($src, new \DateTimeZone($from_tz));
  158. $datetime->setTimezone(new \DateTimeZone($to_tz));
  159. return $datetime->format($fm);
  160. }
  161. /**
  162. * 根据语言获取当前地区时间
  163. * 以本地服务器时间为准
  164. * 比美国纽约快12小时
  165. * 比泰国,印尼快1小时
  166. * 比葡萄牙里本斯快7小时
  167. * @param $language
  168. */
  169. private function format_time_zone($language,$is_format=true){
  170. if($language == 1){
  171. $f_time = strtotime('-12 hours');
  172. }else if($language == 2 || $language == 3){
  173. $f_time = strtotime('-1 hours');
  174. }else{//葡萄牙语
  175. $f_time = strtotime('-7 hours');
  176. }
  177. if($is_format){
  178. $f_time = date('Y-m-d H:i:s',$f_time);
  179. }
  180. return $f_time;
  181. }
  182. private function format_to_time_zone($time_zone){
  183. date_default_timezone_set($time_zone);//设置时区
  184. $f_time = date('Y-m-d H:i:s');
  185. date_default_timezone_set('Asia/Shanghai');//设置回默认的
  186. return $f_time;
  187. }
  188. /**
  189. * 21000 App Store不能读取你提供的JSON对象
  190. * 21002 receipt-data域的数据有问题
  191. * 21003 receipt无法通过验证
  192. * 21004 提供的shared secret不匹配你账号中的shared secret
  193. * 21005 receipt服务器当前不可用
  194. * 21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送
  195. * 21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务
  196. * 21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务
  197. */
  198. private function acurl($receipt_data, $sandbox=0){
  199. //小票信息
  200. $POSTFIELDS = array("receipt-data" => $receipt_data,"password"=>"43f37f26****c66a1be");
  201. $POSTFIELDS = json_encode($POSTFIELDS);
  202. //正式购买地址 沙盒购买地址
  203. $url_buy = "https://buy.itunes.apple.com/verifyReceipt";
  204. $url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt";
  205. $url = $sandbox ? $url_sandbox : $url_buy;
  206. //简单的curl
  207. $ch = curl_init($url);
  208. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  209. curl_setopt($ch, CURLOPT_POST, 1);
  210. curl_setopt($ch, CURLOPT_POSTFIELDS, $POSTFIELDS);
  211. curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); //这两行一定要加,不加会报SSL 错误
  212. curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
  213. $result = curl_exec($ch);
  214. curl_close($ch);
  215. return $result;
  216. }
  217. /**
  218. * 验证AppStore内付
  219. * @param string $receipt_data 付款后凭证
  220. * @return array 验证是否成功
  221. */
  222. private function validate_apple_pay($receipt_data){
  223. // 验证参数
  224. if (strlen($receipt_data)<20){
  225. $result=array(
  226. 'status'=>false,
  227. 'message'=>' Illegal param'
  228. );
  229. return $result;
  230. }
  231. // 请求验证
  232. $html = $this->acurl($receipt_data);
  233. $data = json_decode($html,true);
  234. $data['sandbox'] = '0';
  235. // 如果是沙盒数据 则验证沙盒模式
  236. if($data['status']=='21007'){
  237. $html = $this->acurl($receipt_data, 1);
  238. $data = json_decode($html,true);
  239. $data['sandbox'] = '1';
  240. }
  241. $eventSystem = new \Freeios\Event\SystemEvent();
  242. $eventSystem->add_error('苹果验证','validate_apple_pay',json_encode($data));
  243. // 判断是否购买成功
  244. if(intval($data['status'])===0){ // 成功
  245. $receipts = $data['latest_receipt_info']; // 自动续订的订阅项 时才会有
  246. if(!isset($data['latest_receipt_info'])){
  247. $receipts = $data['receipt']['in_app']; // 消费类型
  248. }
  249. if(count($receipts)>0){
  250. $maxDate = '0'; //最新的日期,时间戳
  251. $appData = null; //最新的那组数组
  252. foreach($receipts as $k=>$app){
  253. if($maxDate<$app['purchase_date_ms']){
  254. $appData = $app;
  255. $maxDate = $app['purchase_date_ms'];
  256. }
  257. }
  258. $result=array(
  259. 'status'=>true,
  260. 'message'=>'Purchase success',
  261. 'data'=>$appData,
  262. 'sandbox'=>$data['sandbox'],
  263. );
  264. }else{
  265. $result=array(
  266. 'status'=>false,
  267. 'message'=>'No data status:'.$data['status']
  268. );
  269. }
  270. }else{ // 失败
  271. $result=array(
  272. 'status'=>false,
  273. 'message'=>'Failed purchase status:'.$data['status']
  274. );
  275. }
  276. return $result;
  277. }
  278. }