每次写微信支付新的内容都要找一些文档,有点烦了,就干脆发一波代码好了,懒得用微信的sdk,就是这么懒,看都没看过
注意,代码使用laravel框架写的,不影响使用,加入你的namespace,替换成你的Exception,配置好你的微信支付参数,相关的信息改写成你需要的,基本就能直接上手使用了
class WeChatPayService
function
1 qrCodePay 生成支付二维码
2 transferIntoWallet 向个人微信钱包转账(企业转账到钱包)
3 sendBasicRedPack 公众号发送微信红包
4 generateSign 参数签名
5 convertArrayToXml 数组参数转为xml参数
6 preOrder 微信预支付(统一下单)
7 refund 订单退款
8 xmlStructureToArray 微信返回的xml结构化为key->value的数组
9 decodeRefundNotifyInfo 解析微信通知的加密内容,返回数组(解密AES的应该用openssl方法)
仅供参考,请勿用于业务开发,出了问题我不负责的昂==
class WeChatPayService { private static $instance; private $conf; //统一下单API const PreOrder = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //普通红包 const BasicRedPack = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack'; //企业转账到零钱 const Transfer = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; //订单退款 const Refund = 'https://api.mch.weixin.qq.com/secapi/pay/refund'; private function __construct(){ $this->conf = Config::get('eqy.WeChatPay'); } private function __clone(){} public static function getInstance() { if(!(self::$instance instanceof self)) self::$instance = new self(); return self::$instance; } /**生成二维码支付 二维码30分钟有效 * @param $price int 订单价格 * @param $order_no string 商家订单编号 * @return array [prepay_id,qr_code_url] * @throws SystemInternalError */ public function qrCodePay($price,$order_no){ $result = $this->preOrder($price,$order_no,'NATIVE'); $xml_paser = xml_parser_create(); xml_parse_into_struct($xml_paser,$result,$re); $prepay_id = $re[17]['value']; $url = $re[21]['value']; return [$prepay_id,$url]; } /**企业直接转账到微信零钱 * @param $order_no * @param int $amount * @param string $dest_open_id * @param bool $check_name * @param null $name * @param string $desc * @throws SystemInternalError */ public function transferIntoWallet($order_no,$amount=500,$dest_open_id='obuxnxAIYyzLDwZ8_7yJo4kXRO-I',$check_name=false,$name=null,$desc='e企印首单返现') { $data = [ 'mch_appid'=>$this->conf['AppId'], 'mchid'=>$this->conf['MchId'], 'nonce_str'=>str_random(24), 'partner_trade_no'=>$order_no, 'openid'=>$dest_open_id, 'amount'=>$amount, 'desc'=>$desc, 'spbill_create_ip'=>$this->conf['ServerIp'] ]; if($check_name) { $data['check_name'] = 'FORCE_CHECK'; $data['re_user_name'] = $name; }else $data['check_name'] = 'NO_CHECK'; $this->generateSign($data); $data = $this->convertArrayToXml($data); $re = CurlService::getInstance()->post(self::Transfer,$data,null,false, $this->conf['CertPath'],$this->conf['PKeyPath']); if($re) { $arr = $this->xmlStructureToArray($re); if($arr['return_code']=='SUCCESS') { if ($arr['result_code'] != 'SUCCESS') throw new SystemInternalError('basic red pack send failed! open_id=' . $dest_open_id); } else throw new SystemInternalError("basic red pack api failed!" . json_encode($arr)); } } /**公众号发送微信红包 * @param $order_no * @param $dest_open_id * @param int $red_pack_amount * @param string $name * @param string $wishing * @param string $act_name * @param string $remark * @return mixed * @throws \Exception */ public function sendBasicRedPack($order_no,$dest_open_id='obuxnxAIYyzLDwZ8_7yJo4kXRO-I', $red_pack_amount=300,$name='e企印',$wishing='图文服务,使命必达!',$act_name='e企印首单返现',$remark='首单返现') { $data = [ 'mch_billno'=>$order_no, 'nonce_str'=>str_random(24), 'mch_id'=>$this->conf['MchId'], 'wxappid'=>$this->conf['AppId'], 'send_name'=>$name, 're_openid'=>$dest_open_id, 'total_amount'=>$red_pack_amount, 'total_num'=>1, 'wishing'=>$wishing, 'client_ip'=>$this->conf['ServerIp'], 'act_name'=>$act_name, 'remark'=>$remark ]; $this->generateSign($data); $data = $this->convertArrayToXml($data); $re = CurlService::getInstance()->post(self::BasicRedPack,$data,null,false, $this->conf['CertPath'],$this->conf['PKeyPath']); if($re) { // //// // // // // // // $xml_parser = xml_parser_create(); // xml_parse_into_struct($xml_parser, $re, $arr); $arr = $this->xmlStructureToArray($re); if($arr['return_code']=='SUCCESS') { if ($arr['result_code'] != 'SUCCESS') throw new SystemInternalError('basic red pack send failed! open_id=' . $dest_open_id); }else throw new SystemInternalError("basic red pack api failed!" . json_encode($arr)); } } /**参数签名 * @param $data */ private function generateSign(&$data) { ksort($data);//参数按照ASCII有小到大排序 $stringA = ''; //生成stringA $length = count($data); $i = 0; foreach($data as $k=>$v) { $i++; $stringA.=$k.'='.$v; if($i != $length) { $stringA.='&'; } } $stringSignTemp = $stringA.'&key='.$this->conf['Key']; $data['sign'] = strtoupper(md5($stringSignTemp)); } /**将array转变为xml * @param $data * @return string */ private function convertArrayToXml($data) { if(empty($data)) return 'error'; $xml = ' ' ."\n"; foreach($data as $k=>$v) { $xml.='<'.$k.'>'.$v.''.$k.'>'."\n"; } $xml.=''; return $xml; } /**生成微信的预支付订单 * @param $price int 订单金额,单位分 * @param $order_no string 商家自定义的订单编号 * @param string $trade_type string JSAPI 公众号支付 NATIVE 扫码支付 APP APP支付 * @return mixed * @throws SystemInternalError */ private function preOrder($price,$order_no,$trade_type='NATIVE') { $time = time(); $data = [ 'appid'=>$this->conf['AppId'], 'mch_id'=>$this->conf['MchId'], 'nonce_str'=>str_random(18), 'detail'=>'e企印用户支付('.($price/100).'元)', 'attach'=>'杭州牛印科技有限公司', 'device_info'=>'WEB', 'body'=>'e企印', 'total_fee'=>$price, 'fee_type'=>'CNY', 'time_start'=>date('YmdHis',$time), 'time_expire'=>date('YmdHis',$time+600), 'trade_type'=>$trade_type, 'out_trade_no'=>$order_no, 'notify_url'=>HOST.$this->conf['NotifyUrl'] ]; $this->generateSign($data); $data = $this->convertArrayToXml($data); $result = CurlService::getInstance()->post(self::PreOrder,$data); if(empty($result)) throw new SystemInternalError('wechat preorder failed!'); return $result; } /**微信订单退款 * @param $order_no * @param $refund_fee * @param $total_fee * @return array * @throws SystemInternalError * https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_4 */ public function refund($order_no,$refund_fee,$total_fee) { $data = [ 'appid'=>$this->conf['AppId'], 'mch_id'=>$this->conf['MchId'], 'nonce_str'=>str_random(18), 'sign_type'=>'MD5', 'out_trade_no'=>$order_no, 'out_refund_no'=>str_random(18), 'total_fee'=>$total_fee, 'refund_fee'=>$refund_fee, 'refund_fee_type'=>'CNY', 'refund_desc'=>'订单退款,标号:'.$order_no, 'notify_url'=>HOST.$this->conf['RefundNotifyUrl'] ]; $this->generateSign($data); $data = $this->convertArrayToXml($data); $re = CurlService::getInstance()->post(self::Refund,$data,null,false,$this->conf['CertPath'],$this->conf['PKeyPath']); if(empty($re)) throw new SystemInternalError('wechat refund failed!'); $arr = $this->xmlStructureToArray($re); if($arr['return_code']=='SUCCESS' || $arr['result_code'] == 'SUCCESS') return [true,'success']; else { return [false,$arr['err_code']]; } } /**针对微信返回的xml参数,结构化为操作便利的数组 * @param $xml * @return array */ public function xmlStructureToArray($xml) { $parser = xml_parser_create(); xml_parse_into_struct($parser,$xml,$arr); $data = []; foreach ($arr as $item) { if($item['tag']!='XML') $data[strtolower($item['tag'])] = $item['value']; } unset($arr,$xml,$parser); return $data; } /**解析微信退款内容 AES-256-ECB PKCS7Padding * @param $info * @return array * @throws SystemInternalError */ public function decodeRefundNotifyInfo($info) { $data = base64_decode($info); $key = strtolower(md5($this->conf['Key'])); $re = openssl_decrypt($data,'AES-256-ECB',$key,OPENSSL_NO_PADDING); if($re == false) throw new SystemInternalError('info decryption failed!'); $len = strlen($re); $pad = ord($re[$len - 1]); if ($pad < 1 || $pad > 32) $pad = 0; $xml = substr($re, 0, $len - $pad); return $this->xmlStructureToArray($xml); } }
附CurlService(网络请求)
class CurlService{ private static $instance; private function __construct(){} private function __clone(){} public static function getInstance() { if(!(self::$instance instanceof self)) self::$instance = new self(); return self::$instance; } /** post方法 * @param $url string 目标api地址 * @param $data array|json string 参数 * @param null $header array http请求头 * @param bool $is_debug bool 调试模式 * @param null $cert_path string 证书路径 * @param null $pkey_path string 私钥路径 * @param null $ca_path string 根证书路径 * @return mixed * @throws SystemInternalError */ public function post($url,$data,$header=null,$is_debug=false,$cert_path=null,$pkey_path=null,$ca_path=null) { $ch = curl_init(); curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); if($header) curl_setopt($ch,CURLOPT_HTTPHEADER,$header); curl_setopt($ch,CURLOPT_POST,true); curl_setopt($ch,CURLOPT_POSTFIELDS,$data); curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false); curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false); if($cert_path && $pkey_path) { curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLCERT,$cert_path); curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); curl_setopt($ch,CURLOPT_SSLKEY,$pkey_path); } if($ca_path) { curl_setopt($ch, CURLOPT_CAINFO, 'PEM'); curl_setopt($ch, CURLOPT_CAPATH, $ca_path); } $re = curl_exec($ch); if($is_debug) return $re; $code = curl_getinfo($ch,CURLINFO_HTTP_CODE); if($code != 200) throw new SystemInternalError('curl function failed! http code:'.$code); return $re; } /** get方法 * @param $url string 目标api地址 * @param null $data array 数组参数 * @param null $header array 请求头 * @param bool $is_debug bool 调试模式 * @return mixed * @throws SystemInternalError */ public function get($url,$data=null,$header=null,$is_debug=false) { if(is_array($data)) { $url .= '?'; $size = count($data); $index = 1; foreach ($data as $k=>$v) { $url .= $k.'='.$v; if($index != $size) $url .= '&'; $index ++; } } $ch = curl_init(); curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); if($header) curl_setopt($ch,CURLOPT_HTTPHEADER,$header); curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false); curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false); $re = curl_exec($ch); if($is_debug) return $re; $code = curl_getinfo($ch,CURLINFO_HTTP_CODE); if($code != 200) throw new SystemInternalError('curl function failed! http code:'.$code); return $re; }
配置文件截图如下(laravel框架下)