首先吐槽一下支付寶的官方文檔,它只是簡單介紹一下開發的流程和參數,而對於新人來說如果只看它的官方文檔很多時候是看不懂的,我也是邊看文檔邊網上查資料才把它弄懂。下面我詳細介紹支付寶的電腦支付是如何實現
電腦網站支付
第一步:進入官網,在電腦網站支付下載它的demo
第二步:沙箱環境
想要實現支付寶支付,你要准備一堆東西,對於只想測試的人來說這太麻煩了,而支付寶為我們提供了沙箱環境,這里面有我們開發測試需要的東西
進入沙箱環境,在沙箱應用里下載沙箱錢包(只支持掃一掃、付款碼、門店詳情頁功能,其余功能不提供),它是用戶測試掃碼支付用的
第三步:把demo放進項目
一般來說支付寶demo屬於擴展文件,所以我把它放到extend目錄下
然后把config.php放到application\extra(注:可以不放,我放在里面只是方便調用它配置參數)
第四步: 填寫配置參數
這里的參數在沙箱環境都已經有,你就根據它的注釋填寫就可以
支付寶的公鑰和商戶私鑰,你要下載"支付寶開放平台開發助手",它可以生成秘鑰,然后把應用公鑰放在設置里面
這樣他就生成支付寶公鑰,你就把它放到alipay的里面的支付寶公鑰,而應用私鑰直接放到alipay的商戶秘鑰
這里我主要說一下同步和異步通知地址,支付寶支付成功后會執行這兩個方法(注:同步是支付寶支付成功要跳轉的地址),系統會把你支付的信息用POST方式異步傳給你的方法。因為是異步,所以頁面是沒有變化的,在異步這個方法里可以寫你自己的業務邏輯。比如接收值,存入數據庫之類。這里有個大坑,坑了我兩天,即在異步方法里是沒法用session取值的,我原本想用session取用戶登錄id存入數據庫中,后來問了師傅才知道,異步是服務器和服務器之間的交互,所以沒有cookieId,沒有cookieId當然沒有session值。
注意:異步通知程序執行完后必須打印輸出“success”(不包含引號)。如果商戶反饋給支付寶的字符不是success這7個字符,支付寶服務器會不斷重發通知,直到超過24小時22分鍾。一般情況下,25小時以內完成8次通知(通知的間隔頻率一般是:4m,10m,10m,1h,2h,6h,15h);
第五步: 調用支付接口
首先我們要修改extend\alipay\pagepay\Pagepay.php,因為直接調用的話會報錯:

