PHP微信支付开发
1.開(kāi)發(fā)環(huán)境
Thinkphp 3.2.3
微信:服務(wù)號(hào),已認(rèn)證
開(kāi)發(fā)域名:http://test.paywechat.com (自定義的域名,外網(wǎng)不可訪問(wèn))
2.需要相關(guān)文件和權(quán)限
微信支付需申請(qǐng)開(kāi)通
微信公眾平臺(tái)開(kāi)發(fā)者文檔:http://mp.weixin.qq.com/wiki/home/index.html
微信支付開(kāi)發(fā)者文檔:https://pay.weixin.qq.com/wiki/doc/api/index.html
微信支付SDK下載地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
3.開(kāi)發(fā)
下載好微信支付PHP版本的SDK,文件目錄為下圖:
把微信支付SDK的Cert和Lib目錄放入Thinkphp,目錄為
現(xiàn)在介紹微信支付授權(quán)目錄問(wèn)題,首先是微信支付開(kāi)發(fā)配置里面的支付授權(quán)目錄填寫(xiě),
然后填寫(xiě)JS接口安全域。
最后設(shè)置網(wǎng)頁(yè)授權(quán)
這些設(shè)置完,基本完成一半,注意設(shè)置的目錄和我thinkphp里面的目錄。
4.微信支付配置
把相關(guān)配置填寫(xiě)正確。
/** * 配置賬號(hào)信息 */class WxPayConfig {//=======【基本信息設(shè)置】=====================================///*** TODO: 修改這里配置為您自己申請(qǐng)的商戶信息* 微信公眾號(hào)信息配置* * APPID:綁定支付的APPID(必須配置,開(kāi)戶郵件中可查看)* * MCHID:商戶號(hào)(必須配置,開(kāi)戶郵件中可查看)* * KEY:商戶支付密鑰,參考開(kāi)戶郵件設(shè)置(必須配置,登錄商戶平臺(tái)自行設(shè)置)* 設(shè)置地址:https://pay.weixin.qq.com/index.php/account/api_cert* * APPSECRET:公眾帳號(hào)secert(僅JSAPI支付的時(shí)候需要配置, 登錄公眾平臺(tái),進(jìn)入開(kāi)發(fā)者中心可設(shè)置),* 獲取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN* @var string*/const APPID = '';const MCHID = '';const KEY = '';const APPSECRET = '';//=======【證書(shū)路徑設(shè)置】=====================================/*** TODO:設(shè)置商戶證書(shū)路徑* 證書(shū)路徑,注意應(yīng)該填寫(xiě)絕對(duì)路徑(僅退款、撤銷(xiāo)訂單時(shí)需要,可登錄商戶平臺(tái)下載,* API證書(shū)下載地址:https://pay.weixin.qq.com/index.php/account/api_cert,下載之前需要安裝商戶操作證書(shū))* @var path*/const SSLCERT_PATH = '../cert/apiclient_cert.pem';const SSLKEY_PATH = '../cert/apiclient_key.pem';//=======【curl代理設(shè)置】===================================/*** TODO:這里設(shè)置代理機(jī)器,只有需要代理的時(shí)候才設(shè)置,不需要代理,請(qǐng)?jiān)O(shè)置為0.0.0.0和0* 本例程通過(guò)curl使用HTTP POST方法,此處可修改代理服務(wù)器,* 默認(rèn)CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此時(shí)不開(kāi)啟代理(如有需要才設(shè)置)* @var unknown_type*/const CURL_PROXY_HOST = "0.0.0.0";//"10.152.18.220";const CURL_PROXY_PORT = 0;//8080;//=======【上報(bào)信息配置】===================================/*** TODO:接口調(diào)用上報(bào)等級(jí),默認(rèn)緊錯(cuò)誤上報(bào)(注意:上報(bào)超時(shí)間為【1s】,上報(bào)無(wú)論成敗【永不拋出異常】,* 不會(huì)影響接口調(diào)用流程),開(kāi)啟上報(bào)之后,方便微信監(jiān)控請(qǐng)求調(diào)用的質(zhì)量,建議至少* 開(kāi)啟錯(cuò)誤上報(bào)。* 上報(bào)等級(jí),0.關(guān)閉上報(bào); 1.僅錯(cuò)誤出錯(cuò)上報(bào); 2.全量上報(bào)* @var int*/const REPORT_LEVENL = 1; }現(xiàn)在開(kāi)始貼出代碼:
namespace Wechat\Controller; use Think\Controller; /*** 父類(lèi)控制器,需要繼承* @file ParentController.class.php* @author Gary <lizhiyong2204@sina.com>* @date 2015年8月4日* @todu*/ class ParentController extends Controller { protected $options = array ('token' => '', // 填寫(xiě)你設(shè)定的key'encodingaeskey' => '', // 填寫(xiě)加密用的EncodingAESKey'appid' => '', // 填寫(xiě)高級(jí)調(diào)用功能的app id'appsecret' => '', // 填寫(xiě)高級(jí)調(diào)用功能的密鑰'debug' => false,'logcallback' => ''); public $errCode = 40001; public $errMsg = "no access"; /*** 獲取access_token* @return mixed|boolean|unknown*/public function getToken(){$cache_token = S('exp_wechat_pay_token');if(!empty($cache_token)){return $cache_token;}$url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s';$url = sprintf($url,$this->options['appid'],$this->options['appsecret']); $result = $this->http_get($url);$result = json_decode($result,true); if(empty($result)){return false;} S('exp_wechat_pay_token',$result['access_token'],array('type'=>'file','expire'=>3600));return $result['access_token'];}/*** 發(fā)送客服消息* @param array $data 消息結(jié)構(gòu){"touser":"OPENID","msgtype":"news","news":{...}}*/public function sendCustomMessage($data){$token = $this->getToken();if (empty($token)) return false; $url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s';$url = sprintf($url,$token);$result = $this->http_post($url,self::json_encode($data));if ($result){$json = json_decode($result,true);if (!$json || !empty($json['errcode'])) {$this->errCode = $json['errcode'];$this->errMsg = $json['errmsg'];return false;}return $json;}return false;}/*** 發(fā)送模板消息* @param unknown $data* @return boolean|unknown*/public function sendTemplateMessage($data){$token = $this->getToken();if (empty($token)) return false;$url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s";$url = sprintf($url,$token);$result = $this->http_post($url,self::json_encode($data));if ($result){$json = json_decode($result,true);if (!$json || !empty($json['errcode'])) {$this->errCode = $json['errcode'];$this->errMsg = $json['errmsg'];return false;}return $json;}return false;}public function getFileCache($name){return S($name);}/*** 微信api不支持中文轉(zhuǎn)義的json結(jié)構(gòu)* @param array $arr*/static function json_encode($arr) {$parts = array ();$is_list = false;//Find out if the given array is a numerical array$keys = array_keys ( $arr );$max_length = count ( $arr ) - 1;if (($keys [0] === 0) && ($keys [$max_length] === $max_length )) { //See if the first key is 0 and last key is length - 1$is_list = true;for($i = 0; $i < count ( $keys ); $i ++) { //See if each key correspondes to its positionif ($i != $keys [$i]) { //A key fails at position check.$is_list = false; //It is an associative array.break;}}}foreach ( $arr as $key => $value ) {if (is_array ( $value )) { //Custom handling for arraysif ($is_list)$parts [] = self::json_encode ( $value ); /* :RECURSION: */else$parts [] = '"' . $key . '":' . self::json_encode ( $value ); /* :RECURSION: */} else {$str = '';if (! $is_list)$str = '"' . $key . '":';//Custom handling for multiple data typesif (!is_string ( $value ) && is_numeric ( $value ) && $value<2000000000)$str .= $value; //Numberselseif ($value === false)$str .= 'false'; //The booleanselseif ($value === true)$str .= 'true';else$str .= '"' . addslashes ( $value ) . '"'; //All other things// :TODO: Is there any more datatype we should be in the lookout for? (Object?)$parts [] = $str;}}$json = implode ( ',', $parts );if ($is_list)return '[' . $json . ']'; //Return numerical JSONreturn '{' . $json . '}'; //Return associative JSON}/**+----------------------------------------------------------* 生成隨機(jī)字符串+----------------------------------------------------------* @param int $length 要生成的隨機(jī)字符串長(zhǎng)度* @param string $type 隨機(jī)碼類(lèi)型:0,數(shù)字+大小寫(xiě)字母;1,數(shù)字;2,小寫(xiě)字母;3,大寫(xiě)字母;4,特殊字符;-1,數(shù)字+大小寫(xiě)字母+特殊字符+----------------------------------------------------------* @return string+----------------------------------------------------------*/static public function randCode($length = 5, $type = 2){$arr = array(1 => "0123456789", 2 => "abcdefghijklmnopqrstuvwxyz", 3 => "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 4 => "~@#$%^&*(){}[]|");if ($type == 0) {array_pop($arr);$string = implode("", $arr);} elseif ($type == "-1") {$string = implode("", $arr);} else {$string = $arr[$type];}$count = strlen($string) - 1;$code = '';for ($i = 0; $i < $length; $i++) {$code .= $string[rand(0, $count)];}return $code;} /*** GET 請(qǐng)求* @param string $url*/private function http_get($url){$oCurl = curl_init();if(stripos($url,"https://")!==FALSE){curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1}curl_setopt($oCurl, CURLOPT_URL, $url);curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );$sContent = curl_exec($oCurl);$aStatus = curl_getinfo($oCurl);curl_close($oCurl);if(intval($aStatus["http_code"])==200){return $sContent;}else{return false;}}/*** POST 請(qǐng)求* @param string $url* @param array $param* @param boolean $post_file 是否文件上傳* @return string content*/private function http_post($url,$param,$post_file=false){$oCurl = curl_init();if(stripos($url,"https://")!==FALSE){curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1}if (is_string($param) || $post_file) {$strPOST = $param;} else {$aPOST = array();foreach($param as $key=>$val){$aPOST[] = $key."=".urlencode($val);}$strPOST = join("&", $aPOST);}curl_setopt($oCurl, CURLOPT_URL, $url);curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );curl_setopt($oCurl, CURLOPT_POST,true);curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);$sContent = curl_exec($oCurl);$aStatus = curl_getinfo($oCurl);curl_close($oCurl);if(intval($aStatus["http_code"])==200){return $sContent;}else{return false;}} }namespace Wechat\Controller; use Wechat\Controller\ParentController; /*** 微信支付測(cè)試控制器* @file TestController.class.php* @author Gary <lizhiyong2204@sina.com>* @date 2015年8月4日* @todu*/ class TestController extends ParentController {private $_order_body = 'xxx';private $_order_goods_tag = 'xxx';public function __construct(){parent::__construct();require_once ROOT_PATH."Api/lib/WxPay.Api.php";require_once ROOT_PATH."Api/lib/WxPay.JsApiPay.php";}public function index(){//①、獲取用戶openid$tools = new \JsApiPay();$openId = $tools->GetOpenid(); //②、統(tǒng)一下單$input = new \WxPayUnifiedOrder(); //商品描述$input->SetBody($this->_order_body);//附加數(shù)據(jù),可以添加自己需要的數(shù)據(jù),微信回異步回調(diào)時(shí)會(huì)附加這個(gè)數(shù)據(jù)$input->SetAttach('xxx');//商戶訂單號(hào)$out_trade_no = \WxPayConfig::MCHID.date("YmdHis");$input->SetOut_trade_no($out_trade_no);//總金額,訂單總金額,只能為整數(shù),單位為分 $input->SetTotal_fee(1);//交易起始時(shí)間$input->SetTime_start(date("YmdHis"));//交易結(jié)束時(shí)間$input->SetTime_expire(date("YmdHis", time() + 600));//商品標(biāo)記$input->SetGoods_tag($this->_order_goods_tag);//通知地址,接收微信支付異步通知回調(diào)地址 SITE_URL=http://test.paywechat.com/Charge$notify_url = SITE_URL.'/index.php/Test/notify.html';$input->SetNotify_url($notify_url);//交易類(lèi)型$input->SetTrade_type("JSAPI");$input->SetOpenid($openId);$order = \WxPayApi::unifiedOrder($input);$jsApiParameters = $tools->GetJsApiParameters($order);//獲取共享收貨地址js函數(shù)參數(shù)$editAddress = $tools->GetEditAddressParameters();$this->assign('openId',$openId);$this->assign('jsApiParameters',$jsApiParameters);$this->assign('editAddress',$editAddress);$this->display(); }/*** 異步通知回調(diào)方法*/public function notify(){require_once ROOT_PATH."Api/lib/notify.php";$notify = new \PayNotifyCallBack();$notify->Handle(false);//這里的IsSuccess是我自定義的一個(gè)方法,后面我會(huì)貼出這個(gè)文件的代碼,供參考。//不建議這么寫(xiě),盡量使用官方的重寫(xiě)NotifyProcess方法,并把事務(wù)邏輯寫(xiě)在里面。$is_success = $notify->IsSuccess(); $bdata = $is_success['data']; //支付成功if($is_success['code'] == 1){ $news = array('touser' => $bdata['openid'],'msgtype' => 'news','news' => array ('articles'=> array (array('title' => '訂單支付成功','description' => "支付金額:{$bdata['total_fee']}\n"."微信訂單號(hào):{$bdata['transaction_id']}\n"'picurl' => '','url' => '' ))));//發(fā)送微信支付通知$this->sendCustomMessage($news); }else{//支付失敗}}/*** 支付成功頁(yè)面* 不可靠的回調(diào)* 可以在這里顯示一下支付成功跳轉(zhuǎn),不建議在這里直接寫(xiě)后臺(tái)支付成功邏輯。*/public function ajax_PaySuccess(){//訂單號(hào)$out_trade_no = I('post.out_trade_no');//支付金額$total_fee = I('post.total_fee');/*相關(guān)邏輯處理*/}
貼上模板HTML
<html> <head><meta http-equiv="content-type" content="text/html;charset=utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/> <title>微信支付樣例-支付</title><script type="text/javascript">//調(diào)用微信JS api 支付function jsApiCall(){WeixinJSBridge.invoke('getBrandWCPayRequest',{$jsApiParameters},function(res){WeixinJSBridge.log(res.err_msg);//取消支付if(res.err_msg == 'get_brand_wcpay_request:cancel'){//處理取消支付的事件邏輯}else if(res.err_msg == "get_brand_wcpay_request:ok"){/*使用以上方式判斷前端返回,微信團(tuán)隊(duì)鄭重提示:res.err_msg將在用戶支付成功后返回 ok,但并不保證它絕對(duì)可靠。這里可以使用Ajax提交到后臺(tái),處理一些日志,如Test控制器里面的ajax_PaySuccess方法。*/}alert(res.err_code+res.err_desc+res.err_msg);});}function callpay(){if (typeof WeixinJSBridge == "undefined"){if( document.addEventListener ){document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);}else if (document.attachEvent){document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall);}}else{jsApiCall();}}//獲取共享地址function editAddress(){WeixinJSBridge.invoke('editAddress',{$editAddress},function(res){var value1 = res.proviceFirstStageName;var value2 = res.addressCitySecondStageName;var value3 = res.addressCountiesThirdStageName;var value4 = res.addressDetailInfo;var tel = res.telNumber; alert(value1 + value2 + value3 + value4 + ":" + tel);});}window.onload = function(){if (typeof WeixinJSBridge == "undefined"){if( document.addEventListener ){document.addEventListener('WeixinJSBridgeReady', editAddress, false);}else if (document.attachEvent){document.attachEvent('WeixinJSBridgeReady', editAddress); document.attachEvent('onWeixinJSBridgeReady', editAddress);}}else{editAddress();}};</script> </head> <body><br/><font color="#9ACD32"><b>該筆訂單支付金額為<span style="color:#f00;font-size:50px">1分</span>錢(qián)</b></font><br/><br/><div align="center"><button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >立即支付</button></div> </body> </html>notify.php文件代碼,這里有在官方文件里新添加的一個(gè)自定義方法。
require_once ROOT_PATH."Api/lib/WxPay.Api.php"; require_once ROOT_PATH.'Api/lib/WxPay.Notify.php'; require_once ROOT_PATH.'Api/lib/log.php';//初始化日志 $logHandler= new \CLogFileHandler(ROOT_PATH."/logs/".date('Y-m-d').'.log'); $log = \Log::Init($logHandler, 15);class PayNotifyCallBack extends WxPayNotify {protected $para = array('code'=>0,'data'=>'');//查詢訂單public function Queryorder($transaction_id){$input = new \WxPayOrderQuery();$input->SetTransaction_id($transaction_id);$result = \WxPayApi::orderQuery($input);\Log::DEBUG("query:" . json_encode($result));if(array_key_exists("return_code", $result)&& array_key_exists("result_code", $result)&& $result["return_code"] == "SUCCESS"&& $result["result_code"] == "SUCCESS"){return true;}$this->para['code'] = 0;$this->para['data'] = '';return false;}//重寫(xiě)回調(diào)處理函數(shù)public function NotifyProcess($data, &$msg){\Log::DEBUG("call back:" . json_encode($data));$notfiyOutput = array();if(!array_key_exists("transaction_id", $data)){$msg = "輸入?yún)?shù)不正確";$this->para['code'] = 0;$this->para['data'] = '';return false;}//查詢訂單,判斷訂單真實(shí)性if(!$this->Queryorder($data["transaction_id"])){$msg = "訂單查詢失敗";$this->para['code'] = 0;$this->para['data'] = '';return false;}$this->para['code'] = 1;$this->para['data'] = $data;return true;}/*** 自定義方法 檢測(cè)微信端是否回調(diào)成功方法* 不建議這么寫(xiě),盡量使用官方的重寫(xiě)NotifyProcess方法,并把事務(wù)邏輯寫(xiě)在里面。* @return multitype:number string*/public function IsSuccess(){return $this->para;} }到這里基本上完成,可以在微信端打開(kāi)http://test.paywechat.com/Charge/index.php/Test/index/
我的環(huán)境,HTTP服務(wù)器沒(méi)有重寫(xiě)url,微信支付繼續(xù)探索中,有些地方可能寫(xiě)的有問(wèn)題或不足,望大家諒解,互相學(xué)習(xí)。
總結(jié)
- 上一篇: 175. Combine Two Tab
- 下一篇: 面向对象与软件工程—团队作业1