ApplePay.php 17 KB

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