記錄一個使用者微信支付後立即分賬的功能
開發步驟
一.新增分賬接收方
// 新增分賬接收方
public function add_sub(User $user)
{
header("Content-Type:text/html;charset=utf8");
$open_id = $user->open_id;
$request_para = array();
$request_para['mch_id'] = env('MCH_ID');// 商戶號
$request_para['appid'] = env('APP_ID');// 公眾賬號ID
$request_para['nonce_str'] = $this->createNoncestr();// 隨機字串
$request_para['sign_type'] = "HMAC-SHA256";// 簽名型別
$receiver = array(
// MERCHANT_ID:商戶ID
// PERSONAL_WECHATID:個人微訊號
// PERSONAL_OPENID:個人openid
'type' => 'PERSONAL_OPENID',
'account' => $open_id,
'relation_type' => 'STAFF'
);
$request_para["receiver"] = json_encode($receiver);
// 簽名
$request_para["sign"] = $this->appgetSign($request_para, env('MCH_KEY'), false);
//把陣列轉化成xml格式
$xmlData = $this->arrayToXml($request_para, true);
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver';
//傳送請求
$get_data = $this->sendPrePayCurl($xmlData, $url);
\Log::alert('新增分賬接收方返回結果:');
\Log::alert($get_data);
}
//隨機字串
public function createNoncestr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
//格式化引數格式化成url引數 生成簽名sign
public function appgetSign($Obj, $appwxpay_key, $bool = true)
{
foreach ($Obj as $k => $v) {
$Parameters[$k] = $v;
}
//簽名步驟一:按字典序排序引數
ksort($Parameters); // ksort — 對陣列按照鍵名排序
$String = $this->formatBizQueryParaMap($Parameters, false);
//簽名步驟二:在string後加入KEY
if ($appwxpay_key) {
$String = $String . "&key=" . $appwxpay_key;
}
if ($bool) {
//簽名步驟三:MD5加密
$String = md5($String);
} else {
//HMAC-SHA256簽名方式
$String = hash_hmac("sha256", $String, $appwxpay_key);
}
//簽名步驟四:所有字元轉為大寫
$result_ = strtoupper($String);
return $result_;
}
//按字典序排序引數
public function formatBizQueryParaMap($paraMap, $urlencode)
{
$buff = "";
ksort($paraMap);
foreach ($paraMap as $k => $v) {
if ($urlencode) {
$v = urlencode($v); //urlencode — 編碼 URL 字串
}
//$buff .= strtolower($k) . "=" . $v . "&";
$buff .= $k . "=" . $v . "&";
}
$reqPar = '';
if (strlen($buff) > 0) {
$reqPar = substr($buff, 0, strlen($buff) - 1);
}
return $reqPar;
}
//將陣列轉換為xml格式
public function arrayToXml($arr, $bool)
{
$xml = "<xml>";
foreach ($arr as $key => $val) {
if ($bool) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
} else{
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
//傳送請求
public function sendPrePayCurl($xml, $url = '', $options = array())
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
if (!empty($options)) {
curl_setopt_array($ch, $options);
}
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$data = curl_exec($ch);
curl_close($ch);
$data_xml_arr = $this->XMLDataParse($data);
if ($data_xml_arr) {
return $data_xml_arr;
} else {
return false;
}
}
//xml格式資料解析函式
public function XMLDataParse($data)
{
$xml = simplexml_load_string($data, NULL, LIBXML_NOCDATA);
$array = json_decode(json_encode($xml), true);
return $array;
}
二.在統一下單API新增profit_sharing引數
$result = $app->order->unify([
'body' => '支付人民幣' . $money . '元',
'out_trade_no' => $order_no,
'total_fee' => $total_fee,
'notify_url' => env('BACKURL'),
'trade_type' => 'JSAPI',
'openid' => $open_id,
'attach' => $attach,
'profit_sharing' => 'Y',
// Y-是,需要分賬 N-否,不分賬 字母要求大寫,不傳預設不分賬
]);
三.支付完成後,呼叫請求分賬介面,完成分賬
支付成功後,在回撥地址請求單次分賬
public function sub_account($transaction_id,$order,$type)
{
sleep(2);
header("Content-Type:text/html;charset=utf8");
$newPara = array();
// 公眾賬號ID
$newPara["appid"] = env('APP_ID');
// 商戶號
$newPara["mch_id"] = env('MCH_ID');
// 隨機字串
$newPara["nonce_str"] = $this->createNoncestr();
// 商戶分賬單號
$out_order_no = mt_rand(100000,999999).time().mt_rand(100,999);
$newPara["out_order_no"] = $out_order_no;
// 微信訂單號
$newPara["transaction_id"] = $transaction_id;
//簽名型別 HMAC-SHA256
$newPara["sign_type"] = "HMAC-SHA256";
$user = User::where('is_sub',1)->select(['id','open_id','proportion'])->get();
// 分賬接收方列表
$receivers = [];
foreach ($user as $k => $val) {
if($order->money >= 1){
// 扣除0.6%微信手續費
$money = floor($order->money * 0.994 * $val->proportion); // 捨去法取整
}else{
$money = floor($order->money * $val->proportion); // 捨去法取整
}
$receivers[$k]['type'] = 'PERSONAL_OPENID';
$receivers[$k]['account'] = $val->open_id;
$receivers[$k]['amount'] = $money;
$receivers[$k]['description'] = '';
}
$newPara["receivers"] = json_encode($receivers);
// 簽名
$newPara["sign"] = $this->appgetSign($newPara, env('MCH_KEY'), false);
//把陣列轉化成xml格式
$xmlData = $this->arrayToXml($newPara, true);
$options = array(
CURLOPT_SSLCERT => '/www/wwwroot/supliao/cert/apiclient_cert.pem',// 客戶端證照,用於雙向認證
CURLOPT_SSLCERTTYPE => 'PEM',// 證照的型別。支援的格式有"PEM" (預設值), "DER"和"ENG"。
CURLOPT_SSLKEY => '/www/wwwroot/supliao/cert/apiclient_key.pem',// 客戶端私鑰的檔案路徑
CURLOPT_SSLKEYTYPE => 'PEM',// 客戶端私鑰型別,支援的私鑰型別為"PEM"(預設值)、"DER"和"ENG"。
CURLOPT_KEYPASSWD => env('MCH_ID'),//客戶端私鑰密碼,私鑰在建立時可以選擇加密。
//API證照呼叫或安裝需要使用到密碼,該密碼的值為微信商戶號(mch_id)
);
$url = "https://api.mch.weixin.qq.com/secapi/pay/profitsharing";
$get_data = $this->sendPrePayCurl($xmlData, $url, $options);
\Log::alert('分賬返回結果:');
\Log::alert($get_data);
//更新訂單資料
return true;
}
四.踩過的坑
公司的要求是使用者支付成功後馬上分賬,於是我選擇在微信支付回撥成功後請求單次分賬,結果發現微信一直在傳送通知,檢視文件發現請求分賬後商戶對微信的通知已經超時,微信會判定本次通知失敗,一直髮送通知
於是我用了個不是方法的方法,在單次請求分賬方法加上sleep(2),讓程式碼沉睡,等到微信第二次通知再執行。
如果小夥伴們有什麼方法能非同步處理,歡迎在評論區討論
結語
微信分賬文件地址
https://pay.weixin.qq.com/wiki/doc/api/all...
本作品採用《CC 協議》,轉載必須註明作者和本文連結