laravel5.5實現支付寶支付
起因
前段時間因為專案中需要實現支付寶手機網站支付功能,所以寫下這篇文章以作記錄,不足之處,歡迎指教。
後端框架:Laravel 5.5
業務功能
適用於商家在移動端網頁應用中整合支付寶支付功能。商家在網頁中呼叫支付寶提供的網頁支付介面調起支付寶客戶端內的支付模組,商家網頁會跳轉到支付寶中完成支付,支付完後跳回到商家網頁內,最後展示支付結果。若無法喚起支付寶客戶端,則在一定的時間後會自動進入網頁支付流程。
一. 建立應用
連結:支付寶螞蟻金服開放平臺
注意:
- 需擁有實名認證的支付寶賬戶。
- 企業或個體工商戶可申請
- 需要有真實有效的營業執照,切網站必須通過ICP備案
進入螞蟻金服開放平臺->開發者中心->網頁&移動應用。按需求建立應用,在這裡我建立的是網頁/移動類應用。
建立完成後提交稽核,大部分應用需要簽約後才能使用,簽約需要營業執照。
二. 配置應用環境
配置完成後,可提交稽核,開發者點選提交稽核後,預計會有一個工作日的稽核時間。應用上線成功後,狀態變為以上線,該狀態下的應用能夠呼叫生產環境的介面。
三. 介面呼叫配置
目前laravel中整合alipay SDK的支付介面很豐富。常用的有下面幾種:
OmniPay-laravel:github OmniPay-laravel連結
latrell/alipay:github latrell/alipay連結
...
因為專案的需要,在這裡我採用的是alipay的原生SDK包。
首先下載PHP版本的Demo:支付寶手機網站支付PHP demo
從index.php中可以看出該demo支援以下功能
手機網站2.0支付(介面名:alipay.trade.wap.pay) 手機網站2.0訂單查詢 (介面名:alipay.trade.query) 手機網站2.0訂單退款 (介面名:alipay.trade.refund) 手機網站2.0訂單退款查詢(介面名:alipay.trade.fastpay.refund.query) 手機網站2.0賬單下載(介面名:alipay.data.dataservice.bill.downloadurl.query)
其中config.php是配置檔案:
<?php $config = array ( //應用ID,您的APPID。 'app_id' => "", //商戶私鑰,您的原始格式RSA私鑰 'merchant_private_key' => "", //非同步通知地址 'notify_url' => "", //http://工程公網訪問地址/alipay.trade.wap.pay-PHP-UTF-8/notify_url.php //同步跳轉 'return_url' => "", //http://mitsein.com/alipay.trade.wap.pay-PHP-UTF-8/return_url.php // jk.mrwangqi.com //編碼格式 'charset' => "UTF-8", //簽名方式 'sign_type'=>"RSA2", //支付寶閘道器 'gatewayUrl' => "https://openapi.alipay.com/gateway.do", //支付寶公鑰,檢視地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。 'alipay_public_key' => "", );
配置完成後,修改demo許可權
sudo chmod -R 777 alipayDemo
訪問demo下的index.php
這樣子這個demo就可以運行了。
具體開發
現在下載SDK:支付寶手機網站支付PHP SDK
一. 引入SDK包
在laravel中引入SDK包的步驟:
- 在app/新建libs資料夾,將SDK包放在該目錄下
2. 找到根目錄下的composer.json檔案,新增如下配置:
"autoload": { "classmap": [ "database", "app/libs/alipay" //這裡是自定義包的檔案位置,我將我專案中的該SDK包命名為alipay ], "psr-4": { "App\\": "app/" } },
3. 執行以下命令
composer dump-autoload //當在包中加入新的類,需要更新autoloader
二. 移動/新建檔案
在alipay目錄下新建wappay目錄,在wappay目錄下新建buildermodel和service兩個目錄。將上面demo目錄下的wappay/buildermodel/AlipayTradeWapPayContentBuilder.php和wappay/service/AlipayTradeService.php兩個檔案分別複製到自己專案SDK包中新建的wappay中的相應目錄下。
AlipayTradeWapPayContentBuilder.php是alipay demo對支付寶手機網站支付介面業務引數的封裝。AlipayTradeService.php是alipay demo對支付寶手機網站支付介面業務功能的封裝。
在SDK目錄下新建log.txt。作為支付寶支付日誌存放檔案
三. 設定/引入名稱空間
對AlipayTradeWapPayContentBuilder.php和AlipayTradeService.php設定名稱空間,我設定的是
namespace App\libs\alipay\wappay\buildermodel; namespace App\libs\alipay\wappay\buildermodel;
對alipay/aop/request/AlipayTradeWapPayRequest.php和alipay/aop/AopClient.php設定名稱空間,我設定的是:
namespace App\libs\alipay\aop\request; namespace App\libs\alipay\aop;
在AlipayTradeWapPayContentBuilder.php中引入上面兩個名稱空間:
use App\libs\alipay\aop\request\AlipayTradeWapPayRequest; use App\libs\alipay\aop\AopClient;
將AlipayTradeService.php中的下面程式碼註釋:
// require_once dirname ( __FILE__ ).DIRECTORY_SEPARATOR.'./../../AopSdk.php'; // require dirname ( __FILE__ ).DIRECTORY_SEPARATOR.'./../../config.php';
四. 配置config(alipay.php)
在上面中alipay的demo中是有一個config.php檔案作為配置檔案的,這裡我們不需要這個檔案,我們利用laravel的特性,在laravel專案目錄下的config目錄新建一個alipay.php:
return [ //應用ID,您的APPID。 'app_id' => "", //商戶私鑰,您的原始格式RSA私鑰 'merchant_private_key' => "", //非同步通知地址 'notify_url' => "", //http://工程公網訪問地址/alipay.trade.wap.pay-PHP-UTF-8/notify_url.php //同步跳轉 'return_url' => "", //http://mitsein.com/alipay.trade.wap.pay-PHP-UTF-8/return_url.php // jk.mrwangqi.com //編碼格式 'charset' => "UTF-8", //簽名方式 'sign_type'=>"RSA2", //支付寶閘道器 'gatewayUrl' => "https://openapi.alipay.com/gateway.do", //支付寶公鑰,檢視地址:https://openhome.alipay.com/platform/keyManage.htm 對應APPID下的支付寶公鑰。 'alipay_public_key' => "", ];
五. 對應config修改函式
在alipay.php中進行配置支付介面所需引數。下面我們修改alipay/wappay/service/AlipayTradeService.php:
class AlipayTradeService { //支付寶閘道器地址 public $gateway_url = "https://openapi.alipay.com/gateway.do"; //支付寶公鑰 public $alipay_public_key; //商戶私鑰 public $private_key; //應用id public $appid; //編碼格式 public $charset = "UTF-8"; public $token = NULL; //返回資料格式 public $format = "json"; //簽名方式 public $signtype = "RSA"; function __construct(){ $this->gateway_url = config('alipay.gatewayUrl'); //獲得config資料夾下的alipay.php中的gatewayUrl引數,下同。 $this->appid = config('alipay.app_id'); $this->private_key = config('alipay.merchant_private_key'); $this->alipay_public_key = config('alipay.alipay_public_key'); $this->charset = config('alipay.charset'); $this->signtype= config('alipay.sign_type'); if(empty($this->appid)||trim($this->appid)==""){ throw new Exception("appid should not be NULL!"); } if(empty($this->private_key)||trim($this->private_key)==""){ throw new Exception("private_key should not be NULL!"); } if(empty($this->alipay_public_key)||trim($this->alipay_public_key)==""){ throw new Exception("alipay_public_key should not be NULL!"); } if(empty($this->charset)||trim($this->charset)==""){ throw new Exception("charset should not be NULL!"); } if(empty($this->gateway_url)||trim($this->gateway_url)==""){ throw new Exception("gateway_url should not be NULL!"); } } function AlipayWapPayService($alipay_config) { $this->__construct($alipay_config); } /** * alipay.trade.wap.pay * @param $builder 業務引數,使用buildmodel中的物件生成。 * @param $return_url 同步跳轉地址,公網可訪問 * @param $notify_url 非同步通知地址,公網可以訪問 * @return $response 支付寶返回的資訊 */ function wapPay($builder,$return_url,$notify_url) { $biz_content=$builder->getBizContent(); //列印業務引數 $this->writeLog($biz_content); $request = new AlipayTradeWapPayRequest(); $request->setNotifyUrl($notify_url); $request->setReturnUrl($return_url); $request->setBizContent ( $biz_content ); // 首先呼叫支付api $response = $this->aopclientRequestExecute ($request,true); // $response = $response->alipay_trade_wap_pay_response; return $response; } function aopclientRequestExecute($request,$ispage=false) { $aop = new AopClient (); $aop->gatewayUrl = $this->gateway_url; $aop->appId = $this->appid; $aop->rsaPrivateKey = $this->private_key; $aop->alipayrsaPublicKey = $this->alipay_public_key; $aop->apiVersion ="1.0"; $aop->postCharset = $this->charset; $aop->format= $this->format; $aop->signType=$this->signtype; // 開啟頁面資訊輸出 $aop->debugInfo=true; if($ispage) { $result = $aop->pageExecute($request,"post"); echo $result; } else { $result = $aop->Execute($request); } //開啟後,將報文寫入log檔案 $this->writeLog("response: ".var_export($result,true)); return $result; } //請確保專案檔案有可寫許可權,不然列印不了日誌。 function writeLog($text) { // $text=iconv("GBK", "UTF-8//IGNORE", $text); //$text = characet ( $text ); file_put_contents ( dirname ( __FILE__ ).DIRECTORY_SEPARATOR."./../../log.txt", date ( "Y-m-d H:i:s" ) . " " . $text . "\r\n", FILE_APPEND ); } } ?>
其他介面暫時用不到,所以在這裡我將其隱去。
六. 新建控制器(AlipayController)
php artisan make:controller AlipayController
因為需要實現手機網站支付,所以需要定義支付介面:
<?php namespace App\Http\Controllers\User\Alipay; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\libs\alipay\wappay\buildermodel\AlipayTradeWapPayContentBuilder; use App\libs\alipay\wappay\service\AlipayTradeService; class AlipayWapController extends Controller { /** *支付介面 */ public function alipayWapPay(Request $request) { $out_trade_no = getTradeNOString(); //公共方法生成唯一訂單號 $subject = 'test'; //資料僅供測試,下同 $total_amount = 0.01; $body = 'test test!'; $timeout_express="1m"; $payRequestBuilder = new AlipayTradeWapPayContentBuilder(); $payRequestBuilder->setBody($body); $payRequestBuilder->setSubject($subject); $payRequestBuilder->setOutTradeNo($out_trade_no); $payRequestBuilder->setTotalAmount($total_amount); $payRequestBuilder->setTimeExpress($timeout_express); $payResponse = new AlipayTradeService(); $result=$payResponse->wapPay($payRequestBuilder,config('alipay.return_url'),config('alipay.notify_url')); } /** *支付同步回撥介面,在config/alipay.php的return_url引數進行配置 */ public function alipayReturn() { } /** *支付非同步回撥介面,在config/alipay.php的notify_url引數進行配置 */ public function alipayNotify() { } }
七. 定義路由
定義支付路由及同步和非同步回撥路由
Route::group(['prefix' => 'alipay'],function() { Route::get('wappay','[email protected]'); Route::get('return','[email protected]'); Route::get('notify','[email protected]'); });
要注意的一點是同步路由是GET形式呼叫,而非同步路由是POST形式呼叫,在呼叫支付介面的時候會出現CSRF錯誤,現在最簡單的方法是利用laravel的中介軟體避免CSRF,在app/Http/Middleware/VerifyCsrfToken.php中增加路由
protected $except = [ // 'alipay/pay', 'alipay/return', 'alipay/notify' ];
八. 修改衝突
這時就可以通過定義路由進行呼叫支付介面,但是在呼叫時會報下面這個錯誤:
Cannot redeclare Encrypt() (previously declared in .../vendor/laravel/lumen-framework/src/helpers.php:126) //或: Cannot redeclare Decrypt() (previously declared in .../vendor/laravel/lumen-framework/src/helpers.php:126)
這是因為Laravel 5使用Alipay SDK時,Laravel內帶的加密解密函式Encrypt()/Decrypt()函式和Alipay SDK中的加密解密函式Encrypt()/Decrypt()函式命名衝突
解決方法:只需修改Alipay SDK中定義的函式名稱,修改引用的函式名稱。
修改步驟:
在Alipay SDK中,一共有需要修改三個檔案的內容:
aop/AopEncrypt.php aop/AopClient.php lotusphp_runtime/Cookie/Cookie.php
在檔案中查詢encrypt/decrypt替換為alipayEncrypt/alipayDecrypt即可。
注:如果伺服器是在Linux下,可能會報一個沒有許可權的錯誤,這是因為我們之前在SDK包中新建了一個log.txt,在alipay/wappay/service/AlipayTradeService.php中的writeLog()函式中向該檔案寫入支付日誌時沒有寫入許可權,給它個許可權就好了。