ApplePay.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. <?php
  2. /**
  3. * ApplePay.php UTF-8
  4. * 苹果支付
  5. * https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW2
  6. *
  7. * @date : 2020/9/14 16:57
  8. *
  9. * @license 这不是一个自由软件,未经授权不许任何使用和传播。
  10. * @author : chenbingling <cbl@huosdk.com>
  11. * @version : H5IOS 1.0
  12. */
  13. namespace huosdk\h5ios\core\controller;
  14. use huo\controller\common\Base;
  15. use huo\controller\pay\Notify;
  16. use huo\controller\queue\ApplePayQueue;
  17. use huo\controller\request\Request;
  18. use huosdk\h5ios\core\constant\OrderConst;
  19. use huosdk\h5ios\core\constant\PaywayConst;
  20. use huosdk\h5ios\core\model\GamePriceModel;
  21. use huosdk\h5ios\core\model\OrderModel;
  22. use huosdk\h5ios\core\model\PayAppleErrorModel;
  23. use huosdk\h5ios\core\model\PayAppleModel;
  24. use huosdk\h5ios\core\status\OrderStatus;
  25. use think\Log;
  26. use think\Session;
  27. class ApplePay extends Base {
  28. const APPLE_STATUS_SUCCESS = 0; /* 0 表示验证成功 */
  29. const APPLE_STATUS_21000 = 21000; /* 21000 App Store无法读取你提供的JSON数据 */
  30. const APPLE_STATUS_21002 = 21002; /* 21002 收据数据不符合格式 */
  31. const APPLE_STATUS_21003 = 21003; /* 21003 收据无法被验证 */
  32. const APPLE_STATUS_21004 = 21004; /* 21004 你提供的共享密钥和账户的共享密钥不一致 */
  33. const APPLE_STATUS_21005 = 21005; /* 21005 收据服务器当前不可用 */
  34. const APPLE_STATUS_21006 = 21006; /* 21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中 */
  35. const APPLE_STATUS_21007 = 21007; /* 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证 */
  36. const APPLE_STATUS_21008 = 21008; /* 21008 收据信息是产品环境中使用,但却被发送到测试环境中验证 */
  37. private $url_buy = '';
  38. private $sb_url_buy = '';
  39. protected $is_sandbox = false;
  40. private $order_id = '';
  41. /**
  42. * 构造函数
  43. *
  44. * @param bool $is_sandbox 是否是沙盒环境
  45. */
  46. public function __construct($is_sandbox = false) {
  47. $this->url_buy = "https://buy.itunes.apple.com/verifyReceipt";
  48. $this->sb_url_buy = "https://sandbox.itunes.apple.com/verifyReceipt";
  49. $this->is_sandbox = $is_sandbox;
  50. }
  51. /**
  52. * 校验苹果订单
  53. *
  54. * @param string $order_id 平台订单ID
  55. * @param string $trans_id 苹果订单ID
  56. * @param string $receipt_data 苹果支付票据
  57. * @param bool $is_sandbox 是否是沙盒环境
  58. * @param string $idfv 苹果IDFV
  59. * @param string $apple_id 苹果应用ID
  60. *
  61. * @return bool
  62. */
  63. public function checkAppleOrder($order_id, $trans_id, $receipt_data = null, $is_sandbox = false, $idfv, $apple_id) {
  64. $_log_message = "func=".__FUNCTION__."&class=".__CLASS__.'&order_id='.$order_id.'&trans_id='.$trans_id
  65. .'&receipt_data='.$receipt_data.'&idfv='.$idfv.'&apple_id='.$apple_id.'&step';
  66. if (empty($trans_id) || empty($receipt_data) || empty($apple_id) || empty($idfv)) {
  67. /* 不处理 Log 记录*/
  68. Log::write($_log_message.'1', LOG::ERROR);
  69. return false;
  70. }
  71. $_apple_order_model = new PayAppleModel();
  72. $_apple_data = $_apple_order_model->getInfoByTransId($trans_id);
  73. if (!empty($order_id)) {
  74. /* 查询是否与trans_id对应 */
  75. if (empty($_apple_data)) {
  76. /* trans_id 与 order_id 未对应*/
  77. /* 获取并更新苹果订单数据 */
  78. $_apple_data = $this->getUpdateDataByOrderId($order_id, $trans_id, $receipt_data);
  79. if (false == $_apple_data) {
  80. /* 订单Id错误 */
  81. $this->code = OrderStatus::ORDER_ID_ERROR;
  82. /* Log 记录 */
  83. Log::write($_log_message.'2', LOG::ERROR);
  84. return false;
  85. }
  86. } elseif (!empty($_apple_data)) {
  87. /* trans_id 与 order_id 已对应 */
  88. if ($_apple_data['order_id'] != $order_id) {
  89. /* 订单Id错误 */
  90. $this->code = OrderStatus::ORDER_ID_ERROR;
  91. /* Log 记录 */
  92. Log::write($_log_message.'3', LOG::ERROR);
  93. return false;
  94. }
  95. }
  96. }
  97. if (!empty($_apple_data)) {
  98. /* 99%概率 order_id不丢 处理使用此流程 */
  99. $_order_id = $_apple_data['order_id'];
  100. $_order_data = (new OrderModel())->getInfoByOrderId($_order_id);
  101. if (OrderConst::PAY_STATUS_SUC == $_order_data['status']) {
  102. /* 苹果支付已校验成功 */
  103. if (OrderConst::CP_STATUS_SUC == $_order_data['cp_status']) {
  104. /* 已通知CP服务器 */
  105. $this->code = OrderStatus::NO_ERROR;
  106. } else {
  107. /* 未通知CP服务器 */
  108. $_rs = (new Notify())->notify($_order_id);
  109. $this->code = $_rs['code'];
  110. }
  111. return true;
  112. }
  113. }
  114. /* 苹果进行二次验证,防止收到的是伪造的数据 */
  115. $_rs = $this->checkReceiptData($receipt_data, $is_sandbox);
  116. if (false == $_rs) {
  117. /* 无法正确获取苹果服务器数据 */
  118. $this->code = OrderStatus::APPLE_CHECK_FAIL;
  119. /* Log 记录 */
  120. Log::write($_log_message.'4', LOG::ERROR);
  121. return false;
  122. }
  123. if (self::APPLE_STATUS_SUCCESS != $_rs['status']) {
  124. $this->code = OrderStatus::RECEIPT_DATA_ERROR;
  125. /* Log 记录 */
  126. Log::write($_log_message.'5', LOG::ERROR);
  127. return false;
  128. }
  129. if (!isset($_rs['receipt'])
  130. || !isset($_rs['receipt']['in_app'])
  131. || !isset($_rs['receipt']['in_app'][0])) {
  132. /* 无法正确获取苹果服务器数据 */
  133. $this->code = OrderStatus::RECEIPT_DATA_ERROR;
  134. /* Log 记录 */
  135. Log::write($_log_message.'6', LOG::ERROR);
  136. return false;
  137. }
  138. /* 获取In-App Purchase Receipt Fields */
  139. $_receipt_data = $_rs['receipt']['in_app'];
  140. $_iap_data = [];
  141. if (!empty($_receipt_data)) {
  142. foreach ($_receipt_data as $_k => $_v) {
  143. if ($_v['transaction_id'] == $trans_id) {
  144. $_iap_data = $_v;
  145. }
  146. }
  147. }
  148. if (empty($_iap_data)) {
  149. /* 无法正确获取苹果服务器数据 */
  150. $this->code = OrderStatus::RECEIPT_DATA_ERROR;
  151. /* Log 记录 */
  152. Log::write($_receipt_data, LOG::ERROR);
  153. Log::write($_log_message.'66', LOG::ERROR);
  154. return false;
  155. }
  156. $_iap_product_id = $_iap_data['product_id'];
  157. //$_iap_request_time = round($_iap_data['request_date_ms'] / 1000);
  158. $_iap_request_time = round($_rs['receipt']['request_date_ms'] / 1000);
  159. $_iap_purchase_time = round($_iap_data['purchase_date_ms'] / 1000);
  160. $_iap_transaction_id = $_iap_data['transaction_id'];
  161. if ($_iap_transaction_id != $trans_id) {
  162. $this->code = OrderStatus::TRANS_ID_ERROR;
  163. /* Log 记录 */
  164. Log::write($_log_message.'8', LOG::ERROR);
  165. return false;
  166. }
  167. $_apple_data['payway'] = PaywayConst::PAYWAY_APPLE;
  168. if (true == $is_sandbox) {
  169. $_apple_data['payway'] = PaywayConst::PAYWAY_APPLE_TEST;
  170. }
  171. if (empty($_apple_data)) {
  172. /* 查找最近订单 */
  173. $_apple_data = (new PayAppleModel())->getLastOrderData(
  174. $_iap_request_time, $idfv, $apple_id, $_iap_product_id
  175. );
  176. if (empty($_apple_data)) {
  177. /* 异常订单记录 报警 */
  178. // TODO: wuyonghong 2018/6/11 收入异常记录
  179. /* Log 记录 */
  180. Log::write($_log_message.'9', LOG::ERROR);
  181. $_pae_data['trans_id'] = $_iap_transaction_id;
  182. $_pae_data['idfv'] = $idfv;
  183. $_pae_data['idfa'] = '';
  184. $_pae_data['apple_id'] = $apple_id;
  185. $_pae_data['product_id'] = $_iap_product_id;
  186. $_pae_data['status'] = OrderConst::PAY_STATUS_SUC;
  187. $_pae_data['payway'] = $_apple_data['payway'];
  188. $_pae_data['pay_time'] = $_iap_purchase_time;
  189. $_pae_data['check_cnt'] = 1;
  190. $_pae_data['last_check_time'] = time();
  191. $_pae_data['receipt_data'] = $receipt_data;
  192. $_pae_data['receipt_field'] = json_encode($_iap_data);
  193. (new PayAppleErrorModel())->addData($_pae_data);
  194. $this->code = OrderStatus::ORDER_NOT_EXISTS;
  195. /* 异常订单不再程序处理 */
  196. return true;
  197. }
  198. } else {
  199. /* 核对苹果支付信息 */
  200. if ($_iap_product_id != $_apple_data['product_id']) {
  201. $this->code = OrderStatus::PRODUCT_ID_ERROR;
  202. /* Log 记录 */
  203. Log::write($_log_message.'7', LOG::ERROR);
  204. return false;
  205. }
  206. }
  207. /* 验证订单合法性 是否与产品对应上 */
  208. $_amount = $this->getProductPrice($_iap_product_id, $order_id);
  209. if (false == $_amount || 0.01 > $_amount) {
  210. $this->code = OrderStatus::ORDER_PRODUCT_ID_ERROR;
  211. /* Log 记录 */
  212. Log::write($_log_message.'&product_id='.$_iap_product_id.'&err_msg=计费点校验出错8', LOG::ERROR);
  213. return false;
  214. }
  215. $this->order_id = $_apple_data['order_id'];
  216. $_apple_data['status'] = OrderConst::PAY_STATUS_SUC;
  217. $_apple_data['check_cnt']++;
  218. $_apple_data['last_check_time'] = time();
  219. $_apple_data['pay_time'] = $_iap_purchase_time;
  220. $_id = $_apple_order_model->getIdByTransId($trans_id);
  221. if (empty($_id)) {
  222. $this->code = OrderStatus::PRODUCT_ID_ERROR;
  223. /* Log 记录 */
  224. Log::write($_log_message.'&trans_id='.$trans_id.'&err_msg=trans_id校验出错9', LOG::ERROR);
  225. return false;
  226. }
  227. $_apple_order_model->updateData($_apple_data, $_id);
  228. $_order_data = (new OrderModel())->getInfoByOrderId($this->order_id);
  229. (new Notify())->payNotify(
  230. $_iap_product_id, $order_id, $trans_id, $_order_data['real_amount'], $_apple_data['payway']
  231. );
  232. $this->code = OrderStatus::NO_ERROR;
  233. return true;
  234. }
  235. /**
  236. * 订单校验
  237. *
  238. * @param string $order_id 平台订单ID
  239. * @param string $trans_id 苹果订单ID
  240. * @param string $receipt_data 苹果支付票据
  241. * @param bool $is_sandbox 是否是沙盒环境
  242. * @param string $idfv 苹果IDFV
  243. * @param string $apple_id 苹果应用ID
  244. *
  245. * @return array
  246. */
  247. public function orderQuery(
  248. $order_id, $trans_id, $receipt_data = null, $is_sandbox = false, $idfv = '', $apple_id = ''
  249. ) {
  250. $_rdata = [
  251. 'order_id' => $order_id,
  252. 'trans_id' => $trans_id,
  253. 'status' => OrderConst::PAY_STATUS_NOT,
  254. 'cp_status' => OrderConst::CP_STATUS_NOT,
  255. ];
  256. $this->order_id = $order_id;
  257. if (empty($trans_id) || empty($receipt_data)) {
  258. $_code = OrderStatus::INVALID_PARAMS;
  259. return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata);
  260. }
  261. $this->checkAppleOrder($order_id, $trans_id, $receipt_data, $is_sandbox, $idfv, $apple_id);
  262. $_code = $this->code;
  263. if (OrderStatus::APPLE_CHECK_FAIL == $_code) {
  264. /* 苹果验单失败 加入队列 */
  265. (new ApplePayQueue($apple_id))->setApplePayQueue(
  266. $order_id, $trans_id, $receipt_data, $is_sandbox, $idfv, $apple_id
  267. );
  268. return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata);
  269. }
  270. if (OrderStatus::NO_ERROR != $_code) {
  271. return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata);
  272. }
  273. /* 从订单中获取状态 */
  274. $_statues = (new OrderModel())->getStatus($this->order_id);
  275. if (false == $_statues) {
  276. $_code = OrderStatus::ORDER_ID_ERROR;
  277. return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata);
  278. }
  279. $_rdata = [
  280. 'order_id' => $this->order_id,
  281. 'trans_id' => $trans_id,
  282. 'status' => $_statues['status'],
  283. 'cp_status' => $_statues['cp_status']
  284. ];
  285. return $this->huoSuccess($_code, OrderStatus::getMsg($_code), $_rdata);
  286. }
  287. /**
  288. * 通过订单ID获取数据
  289. *
  290. * @param string $order_id
  291. * @param string $trans_id
  292. * @param string $receipt_data
  293. *
  294. * @return array|bool|false
  295. */
  296. private function getUpdateDataByOrderId($order_id, $trans_id, $receipt_data) {
  297. $_pa_model = new PayAppleModel();
  298. $_apple_data = $_pa_model->getInfoByOrderId($order_id);
  299. if (!empty($_apple_data['trans_id'])) {
  300. if ($_apple_data['trans_id'] == $trans_id) {
  301. return $_apple_data;
  302. }
  303. /* 已对应其他订单不处理 */
  304. return false;
  305. }
  306. $_apple_data['trans_id'] = $trans_id;
  307. $_apple_data['receipt_data'] = $receipt_data;
  308. $_rs = $_pa_model->updateData($_apple_data, $_apple_data['id']);
  309. if (false === $_rs) {
  310. return false;
  311. }
  312. return $_apple_data;
  313. }
  314. /**
  315. * 向苹果服务器校验ReceiptData
  316. *
  317. * @param $receipt
  318. * @param bool $is_sandbox
  319. *
  320. * @return array|false 失败直接返回false 成功放回以下数组
  321. * array (
  322. * 'status' => 0,
  323. * 'environment' => 'Sandbox',
  324. * 'receipt' =>
  325. * array (
  326. * 'receipt_type' => 'ProductionSandbox',
  327. * 'adam_id' => 0,
  328. * 'app_item_id' => 0, //App Store用来标识程序的字符串
  329. * 'bundle_id' => 'com.abcde.www', //iPhone程序的bundle标识
  330. * 'application_version' => '0.0.9',
  331. * 'download_id' => 0,
  332. * 'version_external_identifier' => 0,
  333. * 'receipt_creation_date' => '2016-07-13 18:22:19 Etc/GMT',
  334. * 'receipt_creation_date_ms' => '1468434139000',
  335. * 'receipt_creation_date_pst' => '2016-07-13 11:22:19 America/Los_Angeles',
  336. * 'request_date' => '2016-07-13 18:22:22 Etc/GMT',
  337. * 'request_date_ms' => '1468434142143',
  338. * 'request_date_pst' => '2016-07-13 11:22:22 America/Los_Angeles',
  339. * 'original_purchase_date' => '2013-08-01 07:00:00 Etc/GMT',
  340. * 'original_purchase_date_ms' => '1375340400000', //购买时间毫秒
  341. * 'original_purchase_date_pst' => '2013-08-01 00:00:00 America/Los_Angeles', //购买时间,太平洋标准时间
  342. * 'original_application_version' => '1.0',
  343. * 'in_app' =>
  344. * array (
  345. * 0 =>
  346. * array (
  347. * 'quantity' => '1', //购买商品的数量
  348. * 'product_id' => 'price_1', ////商品的标识
  349. * 'transaction_id' => '1000000223463280', //交易的标识
  350. * 'original_transaction_id' => '1000000223463280', //原始交易ID
  351. * 'purchase_date' => '2016-07-13 18:22:19 Etc/GMT', //购买时间
  352. * 'purchase_date_ms' => '1468434139000', //购买时间毫秒
  353. * 'purchase_date_pst' => '2016-07-13 11:22:19 America/Los_Angeles',//太平洋标准时间
  354. * 'original_purchase_date' => '2016-07-13 18:22:19 Etc/GMT', //原始购买时间
  355. * 'original_purchase_date_ms' => '1468434139000', //原始购买时间毫秒
  356. * 'original_purchase_date_pst' => '2016-07-13 11:22:19 America/Los_Angeles',//太平洋标准时间
  357. * 'is_trial_period' => 'false',
  358. * ),
  359. * ),
  360. * ),
  361. * )
  362. */
  363. protected function checkReceiptData($receipt, &$is_sandbox = false) {
  364. if ($is_sandbox) {
  365. $_url_buy = $this->sb_url_buy;
  366. } else {
  367. $_url_buy = $this->url_buy;
  368. }
  369. $_param = json_encode(['receipt-data' => $receipt]);
  370. $_rs = Request::httpJsonpost($_url_buy, $_param);
  371. if (false === $_rs) {
  372. /* 请求失败 */
  373. return false;
  374. }
  375. $_rs = json_decode($_rs, true);
  376. if (empty($_rs) || !isset($_rs['status'])) {
  377. return false;
  378. }
  379. switch ($_rs['status']) {
  380. case self::APPLE_STATUS_21007:
  381. /* 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证 苹果官网进行二次验证(沙盒) */
  382. $_rs = Request::httpJsonpost($this->sb_url_buy, $_param);
  383. $is_sandbox = false;
  384. break;
  385. case self::APPLE_STATUS_21008:
  386. /* 21008为正式充值去请求沙盒验单地址返回状态,增加确保正式充值验单成功 */
  387. $_rs = Request::httpJsonpost($this->url_buy, $_param);
  388. $is_sandbox = true;
  389. break;
  390. default:
  391. return $_rs;
  392. }
  393. if (false === $_rs) {
  394. /* 请求失败 */
  395. return false;
  396. }
  397. $_rs = json_decode($_rs, true);
  398. if (empty($_rs) || !isset($_rs['status'])) {
  399. return false;
  400. }
  401. return $_rs;
  402. }
  403. /**
  404. * 获取产品价格
  405. *
  406. * @param $product_id
  407. * @param $order_id
  408. *
  409. * @return bool
  410. */
  411. public function getProductPrice($product_id, $order_id) {
  412. if (empty($product_id) || empty($order_id)) {
  413. return false;
  414. }
  415. $_order_model = new OrderModel();
  416. $_o_data = $_order_model->getInfoByOrderId($order_id);
  417. if (empty($_o_data) || empty($_o_data['real_amount'])) {
  418. return false;
  419. }
  420. $_check_p_id = $_o_data['product_id'];;
  421. if (empty($_check_p_id) || 0 !== strcmp($_check_p_id, $product_id)) {
  422. return false;
  423. }
  424. /* Modified by ou BEGIN 2018-01-06 product_id验证 */
  425. Session::set('order_id', $order_id, 'check_apple_order');
  426. $_app_id = $_o_data['app_id'];
  427. if (!empty($_o_data['vb_id'])) {
  428. /* 马甲包处理 */
  429. $_app_id = $_o_data['vb_id'];
  430. }
  431. $_product_verify = $this->checkProductId($_app_id, $product_id, $_o_data['real_amount']);
  432. if (false == $_product_verify) {
  433. return false;
  434. }
  435. /* Modified by ou END 2018-01-06 product_id验证 */
  436. return $_o_data['real_amount'];
  437. }
  438. /**
  439. * 验证product_id
  440. *
  441. * @param int $app_id
  442. * @param string $product_id
  443. * @param int $amount
  444. *
  445. * @return bool
  446. */
  447. public function checkProductId($app_id = 0, $product_id = '', $amount = 0) {
  448. $_app_id = $app_id;
  449. /*1: 判断是否设置计费点*/
  450. // $_map['app_id'] = $_app_id;
  451. // $_map['product_code'] = $product_id;
  452. // $_gp_data = (new GamePriceModel())->getGamePrice($_map);
  453. // if (empty($_gp_data)) {
  454. // return true;
  455. // }
  456. // if (empty($product_id) || empty($amount)) {
  457. // return false;
  458. // }
  459. /*2: 验证product_id与金额是否与保存在数据库中的一致*/
  460. // $_check_amount = $_gp_data['product_price'];
  461. $_check_amount = (new GamePriceModel())->getPriceByAppProduct($app_id, $product_id);
  462. if (!empty($_check_amount) && 0 === strcmp(floatval($_check_amount), floatval($amount))) {
  463. return true;
  464. } else {
  465. Log::write(
  466. array($product_id, '##', $_app_id, '##', $amount, '##', Session::get('order_id', 'check_apple_order')),
  467. 'error'
  468. );
  469. return false;
  470. }
  471. }
  472. }