业务需求
公司的需求是,在小程序商城支付的钱不要经过我们公司账上(服务商),用户支付后钱直接转到供应商(特约子商户)。备注:我们的需求是一笔订单只支持单个供应商下单。
申请配置
注册服务号!别的不行,然后就是例行的微信认证。
认证完找到微信支付----》服务商申请
其中:无资金结算功能,商户交易资金直接结算至特约商户的账户;
1.开通分账功能
开通分账功能,产品中心->我的产品->分账,开通后产品设置,设置分账比例
2.添加分账接收方
开发
我们项目使用了 “overtrue/laravel-wechat”: “~4.0”
说明:实现分账只是在普通支付下单接口中新增了一个分账参数profit_sharing,其他与普通支付方式完全相同。目前支持付款码支付、JSAPI支付、Native支付、APP支付、小程序支付、H5支付、委托代扣、车主平台。
发起支付
$app = \EasyWeChat::payment();
$notifyUrl = env('APP_URL').'/****/notify';
$param = [
'body' => '样品购买',
'out_trade_no' => $orderInfo->order_no,// 系统订单号
'trade_type' => 'JSAPI', // 必须为JSAPI
'sub_openid' => $userInfo["wx_openid"],// 这里的openid为付款人的openid,巨坑
'total_fee' => $orderInfo->actual_total_fee, // 总价
'profit_sharing' => 'Y', // 是否指定服务商分账 Y-是,需要分账/N-否,不分账/字母要求大写,不传默认不分账
'sub_mch_id' => $supplierInfo->sub_mchid,// 这里对应特约商户号id 付款到对应商户
'notify_url' => $notifyUrl
];
\Log::info("订单发起微信支付,参数:" . json_encode($param));
$result = $app->order->unify($param);
// 如果成功生成统一下单的订单,那么进行二次签名
if ($result['return_code'] === 'SUCCESS') {
// 二次签名的参数必须与下面相同
$params = [
'appId' => config('wechat.mini_program.default.app_id'),//小程序app_id
'timeStamp' => time(),
'nonceStr' => $result['nonce_str'],
'package' => 'prepay_id=' . $result['prepay_id'],
'signType' => 'MD5',
];
// config('wechat.payment.default.key')为商户的key
$params['paySign'] = generate_sign($params, config('wechat.payment.default.key'));
return $this->responseJsonSuccess('success', $params);
} else {
\Log::error("发起微信错误:" . json_encode($result));
return $this->responseJsonError("800062|发起微信支付失败");
}
回调
回调就按正常走,具体参考官方文档。我使用的EasyWeChat的包,参考文档详情
分账
在特约商户后台发现金额是冻结状态
分账的官方文档说明
因为EasyWeChat4.x还没用微信分账的可调用方法,5.x及以上才有,这边我就自己手写(copy)一个类实现。我是直接调用微信分账的完成分账接口。
//格式化参数格式化成url参数 生成签名sign
public function appgetSign($data)
{
foreach ($data as $k => $v) {
$params[$k] = $v;
}
//签名步骤一:按字典序排序参数
ksort($params); // ksort — 对数组按照键名排序
$string = $this->formatBizQueryParaMap($params, false);
$string = $string . "&key=" . config("wechat.payment.default.key");
if ($data["sign_type"] == "HMAC-SHA256") {
$string = hash_hmac("sha256", $string, config("wechat.payment.default.key"));
} else {
//签名步骤三:MD5加密
$string = md5($string);
}
//签名步骤四:所有字符转为大写
$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;
}
// 完结分账
public function finishSplit($subMchId,$transactionId)
{
header("Content-Type:text/html;charset=utf8");
$param = [
"mch_id" => config("wechat.payment.default.mch_id"),
"sub_mch_id" => $subMchId,
"appid" => config("wechat.payment.default.app_id"),
"nonce_str" => $this->createNoncestr(),// 随机字符串
"sign_type" => "HMAC-SHA256",
"out_order_no" => rand(1000,9999),
"transaction_id" => $transactionId,
"description" => "分账已完成"
];
// 签名
$param["sign"] = $this->appgetSign($param);
//把数组转化成xml格式
$xmlData = $this->arrayToXml($param, true);
$options = array(
CURLOPT_SSLCERT => config("wechat.payment.default.cert_path"),// 客户端证书,用于双向认证
CURLOPT_SSLCERTTYPE => 'PEM',// 证书的类型。支持的格式有"PEM" (默认值), "DER"和"ENG"。
CURLOPT_SSLKEY => config("wechat.payment.default.key_path"),// 客户端私钥的文件路径
CURLOPT_SSLKEYTYPE => 'PEM',// 客户端私钥类型,支持的私钥类型为"PEM"(默认值)、"DER"和"ENG"。
CURLOPT_KEYPASSWD => config("wechat.payment.default.key"),//客户端私钥密码,私钥在创建时可以选择加密。
//API证书调用或安装需要使用到密码,该密码的值为微信商户号(mch_id)
);
$url = "https://api.mch.weixin.qq.com/secapi/pay/profitsharingfinish";
$result = $this->sendPrePayCurl($xmlData, $url, $options);
return $result;
}
总结
服务商模式微信的官方文档还是比较混乱的(至少我是这么认为的)。也是网上找了很多人的文章。个人认为这篇文章还不够完善,多个特约服务商分账还没尝试,后续还会更新。
参考:
https://blog.csdn.net/qq_18881987/article/details/94428628
https://blog.csdn.net/qq_29755359/article/details/104963754