<?php
defined('IN_IA') || exit('Access Denied');
class WxPay
{
protected $wxpay;
protected $cert;
protected $mch_id;
public function __construct($pay_type = '')
{
global $_W;
$wechat = $_W['we7_wmall']['config']['payment']['wechat'];
$wechat = $wechat[$wechat['type']];
$pay_type = ((empty($pay_type) ? 'wap' : $pay_type));
$this->mch_id = '123';
if ($pay_type == 'h5app')
{
$wechat = $_W['we7_wmall']['config']['payment']['app_wechat'];
}
else if ($pay_type == 'wxapp')
{
$payment = get_plugin_config('wxapp.payment');
$wechat = $payment['wechat'];
$wechat = $wechat[$wechat['type']];
}
else if ($pay_type == 'H5')
{
$wechat = $_W['we7_wmall']['config']['payment']['h5_wechat'];
}
$this->pay_type = $pay_type;
$this->wxpay = array('appid' => $wechat['appid'], 'mch_id' => $wechat['mchid'], 'sub_mch_id' => $wechat['sub_mch_id'], 'key' => $wechat['apikey']);
$this->cert = array('apiclient_cert' => $wechat['apiclient_cert'], 'apiclient_key' => $wechat['apiclient_key'], 'rootca' => $wechat['rootca']);
}
public function array2url($params, $force = false)
{
$str = '';
foreach ($params as $key => $val )
{
if ($force && empty($val))
{
continue;
}
$str .= $key . '=' . $val . '&';
}
$str = trim($str, '&');
return $str;
}
public function bulidSign($params)
{
unset($params['sign']);
ksort($params);
$string = $this->array2url($params, true);
$string = $string . '&key=' . $this->wxpay['key'];
$string = md5($string);
$result = strtoupper($string);
return $result;
}
public function parseResult($result, $is_check_sign = false)
{
if (substr($result, 0, 5) != '<xml>')
{
return $result;
}
$result = json_decode(json_encode(isimplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
if (!(is_array($result)))
{
return error(-1, 'xml结构错误');
}
if (isset($result['return_code']) && ($result['return_code'] != 'SUCCESS'))
{
$msg = ((empty($result['return_msg']) ? $result['err_code_des'] : $result['return_msg']));
return error(-1, $msg);
}
if ($is_check_sign && ($this->bulidsign($result) != $result['sign']))
{
return error(-1, '验证签名出错');
}
return $result;
}
public function httpWxurl($url, $params, $extra = array())
{
load()->func('communication');
$xml = array2xml($params);
$response = ihttp_request($url, $xml, $extra);
if (is_error($response))
{
return $response;
}
$result = $this->parseResult($response['content']);
return $result;
}
public function shortUrl($url)
{
$params = array('appid' => $this->wxpay['appid'], 'mch_id' => $this->wxpay['mch_id'], 'long_url' => $url, 'nonce_str' => random(32));
$params['sign'] = $this->bulidSign($params);
$result = $this->httpWxurl('https://api.mch.weixin.qq.com/tools/shorturl', $params);
if (is_error($result))
{
return $result;
}
return $result['short_url'];
}
public function checkCert()
{
if (empty($this->cert['apiclient_key']) || empty($this->cert['apiclient_cert']))
{
return error(-1, '支付证书不完整');
}
return true;
}
public function mktTransfers($params, $check_type = 'FORCE_CHECK')
{
global $_W;
$status = $this->checkCert();
if (is_error($status))
{
return $status;
}
$params_origin = $params;
$elements = array('openid', 'amount', 'partner_trade_no', 're_user_name', 'desc');
$params = array_elements($elements, $params);
if (empty($params['openid']))
{
return error(-1, '粉丝信息错误,你可以撤销本次提现,让体现人重新设置提现账户,再次申请提现来解决此问题');
}
if ((($check_type == 'FORCE_CHECK') || ($check_type == 'OPTION_CHECK')) && empty($params['re_user_name']))
{
return error(-1, '收款人真实姓名不能为空');
}
if (empty($params['amount']))
{
return error(-1, '打款金额不能为空');
}
if (empty($params['partner_trade_no']))
{
return error(-1, '商户订单号不能为空');
}
if (empty($params['desc']))
{
return error(-1, '付款描述信息不能为空');
}
$params['check_name'] = $check_type;
$params['mch_appid'] = $this->wxpay['appid'];
$params['mchid'] = $this->wxpay['mch_id'];
$params['nonce_str'] = random(32);
$params['spbill_create_ip'] = CLIENT_IP;
$params['sign'] = $this->bulidSign($params);
$extra = array(CURLOPT_SSLCERT => MODULE_ROOT . '/cert/' . $this->cert['apiclient_cert'] . '/apiclient_cert.pem', CURLOPT_SSLKEY => MODULE_ROOT . '/cert/' . $this->cert['apiclient_key'] . '/apiclient_key.pem', CURLOPT_CAINFO => MODULE_ROOT . '/cert/' . $this->cert['rootca'] . '/rootca.pem');
$result = $this->httpWxurl('https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers', $params, $extra);
if (is_error($result))
{
return $result;
}
if ($result['result_code'] != 'SUCCESS')
{
return error(-1, $result['err_code'] . ':' . $result['err_code_des']);
}
return true;
}
//数组转xml
public function arrayToXml($arr){
$xml = "<xml>";
foreach ($arr as $key=>$val){
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
public function mktTransfersRepeat($params)
{
$result = $this->mktTransfers($params);
return $result;
}
public function check_bank_code($bank){
$arr=array('1002'=>'工商银行','1005'=>'农业银行','1026'=>'中国银行','1003'=>'建设银行',
'1001'=>'招商银行','1066'=>'邮政储蓄','1020'=>'交通银行','1004'=>'浦发银行','1006'=>'民生银行',
'1009'=>'兴业银行','1010'=>'平安银行','1021'=>'中信银行', '1025'=>'华夏银行','1027'=>'广发银行',
'1022'=>'光大银行','4836'=>'北京银行','1056'=>'宁波银行');
if(in_array($bank,$arr)){
return array_flip($arr)[$bank];
}else{
return false;
}
}
/*获取公钥*/
public function RasKey($datainfo="")
{
$data=[
//商户号
"mch_id"=>$this->mch_id,
//随机字符串
"nonce_str"=>random(32),
//加密方式我是MD5
"sign_type"=>"MD5",
];
//微信签名
$data["sign"]=$this->bulidSign($data);
//提交到的URL
$url="https://fraud.mch.weixin.qq.com/risk/getpublickey";
//转换成XML格式POST到服务器
$extra = array(CURLOPT_SSLCERT => MODULE_ROOT . '/cert/cert/apiclient_cert.pem',
CURLOPT_SSLKEY => MODULE_ROOT . '/cert/cert/apiclient_key.pem');
$backxml=$this->httpWxurl($url,$data,$extra);
//保存成PEM文件
file_put_contents(MODULE_ROOT . '/cert/cert/pub_key.pem',$backxml['pub_key']);
return MODULE_ROOT . '/cert/cert/pub_key.pem';
}
/**
* 公钥加密,银行卡号和姓名需要RSA算法加密
* @param string $data 需要加密的字符串,银行卡/姓名
* @return null|string 加密后的字符串
*/
private function publicEncrypt($data,$pubkey)
{
// 进行加密
$pubkey = openssl_pkey_get_public(file_get_contents($pubkey));
$encrypt_data = '';
$encrypted = '';
$r = openssl_public_encrypt($data,$encrypt_data,$pubkey,OPENSSL_PKCS1_OAEP_PADDING);
if($r){//加密成功,返回base64编码的字符串
return base64_encode($encrypted.$encrypt_data);
}else{
return false;
}
}
/**
* 公钥解密
*
* @param $data 加密的数据
* @param $b 加密的数据是否为符合url安全的字符串
*
*/
public function publicDecrypt($encrypted, $pu_key ){
$crypto = '';
foreach (str_split($this->urlsafe_b64decode($encrypted, true), 128) as $chunk) {
openssl_public_decrypt($chunk,$decryptData,$pu_key,OPENSSL_PKCS1_PADDING);
$crypto .= $decryptData;
}
return $crypto;
}
/**
* base64解密
*
* @param $string 加密的数据
* @param $b 加密的数据是否为符合url安全的字符串
*/
public function urlsafe_b64decode($string, $b = true) {
if (!$b) return base64_decode($string);
$data = str_replace(array('-','_'),array('+','/'),$string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}
//微信零钱提现到银行卡
public function bankTransfers($params)
{
global $_W;
$params_origin = $params;
$bank_code=$this->check_bank_code($params['bank_code']);
if (empty($params['enc_bank_no']))
{
return error(-1, '收款方银行卡号不能为空');
}
if ( empty($params['enc_true_name']))
{
return error(-1, '收款人姓名不能为空');
}
if ( empty($params['bank_code']))
{
return error(-1, '收款方开户行不能为空');
}elseif(!$bank_code){
return error(-1, '开户行不存在,请重新确定');
}
if (empty($params['amount']))
{
return error(-1, '打款金额不能为空');
}
if (empty($params['partner_trade_no']))
{
return error(-1, '商户订单号不能为空');
}
if (empty($params['desc']))
{
return error(-1, '付款描述信息不能为空');
}
//$miyao = $this->RasKey();
$pubkey=MODULE_ROOT . '/cert/cert/pub_key.pem';
$params['bank_code'] = $bank_code;
$params['mch_id'] = $this->mch_id;
$params['nonce_str'] = $this->random(32);
$data['amount'] = $params['amount'];
$data['bank_code'] = $params['bank_code'];
$data['desc'] = $params['desc'];
$data['enc_bank_no'] = $this->publicEncrypt($params['enc_bank_no'],$pubkey);
$data['enc_true_name'] = $this->publicEncrypt($params['enc_true_name'],$pubkey);
$data['mch_id'] = $this->mch_id;
$data['nonce_str'] = $params['nonce_str'];
$data['partner_trade_no'] = $params['partner_trade_no'];
$sign = $this->bulidSign($data);
$dataXML="<xml>
<amount>".$data['amount']."</amount>
<bank_code>".$data['bank_code']."</bank_code>
<desc>".$data['desc']."</desc>
<enc_bank_no>".$data['enc_bank_no']."</enc_bank_no>
<enc_true_name>".$data['enc_true_name']."</enc_true_name>
<mch_id>".$data['mch_id']."</mch_id>
<nonce_str>".$data['nonce_str']."</nonce_str>
<partner_trade_no>".$data['partner_trade_no']."</partner_trade_no>
<sign>".$sign."</sign>
</xml>";
$url = 'https://api.mch.weixin.qq.com/mmpaysptrans/pay_bank';
$ret = $this->httpsPost($url,$dataXML,true);
if($ret['return_code'] == 'SUCCESS' && $ret['result_code'] == 'SUCCESS' && $ret['err_code'] == 'SUCCESS'){
return $ret['payment_no'];
}else{
return error(-1, $ret['err_code_des']);
//return error(-1, $ret['err_code'] . ':' . $ret['err_code_des']);
}
}
/**
* 发起POST网络请求
* @params string $url : 请求的url链接地址
* @params string $data : 数据包
* @params bool $ssl : 是否加载证书
* return array $result : 返回的数据结果
*/
private function httpsPost($url,$data,$ssl = false)
{
$ch = curl_init ();
curl_setopt ( $ch, CURLOPT_URL, $url );
curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, FALSE );
curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, FALSE );
if($ssl) {
curl_setopt ( $ch,CURLOPT_SSLCERT,MODULE_ROOT . '/cert/cert/apiclient_cert.pem');
curl_setopt ( $ch,CURLOPT_SSLKEY,MODULE_ROOT . '/cert/cert/apiclient_key.pem');
}
curl_setopt ( $ch, CURLOPT_FOLLOWLOCATION, 1 );
curl_setopt ( $ch, CURLOPT_AUTOREFERER, 1 );
curl_setopt ( $ch, CURLOPT_POSTFIELDS, $data );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
$result = curl_exec($ch);
if (curl_errno($ch)) {
return 'Errno: '.curl_error($ch);
}
curl_close($ch);
return $this->xmlToArray($result);
}
/*
* 将xml转换成数组
* @params xml $xml : xml数据
* return array $data : 返回数组
*/
private function xmlToArray($xml)
{
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
$val = json_decode(json_encode($xmlstring),true);
return $val;
}
//对参数排序,生成MD5加密签名
private function getParam($paramArray, $isencode=false)
{
$paramStr = '';
ksort($paramArray);
$i = 0;
foreach ($paramArray as $key => $value)
{
if ($key == 'Signature'){
continue;
}
if ($i == 0){
$paramStr .= '';
}else{
$paramStr .= '&';
}
$paramStr .= $key . '=' . ($isencode?urlencode($value):$value);
++$i;
}
$stringSignTemp=$paramStr."&key=".$this->key;
$sign=strtoupper(md5($stringSignTemp));
return $sign;
}
/**
* 随机字符串
* @param int $length 长度
* @param string $type 类型
* @param int $convert 转换大小写 1大写 0小写
* @return string
*/
private function random($length = 10, $type='letter', $convert=false)
{
$config = array(
'number' => '1234567890',
'letter'=>'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
'string'=>'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789',
'all'=>'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
);
if (!isset($config[$type])) $type = 'letter';
$string = $config[$type];
$code = '';
$strlen = strlen($string) -1;
for ($i = 0; $i < $length; $i++ ){
$code .= $string{mt_rand(0, $strlen)};
}
if (!empty($convert)) {
$code = ($convert > 0)?strtoupper($code) : strtolower($code);
}
return $code;
}
public function payRefund_build($params)
{
global $_W;
$status = $this->checkCert();
if (is_error($status))
{
return $status;
}
$elements = array('total_fee', 'refund_fee', 'out_trade_no', 'out_refund_no');
$params = array_elements($elements, $params);
if (empty($params['total_fee']))
{
return error(-1, '订单总金额不能为空');
}
if (empty($params['refund_fee']))
{
return error(-1, '退款金额不能为空');
}
if (empty($params['out_trade_no']))
{
return error(-1, '商户订单号不能为空');
}
if (empty($params['out_refund_no']))
{
return error(-1, '商户退款单号不能为空');
}
$params['appid'] = $this->wxpay['appid'];
$params['mch_id'] = $this->wxpay['mch_id'];
$params['sub_mch_id'] = $this->wxpay['sub_mch_id'];
$params['op_user_id'] = $this->wxpay['mch_id'];
$params['nonce_str'] = random(32);
$params['sign'] = $this->bulidSign($params);
$extra = array(CURLOPT_SSLCERT => MODULE_ROOT . '/cert/' . $this->cert['apiclient_cert'] . '/apiclient_cert.pem', CURLOPT_SSLKEY => MODULE_ROOT . '/cert/' . $this->cert['apiclient_key'] . '/apiclient_key.pem', CURLOPT_CAINFO => MODULE_ROOT . '/cert/' . $this->cert['rootca'] . '/rootca.pem');
$result = $this->httpWxurl('https://api.mch.weixin.qq.com/secapi/pay/refund', $params, $extra);
if (is_error($result))
{
if ($result['message'] == 'certificate not match')
{
$order_channel = order_channel($this->pay_type);
return error(-1, '发起退款申请失败:证书文件不匹配。该订单下单渠道为' . $order_channel . ',请检查' . $order_channel . '支付证书是否正确配置。');
}
return error(-1, '发起退款申请失败:' . $result['message']);
}
if ($result['result_code'] != 'SUCCESS')
{
return error(-10, '发起退款申请失败.' . $result['err_code'] . ':' . $result['err_code_des']);
}
return $result;
}
public function payRefund_query($params)
{
$elements = array('out_refund_no');
$params = array_elements($elements, $params);
if (empty($params['out_refund_no']))
{
return error(-1, '商户退款单号不能为空');
}
$params['appid'] = $this->wxpay['appid'];
$params['mch_id'] = $this->wxpay['mch_id'];
$params['sub_mch_id'] = $this->wxpay['sub_mch_id'];
$params['nonce_str'] = random(32);
$params['sign'] = $this->bulidSign($params);
$result = $this->httpWxurl('https://api.mch.weixin.qq.com/pay/refundquery', $params);
if (is_error($result))
{
return error(-1, '查询微信退款进度失败.' . $result['message']);
}
if ($result['result_code'] != 'SUCCESS')
{
return error(-1, '查询微信退款进度失败.' . $result['err_code'] . ':' . $result['err_code_des']);
}
return $result;
}
public function payRefund_status()
{
$wechat_status = array( 'SUCCESS' => array('text' => '成功', 'value' => 3), 'FAIL' => array('text' => '失败', 'value' => 4), 'PROCESSING' => array('text' => '处理中', 'value' => 2), 'NOTSURE' => array('text' => '未确定,需要商户原退款单号重新发起', 'value' => 5) );
return $wechat_status;
}
}
?>