开发框架:thinkphp6
Api封装类:extend/library/WxPay.php
<?php
namespace library;
class WxPay
{
//appId
private $appId;
//appSecret
private $appSecret;
//商户号
private $mchId;
//商户密钥
private $mchKey;
//本地路径
private $path;
//证书文件
private $sslCert = 'apiclient_cert.pem';
private $sslKey = 'apiclient_key.pem';
private $platCert = 'plat_cert.json';
//api地址
private $rootUrl = 'https://api.mch.weixin.qq.com';
//api证书序列号
private $serialNo;
//商户私钥
private $privateKey;
//v3密钥
private $platKey;
//平台证书序列号
private $platSerialNo;
//平台密钥
private $platPublicKey;
//加密方式
private $signAlg = 'sha256WithRSAEncryption';
private $schema = 'WECHATPAY2-SHA256-RSA2048';
//主机头
private $httpHeader;
//请求类型
private $method = 'GET';
//api接口
private $panel = '';
//随机字符串
public $nonce = '';
//时间戳
public $stamp = 0;
//报文主体
public $body = '';
//签名
public $signature = '';
/**
* 构造函数
*/
public function __construct(array $config)
{
$this->appId = isset($config['appId']) ? $config['appId'] : '';
$this->appSecret = isset($config['appSecret']) ? $config['appSecret'] : '';
$this->mchId = isset($config['mchId']) ? $config['mchId'] : '';
$this->mchKey = isset($config['mchKey']) ? $config['mchKey'] : '';
$this->path = str_replace('\\', '/', ROOT_PATH);
$this->sslCert = $this->path . (isset($config['sslCert']) ? $config['sslCert'] : $this->sslCert);
$this->sslKey = $this->path . (isset($config['sslKey']) ? $config['sslKey'] : $this->sslKey);
$this->platCert = $this->path . (isset($config['platCert']) ? $config['platCert'] : $this->platCert);
$folder = dirname($this->platCert);
if (!is_dir($folder)) {
mkdir($folder, 0755, true);
}
$this->serialNo = isset($config['serialNo']) ? $config['serialNo'] : '';
$this->platKey = isset($config['platKey']) ? $config['platKey'] : '';
$this->getPrivateKey();
$this->getCertificates();
}
/**
* 预支付
*/
public function prepay(array $data)
{
$data['appid'] = $this->appId;
$data['mchid'] = $this->mchId;
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->method = 'POST';
$this->panel = '/v3/pay/transactions/jsapi';
$this->body = $data;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->post($url, $data), true);
return $result;
}
/**
* JSAPI支付
*/
public function invoke($preid)
{
$this->body = 'prepay_id=' . $preid;
$this->buildSignature();
$data = array(
'timeStamp' => $this->stamp,
'nonceStr' => $this->nonce,
'package' => $this->body,
'signType' => 'RSA',
'paySign' => $this->signature,
);
return $data;
}
/**
* 原生支付
*/
public function native(array $data)
{
$data['sp_appid'] = $this->appId;
$data['sp_mchid'] = $this->mchId;
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->method = 'POST';
$this->panel = '/v3/pay/partner/transactions/native';
$this->body = $data;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->post($url, $data), true);
return $result;
}
/**
* 合单
*/
public function combine(array $data)
{
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->method = 'POST';
$this->panel = '/v3/combine-transactions/native';
$this->body = $data;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->post($url, $data), true);
return $result;
}
/**
* 退款
*/
public function refund(array $data)
{
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->method = 'POST';
$this->panel = '/v3/refund/domestic/refunds';
$this->body = $data;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->post($url, $data), true);
return $result;
}
/**
* 余额查询
*/
public function balance($sub_mchid)
{
$this->method = 'GET';
$this->panel = '/v3/ecommerce/fund/balance/' . $sub_mchid;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->get($url), true);
return $result;
}
/**
* 交易账单下载
*/
public function bill($sub_mchid, $date = '')
{
if (!$date) {
$date = date('Y-m-d', strtotime('-1 day'));
}
$this->method = 'GET';
$this->panel = '/v3/bill/tradebill?bill_date=' . $date . '&sub_mchid=' . $sub_mchid . '&bill_type=ALL';
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->get($url), true);
if (isset($result['download_url'])) {
$this->panel = str_replace($this->rootUrl, '', $result['download_url']);
$this->buildHeader();
$result = $this->get($result['download_url']);
}
return $result;
}
/**
* 上传图片
*/
public function upload($file)
{
if (!$file && is_file($file)) {
return false;
}
if (!in_array(pathinfo($file, PATHINFO_EXTENSION), array('jpg', 'png', 'bmp'))) {
return false;
}
$file = str_replace('\\', '/', $file);
$info = new \finfo(FILEINFO_MIME_TYPE);
$mime = $info->file($file);
$name = basename($file);
$stream = file_get_contents($file);
$meta = json_encode(array(
'filename' => $name,
'sha256' => hash_file('sha256', $file),
));
$boundary = uniqid();
$this->method = 'POST';
$this->panel = '/v3/merchant/media/upload';
$this->body = $meta;
$this->httpHeader = array(
'Accept: application/json',
'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'],
'Authorization: ' . $this->buildAuthorization(),
'Content-Type: multipart/form-data;boundary=' . $boundary,
);
$url = $this->rootUrl . $this->panel;
$sep = '--' . $boundary . "\r\n";
$out = $sep;
$out .= 'Content-Disposition: form-data; name="meta";' . "\r\n";
$out .= 'Content-Type: application/json' . "\r\n";
$out .= "\r\n";
$out .= $meta . "\r\n";
$out .= $sep;
$out .= 'Content-Disposition: form-data; name="file"; filename="' . $name . '";' . "\r\n";
$out .= 'Content-Type: ' . $mime . "\r\n";
$out .= "\r\n";
$out .= $stream . "\r\n";
$out .= '--' . $boundary . '--' . "\r\n";
$result = json_decode($this->post($url, $out), true);
return $result;
}
/**
* 二级商户进件
*/
public function applyment(array $data)
{
if (!isset($data['id_card_info']['id_card_name'])) {
return false;
}
if (!isset($data['id_card_info']['id_card_number'])) {
return false;
}
if (!isset($data['account_info']['account_name'])) {
return false;
}
if (!isset($data['account_info']['account_number'])) {
return false;
}
if (!isset($data['contact_info']['contact_name'])) {
return false;
}
if (!isset($data['contact_info']['contact_id_card_number'])) {
return false;
}
if (!isset($data['contact_info']['mobile_phone'])) {
return false;
}
if (!isset($data['contact_info']['contact_email'])) {
return false;
}
$data['appid'] = $this->appId;
$data['id_card_info']['id_card_name'] = $this->encrypt($data['id_card_info']['id_card_name']);
$data['id_card_info']['id_card_number'] = $this->encrypt($data['id_card_info']['id_card_number']);
$data['account_info']['account_name'] = $this->encrypt($data['account_info']['account_name']);
$data['account_info']['account_number'] = $this->encrypt($data['account_info']['account_number']);
$data['contact_info']['contact_name'] = $this->encrypt($data['contact_info']['contact_name']);
$data['contact_info']['contact_id_card_number'] = $this->encrypt($data['contact_info']['contact_id_card_number']);
$data['contact_info']['mobile_phone'] = $this->encrypt($data['contact_info']['mobile_phone']);
$data['contact_info']['contact_email'] = $this->encrypt($data['contact_info']['contact_email']);
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->method = 'POST';
$this->panel = '/v3/ecommerce/applyments/';
$this->body = $data;
$this->buildHeader(true);
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->post($url, $data), true);
return $result;
}
/**
* 二级商户审核状态查询
*/
public function auditState($no, $type = 0)
{
if (!$no) {
return false;
}
$this->method = 'GET';
$this->panel = '/v3/ecommerce/applyments/' . ($type ? 'out-request-no/' : '') . $no;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->get($url), true);
return $result;
}
/**
* 添加分账接收方
*/
public function addReceivers(array $data)
{
if (!isset($data['name'])) {
return false;
}
$data['appid'] = $this->appId;
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->method = 'POST';
$this->panel = '/v3/ecommerce/profitsharing/receivers/add';
$this->body = $data;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->post($url, $data), true);
return $result;
}
/**
* 分账
*/
public function divide(array $data)
{
if (!$data && is_array($data)) {
return false;
}
$data['appid'] = $this->appId;
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
$this->method = 'POST';
$this->panel = '/v3/ecommerce/profitsharing/orders';
$this->body = $data;
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->post($url, $data), true);
return $result;
}
/**
* 敏感字段加密
*/
public function encrypt($str)
{
$encrypted = '';
if (openssl_public_encrypt($str, $encrypted, $this->platPublicKey, OPENSSL_PKCS1_OAEP_PADDING)) {
$sign = base64_encode($encrypted);
} else {
throw new Exception('encrypt failed');
}
return $sign;
}
/**
* 报文解密
*/
public function decrypt($text, $assoc, $nonce)
{
$check = extension_loaded('sodium');
if (false === $check) {
return false;
}
$check = sodium_crypto_aead_aes256gcm_is_available();
if (false === $check) {
return false;
}
return sodium_crypto_aead_aes256gcm_decrypt(base64_decode($text), $assoc, $nonce, $this->platKey);
}
/**
* 生成Header
*/
private function buildHeader($usePlatSerial = false)
{
$this->httpHeader = array(
'Accept: application/json',
'User-Agent: ' . $_SERVER['HTTP_USER_AGENT'],
'Content-Type: application/json',
);
if ($usePlatSerial) {
$this->httpHeader[] = 'Wechatpay-Serial: ' . $this->platSerialNo;
}
$this->httpHeader[] = 'Authorization: ' . $this->buildAuthorization();
if ($this->body) {
$this->httpHeader[] = 'Content-Length: ' . strlen($this->body);
}
}
/**
* 生成Authorization
*/
private function buildAuthorization()
{
$this->signer();
$str = 'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"';
$str = sprintf($str, $this->mchId, $this->nonce, $this->stamp, $this->serialNo, $this->signature);
return $this->schema . ' ' . $str;
}
/**
* 签名生成器
*/
private function signer()
{
$this->stamp = time();
$this->nonce = $this->buildNonceStr();
$message = $this->method . "\n" . $this->panel . "\n" . $this->stamp . "\n" . $this->nonce . "\n" . $this->body . "\n";
openssl_sign($message, $signature, $this->privateKey, $this->signAlg);
$this->signature = base64_encode($signature);
}
/**
* 生成JSSDK签名
*/
private function buildSignature()
{
$this->stamp = time();
$this->nonce = $this->buildNonceStr();
$message = $this->appId . "\n" . $this->stamp . "\n" . $this->nonce . "\n" . $this->body . "\n";
openssl_sign($message, $signature, $this->privateKey, $this->signAlg);
$this->signature = base64_encode($signature);
}
/**
* 获取商户私钥
*/
private function getPrivateKey()
{
if (is_file($this->sslKey)) {
$this->privateKey = openssl_pkey_get_private(file_get_contents($this->sslKey));
}
}
/**
* 获取平台证书
*/
private function getCertificates()
{
if (is_file($this->platCert)) {
$json = file_get_contents($this->platCert);
$data = json_decode($json, true);
if (isset($data['serial_no'])) {
$this->platSerialNo = $data['serial_no'];
}
if (isset($data['cipher'])) {
$this->platPublicKey = $data['cipher'];
}
}
if (!($this->platSerialNo && $this->platPublicKey)) {
$this->method = 'GET';
$this->panel = '/v3/certificates';
$this->buildHeader();
$url = $this->rootUrl . $this->panel;
$result = json_decode($this->get($url), true);
if ($result && isset($result['data']) && $result['data']) {
$data = $result['data'][0];
$serial_no = $data['serial_no'];
$cert = $data['encrypt_certificate'];
$cipher = $this->decrypt($cert['ciphertext'], $cert['associated_data'], $cert['nonce']);
$this->platSerialNo = $serial_no;
$this->platPublicKey = $cipher;
$json = json_encode(array(
'serial_no' => $serial_no,
'cipher' => $cipher,
));
file_put_contents($this->platCert, $json);
}
}
}
/*
* 生成随机字符串
*/
private function buildNonceStr($len = 32)
{
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$str = '';
for ($i = 0; $i < $len; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/*
* get请求
*/
private function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->httpHeader);
$out = curl_exec($ch);
curl_close($ch);
return $out;
}
/*
* post请求
*/
private function post($url, $data = '')
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->httpHeader);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* 获取客户端ip
*/
public function getClientIp()
{
if (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
} elseif (getenv('HTTP_X_FORWARDED_FOR')) {
$ip = getenv('HTTP_X_FORWARDED_FOR');
} elseif (getenv('HTTP_X_FORWARDED')) {
$ip = getenv('HTTP_X_FORWARDED');
} elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip = getenv('HTTP_FORWARDED_FOR');
} elseif (getenv('HTTP_FORWARDED')) {
$ip = getenv('HTTP_FORWARDED');
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
}
交易流程:
Step1:上传二级商户营业执照、身份证正反面照片(upload)
Step2:二级商户进件(applyment)
Step3:查询二级商户进件状态,审核通过后邀请二级商户签约(auditState)
Step4:添加分账接收方(addReceivers)
Step5:用户下单付款,指定分账信息(native、combine)
Step6:交易成功发起分账(divide)
使用示例:Sample.php
<?php
namespace app\[module]\controller;
use library\WxPay;
use think\App;
class Sample extends Base
{
protected $app;
private $wxpay;
/**
* 构造方法
*/
public function __construct(App $app)
{
parent::__construct($app);
$this->wxpay = new WxPay(array(
'appId' => '<appId>',
'appSecret' => '<appSecret>',
'mchId' => '<mchId>',
'mchKey' => '<mchKey>',
'serialNo' => '<serialNo>',
'platKey' => '<platKey>',
));
}
/**
* 上传图片示例
*/
public function uploadImage()
{
$file = ROOT_PATH . 'sample.png';
$result = $this->wxpay->upload($file);
var_dump($result);
}
/**
* 二级商户进件示例
*/
public function applyment()
{
$data = array(
'out_request_no' => '<内部编号>',
'organization_type' => '<主体类型>',
'business_license_info' => array(
'business_license_copy' => '<上传营业执照得到的MediaID>',
'business_license_number' => '<营业执照号>',
'merchant_name' => '<商户名称>',
'legal_person' => '<法人姓名>',
),
'id_doc_type' => 'IDENTIFICATION_TYPE_MAINLAND_IDCARD',
'id_card_info' => array(
'id_card_copy' => '<上传身份证正面得到的MediaID>',
'id_card_national' => '<上传身份证背面得到的MediaID>',
'id_card_name' => '<身份证登记姓名>',
'id_card_number' => '<身份证号>',
'id_card_valid_time' => '<身份证有效期>',
),
'need_account_info' => true,
'account_info' => array(
'bank_account_type' => '<银行账号类型>',
'account_bank' => '<开户行>',
'account_name' => '<银行账号开户名>',
'bank_address_code' => '<开户行地区编号>',
'bank_name' => '<银行全称>',
'account_number' => '<银行账号>',
),
'contact_info' => array(
'contact_type' => '64[|65]',
'contact_name' => '<超级管理员姓名>',
'contact_id_card_number' => '<超级管理员身份证号>',
'mobile_phone' => '<超级管理员手机号>',
'contact_email' => '<超级管理员电子信箱>',
),
'sales_scene_info' => array(
'store_name' => '<经营网店名称>',
'store_url' => '<经营网店URL地址>',
),
'merchant_shortname' => '<商户简称>',
);
$result = $this->wxpay->applyment($data);
var_dump($result);
}
/**
* 二级商户审核状态查询示例
*/
public function state()
{
$request_no = '202101010000';
$result = $this->wxpay->auditState($request_no);
var_dump($result);
}
/**
* 添加分账方示例
*/
public function addReceivers()
{
$data = array(
'type' => 'MERCHANT_ID',
'account' => '<二级商户号>',
'name' => '<二级商户全称>',
'relation_type' => 'SUPPLIER',
);
$result = $wxpay->addReceivers($data);
var_dump($result);
}
/**
* 原生支付示例
*/
public function native()
{
$data = array(
'sub_mchid' => '<二级商户的商户号>',
'description' => '<商品描述>',
'out_trade_no' => '<内部订单号>',
'notify_url' => '<通知URL>',
'settle_info' => array(
'profit_sharing' => true,
),
'amount' => array(
'total' => '<订单总金额>',
'currency' => 'CNY',
),
'attach' => '<可选,附加数据>',
);
$result = $wxpay->native($data);
var_dump($result);
}
/**
* 合单支付示例
*/
public function combine()
{
$data = array(
'combine_appid' => '<合单发起方的appid>',
'combine_mchid' => '<合单发起方的商户号>',
'combine_out_trade_no' => '<合单支付总订单号>',
'scene_info' => array(
'payer_client_ip' => $this->wxpay->getClientIp(),
),
'sub_orders' => array(
array(
'mchid' => '<子单发起方的商户号>',
'attach' => '<附加数据>',
'amount' => array(
'total_amount' => 1,
'currency' => 'CNY',
),
'out_trade_no' => '<子单商户订单号>',
'sub_mchid' => '<二级商户号>',
'description' => '<商品描述>',
'settle_info' => array(
'profit_sharing' => true,
),
),
),
'notify_url' => 'https://www.xxx.com/notify.php',
);
$result = $wxpay->combine($data);
var_dump($result);
}
/**
* 退款示例
*/
public function refund()
{
$data = array(
'sub_mchid' => '<二级商户号>',
'transaction_id' => '<微信支付订单号>',
'out_trade_no' => '<内部订单号>',
'out_refund_no' => '<内部退款单号>',
'amount' => array(
'refund' => '<交易总金额>',
'total' => '<退款金额>',
'currency' => 'CNY',
),
);
$result = $wxpay->refund($data);
var_dump($result);
}
/**
* 分账示例
*/
public function divide()
{
$data = array(
'sub_mchid' => '<二级商户号>',
'transaction_id' => '<微信支付订单号>',
'out_order_no' => '<内部订单号>',
'receivers' => array(
array(
'type' => '<分账接收方类型>',
'account' => '<分账接收方账号>',
'amount' => '<分账金额>',
'description' => '<分账描述>',
),
),
'finish' => true,
);
$result = $wxpay->divide($data);
var_dump($result);
}
/**
* 余额查询
*/
public function balance()
{
$result = $wxpay->balance('<二级商户号>');
var_dump($result);
}
/**
* 下载账单
*/
public function bill()
{
$date = date('Y-m-d');
$result = $wxpay->bill('<二级商户号>', $date);
var_dump($result);
}
}