分账接口类 ProfitSharing.class.php
wxConfig = $this->wxConfig();
$this->sign = new ProfitSharingSign();
$this->curl = new ProfitSharingCurl();
}
/**
* @function 发起请求所必须的配置参数
* @return mixed
*/
private function wxConfig()
{
$wxConfig['app_id'] = '';//服务商公众号AppID
$wxConfig['mch_id'] = ''; //服务商商户号
$wxConfig['sub_app_id'] = '';//todo 子服务商公众号AppID
$wxConfig['sub_mch_id'] = ''; //todo 子服务商商户号
$wxConfig['md5_key'] = ''; //md5 秘钥
$wxConfig['app_cert_pem'] = '';//证书路径
$wxConfig['app_key_pem'] = '';//证书路径
return $wxConfig;
}
/**
* @function 请求多次分账接口
* @param $orders array 待分账订单
* @param $accounts array 分账接收方
* @return array
* @throws Exception
*/
public function multiProfitSharing($orders,$accounts)
{
if(empty($orders)){
throw new Exception('没有待分帐订单');
}
if(empty($accounts)){
throw new Exception('接收分账账户为空');
}
//1.设置分账账号
$receivers = array();
foreach ($accounts as $account)
{
$tmp = array(
'type'=>$account['type'],
'account'=>$account['account'],
'amount'=>intval($account['amount']),
'description'=>$account['desc'],
);
$receivers[] = $tmp;
}
$receivers = json_encode($receivers,JSON_UNESCAPED_UNICODE);
$totalCount = count($orders);
$successCount = 0;
$failCount = 0;
$now = time();
foreach ($orders as $order)
{
//2.生成签名
$postArr = array(
'appid'=>$this->wxConfig['app_id'],
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'sub_appid'=>$this->wxConfig['sub_app_id'],
'nonce_str'=>md5(time() . rand(1000, 9999)),
'transaction_id'=>$order['trans_id'],
'out_order_no'=>$order['order_no'].$order['ticket_no'],
'receivers'=>$receivers,
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//3.发送请求
$url = 'https://api.mch.weixin.qq.com/secapi/pay/multiprofitsharing';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("multiProfitSharing.postXML: " . $postXML);
$opts = array(
CURLOPT_HEADER => 0,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSLCERTTYPE => 'PEM', //默认支持的证书的类型,可以注释
CURLOPT_SSLCERT => $this->wxConfig['app_cert_pem'],
CURLOPT_SSLKEY => $this->wxConfig['app_key_pem'],
);
Ilog::DEBUG("multiProfitSharing.opts: " . json_encode($opts));
$curl_res = $this->curl->setOption($opts)->post($url,$postXML);
Ilog::DEBUG("multiProfitSharing.curl_res: " . $curl_res);
$ret = $this->toArray($curl_res);
if($ret['return_code']=='SUCCESS' and $ret['result_code']=='SUCCESS')
{
//更新分账订单状态
$params = array();
$params['order_no'] = $order['order_no'];
$params['trans_id'] = $order['trans_id'];
$params['ticket_no'] = $order['ticket_no'];
$data = array();
$data['profitsharing'] = $receivers;
$data['state'] = 2;
pdo_update('ticket_orders_profitsharing',$data,$params);
$successCount++;
}else{
$failCount++;
}
usleep(500000);//微信会报频率过高,所以停一下
}
return array('processTime'=>date('Y-m-d H:i:s',$now),'totalCount'=>$totalCount,'successCount'=>$successCount,'failCount'=>$failCount);
}
/**
* @function 请求单次分账接口
* @param $profitSharingOrders array 待分账订单
* @param $profitSharingAccounts array 分账接收方
* @return array
* @throws Exception
*/
public function profitSharing($profitSharingOrders,$profitSharingAccounts)
{
if(empty($profitSharingOrders)){
throw new Exception('没有待分帐订单');
}
if(empty($profitSharingAccounts)){
throw new Exception('接收分账账户为空');
}
//1.设置分账账号
$receivers = array();
foreach ($profitSharingAccounts as $profitSharingAccount)
{
$tmp = array(
'type'=>$profitSharingAccount['type'],
'account'=>$profitSharingAccount['account'],
'amount'=>intval($profitSharingAccount['amount']),
'description'=>$profitSharingAccount['desc'],
);
$receivers[] = $tmp;
}
$receivers = json_encode($receivers,JSON_UNESCAPED_UNICODE);
$totalCount = count($profitSharingOrders);
$successCount = 0;
$failCount = 0;
$now = time();
foreach ($profitSharingOrders as $profitSharingOrder)
{
//2.生成签名
$postArr = array(
'appid'=>$this->wxConfig['app_id'],
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'sub_appid'=>$this->wxConfig['sub_app_id'],
'nonce_str'=>md5(time() . rand(1000, 9999)),
'transaction_id'=>$profitSharingOrder['trans_id'],
'out_order_no'=>$profitSharingOrder['order_no'].$profitSharingOrder['ticket_no'],
'receivers'=>$receivers,
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//3.发送请求
$url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharing';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("profitSharing.postXML: " . $postXML);
$opts = array(
CURLOPT_HEADER => 0,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSLCERTTYPE => 'PEM', //默认支持的证书的类型,可以注释
CURLOPT_SSLCERT => $this->wxConfig['app_cert_pem'],
CURLOPT_SSLKEY => $this->wxConfig['app_key_pem'],
);
Ilog::DEBUG("profitSharing.opts: " . json_encode($opts));
$curl_res = $this->curl->setOption($opts)->post($url,$postXML);
Ilog::DEBUG("profitSharing.curl_res: " . $curl_res);
$ret = $this->toArray($curl_res);
if($ret['return_code']=='SUCCESS' and $ret['result_code']=='SUCCESS')
{
//更新分账订单状态
$params = array();
$params['order_no'] = $profitSharingOrder['order_no'];
$params['trans_id'] = $profitSharingOrder['trans_id'];
$params['ticket_no'] = $profitSharingOrder['ticket_no'];
$data = array();
$data['profitsharing'] = $receivers;
$data['state'] = 2;
pdo_update('ticket_orders_profitsharing',$data,$params);
$successCount++;
}else{
$failCount++;
}
}
return array('processTime'=>date('Y-m-d H:i:s',$now),'totalCount'=>$totalCount,'successCount'=>$successCount,'failCount'=>$failCount);
}
/**
* @function 查询分账结果
* @param $trans_id string 微信支付单号
* @param $out_order_no string 分账单号
* @return array|false
* @throws Exception
*/
public function query($trans_id,$out_order_no)
{
//1.生成签名
$postArr = array(
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'transaction_id'=>$trans_id,
'out_order_no'=>$out_order_no,
'nonce_str'=>md5(time() . rand(1000, 9999)),
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//2.发送请求
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingquery';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("query.postXML: " . $postXML);
$curl_res = $this->curl->post($url,$postXML);
Ilog::DEBUG("query.curl_res: " . $curl_res);
$ret = $this->toArray($curl_res);
return $ret;
}
/**
* @function 添加分账接收方
* @param $profitSharingAccount array 分账接收方
* @return array|false
* @throws Exception
*/
public function addReceiver($profitSharingAccount)
{
//1.接收分账账户
$receiver = array(
'type'=>$profitSharingAccount['type'],
'account'=>$profitSharingAccount['account'],
'name'=>$profitSharingAccount['name'],
'relation_type'=>$profitSharingAccount['relation_type'],
);
$receiver = json_encode($receiver,JSON_UNESCAPED_UNICODE);
//2.生成签名
$postArr = array(
'appid'=>$this->wxConfig['app_id'],
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'sub_appid'=>$this->wxConfig['sub_app_id'],
'nonce_str'=>md5(time() . rand(1000, 9999)),
'receiver'=>$receiver
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//3.发送请求
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("addReceiver.postXML: " . $postXML);
$curl_res = $this->curl->post($url,$postXML);
Ilog::DEBUG("addReceiver.curl_res: " . $curl_res);
$ret = $this->toArray($curl_res);
return $ret;
}
/**
* @function 删除分账接收方
* @param $profitSharingAccount array 分账接收方
* @return array|false
* @throws Exception
*/
public function removeReceiver($profitSharingAccount)
{
//1.接收分账账户
$receiver = array(
'type'=>$profitSharingAccount['type'],
'account'=>$profitSharingAccount['account'],
'name'=>$profitSharingAccount['name'],
);
$receiver = json_encode($receiver,JSON_UNESCAPED_UNICODE);
//2.生成签名
$postArr = array(
'appid'=>$this->wxConfig['app_id'],
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'sub_appid'=>$this->wxConfig['sub_app_id'],
'nonce_str'=>md5(time() . rand(1000, 9999)),
'receiver'=>$receiver
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//3.发送请求
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingremovereceiver';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("removeReceiver.postXML: " . $postXML);
$curl_res = $this->curl->post($url,$postXML);
Ilog::DEBUG("removeReceiver.curl_res: " . $curl_res);
$ret = $this->toArray($curl_res);
return $ret;
}
/**
* @function 完结分账
* @param $profitOrder array 分账订单
* @param $description string 完结分账描述
* @return array|false
* @throws Exception
*/
public function finish($profitOrder,$description='分账完结')
{
$ret = array();
if(!empty($profitOrder))
{
//1.签名
$postArr = array(
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'appid'=>$this->wxConfig['app_id'],
'nonce_str'=>md5(time() . rand(1000, 9999)),
'transaction_id'=>$profitOrder['trans_id'],
'out_order_no'=>'finish'.'_'.$profitOrder['order_no'],
'description'=>$description,
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//2.请求
$url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharingfinish';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("finish.postXML: " . $postXML);
$opts = array(
CURLOPT_HEADER => 0,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSLCERTTYPE => 'PEM', //默认支持的证书的类型,可以注释
CURLOPT_SSLCERT => $this->wxConfig['app_cert_pem'],
CURLOPT_SSLKEY => $this->wxConfig['app_key_pem'],
);
Ilog::DEBUG("finish.opts: " . json_encode($opts));
$curl_res = $this->curl->setOption($opts)->post($url,$postXML);
Ilog::DEBUG("finish.curl_res: " . $curl_res);
$ret = $this->toArray($curl_res);
}
return $ret;
}
/**
* @function 分账回退
* @param $profitOrder array 分账订单
* @return array
* @throws Exception
*/
public function profitSharingReturn($profitOrder)
{
$ret = array();
if(!empty($profitOrder) and $profitOrder['channel']==1)
{
$accounts = json_decode($profitOrder['profitsharing'],true);
foreach ($accounts as $account)
{
//1.签名
$postArr = array(
'appid'=>$this->wxConfig['app_id'],
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'sub_appid'=>$this->wxConfig['sub_app_id'],
'nonce_str'=>md5(time() . rand(1000, 9999)),
'out_order_no'=>$profitOrder['order_no'].$profitOrder['ticket_no'],
'out_return_no'=>'return_'.$profitOrder['order_no'].$profitOrder['ticket_no'].'_'.$account['account'],
'return_account_type'=>'MERCHANT_ID',
'return_account'=>$account['account'],
'return_amount'=>$account['amount'],
'description'=>'用户退款',
'sign_type'=>'HMAC-SHA256',
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//2.请求
$url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharingreturn';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("profitSharingReturn.postXML: " . $postXML);
$opts = array(
CURLOPT_HEADER => 0,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_SSLCERTTYPE => 'PEM', //默认支持的证书的类型,可以注释
CURLOPT_SSLCERT => $this->wxConfig['app_cert_pem'],
CURLOPT_SSLKEY => $this->wxConfig['app_key_pem'],
);
Ilog::DEBUG("profitSharingReturn.opts: " . json_encode($opts));
$curl_res = $this->curl->setOption($opts)->post($url,$postXML);
Ilog::DEBUG("profitSharingReturn.curl_res: " . $curl_res);
$ret[] = $this->toArray($curl_res);
}
}
return $ret;
}
/**
* @function 回退结果查询
* @param $order_no string 本地订单号
* @param $ticket_no string 本地票号
* @return array|false
* @throws \Exception
*/
public function returnQuery($order_no,$ticket_no)
{
$ret = array();
$profitOrder = pdo_fetch("SELECT * FROM zc_ticket_orders_profitsharing WHERE order_no='{$order_no}' AND ticket_no='{$ticket_no}'");
if($profitOrder['channel']==1 and $profitOrder['state']==2)
{
$accounts = json_decode($profitOrder['profitsharing'],true);
foreach ($accounts as $account)
{
//1.签名
$postArr = array(
'appid'=>$this->wxConfig['app_id'],
'mch_id'=>$this->wxConfig['mch_id'],
'sub_mch_id'=>$this->wxConfig['sub_mch_id'],
'nonce_str'=>md5(time() . rand(1000, 9999)),
'out_order_no'=>$profitOrder['order_no'].$profitOrder['ticket_no'],
'out_return_no'=>'return_'.$profitOrder['order_no'].$profitOrder['ticket_no'].'_'.$account['account'],
'sign_type'=>'HMAC-SHA256',
);
$sign = $this->sign->getSign($postArr, 'HMAC-SHA256',$this->wxConfig['md5_key']);
$postArr['sign'] = $sign;
//2.请求
$url = 'https://api.mch.weixin.qq.com/pay/profitsharingreturnquery';
$postXML = $this->toXml($postArr);
Ilog::DEBUG("returnQuery.postXML: " . $postXML);
$curl_res = $this->curl->post($url,$postXML);
Ilog::DEBUG("returnQuery.curl_res: " . $curl_res);
$ret[] = $this->toArray($curl_res);
}
}
return $ret;
}
/**
* @function 将array转为xml
* @param array $values
* @return string|bool
* @author xiewg
**/
public function toXml($values)
{
if (!is_array($values) || count($values) <= 0) {
return false;
}
$xml = "";
foreach ($values as $key => $val) {
if (is_numeric($val)) {
$xml.="<".$key.">".$val."".$key.">";
} else {
$xml.="<".$key.">".$key.">";
}
}
$xml.=" ";
return $xml;
}
/**
* @function 将xml转为array
* @param string $xml
* @return array|false
* @author xiewg
*/
public function toArray($xml)
{
if (!$xml) {
return false;
}
// 检查xml是否合法
$xml_parser = xml_parser_create();
if (!xml_parse($xml_parser, $xml, true)) {
xml_parser_free($xml_parser);
return false;
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $data;
}
}
发送请求类: ProfitSharingCurl.class.php
示例:
*
* // 失败时再重试2次
* $curl = new CUrl(2);
*
* // GET
* $rs = $curl->get('http://phalapi.oschina.mopaas.com/Public/demo/?service=Default.Index');
*
* // POST
* $data = array('username' => 'dogstar');
* $rs = $curl->post('http://phalapi.oschina.mopaas.com/Public/demo/?service=Default.Index', $data);
*
* @package PhalApi\CUrl
* @license http://www.phalapi.net/license GPL 协议
* @link http://www.phalapi.net/
* @author dogstar 2015-01-02
*/
class ProfitSharingCurl {
/**
* 最大重试次数
*/
const MAX_RETRY_TIMES = 10;
/**
* @var int $retryTimes 超时重试次数;注意,此为失败重试的次数,即:总次数 = 1 + 重试次数
*/
protected $retryTimes;
protected $header = array();
protected $option = array();
protected $hascookie = FALSE;
protected $cookie = array();
/**
* @param int $retryTimes 超时重试次数,默认为1
*/
public function __construct($retryTimes = 1) {
$this->retryTimes = $retryTimes < static::MAX_RETRY_TIMES
? $retryTimes : static::MAX_RETRY_TIMES;
}
/** ------------------ 核心使用方法 ------------------ **/
/**
* GET方式的请求
* @param string $url 请求的链接
* @param int $timeoutMs 超时设置,单位:毫秒
* @return string 接口返回的内容,超时返回false
*/
public function get($url, $timeoutMs = 3000) {
return $this->request($url, array(), $timeoutMs);
}
/**
* POST方式的请求
* @param string $url 请求的链接
* @param array $data POST的数据
* @param int $timeoutMs 超时设置,单位:毫秒
* @return string 接口返回的内容,超时返回false
*/
public function post($url, $data, $timeoutMs = 3000) {
return $this->request($url, $data, $timeoutMs);
}
/** ------------------ 前置方法 ------------------ **/
/**
* 设置请求头,后设置的会覆盖之前的设置
*
* @param array $header 传入键值对如:
```
* array(
* 'Accept' => 'text/html',
* 'Connection' => 'keep-alive',
* )
```
*
* @return $this
*/
public function setHeader($header) {
$this->header = array_merge($this->header, $header);
return $this;
}
/**
* 设置curl配置项
*
* - 1、后设置的会覆盖之前的设置
* - 2、开发者设置的会覆盖框架的设置
*
* @param array $option 格式同上
*
* @return $this
*/
public function setOption($option) {
$this->option = $option + $this->option;
return $this;
}
/**
* @param array $cookie
*/
public function setCookie($cookie) {
$this->cookie = $cookie;
return $this;
}
/**
* @return array
*/
public function getCookie() {
return $this->cookie;
}
public function withCookies() {
$this->hascookie = TRUE;
if (!empty($this->cookie)) {
$this->setHeader(array('Cookie' => $this->getCookieString()));
}
$this->setOption(array(CURLOPT_COOKIEFILE => ''));
return $this;
}
/** ------------------ 辅助方法 ------------------ **/
/**
* 统一接口请求
* @param string $url 请求的链接
* @param array $data POST的数据
* @param int $timeoutMs 超时设置,单位:毫秒
* @return string 接口返回的内容,超时返回false
* @throws Exception
*/
protected function request($url, $data, $timeoutMs = 3000) {
$options = array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HEADER => 0,
CURLOPT_CONNECTTIMEOUT_MS => $timeoutMs,
CURLOPT_HTTPHEADER => $this->getHeaders(),
);
if (!empty($data)) {
$options[CURLOPT_POST] = 1;
$options[CURLOPT_POSTFIELDS] = $data;
}
$options = $this->option + $options; //$this->>option优先
$ch = curl_init();
curl_setopt_array($ch, $options);
$curRetryTimes = $this->retryTimes;
do {
$rs = curl_exec($ch);
$curRetryTimes--;
} while ($rs === FALSE && $curRetryTimes >= 0);
$errno = curl_errno($ch);
if ($errno) {
throw new InternalServerErrorException(sprintf("%s::%s(%d)\n", $url, curl_error($ch), $errno));
}
//update cookie
if ($this->hascookie) {
$cookie = $this->getRetCookie(curl_getinfo($ch, CURLINFO_COOKIELIST));
!empty($cookie) && $this->cookie = $cookie + $this->cookie;
$this->hascookie = FALSE;
unset($this->header['Cookie']);
unset($this->option[CURLOPT_COOKIEFILE]);
}
curl_close($ch);
return $rs;
}
/**
*
* @return array
*/
protected function getHeaders() {
$arrHeaders = array();
foreach ($this->header as $key => $val) {
$arrHeaders[] = $key . ': ' . $val;
}
return $arrHeaders;
}
protected function getRetCookie(array $cookies) {
$ret = array();
foreach ($cookies as $cookie) {
$arr = explode("\t", $cookie);
if (!isset($arr[6])) {
continue;
}
$ret[$arr[5]] = $arr[6];
}
return $ret;
}
protected function getCookieString() {
$ret = '';
foreach ($this->getCookie() as $key => $val) {
$ret .= $key . '=' . $val . ';';
}
return trim($ret, ';');
}
}
签名类 ProfitSharing.class.php
paraFilter($param);
$values = $this->arraySort($values);
$signStr = $this->createLinkstring($values);
$signStr .= '&key=' . $md5Key;
switch ($signType)
{
case 'MD5':
$sign = md5($signStr);
break;
case 'HMAC-SHA256':
$sign = hash_hmac('sha256', $signStr, $md5Key);
break;
default:
$sign = '';
}
return strtoupper($sign);
}
/**
* 移除空值的key
* @param $para
* @return array
*/
public function paraFilter($para)
{
$paraFilter = array();
while (list($key, $val) = each($para))
{
if ($val == "") {
continue;
} else {
if (! is_array($para[$key])) {
$para[$key] = is_bool($para[$key]) ? $para[$key] : trim($para[$key]);
}
$paraFilter[$key] = $para[$key];
}
}
return $paraFilter;
}
/**
* @function 对输入的数组进行字典排序
* @param array $param 需要排序的数组
* @return array
* @author helei
*/
public function arraySort(array $param)
{
ksort($param);
reset($param);
return $param;
}
/**
* @function 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
* @param array $para 需要拼接的数组
* @return string
* @throws \Exception
*/
public function createLinkString($para)
{
if (! is_array($para)) {
throw new \Exception('必须传入数组参数');
}
reset($para);
$arg = "";
while (list($key, $val) = each($para)) {
if (is_array($val)) {
continue;
}
$arg.=$key."=".urldecode($val)."&";
}
//去掉最后一个&字符
$arg = substr($arg, 0, count($arg) - 2);
//如果存在转义字符,那么去掉转义
if (get_magic_quotes_gpc()) {
$arg = stripslashes($arg);
}
return $arg;
}
}