'编辑中', self::APPLYMENT_STATE_AUDITING => '审核中', self::APPLYMENT_STATE_REJECTED => '已驳回', self::APPLYMENT_STATE_TO_BE_CONFIRMED => '待账户验证', self::APPLYMENT_STATE_TO_BE_SIGNED => '待签约', self::APPLYMENT_STATE_SIGNING => '开通权限中', self::APPLYMENT_STATE_FINISHED => '已完成', self::APPLYMENT_STATE_CANCELED => '已作废', ]; public static $valid_state_status = [ self::APPLYMENT_STATE_EDITTING => 0, self::APPLYMENT_STATE_AUDITING => 1, self::APPLYMENT_STATE_REJECTED => 2, self::APPLYMENT_STATE_TO_BE_CONFIRMED => 3, self::APPLYMENT_STATE_TO_BE_SIGNED => 3, self::APPLYMENT_STATE_SIGNING => 3, self::APPLYMENT_STATE_FINISHED => 3, self::APPLYMENT_STATE_CANCELED => 2, ]; /** * @var string 获取平台证书接口 */ private $certificates_url = 'https://api.mch.weixin.qq.com/v3/certificates'; public $submitErrRollback = 0; public function __construct() { $keys = [ 'platform_appid', 'platform_mch_id', 'platform_key', 'platform_apiclient_cert', 'platform_apiclient_key', 'platform_api_key', 'platform_public_key', 'platform_public_key_id', 'platform_serial_no', 'platform_mch_appid', 'sp_mch_id', 'sp_apiclient_key' ]; $data = Option::get($keys, 0, 'saas'); try { if (empty($data)) { throw new \Exception('配置信息有误0', 1); } else { $arr = []; foreach ($data as $value) { $index = array_search($value['name'], $keys); unset($keys[$index]); $arr[$value['name']] = $value['value']; } foreach ($keys as $key) { $arr[$key] = ''; } $data = $arr; } if (empty($data['platform_serial_no']) || empty($data['platform_api_key']) || empty($data['platform_apiclient_key'])) { throw new \Exception('配置信息有误1', 1); } if (\Yii::$app->prod_is_dandianpu()) { //供应链系统没必要去判断服务端小程序是否存在 // if (empty($data['platform_mch_appid'])) { // throw new \Exception('配置信息有误2', 1); // } } else { if (empty($data['platform_appid'])) { throw new \Exception('配置信息有误3', 1); } } } catch (\Exception $e) { return [ 'code' => 1, 'message' => $e->getMessage(), ]; } $cert_pem_file = $this->mch_private_key; if (!file_exists($cert_pem_file) || (md5($data['sp_apiclient_key']) != md5_file($cert_pem_file))) { file_put_contents($cert_pem_file, $data['sp_apiclient_key']);//platform_apiclient_key if (file_exists($this->public_key_path)){ @unlink($this->public_key_path); \Yii::error([__METHOD__]); } } // $this->mch_id = '1508102711'; //sp_mch_id // $this->mch_api_key = 'L6u9gjIhOnFC1bzPA4s8JMII6wVCtRCo'; //platform_api_key // $this->serial_no = '4956AC2C151A50213DF52A28AB26CEAD092C822F'; //platform_serial_no // $this->service_app_id = 'wxb842f5f7c833049e'; //platform_appid $this->mch_id = $data['sp_mch_id']; $this->mch_api_key = $data['platform_api_key']; $this->serial_no = $data['platform_serial_no']; $this->service_app_id = $data['platform_appid']; $this->public_key = $data['platform_public_key']; $this->public_key_id = $data['platform_public_key_id']; \Yii::error([$this->mch_id, $this->mch_api_key, $this->serial_no, $this->service_app_id, $cert_pem_file]); } /** * 进件提交 * @param $contact_info * @param $subject_info * @param $business_info * @param $bank_account_info * @param $apply_id */ public function submit($contact_info, $subject_info, $business_info, $bank_account_info, $apply_id = 0,$appid="", $store_id = 0, $settlement_info = []) { if ((empty($settlement_info) || empty($settlement_info['qualification_type']))) { if ($apply_id) { $merchant_info = MerchantInfo::findOne($apply_id); $settlement_info = json_decode($merchant_info->settlement_info, true); }else{ $settlement_info = []; } } $handle_res = $this->handleFormat($contact_info, $subject_info, $business_info, $bank_account_info,$appid,$settlement_info); $store_id = $store_id ?: get_store_id(); if ($handle_res['code'] != 0) { return $handle_res; } $t = \Yii::$app->db->beginTransaction(); // 入表 if ($apply_id) { $merchant_info = MerchantInfo::findOne($apply_id); if($merchant_info->state == 'APPLYMENT_STATE_REJECTED'){ $business_code = $this->getBusinessCode(); $merchant_info->business_code = $business_code; } $merchant_info->refuse_desc = ''; $merchant_info->state = ''; $merchant_info->status = 0; } else { $merchant_info = MerchantInfo::findOne(['bind_store_id' => $store_id, 'status' => [0, 2], 'is_delete' => 0]); $merchant_info = !empty($merchant_info) ? $merchant_info : new MerchantInfo(); // todo: man_id $merchant_info->saas_user_id = get_saas_user_id(); $business_code = $this->getBusinessCode(); $merchant_info->business_code = $business_code; $merchant_info->refuse_desc = ''; $merchant_info->state = ''; $merchant_info->status = 0; } $merchant_info->store_id = get_store_id(); if ($store_id) { $merchant_info->bind_store_id = $store_id; } $merchant_info->contact_info = Json::encode($handle_res['data']['original_data']['contact_info']); $merchant_info->subject_info = Json::encode($handle_res['data']['original_data']['subject_info']); $merchant_info->business_info = Json::encode($handle_res['data']['original_data']['business_info']); $merchant_info->settlement_info = Json::encode($handle_res['data']['original_data']['settlement_info']); $merchant_info->bank_account_info = Json::encode($handle_res['data']['original_data']['bank_account_info']); if (!$merchant_info->save()) { return [ 'code' => 1, 'msg' => $merchant_info->errors[0] ]; } // 调接口 $submitSer = $this->submitSer($merchant_info, $handle_res); if ($submitSer['code'] != 0) { if($this->submitErrRollback){ $t->rollback(); }else{ $t->commit(); } return $submitSer; } $t->commit(); return [ 'code' => 0, 'msg' => '提交成功', 'data' => $merchant_info, ]; } // 调接口 public function submitSer($merchant_info, $handle_res) { $merchant_info_id = $merchant_info->id; $data = $handle_res['data']['commit_data']; $business_code = $merchant_info->business_code; $data['business_code'] = $business_code; // 处理图片 $data = $this->submitPic($data, $handle_res); \Yii::warning($data); if ($data['code'] != 0) { return $data; } // 调接口 $result = $this->subApplyment($data, $handle_res); \Yii::warning($result); if ($result['code'] != 0) { $merchant_info->status = 0; if (!$merchant_info->save()) { return [ 'code' => 1, 'msg' => "数据填充失败" ]; }; $result['data'] = [ 'id' => $merchant_info->id, 'saas_user_id' => $merchant_info->saas_user_id, 'business_code' => $merchant_info->business_code, 'contact_info' => Json::decode($merchant_info->contact_info), 'subject_info' => Json::decode($merchant_info->subject_info), 'business_info' => Json::decode($merchant_info->business_info), 'settlement_info' => Json::decode($merchant_info->settlement_info), 'bank_account_info' => Json::decode($merchant_info->bank_account_info), 'applyment_id' => $merchant_info->applyment_id, 'status' => $merchant_info->status ]; return $result; } $merchant_info->applyment_id = (string)$result['data']; $merchant_info->status = 1; if (!$merchant_info->save()) { \Yii::warning(['<================> 申请进件applyment_id入库失败', $merchant_info->errors]); return [ 'code' => 1, 'msg' => 'applyment_id存储异常' ]; } return [ 'code' => 0, 'msg' => '提交成功' ]; } // 处理图片 public function submitPic($data, $handle_res) { if($data['settlement_info']['qualifications']){ foreach($data['settlement_info']['qualifications'] as &$item){ $mediaUpload = $this->mediaUpload($item); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $item = $mediaUpload['data']; } } if($data['settlement_info']['activities_additions']){ foreach($data['settlement_info']['activities_additions'] as &$item){ $mediaUpload = $this->mediaUpload($item); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $item = $mediaUpload['data']; } } if($data['contact_info']['business_authorization_letter']){ $mediaUpload = $this->mediaUpload($data['contact_info']['business_authorization_letter']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['contact_info']['business_authorization_letter'] = $mediaUpload['data']; } if($data['contact_info']['contact_id_doc_copy']){ $mediaUpload = $this->mediaUpload($data['contact_info']['contact_id_doc_copy']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['contact_info']['contact_id_doc_copy'] = $mediaUpload['data']; } if($data['contact_info']['contact_id_doc_copy_back']){ $mediaUpload = $this->mediaUpload($data['contact_info']['contact_id_doc_copy_back']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['contact_info']['contact_id_doc_copy_back'] = $mediaUpload['data']; } if ($data['subject_info']['subject_type'] == self::SUBJECT_TYPE_INDIVIDUAL || $data['subject_info']['subject_type'] == self::SUBJECT_TYPE_ENTERPRISE) { $mediaUpload = $this->mediaUpload($data['subject_info']['business_license_info']['license_copy']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['subject_info']['business_license_info']['license_copy'] = $mediaUpload['data']; unset($data['subject_info']['certificate_info']); } if ($data['subject_info']['subject_type'] == self::SUBJECT_TYPE_INSTITUTIONS || $data['subject_info']['subject_type'] == self::SUBJECT_TYPE_OTHERS) { $mediaUpload = $this->mediaUpload($data['subject_info']['certificate_info']['cert_copy']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['subject_info']['certificate_info']['cert_copy'] = $mediaUpload['data']; if (!$handle_res['data']['original_data']['subject_info']['organization_info']['is_composite']) { $mediaUpload = $this->mediaUpload($data['subject_info']['organization_info']['organization_copy']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['subject_info']['organization_info']['organization_copy'] = $mediaUpload['data']; } unset($data['subject_info']['business_license_info']); } else { unset($data['subject_info']['organization_info']); } if ($data['subject_info']['identity_info']['id_doc_type'] == self::IDENTIFICATION_TYPE_IDCARD) { $mediaUpload = $this->mediaUpload($data['subject_info']['identity_info']['id_card_info']['id_card_copy']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['subject_info']['identity_info']['id_card_info']['id_card_copy'] = $mediaUpload['data']; $mediaUpload = $this->mediaUpload($data['subject_info']['identity_info']['id_card_info']['id_card_national']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['subject_info']['identity_info']['id_card_info']['id_card_national'] = $mediaUpload['data']; unset($data['subject_info']['identity_info']['id_doc_info']); } else { $mediaUpload = $this->mediaUpload($data['subject_info']['identity_info']['id_doc_info']['id_doc_copy']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['subject_info']['identity_info']['id_doc_info']['id_doc_copy'] = $mediaUpload['data']; $mediaUpload = $this->mediaUpload($data['subject_info']['identity_info']['id_doc_info']['id_doc_copy_back']); if ($mediaUpload['code'] !== 0) { return $mediaUpload; } $data['subject_info']['identity_info']['id_doc_info']['id_doc_copy_back'] = $mediaUpload['data']; unset($data['subject_info']['identity_info']['id_card_info']); } return $data; } /** * 数据处理 * @param $contact_info * @param $subject_info * @param $business_info * @param $bank_account_info * @return array */ private function handleFormat($contact_info, $subject_info, $business_info, $bank_account_info, $sub_appid='', $settlement_info = []) { if (empty($contact_info) || empty($subject_info) || empty($business_info) || empty($bank_account_info)) { return [ 'code' => 1, 'msg' => '申请信息缺失' ]; } $pay_rate = Option::get('pay_rate', 0, 'saas')['value']; if (empty($pay_rate)) { $pay_rate = '0.2'; } // 判断超级管理员信息 if (empty($contact_info['contact_name']) || empty($contact_info['contact_id_number']) || empty($contact_info['mobile_phone']) || empty($contact_info['contact_email'])) { return [ 'code' => 1, 'msg' => '超级管理员信息有误' ]; } // 判断主体资料 if (!in_array($subject_info['subject_type'], self::$subject_type)) { return [ 'code' => 1, 'msg' => '主体类型缺失' ]; } // 主体为个体户/企业 营业执照信息必填 if ($subject_info['subject_type'] == self::SUBJECT_TYPE_ENTERPRISE || $subject_info['subject_type'] == self::SUBJECT_TYPE_INDIVIDUAL) { if (empty($subject_info['business_license_info'])) { return [ 'code' => 1, 'msg' => '个体户或企业,营业执照信息必填' ]; } if (empty($subject_info['business_license_info']['license_copy']) || empty($subject_info['business_license_info']['license_number']) || empty($subject_info['business_license_info']['merchant_name']) || empty($subject_info['business_license_info']['legal_person'])) { return [ 'code' => 1, 'msg' => '缺少营业执照相关信息' ]; } //判断营业执照大小 $license_copy = $this->saveTempImage($subject_info['business_license_info']['license_copy']); if ((floatval(filesize($license_copy)) / 1024 / 1024) > 2) { //判断是否大于2M return [ 'code' => 1, 'msg' => '营业执照图片大小过大' ]; } //如果为个体 if (empty($subject_info['business_license_info']['period_begin']) && empty($subject_info['business_license_info']['period_end'])) { return [ 'code' => 1, 'msg' => '缺少营业执照开始结束日期' ]; } if ($subject_info['business_license_info']['period_end'] === '2999-12-31' || $subject_info['business_license_info']['is_long']) { $subject_info['business_license_info']['period_end'] = '长期'; empty($subject_info['business_license_info']['period_begin']) && $subject_info['business_license_info']['period_begin'] = date('Y-m-d'); } } // 党政、机关及事业单位/其他组织 登记证书信息必填 if ($subject_info['subject_type'] == self::SUBJECT_TYPE_INSTITUTIONS || $subject_info['subject_type'] == self::SUBJECT_TYPE_OTHERS) { if (empty($subject_info['certificate_info'])) { return [ 'code' => 1, 'msg' => '党政、机关及事业单位或其他组织,登记证书信息信息必填' ]; } if (empty($subject_info['certificate_info']['cert_copy']) || empty($subject_info['certificate_info']['cert_type']) || empty($subject_info['certificate_info']['cert_number']) || empty($subject_info['certificate_info']['merchant_name']) || empty($subject_info['certificate_info']['company_address']) || empty($subject_info['certificate_info']['legal_person']) || empty($subject_info['certificate_info']['period_begin']) ) { return [ 'code' => 1, 'msg' => '缺少登记证书信息相关信息' ]; } if (!$subject_info['certificate_info']['is_long']) { if (empty($subject_info['certificate_info']['period_end'])) { return [ 'code' => 1, 'msg' => '登记证书证件有效期限结束日期需要填写' ]; } } else { $subject_info['certificate_info']['period_end'] = '长期'; } if (!in_array($subject_info['certificate_info']['cert_type'], self::$certificate_type)) { return [ 'code' => 1, 'msg' => '登记证书类型有误' ]; } } // 主体为企业/党政、机关及事业单位/其他组织,且证件号码不是18位时必填 // 若营业执照未三证合一 ,该参数必传 // 若营业执照三证合一 ,该参数可不传 if ($subject_info['subject_type'] == self::SUBJECT_TYPE_ENTERPRISE || $subject_info['subject_type'] == self::SUBJECT_TYPE_INSTITUTIONS || $subject_info['subject_type'] == self::SUBJECT_TYPE_OTHERS) { if (!$subject_info['organization_info']['is_composite']) { if (empty($subject_info['organization_info'])) { return [ 'code' => 1, 'msg' => '企业,党政、机关及事业单位或其他组织,组织机构信息必填' ]; } if (empty($subject_info['organization_info']['organization_copy']) || empty($subject_info['organization_info']['organization_code']) || empty($subject_info['organization_info']['org_period_begin'])) { return [ 'code' => 1, 'msg' => '缺少组织结构信息相关信息' ]; } if (!$subject_info['organization_info']['is_long']) { if (empty($subject_info['organization_info']['org_period_end'])) { return [ 'code' => 1, 'msg' => '组织机构证件有效期限结束日期需要填写' ]; } } else { $subject_info['organization_info']['org_period_end'] = '长期'; } } } // 经营者/法人身份证件 if (empty($subject_info['identity_info'])) { return [ 'code' => 1, 'msg' => '经营者/法人身份证件需要填写' ]; } if ($subject_info['identity_info']['id_doc_type'] == self::IDENTIFICATION_TYPE_IDCARD) { if (empty($subject_info['identity_info']['id_card_info'])) { return [ 'code' => 1, 'msg' => '身份证信息需要填写' ]; } if (empty($subject_info['identity_info']['id_card_info']['id_card_copy']) || empty($subject_info['identity_info']['id_card_info']['id_card_national']) || empty($subject_info['identity_info']['id_card_info']['id_card_name']) || empty($subject_info['identity_info']['id_card_info']['id_card_number']) || empty($subject_info['identity_info']['id_card_info']['id_card_address']) || empty($subject_info['identity_info']['id_card_info']['card_period_begin'])) { return [ 'code' => 1, 'msg' => '身份证信息填写有误' ]; } if (!$subject_info['identity_info']['is_long']) { if (empty($subject_info['identity_info']['id_card_info']['card_period_end'])) { return [ 'code' => 1, 'msg' => '身份证证件有效期限结束日期需要填写' ]; } } else { $subject_info['identity_info']['id_card_info']['card_period_end'] = '长期'; } } else { if (empty($subject_info['identity_info']['id_doc_info'])) { return [ 'code' => 1, 'msg' => '护照或通行证信息需要填写' ]; } if (empty($subject_info['identity_info']['id_doc_info']['id_doc_copy']) || empty($subject_info['identity_info']['id_doc_info']['id_doc_copy_back']) || empty($subject_info['identity_info']['id_doc_info']['id_doc_name']) || empty($subject_info['identity_info']['id_doc_info']['id_doc_number']) || empty($subject_info['identity_info']['id_doc_info']['doc_period_begin'])) { return [ 'code' => 1, 'msg' => '护照或通行证信息填写有误' ]; } if (!$subject_info['identity_info']['is_long']) { if (empty($subject_info['identity_info']['id_doc_info']['doc_period_end'])) { return [ 'code' => 1, 'msg' => '护照或通行证有效期限结束日期需要填写' ]; } } else { $subject_info['identity_info']['id_doc_info']['doc_period_end'] = '长期'; } } if ($subject_info['subject_type'] == self::SUBJECT_TYPE_INDIVIDUAL) { $subject_info['identity_info']['owner'] = null; } else { $subject_info['identity_info']['owner'] = true; } // 经营资料 if (empty($business_info['merchant_shortname']) || empty($business_info['service_phone'])) { return [ 'code' => 1, 'msg' => '经营资料填写有误' ]; } $business_info['sales_info']['sales_scenes_type'] = ['SALES_SCENES_MINI_PROGRAM']; // 小程序 if(!empty($sub_appid)){ $business_info['sales_info']['mini_program_info']['mini_program_sub_appid'] = $sub_appid;//使用商户的小程序信息 }else{ $business_info['sales_info']['mini_program_info']['mini_program_appid'] = $this->service_app_id;//使用服务商的小程序信息 } // 结算银行账户信息 if (!in_array($bank_account_info['bank_account_type'], self::$bank_type)) { return [ 'code' => 1, 'msg' => '结算银行账户类型有误' ]; } // if (empty($bank_account_info['bank_address_code'])) { $bank_account_info['bank_address_code'] = end($bank_account_info["code_list"]); // } if (empty($bank_account_info['account_name']) || empty($bank_account_info['account_bank']) || empty($bank_account_info['bank_address_code']) || empty($bank_account_info['account_number'])) { return [ 'code' => 1, 'msg' => '结算银行账户填写有误' ]; } if ($bank_account_info['account_bank'] != '其他银行' && isset($bank_account_info['bank_name'])) { unset($bank_account_info['bank_name']); } if ($bank_account_info['account_bank'] == '其他银行' && empty($bank_account_info['bank_name'])) { return [ 'code' => 1, 'msg' => '开户银行全称(含支行)填写有误' ]; } $settlement_info_default = [ 'settlement_id' => '719', 'qualification_type' => '零售', 'activities_id'=>'20191030111cff5b5e', ]; $settlement_info = $settlement_info ? : $settlement_info_default; $settlement_info['activities_rate'] = $pay_rate; if ($subject_info['subject_type'] == self::SUBJECT_TYPE_INDIVIDUAL) { $settlement_info['settlement_id'] = '719'; } else if ($subject_info['subject_type'] == self::SUBJECT_TYPE_ENTERPRISE) { $settlement_info['settlement_id'] = '716'; } else if ($subject_info['subject_type'] == self::SUBJECT_TYPE_OTHERS) { $settlement_info['settlement_id'] = '727'; } else { $settlement_info['settlement_id'] = '716'; } $original_data = [ 'subject_info' => $subject_info, 'contact_info' => $contact_info, 'business_info' => $business_info, 'settlement_info' => $settlement_info, 'bank_account_info' => $bank_account_info ]; $contact_info['contact_name'] = $this->getEncrypt($contact_info['contact_name']); $contact_info['contact_id_number'] = $this->getEncrypt($contact_info['contact_id_number']); $contact_info['mobile_phone'] = $this->getEncrypt($contact_info['mobile_phone']); $contact_info['contact_email'] = $this->getEncrypt($contact_info['contact_email']); $contact_info['contact_type'] = $contact_info['contact_type'] ? $contact_info['contact_type'] : 'LEGAL'; if ($subject_info['identity_info']['id_doc_type'] == self::IDENTIFICATION_TYPE_IDCARD) { $subject_info['identity_info']['id_card_info']['id_card_name'] = $this->getEncrypt($subject_info['identity_info']['id_card_info']['id_card_name']); $subject_info['identity_info']['id_card_info']['id_card_number'] = $this->getEncrypt($subject_info['identity_info']['id_card_info']['id_card_number']); $subject_info['identity_info']['id_card_info']['id_card_address'] = $this->getEncrypt($subject_info['identity_info']['id_card_info']['id_card_address']); } else { $subject_info['identity_info']['id_doc_info']['id_doc_name'] = $this->getEncrypt($subject_info['identity_info']['id_doc_info']['id_doc_name']); $subject_info['identity_info']['id_doc_info']['id_doc_number'] = $this->getEncrypt($subject_info['identity_info']['id_doc_info']['id_doc_number']); $subject_info['identity_info']['id_doc_info']['id_doc_address'] = $this->getEncrypt($subject_info['identity_info']['id_doc_info']['id_doc_address']); } $bank_account_info['account_name'] = $this->getEncrypt($bank_account_info['account_name']); $bank_account_info['account_number'] = $this->getEncrypt($bank_account_info['account_number']); $commit_data = [ 'subject_info' => $subject_info, 'contact_info' => $contact_info, 'business_info' => $business_info, 'settlement_info' => $settlement_info, 'bank_account_info' => $bank_account_info ]; unset($commit_data['subject_info']['certificate_info']['is_long']); unset($commit_data['subject_info']['organization_info']['is_composite']); unset($commit_data['subject_info']['organization_info']['is_long']); unset($commit_data['subject_info']['identity_info']['is_long']); unset($commit_data['bank_account_info']['code_list']); unset($commit_data['bank_account_info']['address_list']); unset($commit_data['business_info']['sales_info']['mini_program_appid']); return [ 'code' => 0, 'data' => [ 'original_data' => $original_data, 'commit_data' => $commit_data ] ]; } /** * 商户进件 * @param $data * @return array * @throws Exception */ public function subApplyment($data) { $url = $this->applyment_url; // 获取支付平台证书编码(也可以用接口中返回的serial_no 来源:https://api.mch.weixin.qq.com/v3/certificates) if($this->public_key){ $serial_no = $this->public_key_id; }else{ $serial_no = $this->parseSerialNo($this->getCertFicates()); } $bodyData = json_encode($data); // 获取认证信息 $authorization = $this->getAuthorization($url, 'POST', $bodyData); $header = [ 'Content-Type:application/json', 'Accept:application/json', 'User-Agent:*/*', 'Authorization:' . $authorization, 'Wechatpay-Serial:' . $serial_no ]; $json = $this->getCurl('POST', $url, $bodyData, $header); $data = json_decode($json, true); if (isset($data['code']) && isset($data['message'])) { return [ 'code' => 1, 'msg' => $data['message'] ]; } if (empty($applyment_id = $data['applyment_id'])) { return [ 'code' => 1, 'msg' => '返回错误' ]; } return ['code' => 0, 'data' => $applyment_id]; } /** * 进件查询 */ public function queryApplyment($applyment_id, $code = 0) { $url = $this->query_applyment_url . $applyment_id; if(empty($applyment_id) && $code){ $url = $this->query_applyment_url_code . $code; \Yii::error([__METHOD__, $url]); } if($this->public_key){ $serial_no = $this->public_key_id; }else{ $serial_no = $this->parseSerialNo($this->getCertFicates()); } // 获取认证信息 $authorization = $this->getAuthorization($url); $header = [ 'Content-Type:application/json', 'Accept:application/json', 'User-Agent:*/*', 'Authorization:' . $authorization, 'Wechatpay-Serial:' . $serial_no ]; $json = $this->getCurl('GET', $url, '', $header); $data = json_decode($json, true); if (isset($data['code']) && isset($data['message'])) { return [ 'code' => 1, 'msg' => '请求错误:' . $data['message'] ]; } return $data; } /** * 上传文件 */ public function mediaUpload($file_path) { $file = file_get_contents($file_path); $size = strlen($file)/1024/1024; \Yii::error([__METHOD__, $size]); if ($size >= 2) { return [ 'code' => 1, 'msg' => '图片大小不能超过2M', ]; } $path_info = pathinfo($file_path); $file_name = $path_info['basename']; $url = $this->upload_image_url; if (\function_exists('exif_imagetype')) { $type = exif_imagetype($file_path); } else { list($width, $height, $type, $attr) = getimagesize($file_path); } if($this->public_key){ $serial_no = $this->public_key_id; }else{ $serial_no = $this->parseSerialNo($this->getCertFicates()); } $mime_type = image_type_to_mime_type($type); $meta = [ "filename" => $file_name, "sha256" => hash_file('sha256', $file_path) ]; // 获取认证信息 $authorization = $this->getAuthorization($url, 'POST', json_encode($meta)); $boundary = uniqid(); $header = [ 'Accept:application/json', 'User-Agent:*/*', 'Content-Type:multipart/form-data;boundary=' . $boundary, 'Authorization:' . $authorization, 'Wechatpay-Serial:' . $serial_no ]; // 组合参数 $boundaryStr = "--{$boundary}\r\n"; $out = $boundaryStr; $out .= 'Content-Disposition: form-data; name="meta"' . "\r\n"; $out .= 'Content-Type: multipart/form-data' . "\r\n"; $out .= "\r\n"; $out .= json_encode($meta) . "\r\n"; $out .= $boundaryStr; $out .= 'Content-Disposition: form-data; name="file"; filename="' . $file_name . '"' . "\r\n"; $out .= 'Content-Type: ' . $mime_type . ';' . "\r\n"; $out .= "\r\n"; $out .= $file . "\r\n"; $out .= "--{$boundary}--\r\n"; $json = $this->getCurl('POST', $url, $out, $header); $data = json_decode($json, true); if (isset($data['code']) && isset($data['message'])) { return [ 'code' => 1, 'msg' => $data['message'] ]; } if (empty($media_id = $data['media_id'])) { return [ 'code' => 1, 'msg' => '返回错误' ]; } return ['code' => 0, 'data' => $media_id]; } /** * 获取微信支付平台证书 */ public function certFicates() { $url = $this->certificates_url; // 获取认证信息 $authorization = $this->getAuthorization($url); $header = [ 'Content-Type:application/json', 'Accept:application/json', 'User-Agent:' . $this->mch_id, 'Authorization:' . $authorization ]; $json = $this->getCurl('GET', $url, '', $header); $data = json_decode($json, true); if (isset($data['code']) && isset($data['message'])) { return [ 'code' => 1, 'msg' => $data['message'] ]; } if (empty($cfdata = $data['data'][0])) { return [ 'code' => 1, 'msg' => '返回错误' ]; } return $cfdata; } /** * 获取认证信息 * @param string $url * @param string $http_method * @param string $body * @return string * @throws Exception */ public function getAuthorization($url, $http_method = 'GET', $body = '') { if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) { throw new \Exception("当前PHP环境不支持SHA256withRSA"); } //私钥地址 $mch_private_key = $this->mch_private_key; //商户号 $merchant_id = $this->mch_id; //当前时间戳 $timestamp = time(); //随机字符串 $nonce = $this->getNonceStr(); //证书编号 $serial_no = $this->serial_no; $url_parts = parse_url($url); $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : "")); $message = $http_method . "\n" . $canonical_url . "\n" . $timestamp . "\n" . $nonce . "\n" . $body . "\n"; openssl_sign($message, $raw_sign, \openssl_get_privatekey(\file_get_contents($mch_private_key)), 'sha256WithRSAEncryption'); $sign = base64_encode($raw_sign); $schema = 'WECHATPAY2-SHA256-RSA2048'; $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign); return $schema . ' ' . $token; } /** * 敏感字符加密 * @param $str * @return string * @throws Exception */ private function getEncrypt($str) { static $content; if (empty($content)) { $content = $this->getCertFicates(); } // 去除内容中的空格 $str = preg_replace("/\s| /", '', $str); $encrypted = ''; if (openssl_public_encrypt($str, $encrypted, $content, OPENSSL_PKCS1_OAEP_PADDING)) { //base64编码 $sign = base64_encode($encrypted); } else { throw new \Exception('encrypt failed'); } return $sign; } /** * 获取支付平台证书 * @return false|string */ public function getCertFicates() { if($this->public_key){ return $this->public_key; } $public_key_path = $this->public_key_path; if (!file_exists($public_key_path)) { \Yii::error([__METHOD__]); $cfData = $this->certFicates(); $content = $this->decryptToString($cfData['encrypt_certificate']['associated_data'], $cfData['encrypt_certificate']['nonce'], $cfData['encrypt_certificate']['ciphertext'], $this->mch_api_key); file_put_contents($public_key_path, $content); } else { $content = file_get_contents($public_key_path); } return $content; } /** * 业务编号 * @return string */ public function getBusinessCode() { return date('Ymd') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99)); } /** * 随机字符串 * @param int $length * @return string */ private function getNonceStr($length = 16) { // 密码字符集,可任意添加你需要的字符 $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $str = ""; for ($i = 0; $i < $length; $i++) { $str .= $chars[mt_rand(0, strlen($chars) - 1)]; } return $str; } /** * @param string $method * @param string $url * @param array|string $data * @param array $headers * @param int $timeout * @return bool|string */ private function getCurl($method = 'GET', $url, $data, $headers = [], $timeout = 30) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_SSLVERSION, false); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); if (!empty($headers)) { curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } if ($method == 'POST') { curl_setopt($curl, CURLOPT_POST, TRUE); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); } $result = curl_exec($curl); curl_close($curl); return $result; } /** * Decrypt AEAD_AES_256_GCM ciphertext(官方案例-已改造) * * @param string $associatedData AES GCM additional authentication data * @param string $nonceStr AES GCM nonce * @param string $ciphertext AES GCM cipher text * * @return string|bool Decrypted string on success or FALSE on failure */ private function decryptToString($associatedData, $nonceStr, $ciphertext, $aesKey) { $auth_tag_length_byte = 16; $ciphertext = \base64_decode($ciphertext); if (strlen($ciphertext) <= $auth_tag_length_byte) { return false; } // ext-sodium (default installed on >= PHP 7.2) if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) { return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey); } // ext-libsodium (need install libsodium-php 1.x via pecl) if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) { return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $aesKey); } // openssl (PHP >= 7.1 support AEAD) if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) { $ctext = substr($ciphertext, 0, -$auth_tag_length_byte); $authTag = substr($ciphertext, -$auth_tag_length_byte); return \openssl_decrypt($ctext, 'aes-256-gcm', $aesKey, \OPENSSL_RAW_DATA, $nonceStr, $authTag, $associatedData); } throw new \Exception('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php'); } /** * 获取证书编号(官方案例-已改造) * @param $certificate * @return string */ private function parseSerialNo($certificate) { $info = \openssl_x509_parse($certificate); if (!isset($info['serialNumber']) && !isset($info['serialNumberHex'])) { throw new \InvalidArgumentException('证书格式错误'); } $serialNo = ''; // PHP 7.0+ provides serialNumberHex field if (isset($info['serialNumberHex'])) { $serialNo = $info['serialNumberHex']; } else { // PHP use i2s_ASN1_INTEGER in openssl to convert serial number to string, // i2s_ASN1_INTEGER may produce decimal or hexadecimal format, // depending on the version of openssl and length of data. if (\strtolower(\substr($info['serialNumber'], 0, 2)) == '0x') { // HEX format $serialNo = \substr($info['serialNumber'], 2); } else { // DEC format $value = $info['serialNumber']; $hexvalues = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; while ($value != '0') { $serialNo = $hexvalues[\bcmod($value, '16')] . $serialNo; $value = \bcdiv($value, '16', 0); } } } return \strtoupper($serialNo); } //获取网络图片到临时目录 public function saveTempImage($url) { if (strpos($url, 'http') === false) { $url = 'http:' . trim($url); } if (!is_dir(\Yii::$app->runtimePath . '/image')) { mkdir(\Yii::$app->runtimePath . '/image'); } $save_path = \Yii::$app->runtimePath . '/image/' . md5($url) . '.jpg'; CurlHelper::download($url, $save_path); return $save_path; } }