前面的文章已經描述過在螞蟻金服開放平台創建應用簽約等流程,詳見:PHP App端支付寶支付,這里就不多說了,剩下的分兩步,第一步是支付前的准備工作,也就是整合支付類文件,我已經整合好可以直接用,代碼開始:
支付類文件總共三個:
<?php class alipay_pc{ /** * 簽名字符串 * @param $prestr 需要簽名的字符串 * @param $key 私鑰 * return 簽名結果 */ function md5Sign($prestr, $key) { $prestr = $prestr . $key; return md5($prestr); } /** * 驗證簽名 * @param $prestr 需要簽名的字符串 * @param $sign 簽名結果 * @param $key 私鑰 * return 簽名結果 */ function md5Verify($prestr, $sign, $key) { $prestr = $prestr . $key; $mysgin = md5($prestr); if($mysgin == $sign) { return true; } else { return false; } } /** * 把數組所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串 * @param $para 需要拼接的數組 * return 拼接完成以后的字符串 */ function createLinkstring($para) { $arg = ""; while (list ($key, $val) = each ($para)) { $arg.=$key."=".$val."&"; } //去掉最后一個&字符 $arg = substr($arg,0,count($arg)-2); //如果存在轉義字符,那么去掉轉義 if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} return $arg; } /** * 把數組所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串,並對字符串做urlencode編碼 * @param $para 需要拼接的數組 * return 拼接完成以后的字符串 */ function createLinkstringUrlencode($para) { $arg = ""; while (list ($key, $val) = each ($para)) { $arg.=$key."=".urlencode($val)."&"; } //去掉最后一個&字符 $arg = substr($arg,0,count($arg)-2); //如果存在轉義字符,那么去掉轉義 if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} return $arg; } /** * 除去數組中的空值和簽名參數 * @param $para 簽名參數組 * return 去掉空值與簽名參數后的新簽名參數組 */ function paraFilter($para) { $para_filter = array(); while (list ($key, $val) = each ($para)) { if($key == "sign" || $key == "sign_type" || $val == "")continue; else $para_filter[$key] = $para[$key]; } return $para_filter; } /** * 對數組排序 * @param $para 排序前的數組 * return 排序后的數組 */ function argSort($para) { ksort($para); reset($para); return $para; } /** * 寫日志,方便測試(看網站需求,也可以改成把記錄存入數據庫) * 注意:服務器需要開通fopen配置 * @param $word 要寫入日志里的文本內容 默認值:空值 */ function logResult($word='') { $fp = fopen("log.txt","a"); flock($fp, LOCK_EX) ; fwrite($fp,"執行日期:".strftime("%Y%m%d%H%M%S",time())."\n".$word."\n"); flock($fp, LOCK_UN); fclose($fp); } /** * 遠程獲取數據,POST模式 * 注意: * 1.使用Crul需要修改服務器中php.ini文件的設置,找到php_curl.dll去掉前面的";"就行了 * 2.文件夾中cacert.pem是SSL證書請保證其路徑有效,目前默認路徑是:getcwd().'\\cacert.pem' * @param $url 指定URL完整路徑地址 * @param $cacert_url 指定當前工作目錄絕對路徑 * @param $para 請求的數據 * @param $input_charset 編碼格式。默認值:空值 * return 遠程輸出的數據 */ function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = '') { if (trim($input_charset) != '') { $url = $url."_input_charset=".$input_charset; } $curl = curl_init($url); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL證書認證 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//嚴格認證 curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//證書地址 curl_setopt($curl, CURLOPT_HEADER, 0 ); // 過濾HTTP頭 curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 顯示輸出結果 curl_setopt($curl,CURLOPT_POST,true); // post傳輸數據 curl_setopt($curl,CURLOPT_POSTFIELDS,$para);// post傳輸數據 $responseText = curl_exec($curl); //var_dump( curl_error($curl) );//如果執行curl過程中出現異常,可打開此開關,以便查看異常內容 curl_close($curl); return $responseText; } /** * 遠程獲取數據,GET模式 * 注意: * 1.使用Crul需要修改服務器中php.ini文件的設置,找到php_curl.dll去掉前面的";"就行了 * 2.文件夾中cacert.pem是SSL證書請保證其路徑有效,目前默認路徑是:getcwd().'\\cacert.pem' * @param $url 指定URL完整路徑地址 * @param $cacert_url 指定當前工作目錄絕對路徑 * return 遠程輸出的數據 */ function getHttpResponseGET($url,$cacert_url) { $curl = curl_init($url); curl_setopt($curl, CURLOPT_HEADER, 0 ); // 過濾HTTP頭 curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 顯示輸出結果 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);//SSL證書認證 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//嚴格認證 curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//證書地址 $responseText = curl_exec($curl); //var_dump( curl_error($curl) );//如果執行curl過程中出現異常,可打開此開關,以便查看異常內容 curl_close($curl); return $responseText; } /** * 實現多種字符編碼方式 * @param $input 需要編碼的字符串 * @param $_output_charset 輸出的編碼格式 * @param $_input_charset 輸入的編碼格式 * return 編碼后的字符串 */ function charsetEncode($input,$_output_charset ,$_input_charset) { $output = ""; if(!isset($_output_charset) )$_output_charset = $_input_charset; if($_input_charset == $_output_charset || $input ==null ) { $output = $input; } elseif (function_exists("mb_convert_encoding")) { $output = mb_convert_encoding($input,$_output_charset,$_input_charset); } elseif(function_exists("iconv")) { $output = iconv($_input_charset,$_output_charset,$input); } else die("sorry, you have no libs support for charset change."); return $output; } /** * 實現多種字符解碼方式 * @param $input 需要解碼的字符串 * @param $_output_charset 輸出的解碼格式 * @param $_input_charset 輸入的解碼格式 * return 解碼后的字符串 */ function charsetDecode($input,$_input_charset ,$_output_charset) { $output = ""; if(!isset($_input_charset) )$_input_charset = $_input_charset ; if($_input_charset == $_output_charset || $input ==null ) { $output = $input; } elseif (function_exists("mb_convert_encoding")) { $output = mb_convert_encoding($input,$_output_charset,$_input_charset); } elseif(function_exists("iconv")) { $output = iconv($_input_charset,$_output_charset,$input); } else die("sorry, you have no libs support for charset changes."); return $output; } }
<?php /* * * 類名:AlipaySubmit * 功能:支付寶各接口請求提交類 * 詳細:構造支付寶各接口表單HTML文本,獲取遠程HTTP數據 */ vendor("Alipaypc.alipay_pc"); class AlipaySubmit { var $alipay_config; /** *支付寶網關地址(新) */ var $alipay_gateway_new = 'https://mapi.alipay.com/gateway.do?'; function __construct($alipay_config){ $this->alipay_config = $alipay_config; } function AlipaySubmit($alipay_config) { $this->__construct($alipay_config); } /** * 生成簽名結果 * @param $para_sort 已排序要簽名的數組 * return 簽名結果字符串 */ function buildRequestMysign($para_sort) { $alipaypc = new \Alipaypc(); //把數組所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串 $prestr = $alipaypc->createLinkstring($para_sort); $mysign = ""; switch (strtoupper(trim($this->alipay_config['sign_type']))) { case "MD5" : $mysign = $alipaypc->md5Sign($prestr, $this->alipay_config['key']); break; default : $mysign = ""; } return $mysign; } /** * 生成要請求給支付寶的參數數組 * @param $para_temp 請求前的參數數組 * @return 要請求的參數數組 */ function buildRequestPara($para_temp) { $alipaypc = new \Alipaypc(); //除去待簽名參數數組中的空值和簽名參數 $para_filter = $alipaypc->paraFilter($para_temp); //對待簽名參數數組排序 $para_sort = $alipaypc->argSort($para_filter); //生成簽名結果 $mysign = $this->buildRequestMysign($para_sort); //簽名結果與簽名方式加入請求提交參數組中 $para_sort['sign'] = $mysign; $para_sort['sign_type'] = strtoupper(trim($this->alipay_config['sign_type'])); return $para_sort; } /** * 生成要請求給支付寶的參數數組 * @param $para_temp 請求前的參數數組 * @return 要請求的參數數組字符串 */ function buildRequestParaToString($para_temp) { $alipaypc = new \Alipaypc(); //待請求參數數組 $para = $this->buildRequestPara($para_temp); //把參數組中所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串,並對字符串做urlencode編碼 $request_data = $alipaypc->createLinkstringUrlencode($para); return $request_data; } /** * 建立請求,以表單HTML形式構造(默認) * @param $para_temp 請求參數數組 * @param $method 提交方式。兩個值可選:post、get * @param $button_name 確認按鈕顯示文字 * @return 提交表單HTML文本 */ function buildRequestForm($para_temp, $method, $button_name) { //待請求參數數組 $para = $this->buildRequestPara($para_temp); $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$this->alipay_gateway_new."_input_charset=".trim(strtolower($this->alipay_config['input_charset']))."' method='".$method."'>"; while (list ($key, $val) = each ($para)) { $sHtml.= "<input type='hidden' name='".$key."' value='".$val."'/>"; } //submit按鈕控件請不要含有name屬性 $sHtml = $sHtml."<input type='submit' value='".$button_name."' style='display:none;'></form>"; $sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>"; return $sHtml; } /** * 用於防釣魚,調用接口query_timestamp來獲取時間戳的處理函數 * 注意:該功能PHP5環境及以上支持,因此必須服務器、本地電腦中裝有支持DOMDocument、SSL的PHP配置環境。建議本地調試時使用PHP開發軟件 * return 時間戳字符串 */ function query_timestamp() { $url = $this->alipay_gateway_new."service=query_timestamp&partner=".trim(strtolower($this->alipay_config['partner']))."&_input_charset=".trim(strtolower($this->alipay_config['input_charset'])); $encrypt_key = ""; $doc = new DOMDocument(); $doc->load($url); $itemEncrypt_key = $doc->getElementsByTagName( "encrypt_key" ); $encrypt_key = $itemEncrypt_key->item(0)->nodeValue; return $encrypt_key; } }
<?php /* * * 類名:AlipayNotify * 功能:支付寶通知處理類 * 詳細:處理支付寶各接口通知返回 * 版本:3.2 *************************注意************************* * 調試通知返回時,可查看或改寫log日志的寫入TXT里的數據,來檢查通知返回是否正常 */ vendor("Alipaypc.Alipaypc"); class AlipayNotify { /** * HTTPS形式消息驗證地址 */ var $https_verify_url = 'https://mapi.alipay.com/gateway.do?service=notify_verify&'; /** * HTTP形式消息驗證地址 */ var $http_verify_url = 'http://notify.alipay.com/trade/notify_query.do?'; var $alipay_config; function __construct($alipay_config){ $this->alipay_config = $alipay_config; } function AlipayNotify($alipay_config) { $this->__construct($alipay_config); } /** * 針對notify_url驗證消息是否是支付寶發出的合法消息 * @return 驗證結果 */ function verifyNotify(){ $alipaypc = new \Alipaypc(); if(empty($_POST)) {//判斷POST來的數組是否為空 return false; } else { //生成簽名結果 $isSign = $this->getSignVeryfy($_POST, $_POST["sign"]); //獲取支付寶遠程服務器ATN結果(驗證是否是支付寶發來的消息) $responseTxt = 'false'; if (! empty($_POST["notify_id"])) {$responseTxt = $this->getResponse($_POST["notify_id"]);} //寫日志記錄 if ($isSign) { $isSignStr = 'true'; } else { $isSignStr = 'false'; } $log_text = "responseTxt=".$responseTxt."\n notify_url_log:isSign=".$isSignStr.","; $log_text = $log_text.$alipaypc->createLinkString($_POST); $alipaypc->logResult($log_text); //驗證 //$responsetTxt的結果不是true,與服務器設置問題、合作身份者ID、notify_id一分鍾失效有關 //isSign的結果不是true,與安全校驗碼、請求時的參數格式(如:帶自定義參數等)、編碼格式有關 if (preg_match("/true$/i",$responseTxt) && $isSign) { return true; } else { return false; } } } /** * 針對return_url驗證消息是否是支付寶發出的合法消息 * @return 驗證結果 */ function verifyReturn(){ $alipaypc = new \Alipaypc(); if(empty($_GET)) {//判斷POST來的數組是否為空 return false; } else { //生成簽名結果 $isSign = $this->getSignVeryfy($_GET, $_GET["sign"]); //獲取支付寶遠程服務器ATN結果(驗證是否是支付寶發來的消息) $responseTxt = 'false'; if (! empty($_GET["notify_id"])) {$responseTxt = $this->getResponse($_GET["notify_id"]);} //寫日志記錄 if ($isSign) { $isSignStr = 'true'; } else { $isSignStr = 'false'; } $log_text = "responseTxt=".$responseTxt."\n return_url_log:isSign=".$isSignStr.","; $log_text = $log_text.$alipaypc->createLinkString($_GET); $alipaypc->logResult($log_text); //驗證 //$responsetTxt的結果不是true,與服務器設置問題、合作身份者ID、notify_id一分鍾失效有關 //isSign的結果不是true,與安全校驗碼、請求時的參數格式(如:帶自定義參數等)、編碼格式有關 if (preg_match("/true$/i",$responseTxt) && $isSign) { return true; } else { return false; } } } /** * 獲取返回時的簽名驗證結果 * @param $para_temp 通知返回來的參數數組 * @param $sign 返回的簽名結果 * @return 簽名驗證結果 */ function getSignVeryfy($para_temp, $sign) { $alipaypc = new \Alipaypc(); //除去待簽名參數數組中的空值和簽名參數 $para_filter = $alipaypc->paraFilter($para_temp); //對待簽名參數數組排序 $para_sort = $alipaypc->argSort($para_filter); //把數組所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串 $prestr = $alipaypc->createLinkstring($para_sort); $isSgin = false; switch (strtoupper(trim($this->alipay_config['sign_type']))) { case "MD5" : $isSgin = $alipaypc->md5Verify($prestr, $sign, $this->alipay_config['key']); break; default : $isSgin = false; } return $isSgin; } /** * 獲取遠程服務器ATN結果,驗證返回URL * @param $notify_id 通知校驗ID * @return 服務器ATN結果 * 驗證結果集: * invalid命令參數不對 出現這個錯誤,請檢測返回處理中partner和key是否為空 * true 返回正確信息 * false 請檢查防火牆或者是服務器阻止端口問題以及驗證時間是否超過一分鍾 */ function getResponse($notify_id) { $alipaypc = new \Alipaypc(); $transport = strtolower(trim($this->alipay_config['transport'])); $partner = trim($this->alipay_config['partner']); $veryfy_url = ''; if($transport == 'https') { $veryfy_url = $this->https_verify_url; } else { $veryfy_url = $this->http_verify_url; } $veryfy_url = $veryfy_url."partner=" . $partner . "¬ify_id=" . $notify_id; $responseTxt = $alipaypc->getHttpResponseGET($veryfy_url, $this->alipay_config['cacert']); return $responseTxt; } }
將上述的三個文件整合好后放到第三方類庫,在控制器中引入並即可:
<?php namespace app\huihui\controller\one; use app\huihui\controller\A_Controller; use app\huihui\model\MeetingOrder; use think\Controller; use think\Request; /** * 支付寶PC端購票掃碼支付 */ vendor('Alipaypc.alipay_notify'); vendor('Alipaypc.alipay_submit'); vendor('Alipaypc.alipay_pc'); class Alipaypc extends A_Controller { public $alipay_config = array( 'partner' => '', //收款支付寶賬號,以2088開頭由16位純數字組成的字符串,一般情況下收款賬號就是簽約賬號 'seller_id' => '', // MD5密鑰,安全檢驗碼,由數字和字母組成的32位字符串,查看地址:https://b.alipay.com/order/pidAndKey.htm 'key' => '', // 服務器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問 'notify_url'=> '', // 頁面跳轉同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數,必須外網可以正常訪問 'return_url' => '', //字符編碼格式 目前支持 gbk 或 utf-8 'input_charset' => 'utf-8', //簽名方式 不需修改 'sign_type' => 'MD5', //ca證書路徑地址,用於curl中ssl校驗,請保證cacert.pem文件在當前文件夾目錄中 'cacert' => '/cacert.pem', //訪問模式,根據自己的服務器是否支持ssl訪問,若支持請選擇https;若不支持請選擇http 'transport' => 'http', // 支付類型 ,無需修改 'payment_type' => '1', // 產品類型,無需修改 'service' => 'create_direct_pay_by_user', ); //異步回調 public function notifypc() { //計算得出通知驗證結果 $alipayNotify = new \AlipayNotify($this->alipay_config); $verify_result = $alipayNotify->verifyNotify(); if($verify_result) { //驗證成功 //獲取支付寶的通知返回參數,可參考技術文檔中服務器異步通知參數列表 //商戶訂單號 $out_trade_no = $_POST['out_trade_no']; $order = new Order(); //實例化訂單 $ret = $order->getOrderN2($out_trade_no); //查詢訂單信息 if($_POST['trade_status'] == 'TRADE_FINISHED') { //判斷該筆訂單是否在商戶網站中已經做過處理 //如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序 //請務必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的 //如果有做過處理,不執行商戶的業務程序 //注意: //退款日期超過可退款期限后(如三個月可退款),支付寶系統發送該交易狀態通知 //調試用,寫文本函數記錄程序運行情況是否正常 //logResult("這里寫入想要調試的代碼變量值,或其他運行的結果記錄"); $total_amount=$ret['money']; $total_fee = $_POST['total_fee']; if($total_amount==$total_fee) { //你的業務邏輯操作 }else{//交易異常 logResult($_POST); } }else if ($_POST['trade_status'] == 'TRADE_SUCCESS') { //判斷該筆訂單是否在商戶網站中已經做過處理 //如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序 //請務必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的 //如果有做過處理,不執行商戶的業務程序 //注意: //付款完成后,支付寶系統發送該交易狀態通知 //調試用,寫文本函數記錄程序運行情況是否正常 //logResult("這里寫入想要調試的代碼變量值,或其他運行的結果記錄"); $total_amount=$ret['money']; $total_fee = $_POST['total_fee']; if($total_amount==$total_fee) { //你的業務邏輯操作 }else {//交易異常 logResult($_POST); } } echo "success"; //請不要修改或刪除 }else { //驗證失敗 echo "fail"; //調試用,寫文本函數記錄程序運行情況是否正常 //logResult("這里寫入想要調試的代碼變量值,或其他運行的結果記錄"); } } //同步回調 public function returnpc() { //計算得出通知驗證結果 $alipayNotify = new \AlipayNotify($this->alipay_config); $verify_result = $alipayNotify->verifyReturn(); if($verify_result) {//驗證成功 //獲取支付寶的通知返回參數,可參考技術文檔中頁面跳轉同步通知參數列表 //商戶訂單號 $out_trade_no = $_GET['out_trade_no']; $order = new Order(); //實例化訂單 $ret = $order->getOrderN2($out_trade_no); //查詢訂單信息 if($_GET['trade_status'] == 'TRADE_FINISHED' || $_GET['trade_status'] == 'TRADE_SUCCESS') { //判斷該筆訂單是否在商戶網站中已經做過處理 //如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序 //如果有做過處理,不執行商戶的業務程序 $total_amount=$ret['money']; $total_fee = $_GET['total_fee']; if(floatval($total_amount)==floatval($total_fee)) { //交易成功跳轉 echo "<script>window.location.href='';</script>"; } else {//交易異常跳轉 echo "<script>window.location.href='"; } }else { //交易失敗跳轉 echo "<script>window.location.href='"; } }else { //驗證失敗 //如要調試,請看alipay_notify.php頁面的verifyReturn函數 echo "<script>window.location.href='';</script>"; } } //調用統一下單接口生成預支付訂單並把數據返回給APP public function alipaypc(Request $request) { $param = $request->param(); //接收值 $out_trade_no = $param['orderCode'];//訂單號 $money = MeetingOrder::where('orderCode', $out_trade_no)->value('money'); //商戶訂單號,商戶網站訂單系統中唯一訂單號,必填 $out_trade_no = $out_trade_no; //訂單名稱,必填 $subject = ''; //自定義 //付款金額,必填 $total_fee = $money; //收銀台頁面上,商品展示的超鏈接,必填 $show_url = '';//自定義 //商品描述,可空 $body = ''; //自定義 //構造要請求的參數數組,無需改動 $parameter = array( "service" => $this->alipay_config['service'], "partner" => $this->alipay_config['partner'], "seller_id" => $this->alipay_config['seller_id'], "payment_type" => $this->alipay_config['payment_type'], "notify_url" => $this->alipay_config['notify_url'], "return_url" => $this->alipay_config['return_url'], "_input_charset" => trim(strtolower($this->alipay_config['input_charset'])), "out_trade_no" => $out_trade_no, "subject" => $subject, "total_fee" => $total_fee, "show_url" => $show_url, "body" => $body, //其他業務參數根據在線開發文檔,添加參數.文檔地址:https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.2Z6TSk&treeId=60&articleId=103693&docType=1 //如"參數名" => "參數值" 注:上一個參數末尾需要“,”逗號。 ); //建立請求 $alipaySubmit = new \AlipaySubmit($this->alipay_config); $html_text = $alipaySubmit->buildRequestForm($parameter,"get", "確認"); echo $html_text; } }
到這里就結束了,在控制器中的方法內是需要有一些替換成你自己的東西,比如查詢訂單,支付成功后的業務邏輯等,仔細排查一遍代碼將相應的地方改為自己的,如果不出意外掃碼支付已經成功了,趕緊去試試是否可以調起掃碼支付功能啦!!!