首先吐槽一下支付寶的官方文檔,它只是簡單介紹一下開發的流程和參數,而對於新人來講若是隻看它的官方文檔不少時候是看不懂的,我也是邊看文檔邊網上查資料才把它弄懂。下面我詳細介紹支付寶的電腦支付是如何實現php
電腦網站支付數據庫
第一步:進入官網,在電腦網站支付下載它的demojson
第二步:沙箱環境api
想要實現支付寶支付,你要準備一堆東西,對於只想測試的人來講這太麻煩了,而支付寶爲咱們提供了沙箱環境,這裏面有咱們開發測試須要的東西數組
進入沙箱環境,在沙箱應用裏下載沙箱錢包(只支持掃一掃、付款碼、門店詳情頁功能,其他功能不提供),它是用戶測試掃碼支付用的瀏覽器
第三步:把demo放進項目服務器
通常來講支付寶demo屬於擴展文件,因此我把它放到extend目錄下cookie
而後把config.php放到application\extra(注:能夠不放,我放在裏面只是方便調用它配置參數)session
第四步: 填寫配置參數php7
這裏的參數在沙箱環境都已經有,你就根據它的註釋填寫就能夠
支付寶的公鑰和商戶私鑰,你要下載"支付寶開放平臺開發助手",它能夠生成祕鑰,而後把應用公鑰放在設置裏面
這樣他就生成支付寶公鑰,你就把它放到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); /* 實際驗證過程建議商戶添加如下校驗。 一、商戶須要驗證該通知數據中的out_trade_no是否爲商戶系統中建立的訂單號, 二、判斷total_amount是否確實爲該訂單的實際金額(即商戶訂單建立時的金額), 三、校驗通知中的seller_id(或者seller_email) 是否爲out_trade_no這筆單據的對應的操做方(有的時候,一個商戶可能有多個seller_id/seller_email) 四、驗證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); /* 實際驗證過程建議商戶添加如下校驗。 一、商戶須要驗證該通知數據中的out_trade_no是否爲商戶系統中建立的訂單號, 二、判斷total_amount是否確實爲該訂單的實際金額(即商戶訂單建立時的金額), 三、校驗通知中的seller_id(或者seller_email) 是否爲out_trade_no這筆單據的對應的操做方(有的時候,一個商戶可能有多個seller_id/seller_email) 四、驗證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
總結:
一、notify_url:經過 POST 請求的形式將支付結果做爲參數通知到商戶系統,使用$_POST獲取數據
二、return_url:經過 GET 請求的形式,使用$_GET獲取數據
三、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):
一、全額退款不傳,部分退款必傳
二、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.交易開始不是在點付款時,而是在掃碼付款但沒有支付時,這是才能夠關閉交易