* @version : H5IOS 1.0 */ namespace huosdk\h5ios\core\controller; use huo\controller\common\Base; use huo\controller\pay\Notify; use huo\controller\queue\ApplePayQueue; use huo\controller\request\Request; use huosdk\h5ios\core\constant\OrderConst; use huosdk\h5ios\core\constant\PaywayConst; use huosdk\h5ios\core\model\GamePriceModel; use huosdk\h5ios\core\model\OrderModel; use huosdk\h5ios\core\model\PayAppleErrorModel; use huosdk\h5ios\core\model\PayAppleModel; use huosdk\h5ios\core\status\OrderStatus; use think\Log; use think\Session; class ApplePay extends Base { const APPLE_STATUS_SUCCESS = 0; /* 0 表示验证成功 */ const APPLE_STATUS_21000 = 21000; /* 21000 App Store无法读取你提供的JSON数据 */ const APPLE_STATUS_21002 = 21002; /* 21002 收据数据不符合格式 */ const APPLE_STATUS_21003 = 21003; /* 21003 收据无法被验证 */ const APPLE_STATUS_21004 = 21004; /* 21004 你提供的共享密钥和账户的共享密钥不一致 */ const APPLE_STATUS_21005 = 21005; /* 21005 收据服务器当前不可用 */ const APPLE_STATUS_21006 = 21006; /* 21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中 */ const APPLE_STATUS_21007 = 21007; /* 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证 */ const APPLE_STATUS_21008 = 21008; /* 21008 收据信息是产品环境中使用,但却被发送到测试环境中验证 */ private $url_buy = ''; private $sb_url_buy = ''; protected $is_sandbox = false; private $order_id = ''; /** * 构造函数 * * @param bool $is_sandbox 是否是沙盒环境 */ public function __construct($is_sandbox = false) { $this->url_buy = "https://buy.itunes.apple.com/verifyReceipt"; $this->sb_url_buy = "https://sandbox.itunes.apple.com/verifyReceipt"; $this->is_sandbox = $is_sandbox; } /** * 校验苹果订单 * * @param string $order_id 平台订单ID * @param string $trans_id 苹果订单ID * @param string $receipt_data 苹果支付票据 * @param bool $is_sandbox 是否是沙盒环境 * @param string $idfv 苹果IDFV * @param string $apple_id 苹果应用ID * * @return bool */ public function checkAppleOrder($order_id, $trans_id, $receipt_data = null, $is_sandbox = false, $idfv, $apple_id) { $_log_message = "func=".__FUNCTION__."&class=".__CLASS__.'&order_id='.$order_id.'&trans_id='.$trans_id .'&receipt_data='.$receipt_data.'&idfv='.$idfv.'&apple_id='.$apple_id.'&step'; if (empty($trans_id) || empty($receipt_data) || empty($apple_id) || empty($idfv)) { /* 不处理 Log 记录*/ Log::write($_log_message.'1', LOG::ERROR); return false; } $_apple_order_model = new PayAppleModel(); $_apple_data = $_apple_order_model->getInfoByTransId($trans_id); if (!empty($order_id)) { /* 查询是否与trans_id对应 */ if (empty($_apple_data)) { /* trans_id 与 order_id 未对应*/ /* 获取并更新苹果订单数据 */ $_apple_data = $this->getUpdateDataByOrderId($order_id, $trans_id, $receipt_data); if (false == $_apple_data) { /* 订单Id错误 */ $this->code = OrderStatus::ORDER_ID_ERROR; /* Log 记录 */ Log::write($_log_message.'2', LOG::ERROR); return false; } } elseif (!empty($_apple_data)) { /* trans_id 与 order_id 已对应 */ if ($_apple_data['order_id'] != $order_id) { /* 订单Id错误 */ $this->code = OrderStatus::ORDER_ID_ERROR; /* Log 记录 */ Log::write($_log_message.'3', LOG::ERROR); return false; } } } if (!empty($_apple_data)) { /* 99%概率 order_id不丢 处理使用此流程 */ $_order_id = $_apple_data['order_id']; $_order_data = (new OrderModel())->getInfoByOrderId($_order_id); if (OrderConst::PAY_STATUS_SUC == $_order_data['status']) { /* 苹果支付已校验成功 */ if (OrderConst::CP_STATUS_SUC == $_order_data['cp_status']) { /* 已通知CP服务器 */ $this->code = OrderStatus::NO_ERROR; } else { /* 未通知CP服务器 */ $_rs = (new Notify())->notify($_order_id); $this->code = $_rs['code']; } return true; } } /* 苹果进行二次验证,防止收到的是伪造的数据 */ $_rs = $this->checkReceiptData($receipt_data, $is_sandbox); if (false == $_rs) { /* 无法正确获取苹果服务器数据 */ $this->code = OrderStatus::APPLE_CHECK_FAIL; /* Log 记录 */ Log::write($_log_message.'4', LOG::ERROR); return false; } if (self::APPLE_STATUS_SUCCESS != $_rs['status']) { $this->code = OrderStatus::RECEIPT_DATA_ERROR; /* Log 记录 */ Log::write($_log_message.'5', LOG::ERROR); return false; } if (!isset($_rs['receipt']) || !isset($_rs['receipt']['in_app']) || !isset($_rs['receipt']['in_app'][0])) { /* 无法正确获取苹果服务器数据 */ $this->code = OrderStatus::RECEIPT_DATA_ERROR; /* Log 记录 */ Log::write($_log_message.'6', LOG::ERROR); return false; } /* 获取In-App Purchase Receipt Fields */ $_receipt_data = $_rs['receipt']['in_app']; $_iap_data = []; if (!empty($_receipt_data)) { foreach ($_receipt_data as $_k => $_v) { if ($_v['transaction_id'] == $trans_id) { $_iap_data = $_v; } } } if (empty($_iap_data)) { /* 无法正确获取苹果服务器数据 */ $this->code = OrderStatus::RECEIPT_DATA_ERROR; /* Log 记录 */ Log::write($_receipt_data, LOG::ERROR); Log::write($_log_message.'66', LOG::ERROR); return false; } $_iap_product_id = $_iap_data['product_id']; //$_iap_request_time = round($_iap_data['request_date_ms'] / 1000); $_iap_request_time = round($_rs['receipt']['request_date_ms'] / 1000); $_iap_purchase_time = round($_iap_data['purchase_date_ms'] / 1000); $_iap_transaction_id = $_iap_data['transaction_id']; if ($_iap_transaction_id != $trans_id) { $this->code = OrderStatus::TRANS_ID_ERROR; /* Log 记录 */ Log::write($_log_message.'8', LOG::ERROR); return false; } $_apple_data['payway'] = PaywayConst::PAYWAY_APPLE; if (true == $is_sandbox) { $_apple_data['payway'] = PaywayConst::PAYWAY_APPLE_TEST; } if (empty($_apple_data)) { /* 查找最近订单 */ $_apple_data = (new PayAppleModel())->getLastOrderData( $_iap_request_time, $idfv, $apple_id, $_iap_product_id ); if (empty($_apple_data)) { /* 异常订单记录 报警 */ // TODO: wuyonghong 2018/6/11 收入异常记录 /* Log 记录 */ Log::write($_log_message.'9', LOG::ERROR); $_pae_data['trans_id'] = $_iap_transaction_id; $_pae_data['idfv'] = $idfv; $_pae_data['idfa'] = ''; $_pae_data['apple_id'] = $apple_id; $_pae_data['product_id'] = $_iap_product_id; $_pae_data['status'] = OrderConst::PAY_STATUS_SUC; $_pae_data['payway'] = $_apple_data['payway']; $_pae_data['pay_time'] = $_iap_purchase_time; $_pae_data['check_cnt'] = 1; $_pae_data['last_check_time'] = time(); $_pae_data['receipt_data'] = $receipt_data; $_pae_data['receipt_field'] = json_encode($_iap_data); (new PayAppleErrorModel())->addData($_pae_data); $this->code = OrderStatus::ORDER_NOT_EXISTS; /* 异常订单不再程序处理 */ return true; } } else { /* 核对苹果支付信息 */ if ($_iap_product_id != $_apple_data['product_id']) { $this->code = OrderStatus::PRODUCT_ID_ERROR; /* Log 记录 */ Log::write($_log_message.'7', LOG::ERROR); return false; } } /* 验证订单合法性 是否与产品对应上 */ $_amount = $this->getProductPrice($_iap_product_id, $order_id); if (false == $_amount || 0.01 > $_amount) { $this->code = OrderStatus::ORDER_PRODUCT_ID_ERROR; /* Log 记录 */ Log::write($_log_message.'&product_id='.$_iap_product_id.'&err_msg=计费点校验出错8', LOG::ERROR); return false; } $this->order_id = $_apple_data['order_id']; $_apple_data['status'] = OrderConst::PAY_STATUS_SUC; $_apple_data['check_cnt']++; $_apple_data['last_check_time'] = time(); $_apple_data['pay_time'] = $_iap_purchase_time; $_id = $_apple_order_model->getIdByTransId($trans_id); if (empty($_id)) { $this->code = OrderStatus::PRODUCT_ID_ERROR; /* Log 记录 */ Log::write($_log_message.'&trans_id='.$trans_id.'&err_msg=trans_id校验出错9', LOG::ERROR); return false; } $_apple_order_model->updateData($_apple_data, $_id); $_order_data = (new OrderModel())->getInfoByOrderId($this->order_id); (new Notify())->payNotify( $_iap_product_id, $order_id, $trans_id, $_order_data['real_amount'], $_apple_data['payway'] ); $this->code = OrderStatus::NO_ERROR; return true; } /** * 订单校验 * * @param string $order_id 平台订单ID * @param string $trans_id 苹果订单ID * @param string $receipt_data 苹果支付票据 * @param bool $is_sandbox 是否是沙盒环境 * @param string $idfv 苹果IDFV * @param string $apple_id 苹果应用ID * * @return array */ public function orderQuery( $order_id, $trans_id, $receipt_data = null, $is_sandbox = false, $idfv = '', $apple_id = '' ) { $_rdata = [ 'order_id' => $order_id, 'trans_id' => $trans_id, 'status' => OrderConst::PAY_STATUS_NOT, 'cp_status' => OrderConst::CP_STATUS_NOT, ]; $this->order_id = $order_id; if (empty($trans_id) || empty($receipt_data)) { $_code = OrderStatus::INVALID_PARAMS; return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata); } $this->checkAppleOrder($order_id, $trans_id, $receipt_data, $is_sandbox, $idfv, $apple_id); $_code = $this->code; if (OrderStatus::APPLE_CHECK_FAIL == $_code) { /* 苹果验单失败 加入队列 */ (new ApplePayQueue($apple_id))->setApplePayQueue( $order_id, $trans_id, $receipt_data, $is_sandbox, $idfv, $apple_id ); return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata); } if (OrderStatus::NO_ERROR != $_code) { return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata); } /* 从订单中获取状态 */ $_statues = (new OrderModel())->getStatus($this->order_id); if (false == $_statues) { $_code = OrderStatus::ORDER_ID_ERROR; return $this->huoError($_code, OrderStatus::getMsg($_code), $_rdata); } $_rdata = [ 'order_id' => $this->order_id, 'trans_id' => $trans_id, 'status' => $_statues['status'], 'cp_status' => $_statues['cp_status'] ]; return $this->huoSuccess($_code, OrderStatus::getMsg($_code), $_rdata); } /** * 通过订单ID获取数据 * * @param string $order_id * @param string $trans_id * @param string $receipt_data * * @return array|bool|false */ private function getUpdateDataByOrderId($order_id, $trans_id, $receipt_data) { $_pa_model = new PayAppleModel(); $_apple_data = $_pa_model->getInfoByOrderId($order_id); if (!empty($_apple_data['trans_id'])) { if ($_apple_data['trans_id'] == $trans_id) { return $_apple_data; } /* 已对应其他订单不处理 */ return false; } $_apple_data['trans_id'] = $trans_id; $_apple_data['receipt_data'] = $receipt_data; $_rs = $_pa_model->updateData($_apple_data, $_apple_data['id']); if (false === $_rs) { return false; } return $_apple_data; } /** * 向苹果服务器校验ReceiptData * * @param $receipt * @param bool $is_sandbox * * @return array|false 失败直接返回false 成功放回以下数组 * array ( * 'status' => 0, * 'environment' => 'Sandbox', * 'receipt' => * array ( * 'receipt_type' => 'ProductionSandbox', * 'adam_id' => 0, * 'app_item_id' => 0, //App Store用来标识程序的字符串 * 'bundle_id' => 'com.abcde.www', //iPhone程序的bundle标识 * 'application_version' => '0.0.9', * 'download_id' => 0, * 'version_external_identifier' => 0, * 'receipt_creation_date' => '2016-07-13 18:22:19 Etc/GMT', * 'receipt_creation_date_ms' => '1468434139000', * 'receipt_creation_date_pst' => '2016-07-13 11:22:19 America/Los_Angeles', * 'request_date' => '2016-07-13 18:22:22 Etc/GMT', * 'request_date_ms' => '1468434142143', * 'request_date_pst' => '2016-07-13 11:22:22 America/Los_Angeles', * 'original_purchase_date' => '2013-08-01 07:00:00 Etc/GMT', * 'original_purchase_date_ms' => '1375340400000', //购买时间毫秒 * 'original_purchase_date_pst' => '2013-08-01 00:00:00 America/Los_Angeles', //购买时间,太平洋标准时间 * 'original_application_version' => '1.0', * 'in_app' => * array ( * 0 => * array ( * 'quantity' => '1', //购买商品的数量 * 'product_id' => 'price_1', ////商品的标识 * 'transaction_id' => '1000000223463280', //交易的标识 * 'original_transaction_id' => '1000000223463280', //原始交易ID * 'purchase_date' => '2016-07-13 18:22:19 Etc/GMT', //购买时间 * 'purchase_date_ms' => '1468434139000', //购买时间毫秒 * 'purchase_date_pst' => '2016-07-13 11:22:19 America/Los_Angeles',//太平洋标准时间 * 'original_purchase_date' => '2016-07-13 18:22:19 Etc/GMT', //原始购买时间 * 'original_purchase_date_ms' => '1468434139000', //原始购买时间毫秒 * 'original_purchase_date_pst' => '2016-07-13 11:22:19 America/Los_Angeles',//太平洋标准时间 * 'is_trial_period' => 'false', * ), * ), * ), * ) */ protected function checkReceiptData($receipt, &$is_sandbox = false) { if ($is_sandbox) { $_url_buy = $this->sb_url_buy; } else { $_url_buy = $this->url_buy; } $_param = json_encode(['receipt-data' => $receipt]); $_rs = Request::httpJsonpost($_url_buy, $_param); if (false === $_rs) { /* 请求失败 */ return false; } $_rs = json_decode($_rs, true); if (empty($_rs) || !isset($_rs['status'])) { return false; } switch ($_rs['status']) { case self::APPLE_STATUS_21007: /* 21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证 苹果官网进行二次验证(沙盒) */ $_rs = Request::httpJsonpost($this->sb_url_buy, $_param); $is_sandbox = false; break; case self::APPLE_STATUS_21008: /* 21008为正式充值去请求沙盒验单地址返回状态,增加确保正式充值验单成功 */ $_rs = Request::httpJsonpost($this->url_buy, $_param); $is_sandbox = true; break; default: return $_rs; } if (false === $_rs) { /* 请求失败 */ return false; } $_rs = json_decode($_rs, true); if (empty($_rs) || !isset($_rs['status'])) { return false; } return $_rs; } /** * 获取产品价格 * * @param $product_id * @param $order_id * * @return bool */ public function getProductPrice($product_id, $order_id) { if (empty($product_id) || empty($order_id)) { return false; } $_order_model = new OrderModel(); $_o_data = $_order_model->getInfoByOrderId($order_id); if (empty($_o_data) || empty($_o_data['real_amount'])) { return false; } $_check_p_id = $_o_data['product_id'];; if (empty($_check_p_id) || 0 !== strcmp($_check_p_id, $product_id)) { return false; } /* Modified by ou BEGIN 2018-01-06 product_id验证 */ Session::set('order_id', $order_id, 'check_apple_order'); $_app_id = $_o_data['app_id']; if (!empty($_o_data['vb_id'])) { /* 马甲包处理 */ $_app_id = $_o_data['vb_id']; } $_product_verify = $this->checkProductId($_app_id, $product_id, $_o_data['real_amount']); if (false == $_product_verify) { return false; } /* Modified by ou END 2018-01-06 product_id验证 */ return $_o_data['real_amount']; } /** * 验证product_id * * @param int $app_id * @param string $product_id * @param int $amount * * @return bool */ public function checkProductId($app_id = 0, $product_id = '', $amount = 0) { $_app_id = $app_id; /*1: 判断是否设置计费点*/ // $_map['app_id'] = $_app_id; // $_map['product_code'] = $product_id; // $_gp_data = (new GamePriceModel())->getGamePrice($_map); // if (empty($_gp_data)) { // return true; // } // if (empty($product_id) || empty($amount)) { // return false; // } /*2: 验证product_id与金额是否与保存在数据库中的一致*/ // $_check_amount = $_gp_data['product_price']; $_check_amount = (new GamePriceModel())->getPriceByAppProduct($app_id, $product_id); if (!empty($_check_amount) && 0 === strcmp(floatval($_check_amount), floatval($amount))) { return true; } else { Log::write( array($product_id, '##', $_app_id, '##', $amount, '##', Session::get('order_id', 'check_apple_order')), 'error' ); return false; } } }