准备资料
商户mchid
商户API v3密钥(微信服务商-账户中心-API安全 api v3密钥 pay.weixin.qq.com/index.php/c…)
证书编号 (apiclient_cert.pem证书解析后获得)
支付平台公钥(接口获取)
<?php
declare (strict_types=1);
namespace app\common\service;
use app\common\enum\wechat\ActivitiesRateEnum;
use app\common\enum\wechat\ContactType;
use app\common\enum\wechat\SubjectTypeEnum;
use app\common\model\Region;
use app\common\model\SubMerchant;
use app\common\model\WxServerSetting;
/**
* 微信服务商V3
*/
class WxpaymchService
{
// 错误信息
private $error = '';
// 商户mchid
private $mch_id = '';
// 商户API v3密钥(微信服务商-账户中心-API安全 api v3密钥 https://pay.weixin.qq.com/index.php/core/cert/api_cert)
private $mch_api_key = '';
// 证书编号 (apiclient_cert.pem证书解析后获得)
private $serial_no = '';
// 私钥 apiclient_key.pem(微信服务商-账户中心-API安全 自行下载 https://pay.weixin.qq.com/index.php/core/cert/api_cert)
private $mch_private_key = '';
// 支付平台公钥(接口获取)
private $public_key_path = 'cert_ficates_v3.pem';
public function __construct($store_id)
{
$this->store_id = $store_id;
// 查询微信服务器设置
$wechat_sp = WxServerSetting::where(['store_id' => 10001])->find();
// 如果存在证书
if ($wechat_sp->cert_pem) {
// 设置公钥路径
$this->public_key_path = root_path('static/wechat') . 'public_key.pem';
}
// 如果存在私钥
if ($wechat_sp->key_pem) {
// 设置商户私钥路径
$this->mch_private_key = root_path('static/wechat') . 'apiclient_key.pem';
}
// 设置商户号
$this->mch_id = '1695987447';
// $this->mch_id = $wechat_sp->sp_mch_id;
// 设置商户API密钥
$this->mch_api_key = "zhaotonsoutekejiyouxiangonsi2233";
// 设置序列号
$this->serial_no = "4CD591572D80EBFDC8E41C0CCAEF82E1BCDBA40A";
// 设置服务应用ID
$this->service_app_id = "wxb4b7d70412c85bb2";
}
// public function getW()
// {
// // 设置参数
//// 商户号
// $merchantId = '1679974523';
//
//// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
// $merchantPrivateKeyFilePath = "file:///" . root_path('static/wechat') . 'apiclient_key.pem';
// $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
//
//// 「商户API证书」的「证书序列号」
// $merchantCertificateSerial = $this->serial_no;
//
//// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
// $platformCertificateFilePath = "file:///" . root_path('static/wechat') . 'cert.pem';
// $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
//
//// 从「微信支付平台证书」中获取「证书序列号」
// $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
//
//// 构造一个 APIv3 客户端实例
// $instance = Builder::factory([
// 'mchid' => $merchantId,
// 'serial' => $merchantCertificateSerial,
// 'privateKey' => $merchantPrivateKeyInstance,
// 'certs' => [
// $platformCertificateSerial => $platformPublicKeyInstance,
// ],
// ]);
//
//// 发送请求
// $resp = $instance->chain('v3/certificates')->get(
// /** @see https://docs.guzzlephp.org/en/stable/request-options.html#debug */
// // ['debug' => true] // 调试模式
// );
// echo (string)$resp->getBody(), PHP_EOL;
// }
/*
* 微信特约商户进件接口
*/
public function subApplyment(array $params, $user)
{
$subMerchant = new SubMerchant();
$sub_merchant = $subMerchant->where('store_id', $this->store_id)->find();
$sub_merchant_data = [
'store_id' => $this->store_id,
'type' => 1,
'admin_id' => $user->user_id,
'business_code' => $this->getBusinessCode(),
];
if (ContactType::LEGAL == $params['contact_info']['contact_type']) {
$sub_merchant_data['contact_type'] = ContactType::LEGAL;
$sub_merchant_data['contact_name'] = $params['contact_info']['contact_name'];
$sub_merchant_data['contact_id_number'] = $params['contact_info']['contact_id_number'];
$sub_merchant_data['mobile_phone'] = $params['contact_info']['mobile_phone'];
$sub_merchant_data['contact_email'] = $params['contact_info']['contact_email'];
} else {
$sub_merchant_data['contact_id_doc_type'] = $params['contact_info']['contact_id_doc_type'];
$sub_merchant_data['contact_id_doc_copy'] = $params['contact_info']['contact_id_doc_copy'];
$sub_merchant_data['contact_id_doc_copy_back'] = $params['contact_info']['contact_id_doc_copy_back'];
$sub_merchant_data['contact_period_begin'] = $params['contact_info']['contact_period_begin'];
$sub_merchant_data['contact_period_end'] = $params['contact_info']['contact_period_end'];
$sub_merchant_data['business_authorization_letter'] = $params['contact_info']['business_authorization_letter'];
}
$sub_merchant_data['subject_type'] = $params['subject_info']['subject_type'];
$sub_merchant_data['license_copy'] = $params['subject_info']['business_license_info']['license_copy'];
$sub_merchant_data['license_number'] = $params['subject_info']['business_license_info']['license_number'];
$sub_merchant_data['merchant_name'] = $params['subject_info']['business_license_info']['merchant_name'];
$sub_merchant_data['legal_person'] = $params['subject_info']['business_license_info']['legal_person'];
$sub_merchant_data['license_address'] = $params['subject_info']['business_license_info']['license_address'] ?? "";
$sub_merchant_data['period_begin'] = $params['subject_info']['business_license_info']['period_begin'] ?? "";
$sub_merchant_data['period_end'] = $params['subject_info']['business_license_info']['period_end'] ?? "";
$sub_merchant_data['merchant_shortname'] = $params['business_info']['merchant_shortname'];
$sub_merchant_data['service_phone'] = $params['business_info']['service_phone'];
$sub_merchant_data['sales_scenes_type'] = $params['business_info']['sales_info']['sales_scenes_type'];
$sub_merchant_data['qualification_type'] = $params['settlement_info']['qualification_type'];
$sub_merchant_data['activities_additions'] = $params['settlement_info']['activities_additions'];
$sub_merchant_data['activities_id'] = '20191030111cff5b5e';
$sub_merchant_data['activities_rate'] = ActivitiesRateEnum::PUBLIC_RATE;
$sub_merchant_data['bank_account_type'] = $params['bank_account_info']['bank_account_type'];
$sub_merchant_data['account_name'] = $params['bank_account_info']['account_name'];
$sub_merchant_data['account_bank'] = $params['bank_account_info']['account_bank'];
$sub_merchant_data['bank_address_code'] = Region::getById($params['district_id']) ? Region::getById($params['district_id'])['code'] : $params['bank_address_code'];
$sub_merchant_data['account_number'] = $params['bank_account_info']['account_number'];
$sub_merchant_data['id_doc_type'] = $params['subject_info']['identity_info']['id_doc_type'];
$sub_merchant_data['id_card_copy'] = $params['subject_info']['identity_info']['id_card_info']['id_card_copy'];
$sub_merchant_data['id_card_national'] = $params['subject_info']['identity_info']['id_card_info']['id_card_national'];
$sub_merchant_data['id_card_name'] = $params['subject_info']['identity_info']['id_card_info']['id_card_name'];
$sub_merchant_data['id_card_number'] = $params['subject_info']['identity_info']['id_card_info']['id_card_number'];
$sub_merchant_data['card_period_begin'] = $params['subject_info']['identity_info']['id_card_info']['card_period_begin'];
$sub_merchant_data['card_period_end'] = $params['subject_info']['identity_info']['id_card_info']['card_period_end'];
$sub_merchant_data['id_card_address'] = $params['subject_info']['identity_info']['id_card_info']['id_card_address'];
$sub_merchant_data['owner'] = $params['subject_info']['identity_info']['owner'];
if (!$sub_merchant_data['owner']) {
$sub_merchant_data['ubo_id_doc_type'] = $params['subject_info']['identity_info']['ubo_info']['ubo_id_doc_type'];
$sub_merchant_data['ubo_id_doc_copy'] = $params['subject_info']['identity_info']['ubo_info']['ubo_id_doc_copy'];
$sub_merchant_data['ubo_id_doc_copy_back'] = $params['subject_info']['identity_info']['ubo_info']['ubo_id_doc_copy_back'];
$sub_merchant_data['ubo_id_doc_name'] = $params['subject_info']['identity_info']['ubo_info']['ubo_id_doc_name'];
$sub_merchant_data['ubo_id_doc_number'] = $params['subject_info']['identity_info']['ubo_info']['ubo_id_doc_number'];
$sub_merchant_data['ubo_id_doc_address'] = $params['subject_info']['identity_info']['ubo_info']['ubo_id_doc_address'];
$sub_merchant_data['ubo_period_begin'] = $params['subject_info']['identity_info']['ubo_info']['ubo_period_begin'];
$sub_merchant_data['ubo_period_end'] = $params['subject_info']['identity_info']['ubo_info']['ubo_period_end'];
}
if ($sub_merchant) {
$sub_merchant->where('store_id', $this->store_id)->update($sub_merchant_data);
} else {
$sub_merchant = SubMerchant::create($sub_merchant_data);
}
// 参数准备
$data = [
// //业务申请编号
'business_code' => $this->getBusinessCode(),
//超级管理员信息
'contact_info' => [
'contact_type' => $params['contact_info']['contact_type'],//超级管理员类型LEGAL:经营者/法人- SUPER:经办人
'contact_name' => $this->getEncrypt($params['contact_info']['contact_name']),//超级管理员姓名
'contact_id_number' => $this->getEncrypt($params['contact_info']['contact_id_number']),//超级管理员身份证件号码
'mobile_phone' => $this->getEncrypt($params['contact_info']['mobile_phone']),//联系手机
'contact_email' => $this->getEncrypt($params['contact_info']['contact_email']),//联系邮箱
// 'contact_id_doc_type' => $params['contact_info']['contact_id_doc_type'],//超级管理员证件类型
// 'contact_id_doc_copy' => $this->mediaUpload($params['contact_info']['contact_id_doc_copy']),//超级管理员证件正面照片
// 'contact_id_doc_copy_back' => $this->mediaUpload($params['contact_info']['contact_id_doc_copy_back']),//超级管理员证件反面照片
// 'contact_period_begin' => $params['contact_info']['contact_period_begin'],//超级管理员证件有效期开始时间
// 'contact_period_end' => $params['contact_info']['contact_period_end'],//超级管理员证件有效期结束时间
// 'business_authorization_letter' => $this->mediaUpload(['contact_info']['business_authorization_letter']),//业务办理授权函
],
//主体资料
'subject_info' => [
//主体类型SUBJECT_TYPE_INDIVIDUAL(个体户)SUBJECT_TYPE_ENTERPRISE(企业)SUBJECT_TYPE_INSTITUTIONS(党政、机关及事业单位)SUBJECT_TYPE_OTHERS(其他组织)
'subject_type' => $params['subject_info']['subject_type'],
//营业执照
'business_license_info' => [
'license_copy' => $this->mediaUpload($params['subject_info']['business_license_info']['license_copy']),//营业执照照片-1张
'license_number' => $params['subject_info']['business_license_info']['license_number'],//注册号/统一社会信用代码-格式须为18位数字|大写字母
'merchant_name' => $params['subject_info']['business_license_info']['merchant_name'],//商户名称
'legal_person' => $params['subject_info']['business_license_info']['legal_person'],//法人姓名
/*'license_address' => '上海市松江工业区俞塘路512号4幢3层B319室',//注册地址
'period_begin' => '2019-12-11',//有效期限开始日期-2019-12-11
'period_end' => '2039-12-10',//有效期限结束日期-2039-12-10*/
],
//经营者/法人身份证件
'identity_info' => [
'id_doc_type' => $params['subject_info']['identity_info']['id_doc_type'],//证件类型
'owner' => $params['subject_info']['identity_info']['owner'],//经营者/法人是否为受益人
//身份证信息
'id_card_info' => [
'id_card_copy' => $this->mediaUpload($params['subject_info']['identity_info']['id_card_info']['id_card_copy']),
'id_card_national' => $this->mediaUpload($params['subject_info']['identity_info']['id_card_info']['id_card_national']),
'id_card_name' => $this->getEncrypt($params['subject_info']['identity_info']['id_card_info']['id_card_name']),
'id_card_number' => $this->getEncrypt($params['subject_info']['identity_info']['id_card_info']['id_card_number']),
'card_period_begin' => $params['subject_info']['identity_info']['id_card_info']['card_period_begin'],
'card_period_end' => $params['subject_info']['identity_info']['id_card_info']['card_period_end'],
'id_card_address' => $this->getEncrypt($params['subject_info']['identity_info']['id_card_info']['id_card_address']),
],
],
//-最终受益人信息列表(UBO)-仅企业需要填写。
/*'ubo_info_list' => [
[
'ubo_id_doc_address' => $this->getEncrypt($params['id_card_address']),
'ubo_id_doc_copy' => $params['id_card_copy'],
'ubo_id_doc_copy_back' => $params['id_card_national'],
'ubo_id_doc_name' => $this->getEncrypt($params['id_card_name']),
'ubo_id_doc_number' => $this->getEncrypt($params['id_card_number']),
'ubo_id_doc_type' => 'IDENTIFICATION_TYPE_IDCARD',//证件类型
'ubo_period_begin' => $params['card_period_begin'],
'ubo_period_end' => $params['card_period_end'],
]
],*/
],
//经营资料
'business_info' => [
'merchant_shortname' => $params['business_info']['merchant_shortname'],
'service_phone' => $params['business_info']['service_phone'],
'sales_info' => [
'sales_scenes_type' => $params['business_info']['sales_info']['sales_scenes_type'],
// //线下门店场景
// 'biz_store_info' => [
// 'biz_store_name' => $params['biz_store_name'],
// 'biz_address_code' => $params['biz_address_code'],
// 'biz_store_address' => $params['biz_store_address'],
// 'store_entrance_pic' => [$params['store_entrance_pic']],
// 'indoor_pic' => [$params['indoor_pic']],
// //'biz_sub_appid' => $params['biz_sub_appid'],
// ],
],
],
//结算规则
'settlement_info' => [
// $settlement_info['activities_id'] = '20191030111cff5b5e',
// $settlement_info['activities_rate'] = ActivitiesRateEnum::PUBLIC_RATE,
// 'settlement_id' => $params['settlement_info']['settlement_id'],
'qualification_type' => $params['settlement_info']['qualification_type'],
'activities_additions' => $params['settlement_info']['activities_additions'],
'activities_id' => '20191030111cff5b5e',
'activities_rate' => ActivitiesRateEnum::PUBLIC_RATE,
// 'qualifications' => $params['settlement_info']['qualifications']
],
//结算银行账户
'bank_account_info' => [
'bank_account_type' => $params['bank_account_info']['bank_account_type'],
'account_name' => $this->getEncrypt($params['bank_account_info']['account_name']),
'account_bank' => $params['bank_account_info']['account_bank'],
'bank_address_code' => Region::getById($params['district_id']) ? Region::getById($params['district_id'])['code'] : $params['bank_address_code'],
// 'bank_name' => $params['bank_account_info']['bank_name'],
'account_number' => $this->getEncrypt($params['bank_account_info']['account_number']),
],
//补充材料
/*'addition_info'=>[
'legal_person_commitment'=>$params['legal_person_commitment'],
'business_addition_pics'=>$params['business_addition_pics'],
],*/
];
// //没有优惠费率
// if ($params['qualifications'] == '') {
// unset($data['settlement_info']['qualifications']);
// //unset($data['settlement_info']['activities_id']);
// }
// if ($params['contact_type'] == 'LEGAL') {
// unset($data['contact_info']['contact_id_doc_type'],
// $data['contact_info']['contact_id_doc_copy'],
// $data['contact_info']['contact_id_doc_copy_back'],
// $data['contact_info']['contact_period_begin'],
// $data['contact_info']['contact_period_end'],
// $data['contact_info']['business_authorization_letter']
// );
// }
$url = 'https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/';
// 获取支付平台证书编码(也可以用接口中返回的serial_no 来源:https://api.mch.weixin.qq.com/v3/certificates)
$serial_no = $this->parseSerialNo($this->getCertFicates());
// $serial_no = '7E516EBA4EF81D464E28434EE82EF62D72056949';
foreach ($params['business_info']['sales_info']['sales_scenes_type'] as $v) {
if ($v == 'SALES_SCENES_MINI_PROGRAM') { // 小程序
$data['business_info']['sales_info']['mini_program_info']['mini_program_appid'] = $params['business_info']['sales_info']['mini_program_info']['mini_program_appid'];
}
if ($v == 'SALES_SCENES_MP') { // 公众号
$data['business_info']['sales_info']['mp_info'] = $params['business_info']['sales_info']['mp_info'];
foreach ($params['business_info']['sales_info']['mp_info']['mp_pics'] as $key => $item) {
$data['business_info']['sales_info']['mp_info']['mp_pics'][$key] = $this->mediaUpload($item);
}
}
if ($v == 'SALES_SCENES_APP') { // app
// $business_info['sales_info']['app_info']['app_sub_appid'] = $wechat->app_id;
}
}
$data ['settlement_info']['settlement_id'] = '';
if ($params['subject_info']['subject_type'] == SubjectTypeEnum::SUBJECT_TYPE_INDIVIDUAL) {
$data ['settlement_info']['settlement_id'] = '719';
} else if ($params['subject_info']['subject_type'] == SubjectTypeEnum::SUBJECT_TYPE_ENTERPRISE) {
$data ['settlement_info']['settlement_id'] = '716';
} else {
$data ['settlement_info']['settlement_id'] = '716';
}
//$serial_no = $this->serial_no_new;
$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
];
// dd($header);
$json = $this->getCurl('POST', $url, $bodyData, $header);
$data = json_decode($json, true);
// if (isset($data['code']) && isset($data['message'])) {
// return ['code' => '201', 'msg' => '[subApplyment]请求错误 code:' . $data['code'] . ' msg:' . $data['message'], 'data' => ''];
// }
// if (empty($applyment_id = $data['applyment_id'])) {
// return ['code' => '202', 'msg' => '[subApplyment]返回错误', 'data' => ''];
// }
$data['sub_merchant'] = $sub_merchant;
return $data;
// if ($applyment_id = $data['applyment_id']) {
// return 'xxxxx';
// return json(['code' => '200', 'msg' => '资料提交成功', 'data' => $applyment_id]);
// }
}
/**
* 业务编号
* @return string
*/
public function getBusinessCode()
{
return date('Ymd') . substr((string)time(), -5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));
}
/**
* 敏感字符加密
* @param $str
* @return string
* @throws Exception
*/
// private function getEncrypt($str)
// {
//// dd($str);
// static $content;
// if (empty($content)) {
// $content = $this->getCertFicates();
// }
// $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;
// }
private function getEncrypt($str)
{
//$str是待加密字符串
$public_key_path = $this->public_key_path;
$public_key = file_get_contents($public_key_path);
$encrypted = '';
if (openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
//base64编码
$sign = base64_encode($encrypted);
} else {
throw new Exception('encrypt failed');
}
return $sign;
}
/**
* 上传文件
*/
public function mediaUpload($filepath)
{
// 上传图片
$filename = date("YmdHis") . rand(100, 999) . '.png';
//$filepath = __DIR__ . '/' . $filename;
/*if (!file_exists($filepath)) {
//$this->error = '[mediaUpload]文件找不到';
return ['code' => '201', 'msg' => '缺少img参数2', 'data' => $filepath];
}*/
$url = 'https://api.mch.weixin.qq.com/v3/merchant/media/upload';
//$fi = new \finfo(FILEINFO_MIME_TYPE);
//$mime_type = $fi->file($filepath);
$mime_type = 'image/png';
$meta = [
'filename' => $filename,
'sha256' => hash_file('sha256', $filepath)
];
// 获取认证信息
$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
];
// 组合参数
$boundaryStr = "--{$boundary}\r\n";
$out = $boundaryStr;
$out .= 'Content-Disposition: form-data; name="meta"' . "\r\n";
$out .= 'Content-Type: application/json' . "\r\n";
$out .= "\r\n";
$out .= json_encode($meta) . "\r\n";
$out .= $boundaryStr;
$out .= 'Content-Disposition: form-data; name="file"; filename="' . $filename . '"' . "\r\n";
$out .= 'Content-Type: ' . $mime_type . ';' . "\r\n";
$out .= "\r\n";
$out .= file_get_contents($filepath) . "\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' => '201', 'msg' => '[mediaUpload]请求错误 code:' . $data['code'] . ' msg:' . $data['message'], 'data' => ''];
}
if (empty($media_id = $data['media_id'])) {
return ['code' => '201', 'msg' => '[mediaUpload]返回错误', 'data' => $data];
}
return $media_id;
}
/**
* 获取认证信息
* @param string $url
* @param string $http_method
* @param string $body
* @return string
* @throws Exception
*/
private 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 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 = 10)
{
$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_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);
} else {
}
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
/**
* 获取证书编号(官方案例-已改造)
* @param $certificate
* @return string
*/
public 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);
}
/**
* 获取支付平台证书
* @return false|string-private
*/
public function getCertFicates()
{
$public_key_path = $this->public_key_path;
if (!file_exists($public_key_path)) {
$cfData = $this->certFicates();
//dump($cfData);
$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;
}
/**
* 获取微信支付平台证书
*/
public function certFicates()
{
$url = 'https://api.mch.weixin.qq.com/v3/certificates';
// 获取认证信息
$authorization = $this->getAuthorization($url);
$header = [
'Content-Type:application/json',
'Accept:application/json',
'User-Agent:*/*',
'Authorization:' . $authorization
];
$json = $this->getCurl('GET', $url, '', $header);
$data = json_decode($json, true);
if (isset($data['code']) && isset($data['message'])) {
$this->error = '[certFicates]请求错误 code:' . $data['code'] . ' msg:' . $data['message'];
return false;
}
if (empty($cfdata = $data['data'][0])) {
$this->error = '[certFicates]返回错误';
return false;
}
return $cfdata;
}
/**
* 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');
}
/**
* 进件查询
*/
public function queryApplyment($business_code)
{
$url = 'https://api.mch.weixin.qq.com/v3/applyment4sub/applyment/business_code/' . $business_code;
// 获取认证信息
$authorization = $this->getAuthorization($url);
$header = [
'Content-Type:application/json',
'Accept:application/json',
'User-Agent:*/*',
'Authorization:' . $authorization
];
$json = $this->getCurl('GET', $url, '', $header);
$data = json_decode($json, true);
if (isset($data['code']) && isset($data['message'])) {
// $this->error = '[queryApplyment]请求错误 code:' . $data['code'] . ' msg:' . $data['message'];
return ['code' => '201', 'msg' => '[queryApplyment]请求错误 code:' . $data['code'] . ' msg:' . $data['message'], 'data' => $data];
}
return ['code' => '200', 'msg' => '', 'data' => $data];
}
public function getError()
{
return $this->error;
}
}
入库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for yoshop_sub_merchant
-- ----------------------------
DROP TABLE IF EXISTS `yoshop_sub_merchant`;
CREATE TABLE `yoshop_sub_merchant` (
`id` int NOT NULL AUTO_INCREMENT,
`admin_id` int NULL DEFAULT NULL,
`type` tinyint NULL DEFAULT 1 COMMENT '商户类型,1:微信,2:支付宝',
`business_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '业务申请编号',
`contact_name` varchar(5) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员姓名',
`contact_id_number` varchar(22) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员身份证件号码',
`mobile_phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系手机',
`contact_email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系邮箱',
`subject_type` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '主体类型',
`id_doc_type` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '证件类型',
`id_card_copy` varchar(260) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证人像面照片',
`id_card_copy_img` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证人像面照片',
`id_card_national` varchar(260) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证国徽面照片',
`id_card_national_img` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证国徽面照片',
`id_card_name` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证姓名',
`id_card_number` varchar(22) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证号码',
`card_period_begin` char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证有效期开始时间-示例值:2026-06-06',
`card_period_end` char(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证有效期结束时间',
`merchant_shortname` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商户简称',
`service_phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客服电话',
`sales_scenes_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '经营场景类型',
`biz_store_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '线下场所名称',
`biz_address_code` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '线下场所省市编码-示例值:440305',
`biz_store_address` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '线下场所地址-示例值:南山区xx大厦x层xxxx室',
`store_entrance_pic` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '线下场所门头照片',
`store_entrance_pic_img` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '线下场所门头照片',
`indoor_pic` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '线下场所内部照片',
`indoor_pic_img` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '线下场所内部照片',
`settlement_id` varchar(5) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '入驻结算规则ID-719:个体,716:企业',
`qualification_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所属行业',
`activities_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '优惠费率活动ID',
`activities_rate` float(10, 1) NULL DEFAULT NULL COMMENT '优惠费率活动值',
`qualifications` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '特殊资质图片,最多可上传5张照片《食品经营许可证》或《餐饮服务许可证》',
`qualifications_img` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '特殊资质图片,最多可上传5张照片《食品经营许可证》或《餐饮服务许可证》',
`bank_account_type` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '结算银行账户类型',
`account_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开户名称-选择“对公银行账户”时,开户名称必须与营业执照上的“商户名称”一致',
`account_bank` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开户银行',
`bank_address_code` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开户银行省市编码',
`bank_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开户银行全称(含支行)',
`account_number` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '银行账号',
`state` tinyint NULL DEFAULT 0 COMMENT '审核状态,0:待审核,1:确认审核,2:审核失败',
`created_time` datetime NULL DEFAULT NULL,
`updated_time` datetime NULL DEFAULT NULL,
`merchant_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商户名称',
`legal_person` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '法人姓名',
`license_number` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '注册号/统一社会信用代码',
`license_copy` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '营业执照',
`license_copy_img` varchar(260) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '营业执照',
`biz_sub_appid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`id_card_address` varchar(260) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证居住地址-主体类型为企业时,需要填写',
`contact_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员类型',
`contact_id_doc_type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员证件类型',
`contact_id_doc_copy` varchar(260) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员证件正面照片',
`contact_id_doc_copy_back` varchar(260) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员证件反面照片',
`contact_period_begin` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员证件有效期开始时间',
`contact_period_end` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '超级管理员证件有效期结束时间',
`business_authorization_letter` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '业务办理授权函',
`contact_id_doc_copy_img` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`contact_id_doc_copy_back_img` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`business_authorization_letter_img` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`applyment_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`store_id` int NULL DEFAULT NULL,
`license_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`period_begin` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`period_end` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`activities_additions` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`owner` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_id_doc_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_id_doc_copy` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_id_doc_copy_back` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_id_doc_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_id_doc_number` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_id_doc_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_period_begin` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`ubo_period_end` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;