TransferService.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <?php
  2. /**
  3. * 微信企业付款服务
  4. * Class TransferService
  5. * Author SHUI
  6. * @package wxpay
  7. */
  8. define('WEIXIN_SDK_DIR', __DIR__);
  9. require_once WEIXIN_SDK_DIR . '/lib/WxPay.Config.php';
  10. class TransferService {
  11. const TRANSFER_API = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
  12. const QUERY_API = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo';
  13. /**
  14. * 企业付款
  15. *
  16. * @access public
  17. *
  18. * @param string $openid 微信用户OPENID
  19. * @param float $amount 付款金额
  20. * @param string $orderid 订单号
  21. * @param string $ip 来源IP
  22. *
  23. * @return array
  24. */
  25. public function transfer($openid, $amount, $orderid, $ip) {
  26. $param = [
  27. 'mch_appid' => WxPayConfig::APPID,
  28. 'mchid' => WxPayConfig::MCHID,
  29. 'nonce_str' => self::createNonceStr(),
  30. 'openid' => $openid,
  31. 'check_name' => 'NO_CHECK', // 校验用户姓名选项。NO_CHECK:不校验真实姓名,FORCE_CHECK:强校验真实姓名
  32. 're_user_name' => '', // 收款用户真实姓名(不支持给非实名用户打款)
  33. 'partner_trade_no' => $orderid,
  34. 'spbill_create_ip' => $ip,
  35. 'amount' => intval($amount * 100), // 单位 转为分
  36. 'desc' => '提现转账',
  37. ];
  38. $param['sign'] = self::getSign($param, WxPayConfig::KEY);
  39. $responseXml = $this->curlPost(self::TRANSFER_API, self::arrayToXml($param));
  40. if ( empty($responseXml) ) {
  41. return ['flag' => -1, 'msg' => '发送请求失败'];
  42. }
  43. $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
  44. if ( $unifiedOrder === false ) {
  45. return ['flag' => 1, 'msg' => '解析响应数据失败'];
  46. }
  47. if ( 'SUCCESS' != $unifiedOrder->return_code ) {
  48. return ['flag' => 2, 'msg' => "{$unifiedOrder->return_code}:{$unifiedOrder->return_msg}"];
  49. }
  50. if ( 'SUCCESS' != $unifiedOrder->result_code ) {
  51. $unifiedOrder->err_code_des = $this->resetErrCodeDes($unifiedOrder->err_code, $unifiedOrder->err_code_des);
  52. $msg = "{$unifiedOrder->err_code}:{$unifiedOrder->err_code_des}";
  53. $queryOrder = $this->query($orderid); // 查询确认订单状态
  54. if (in_array($queryOrder['flag'], [4,5]) ) {
  55. $msg = $queryOrder['msg'];
  56. } else if ( '0' == $queryOrder['flag'] ) {
  57. return ['flag' => 0, 'msg' => '付款成功', 'orderid' => $queryOrder['orderid']];
  58. }
  59. return ['flag' => 3, 'msg' => $msg];
  60. }
  61. return ['flag' => 0, 'msg' => '付款成功', 'orderid' => $unifiedOrder->partner_trade_no];
  62. }
  63. /**
  64. * 企业付款查询
  65. *
  66. * @access public
  67. *
  68. * @param string $orderid 订单号
  69. *
  70. * @return array
  71. */
  72. public function query($orderid) {
  73. $param = [
  74. 'mch_appid' => WxPayConfig::APPID,
  75. 'mchid' => WxPayConfig::MCHID,
  76. 'nonce_str' => self::createNonceStr(),
  77. 'partner_trade_no' => $orderid,
  78. ];
  79. $param['sign'] = self::getSign($param, WxPayConfig::KEY);
  80. $responseXml = $this->curlPost(self::QUERY_API, self::arrayToXml($param));
  81. if ( empty($responseXml) ) {
  82. return ['flag' => -1, 'msg' => '发送请求失败'];
  83. }
  84. $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
  85. if ( $unifiedOrder === false ) {
  86. return ['flag' => 1, 'msg' => '解析响应数据失败'];
  87. }
  88. if ( 'SUCCESS' != $unifiedOrder->return_code ) {
  89. return ['flag' => 2, 'msg' => "{$unifiedOrder->return_code}:{$unifiedOrder->return_msg}"];
  90. }
  91. if ( 'SUCCESS' != $unifiedOrder->result_code ) {
  92. $unifiedOrder->err_code_des = $this->resetErrCodeDes($unifiedOrder->err_code, $unifiedOrder->err_code_des);
  93. return ['flag' => 3, 'msg' => "{$unifiedOrder->err_code}:{$unifiedOrder->err_code_des}"];
  94. }
  95. if ( 'FAILED' == $unifiedOrder->status ) {
  96. return ['flag' => 4, 'msg' => "{$unifiedOrder->reason}"];
  97. }
  98. if ( 'PROCESSING' == $unifiedOrder->status ) {
  99. return ['flag' => 5, 'msg' => '转账处理中'];
  100. }
  101. return ['flag' => 0, 'msg' => '已经付款成功', 'orderid' => $unifiedOrder->partner_trade_no];
  102. }
  103. public function curlPost($url = '', $postData = '', $options = []) {
  104. if (is_array($postData)) {
  105. $postData = http_build_query($postData);
  106. }
  107. $ch = curl_init();
  108. curl_setopt($ch, CURLOPT_URL, $url);
  109. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  110. curl_setopt($ch, CURLOPT_POST, 1);
  111. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
  112. curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
  113. if (!empty($options)) {
  114. curl_setopt_array($ch, $options);
  115. }
  116. //https请求 不验证证书和host
  117. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  118. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  119. //默认格式为PEM,可以注释
  120. curl_setopt($ch,CURLOPT_SSLCERTTYPE, 'PEM');
  121. curl_setopt($ch,CURLOPT_SSLCERT, WEIXIN_SDK_DIR.'/cert/apiclient_cert.pem');
  122. //默认格式为PEM,可以注释
  123. curl_setopt($ch,CURLOPT_SSLKEYTYPE, 'PEM');
  124. curl_setopt($ch,CURLOPT_SSLKEY, WEIXIN_SDK_DIR.'/cert/apiclient_key.pem');
  125. $data = curl_exec($ch);
  126. curl_close($ch);
  127. return $data;
  128. }
  129. public static function arrayToXml($arr) {
  130. $xml = "<xml>";
  131. foreach ($arr as $key => $val) {
  132. if (is_numeric($val)) {
  133. $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
  134. } else {
  135. $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
  136. }
  137. }
  138. $xml .= "</xml>";
  139. return $xml;
  140. }
  141. public static function createNonceStr($len = 24) {
  142. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  143. $str = '';
  144. for ($i = 0; $i < $len; $i++) {
  145. $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
  146. }
  147. return $str;
  148. }
  149. public static function getSign($param, $key) {
  150. ksort($param, SORT_STRING);
  151. $unSignStr = self::formatQueryParaMap($param, false);
  152. $signStr = strtoupper(md5($unSignStr . '&key=' . $key));
  153. return $signStr;
  154. }
  155. protected static function formatQueryParaMap($paraMap, $urlEncode = false) {
  156. $buff = '';
  157. ksort($paraMap);
  158. foreach ($paraMap as $k => $v) {
  159. if (null != $v && 'null' != $v) {
  160. if ($urlEncode) {
  161. $v = urlencode($v);
  162. }
  163. $buff .= $k . "=" . $v . "&";
  164. }
  165. }
  166. $reqPar = '';
  167. if (strlen($buff) > 0) {
  168. $reqPar = substr($buff, 0, strlen($buff) - 1);
  169. }
  170. return $reqPar;
  171. }
  172. protected static function resetErrCodeDes($errCode, $errCodeDes) {
  173. switch ($errCode) {
  174. case 'NOTENOUGH':
  175. $errCodeDes = '商户余额不足';
  176. break;
  177. }
  178. return $errCodeDes;
  179. }
  180. }