<?php use think\Loader; Loader::import("alipay.pagepay.service.AlipayTradeService",EXTEND_PATH); Loader::import('alipay.pagepay.buildermodel.AlipayTradePagePayContentBuilder',EXTEND_PATH); class Pagepay { //支付入口 public static function pay($params) { //第一步:校檢參數 self::checkParams($params); //第二步:構造參數 $payRequestBuilder = new AlipayTradePagePayContentBuilder(); $payRequestBuilder->setBody($params['t_body']);//描述 $payRequestBuilder->setSubject($params['trade_name']);//訂單名稱,必填 $payRequestBuilder->setTotalAmount($params['total_amount']);//付款金額,必填 $payRequestBuilder->setOutTradeNo($params['out_trade_no']);//商戶訂單號,商戶網站訂單系統中唯一訂單號,必填 //第三步:獲取配置 $config = config('alipay'); $aop = new AlipayTradeService($config); /** * 第四步:電腦網站支付請求(會自動跳轉到支付頁面) * @param $builder 業務參數,使用buildmodel中的對象生成。 * @param $return_url 同步跳轉地址,公網可以訪問 * @param $notify_url 異步通知地址,公網可以訪問 * @return $response 支付寶返回的信息 */ $aop->pagePay($payRequestBuilder, $config['return_url'], $config['notify_url']); } //支付檢驗 private static function checkParams($params) { //商戶訂單號 if(empty(trim($params['out_trade_no']))){ self::processError("你輸入的商戶訂單號有誤!"); } //訂單名稱 if(empty(trim($params['trade_name']))){ self::processError("訂單名稱為空!"); } //付款金額 if(floatval(trim($params['total_amount'])) <= 0){ self::processError("付款金額有誤!!"); } } //統一錯誤處理接口 private static function processError($msg) { throw new \think\Exception($msg); } }
修改完之后我們就可以在控制器里調用支付寶接口:
<?php namespace app\aliyun\controller; use think\Controller; //支付控制器 class Pay extends Controller { public function index(){ return $this->fetch(); } //電腦支付寶接口 public function index2(){ //付款金額 $total_amount = input('WIDtotal_amount'); if($total_amount){ $params = [ //商戶訂單號,商戶網站訂單系統中唯一訂單號,必填 'out_trade_no' => input('WIDout_trade_no'), //訂單名稱,必填 'trade_name' => input('WIDsubject'), //付款金額,必填 'total_amount' => $total_amount, //描述 't_body' => input('WIDbody'), ]; import('alipay.pagepay.Pagepay',EXTEND_PATH); \Pagepay::pay($params); } } //異步通知地址 public function notify_url(){ /* * *************************頁面功能說明************************* * 創建該頁面文件時,請留心該頁面文件中無任何HTML代碼及空格。 * 該頁面不能在本機電腦測試,請到服務器上做測試。請確保外部可以訪問該頁面。 * 如果沒有收到該頁面返回的 success 信息,支付寶會在24小時內按一定的時間策略重發通知 */ import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH); //$arr = $this->request->param();使用這個無法接受數據 $arr = $_POST; $alipaySevice = new \AlipayTradeService(config('alipay')); $alipaySevice->writeLog(var_export($arr, true)); $result = $alipaySevice->check($arr); /* 實際驗證過程建議商戶添加以下校驗。 1、商戶需要驗證該通知數據中的out_trade_no是否為商戶系統中創建的訂單號, 2、判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創建時的金額), 3、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email) 4、驗證app_id是否為該商戶本身。 */ if ($result) {//驗證成功 //商戶訂單號 $out_trade_no = $arr['out_trade_no']; //支付寶交易號 $trade_no = $arr['trade_no']; //交易狀態 $trade_status = $arr['trade_status']; //TRADE_FINISHED:交易完成, TRADE_SUCCESS:支付成功 if ($arr['trade_status'] == 'TRADE_FINISHED' || $arr['trade_status'] == 'TRADE_SUCCESS') { //做邏輯處理,例如:保存數據 file_put_contents("../extend/sites.txt",implode(",",$arr).PHP_EOL,FILE_APPEND | LOCK_EX); } echo "success"; } else { //驗證失敗 echo "fail"; } } //同步跳轉,支付成功,由客戶的瀏覽器觸發的一個通知 public function return_url(){ /* * * 功能:支付寶頁面跳轉同步通知頁面 * 版本:2.0 * 修改日期:2017-05-01 * 說明: * 以下代碼只是為了方便商戶測試而提供的樣例代碼,商戶可以根據自己網站的需要,按照技術文檔編寫,並非一定要使用該代碼。 *************************頁面功能說明************************* * 該頁面可在本機電腦測試 * 可放入HTML等美化頁面的代碼、商戶業務邏輯程序代碼 */ import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH); //$arr = $this->request->param(); $arr = $_GET; $alipaySevice = new \AlipayTradeService(config('alipay')); $result = $alipaySevice->check($arr); /* 實際驗證過程建議商戶添加以下校驗。 1、商戶需要驗證該通知數據中的out_trade_no是否為商戶系統中創建的訂單號, 2、判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創建時的金額), 3、校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據的對應的操作方(有的時候,一個商戶可能有多個seller_id/seller_email) 4、驗證app_id是否為該商戶本身。 */ if ($result) {//驗證成功 //做邏輯處理,例如:保存數據 echo "<pre>"; print_r($arr); //file_put_contents("../extend/sites.txt",implode(",",$arr).PHP_EOL,FILE_APPEND | LOCK_EX); } else { //驗證失敗 echo "驗證失敗"; } } }
到了這一步基本就完成了,點擊付款就會跳轉到這個頁面,我們可以使用沙箱錢包掃碼支付或登錄沙箱賬號支付
付款遇到的問題及解決方案
如果是新手的第一次接入支付寶接口,或多或少遇到一些問題,今天我就把我遇到的問題作總結
問題一:調試錯誤,請回到請求來源地,重新發起請求
這很有可以就是你得網關有問題,沙箱環境支付寶網關如下:
https://openapi.alipaydev.com/gateway.do
沒有修改之前demo文件支付寶網關是已經寫好,但默認的網關沒有加dev,如果你在沙箱環境中必須加上dev
問題二:each()函數報錯
支付寶支付的時候遇到的,因為php7+以上版本拋棄了each函數導致,找到extend\alipay\aop\AopClient.php:
while (list ($key, $val) = each ($para_temp)) {
改為
foreach ($para_temp as $key => $val) {
問題三:收不到異步通知自查方案-支付寶接口常見錯誤系列
1.需http://或者https://格式的完整路徑
例:https://您的域名/notify_url.php ,支持ip地址方式。(推薦使用域名)
2.不能加?id=123這類自定義參數
錯誤示例:https://您的域名/notify_url.php?id=123&test=abc
3.必須外網可以正常訪問,這個不難理解,在您的異步地址沒有代碼邏輯的情況下,直接訪問應該是一個空白頁面並且http狀態是200(不支持http200以外的狀態),即不能在本機電腦測試,要到服務器上做測試,同時也要確保外部可以訪問該頁面。
4.不能有重定向 如:http302
5.異步通知,通過 POST 請求的形式將支付結果作為參數通知到商戶系統,使用POST方式接收,請確保服務器路由已經開放POST通知
支付寶社區:https://openclub.alipay.com/club/history/read/1314
總結:
1、notify_url:通過 POST 請求的形式將支付結果作為參數通知到商戶系統,使用$_POST獲取數據
2、return_url:通過 GET 請求的形式,使用$_GET獲取數據
3、notify_url是異步通知,return_url是同步跳轉;異步無法使用Cookie和Session保存數據
查詢交易記錄
修改extend\alipay\pagepay\Query.php,因為直接調用的話會報錯:

<?php use think\Loader; Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH); Loader::import('alipay.pagepay.buildermodel.AlipayTradeQueryContentBuilder',EXTEND_PATH); class Query { // 商戶訂單號(out_trade_no) or 支付寶交易號(trade_no) 二選一 const QUERY_TYPE = 'out_trade_no'; public static function exec($query_no) { // 1.設置請求參數 $RequestBuilder = new \AlipayTradeQueryContentBuilder(); if (self::QUERY_TYPE == 'trade_no') { $RequestBuilder->setTradeNo(trim($query_no)); } else { $RequestBuilder->setOutTradeNo($query_no); } // 2.獲取配置 $config = config('alipay'); $aop = new \AlipayTradeService($config); // 3.發起請求 $response = $aop->Query($RequestBuilder); // 4.轉為數組格式返回 $response = json_decode(json_encode($response), true); // 5.進行結果處理 if (!empty($response['code']) && $response['code'] != '10000') { self::processError('交易查詢接口出錯, 錯誤碼: '.$response['code'].' 錯誤原因: '.$response['sub_msg']); } return $response; } /** * 統一錯誤處理接口 * @param string $msg 錯誤描述 */ private static function processError($msg) { throw new \think\Exception($msg); } }
商戶訂單號和支付寶交易號二選一,控制器這里默認獲取商戶訂單號:
//交易查詢 public function query(){ //商戶訂單號 $trade_no = input('WIDTQout_trade_no'); echo $trade_no; // return; if($trade_no){ import('alipay.pagepay.Query',EXTEND_PATH); $response = \Query::exec($trade_no); print_r($response); } }
注意:商戶訂單號和支付寶交易號不能同時為空。 trade_no、 out_trade_no如果同時存在優先取trade_no
退款
修改extend\alipay\pagepay\Refund.php

<?php use think\Loader; Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH); Loader::import('alipay.pagepay.buildermodel.AlipayTradeRefundContentBuilder',EXTEND_PATH); class Refund { /** * 主入口 * @param array $params 退款參數, 具體如下 * @param string $params['trade_no']/$params['out_trade_no'] 商戶訂單號或支付寶單號其中之一 * @param string $params['out_request_no'] 商戶退款號(可選, 如同一筆交易多次退款, 則必填) * @param float $params['refund_amount'] 退款金額 */ public static function exec($params) { // 1.校檢參數 self::checkParams($params); // 2.構造請求參數 $RequestBuilder = self::builderParams($params); // 3.獲取配置 $config = config('alipay'); $aop = new \AlipayTradeService($config); // 4.發送請求 $response = $aop->Refund($RequestBuilder); // 5.轉為數組格式返回 $response = json_decode(json_encode($response), true); // 6.進行結果處理 if (!empty($response['code']) && $response['code'] != '10000') { self::processError('交易退款接口出錯, 錯誤碼: '.$response['code'].' 錯誤原因: '.$response['sub_msg']); } return $response; } /** * 校檢參數 */ private static function checkParams($params) { if (empty($params['trade_no']) && empty($params['out_trade_no'])) { self::processError('商戶訂單號(out_trade_no)和支付寶單號(trade_no)不得通知為空'); } if (floatval(trim($params['refund_amount'])) <= 0) { self::processError('退款金額(refund_amount)為大於0的數'); } } /** * 構造請求參數 */ private static function builderParams($params) { $RequestBuilder = new \AlipayTradeRefundContentBuilder(); // 1.判斷單號類型 if (isset($params['trade_no'])) { $RequestBuilder->setTradeNo($params['trade_no']); } else { $RequestBuilder->setOutTradeNo($params['out_trade_no']); } // 2.判斷是否部分退款 if (!empty($params['out_request_no'])) { $RequestBuilder->setOutRequestNo($params['out_request_no']); } $RequestBuilder->setRefundAmount($params['refund_amount']); return $RequestBuilder; } /** * 統一錯誤處理接口 * @param string $msg 錯誤描述 */ private static function processError($msg) { throw new \think\Exception($msg); } }
商戶訂單號和支付寶交易號二選一,控制器這里默認商戶訂單號:
//退款 public function refund(){ //付款金額 $total_amount = input('WIDTRrefund_amount'); if($total_amount){ $params = [ //商戶訂單號,商戶網站訂單系統中唯一訂單號,必填 'out_trade_no' => input('WIDTRout_trade_no'), //付款金額,必填 'refund_amount' => $total_amount, //退款單號,如果是部分退款,必填 'out_request_no' => input('WIDTRout_request_no'), ]; import('alipay.pagepay.Refund',EXTEND_PATH); $res = \Refund::exec($params); echo "<pre>"; print_r($res); } }
退款單號(out_request_no):
1、全額退款不傳,部分退款必傳
2、out_request_no:標識一次退款請求,同一筆交易多次退款需要保證唯一,如需部分退款,則此參數必傳。也可以理解為同一筆交易退款,退款金額小於付款金額是,必須傳這個參數,而且同一筆交易分多次退款的話,out_request_no每次傳值都不能重復,必須保證唯一性
退款查詢
修改extend\alipay\pagepay\RefundQuery.php

<?php use think\Loader; Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH); Loader::import('alipay.pagepay.buildermodel.AlipayTradeFastpayRefundQueryContentBuilder',EXTEND_PATH); class RefundQuery { /** * 主入口 * @param array $params 退款查詢參數, 具體如下: * @param string $params['trade_no']/$params['out_trade_no'] 商戶訂單號或支付寶單號其中之一 * @param string $params['out_request_no'] 可空, 為空時, 退款號為訂單號 */ public static function exec($params) { // 1.校檢參數 if (empty($params['trade_no']) && empty($params['out_trade_no'])) { throw new \think\Exception('商戶訂單號(out_trade_no)和支付寶單號(trade_no)不得通知為空'); } // 2.構造請求參數 $RequestBuilder = self::builderParams($params); // 3.獲取配置 $config = config('alipay'); $aop = new \AlipayTradeService($config); // 4.發起請求 $response = $aop->refundQuery($RequestBuilder); // 5.轉為數組格式返回 $response = json_decode(json_encode($response), true); // 6.進行結果處理 if (!empty($response['code']) && $response['code'] != '10000') { self::processError('退款查詢接口出錯, 錯誤碼為: '.$response['code'].', 錯誤原因為: '.$response['sub_msg']); } return $response; } /** * 構造請求參數 */ private static function builderParams($params) { $RequestBuilder = new \AlipayTradeFastpayRefundQueryContentBuilder(); if (isset($params['trade_no'])) { $RequestBuilder->setTradeNo($params['trade_no']); } else { $RequestBuilder->setOutTradeNo($params['out_trade_no']); } // 如果未傳退款號, 則以單號為退款號查詢 if (isset($params['out_request_no'])) { $RequestBuilder->setOutRequestNo($params['out_request_no']); } else { $out_request_no = isset($params['trade_no']) ? $params['trade_no'] : $params['out_trade_no']; $RequestBuilder->setOutRequestNo($out_request_no); } return $RequestBuilder; } /** * 統一錯誤處理接口 * @param string $msg 錯誤描述 */ private static function processError($msg) { throw new \think\Exception($msg); } }
商戶訂單號和支付寶交易號二選一,控制器這里默認商戶訂單號:
//退款查詢 public function refundquery(){ //商戶訂單號 $params['out_trade_no'] = input('WIDRQout_trade_no'); //退款請求號 $params['out_request_no'] = input('WIDRQout_request_no'); if($params['out_trade_no']){ import('alipay.pagepay.RefundQuery',EXTEND_PATH); $response = \RefundQuery::exec($params); echo "<pre>"; print_r($response); } }
注意:退款請求號值(out_request_no)必傳,退款時傳的值,如果退款時沒傳則無法查詢;商戶訂單號和支付寶交易號不能同時為空。 trade_no、 out_trade_no如果同時存在優先取trade_no
關閉交易
修改extend\alipay\pagepay\Close.php

<?php use think\Loader; Loader::import('alipay.pagepay.service.AlipayTradeService',EXTEND_PATH); Loader::import('alipay.pagepay.buildermodel.AlipayTradeCloseContentBuilder',EXTEND_PATH); class Close { // 商戶訂單號(out_trade_no) or 支付寶交易號(trade_no) 二選一 const QUERY_TYPE = 'out_trade_no'; public static function exec($query_no) { // 1.構建請求參數 $RequestBuilder = new \AlipayTradeCloseContentBuilder(); if (self::QUERY_TYPE == 'trade_no') { $RequestBuilder->setTradeNo(trim($query_no)); } else { $RequestBuilder->setOutTradeNo(trim($query_no)); } // 2.獲取配置 $config = config('alipay'); $aop = new \AlipayTradeService($config); // 3.發起請求 $response = $aop->Close($RequestBuilder); // 4.轉為數組格式返回 $response = json_decode(json_encode($response), true); // 5.進行結果處理 if (!empty($response['code']) && $response['code'] != '10000') { self::processError('交易關閉接口出錯, 錯誤碼: '.$response['code'].' 錯誤原因: '.$response['sub_msg']); } return $response; } /** * 統一錯誤處理接口 * @param string $msg 錯誤描述 */ private static function processError($msg) { throw new \think\Exception($msg); } }
商戶訂單號和支付寶交易號二選一,控制器這里默認商戶訂單號:
//關閉交易 public function close(){ //商戶訂單號 $out_trade_no = input('WIDTCout_trade_no'); if($out_trade_no){ import('alipay.pagepay.Close',EXTEND_PATH); $response = \Close::exec($out_trade_no); echo "<pre>"; print_r($response); } }
注意:
1.商戶訂單號和支付寶交易號不能同時為空。 trade_no、 out_trade_no如果同時存在優先取trade_no
2.交易開始不是在點付款時,而是在掃碼付款但沒有支付時,這是才可以關閉交易