注意事项
下预付单时 务必要添加 profit_sharing
为 Y 否则该笔订单不支持分账。 参考链接 https://pay.weixin.qq.com/wiki/doc/api/allocation.php?chapter=26_3
订单支付成功后不能立刻执行分账逻辑 要任务操作 大于订单支付时间1分钟以上 且有QPS限制 建议做好日志警告 必要时切换到服务商分账(服务商分账QPS限制会比普通商户大)
<?php
namespace app\api\lib\Weixin;
use think\Controller;
class Weixin extends Controller
{
private $sep_url;
private $mch_id;
private $appid;
private $mch_secrect;
function __construct()
{
$this->sep_url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharing';
$this->mch_id = config('wechat.pay_config.mch_id');
$this->appid = config('wechat.pay_config.app_id');
$this->mch_secrect = config('wechat.pay_config.key');
}
function _execSplitAccount($transaction_id, $out_order_no)
{
$receivers = $this->receivers($out_order_no);
if ($receivers['code'] == 0) return ['code' => '分账失败!'];
$tmp_splitting_data = [
'appid' => $this->appid,
'mch_id' => $this->mch_id,
'nonce_str' => $this->getNonceStr(),
'sign_type' => 'HMAC-SHA256',
'transaction_id' => $transaction_id,
'out_order_no' => $out_order_no,
'receivers' => $receivers['res']
];
$tmp_splitting_data['sign'] = $this->makeSign($tmp_splitting_data, $this->mch_secrect);
$xml = $this->arrayToXml($tmp_splitting_data);
$do_arr = $this->curlPostSsl($this->sep_url, $xml);
$result = $this->xmlToArray($do_arr);
return $result;
}
private function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
private function receivers($out_order_no)
{
$out_order = db('split_account_order')
->where(['trade' => $out_order_no, 'status' => 0])
->field('payment, account, shareamount')
->select();
if (!empty($out_order)) {
$receivers_arr = [];
foreach ($out_order as $key => $val) {
$receivers_arr[$key]['type'] = 'PERSONAL_OPENID';
$receivers_arr[$key]['account'] = $val['account'];
$receivers_arr[$key]['amount'] = $val['shareamount'];
$receivers_arr[$key]['description'] = 'payment';
}
return ['code' => 1, 'res' => json_encode($receivers_arr)];
}
return ['code' => 0];
}
private function makeSign($arr, $secret)
{
ksort($arr);
$str = $this->to_url_params($arr);
$str = $str . "&key=" . $secret;
$str = hash_hmac('sha256', $str, $this->mch_secrect);
$result = strtoupper($str);
return $result;
}
private function to_url_params($arr)
{
$str = "";
foreach ($arr as $k => $v) {
if (!empty($v) && ($k != 'sign')) {
$str .= "$k" . "=" . $v . "&";
}
}
$str = rtrim($str, "&");
return $str;
}
private function arrayToXml($arr){
$xml = '<?xml version="1.0" encoding="UTF-8"?><xml>';
foreach ($arr as $key => $val) {
$xml.="<".$key.">$val</".$key.">";
}
$xml.="</xml>";
return $xml;
}
private function xmlToArray($xml){
libxml_disable_entity_loader(true);
$arr= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $arr;
}
function curlPostSsl($url, $vars, $second = 30, $aHeader = array())
{
$isdir = __DIR__ . "/../../../../cert/";
$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, $isdir . 'apiclient_cert.pem');
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, $isdir . 'apiclient_key.pem');
if (count($aHeader) >= 1) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
}
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
$data = curl_exec($ch);
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
echo "call faild, errorCode:$error\n";
curl_close($ch);
return false;
}
}
}