支付寶在全部支付方式中最好開發的了,由於文檔比較清晰,並且開發起來也比較簡單。所以,支付寶的坑是相對較少的。
原文地址php
APP支付步驟爲:html
因爲APP支付是由APP去調起支付寶支付,因此服務端須要作的事情就是將請求參數封裝好以後返回APP便可。node
獲取支付寶的配置信息。
支付時須要的配置信息有:json
生成商家訂單信息。
這個步驟由商家自行生成。支付寶那邊只須要知道的訂單信息爲:api
生成請求給支付寶的加密字符串。數組
$sign = $alipaySubmit->buildRequestParaForApp($para_token);
其中, buildRequestParaForApp
的實現爲:安全
/** * 對數組排序 * @param $para 排序前的數組 * return 排序後的數組 */ function argSort($para) { ksort($para); reset($para); return $para; }
/** * RSA簽名 * @param $data 待簽名數據 * @param $private_key_path 商戶私鑰文件路徑 * return 簽名結果 */ function rsaSign($data, $private_key_path) { $priKey = file_get_contents($private_key_path); $res = openssl_get_privatekey($priKey); openssl_sign($data, $sign, $res); openssl_free_key($res); //base64編碼 $sign = base64_encode($sign); return $sign; }
將待校驗數據和加密字符串拼接,返回給APP。服務器
$url = ""; foreach ($para_token as $key => $value) { $url .= $key."=".urlencode($value)."&"; } return $url."sign=".urlencode($sign);
網頁版支付步驟爲:數據結構
網頁版的支付寶支付相對於APP調起支付寶要複雜,由於網頁支付時,須要屢次請求支付寶服務器獲取支付的必要參數。app
設置支付寶配置信息。
/**調用受權接口alipay.wap.trade.create.direct獲取受權碼token**/ //返回格式 private $format = ""; //必填,不須要修改 //版本 private $v = ""; //必填,不須要修改 //請求號 private $req_id = ""; //必填,須保證每次請求都是惟一 //**req_data詳細信息** //服務器異步通知頁面路徑 private $notify_url = ""; //需http://格式的完整路徑,不容許加?id=123這類自定義參數 //頁面跳轉同步通知頁面路徑 private $call_back_url = ""; //需http://格式的完整路徑,不容許加?id=123這類自定義參數 //賣家支付寶帳戶 private $seller_email = ""; //必填 //商戶訂單號 private $out_trade_no = ""; //商戶網站訂單系統中惟一訂單號,必填 //訂單名稱 private $subject = ""; //必填 //付款金額 private $total_fee = ""; //必填 //請求業務參數詳細 private $req_data = ""; //必填 //配置 private $alipay_config = array(); /************************************************************/
向支付寶申請新訂單,並獲取訂單的token。
請求token的service爲: alipay.wap.trade.create.direct
。
構造參數:
$para_token = array( "service" => "alipay.wap.trade.create.direct", // 合做者身份(partner ID) "partner" => trim($this->alipay_config['partner']), // APP使用的是RSA,網頁版使用的是MD5 "sec_id" => trim($this->alipay_config['sign_type']), // 返回的數據格式 "format" => $this->format, // 版本號? "v" => $this->v, // 惟一的請求號 "req_id" => $this->req_id, // 請求參數 "req_data" => $req_data, // 字符集,通常爲utf8便可。 "_input_charset" => trim(strtolower($this->alipay_config['input_charset'])) );
將構造好的請求參數,進行處理,字典排序,拼接字符串,簽名:
$para_filter = paraFilter($para_temp); $para_sort = argSort($para_filter); $mysign = $this->buildRequestMysign($para_sort); //簽名結果與簽名方式加入請求提交參數組中 $para_sort['sign'] = $mysign; return $para_sort;
處理:過濾值爲空的數據,過濾簽名類型和簽名。
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 排序前的數組
*/ function argSort($para) { ksort($para); reset($para); return $para; } ``` 簽名: ```php /** * 生成簽名結果 * @param $para_sort 已排序要簽名的數組 * return 簽名結果字符串 */ function buildRequestMysign($para_sort) { //把數組全部元素,按照「參數=參數值」的模式用「&」字符拼接成字符串 $prestr = createLinkstring($para_sort); $mysign = ""; switch (strtoupper(trim($this->alipay_config['sign_type']))) { case "MD5" : // MD5直接將密鑰拼接在字符串後面再進行MD5加密。 $mysign = md5Sign($prestr, $this->alipay_config['key']); break; case "RSA" : // RSA則是先讀取商戶的私鑰,再用該密鑰對字符串進行加密。 $mysign = rsaSign($prestr, $this->alipay_config['private_key_path']); break; case "0001" : $mysign = rsaSign($prestr, $this->alipay_config['private_key_path']); break; default : $mysign = ""; } return $mysign; } ``` 3. 用構造好的參數請求支付寶後臺申請新訂單: **注意:請求時,必須帶上SSL證書。** ```php $sResult = getHttpResponsePOST($this->alipay_gateway_new, $this->alipay_config['cacert'],$request_data,trim(strtolower($this->alipay_config['input_charset']))); ``` 請求函數的實現: ```php /** * 遠程獲取數據,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; } ``` 處理支付寶返回的數據,並獲取token。 ```php //URLDECODE返回的信息 $html_text = urldecode($html_text); //解析遠程模擬提交後返回的信息 $para_html_text = parseResponse($html_text); //獲取request_token $request_token = $para_html_text['request_token']; ``` parseResponse函數的實現: ```php /** * 解析遠程模擬提交後返回的信息 * @param $str_text 要解析的字符串 * @return 解析結果 */ function parseResponse($str_text) { //以「&」字符切割字符串 $para_split = explode('&',$str_text); //把切割後的字符串數組變成變量與數值組合的數組 foreach ($para_split as $item) { //得到第一個=字符的位置 $nPos = strpos($item,'='); //得到字符串長度 $nLen = strlen($item); //得到變量名 $key = substr($item,0,$nPos); //得到數值 $value = substr($item,$nPos+1,$nLen-$nPos-1); //放入數組中 $para_text[$key] = $value; } if( ! empty ($para_text['res_data'])) { //解析加密部分字符串 if($this->alipay_config['sign_type'] == '0001') { $para_text['res_data'] = rsaDecrypt($para_text['res_data'], $this->alipay_config['private_key_path']); } //token從res_data中解析出來(也就是說res_data中已經包含token的內容) $doc = new DOMDocument(); $doc->loadXML($para_text['res_data']); $para_text['request_token'] = $doc->getElementsByTagName( "request_token" )->item(0)->nodeValue; } return $para_text; } ```
攜帶token進行訂單支付。
成功請求token回來後,就能夠向支付寶發出一次支付請求。
一樣構造請求數據:
//業務詳細只須要攜帶步驟2的token便可。 $req_data = '<auth_and_execute_req><request_token>' . $request_token . '</request_token></auth_and_execute_req>'; //必填 //構造要請求的參數數組,無需改動 $parameter = array( "service" => "alipay.wap.auth.authAndExecute", // 合做者身份(partner ID) "partner" => trim($this->alipay_config['partner']), // 簽名類型 "sec_id" => trim($this->alipay_config['sign_type']), // 和步驟2一致 "format" => $this->format, "v" => $this->v, "req_id" => $this->req_id, // 業務詳細參數 "req_data" => $req_data, // 字符集,通常爲utf8. "_input_charset" => trim(strtolower($this->alipay_config['input_charset'])) );
將這些參數,在頁面中傳送給支付寶便可發起一次支付請求。
在PHP 中的實現就是將這些參數,渲染至HTML中,再將HTML中的表單提交便可。
到此,網頁版的支付寶支付完成整個流程。
在上面,咱們看到有兩個參數傳給了支付寶:
call_back_url
: 交易成功後,支付寶頁面上「返回到商家頁面」的地址(同步回調)notify_url
: 交易狀態變動後,支付寶通知網站的回調地址(異步通知)對於手機網站支付產生的交易,支付寶會根據原始支付API中傳入的異步通知地址notify_url,經過POST請求的形式將支付結果做爲參數通知到商戶系統。對於App支付產生的交易,支付寶會根據原始支付API中傳入的異步通知地址notify_url,經過POST請求的形式將支付結果做爲參數通知到商戶系統。
支付寶異步通知官方文檔中寫的比較清楚,何時出發通知,返回什麼參數,注意事項都有,開發者能夠根據本身的狀況查看具體信息。
驗籤步驟能夠移步至這裏
這裏就簡單的用手上的項目舉例說明,支付寶通知後,後臺是如何進行驗籤和處理訂單。
public function app_notifyOp(){ $payment_api = $this->_get_payment_api(); $payment_config = $this->_get_payment_config(); // 支付寶是用POST方式發送通知信息 $callback_info = $payment_api->getNotifyInfoApp($_POST); if($callback_info) { //驗證成功 if ($callback_info['order_state']) { // 若是是支付成功則改變訂單狀態 $result = $this->_update_order($callback_info['out_trade_no'], $callback_info['trade_no']); }else{ // 若是是退款成功則修改退訂的相關狀態 $result = $this->_app_refund($callback_info['out_trade_no'], $callback_info['trade_no'], $callback_info['refund_fee']); } if($result['state']) { echo 'success';die; } } //驗證失敗 echo "fail";die; }
獲取支付寶通知數據
支付寶異步通知是POST請求,返回的數據結構以下:
{ "total_amount": "31.00", "buyer_id": "ID", "trade_no": "TRADE_NO", "body": "pay_sn:580546601841783375", "notify_time": "2017-04-27 09:50:59", "subject": "580546601841783375", "sign_type": "RSA", "buyer_logon_id": "ID", "auth_app_id": "APPID", "charset": "utf-8", "notify_type": "trade_status_sync", "invoice_amount": "31.00", "out_trade_no": "580546601841783375_r", "trade_status": "TRADE_SUCCESS", "gmt_payment": "2017-04-27 09:50:58", "version": "1.0", "point_amount": "0.00", "sign": "SIGNATURE", "gmt_create": "2017-04-27 09:50:58", "buyer_pay_amount": "31.00", "receipt_amount": "31.00", "fund_bill_list": "[{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}]", "app_id": "APPID", "seller_id": "SELLERID", "notify_id": "8414394a1190f25edbbec9ba4b98642mem", "seller_email": "YOUR_ALIPAY_ACCOUNT" }
驗籤數據
驗籤須要支付寶的公鑰
驗籤和簽名的流程是同樣的,都是將全部除了 sign
之外的參數,進行字典排序,並以 key=value
的形式以 &
符號拼成字符串,再使用密鑰進行簽名,將獲得的簽名與支付寶返回的簽名進行對比,完成驗簽過程。
function getSignVeryfy($para_temp, $sign) { //除去待簽名參數數組中的空值和簽名參數 $para = paraFilter($para_temp); //對待簽名參數數組排序 $para = argSort($para); //把數組全部元素,按照「參數=參數值」的模式用「&」字符拼接成字符串 $prestr = createLinkstring($para); $prestr = htmlspecialchars_decode($prestr); $isSgin = false; switch (strtoupper(trim($this->alipay_config['sign_type']))) { case "MD5" : $isSgin = md5Verify($prestr, $sign, $this->alipay_config['key']); break; case "RSA" : $isSgin = rsaVerify($prestr, trim($this->alipay_config['ali_public_key_path']), $sign); break; case "0001" : $isSgin = rsaVerify($prestr, trim($this->alipay_config['ali_public_key_path']), $sign); break; default : $isSgin = false; } logResult($log); return $isSgin; }
可是這裏有個坑,就是返回數據中的 fund_bill_list
是通過html轉義的(如例子中的數據: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}]
),若是直接使用該參數進行簽名,則會致使簽名失敗。這裏就須要將字符串轉義了: [{"amount":"31.00","fundChannel":"ALIPAYACCOUNT"}]
,用轉義後的參數值進行簽名,經過校驗。
驗簽完畢後,後臺就能夠根據實際狀況進行訂單狀態的更改。
祝各位程序猿在開發支付寶支付時再也不有坑,也但願支付寶在後續的更新中再也不埋雷。