由於去年做手機Portl接口的工作,需要使用支付寶的支付,於是手機網站支付接口就成了首選。
1.首先下載接口包
支付寶商家服務中心鏈接:https://b.alipay.com/login.htm?goto=https://b.alipay.com:443/newIndex.htm
手機網站支付的產品介紹:https://b.alipay.com/order/productDetail.htm?productId=2013080604609688
demo下載鏈接:https://doc.open.alipay.com/doc2/detail.htm?treeId=54&articleId=104511&docType=1 (請點擊關鍵字demo,進行下載)
解壓下載的文件可以看到文件夾的結構如下圖:
我使用的是RSA簽名方式,PHP-UTF-8的文件夾
2.readme.txt的文檔說明
紅色字體的文件是最重要的文件,也是必需的!
│
├lib┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈類文件夾
│ │
│ ├alipay_core.function.php ┈┈┈┈┈┈支付寶接口公用函數文件
│ │
│ ├alipay_notify.class.php┈┈┈┈┈┈┈支付寶通知處理類文件
│ │
│ ├alipay_submit.class.php┈┈┈┈┈┈┈支付寶各接口請求提交類文件
│ │
│ └alipay_rsa.function.php┈┈┈┈┈┈┈支付寶接口RSA函數文件
│
├log.txt┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈日志文件
│
├alipay.config.php┈┈┈┈┈┈┈┈┈┈┈┈基礎配置類文件
│
├alipayapi.php┈┈┈┈┈┈┈┈┈┈┈┈┈┈支付寶接口入口文件
│
├notify_url.php ┈┈┈┈┈┈┈┈┈┈┈┈┈服務器異步通知頁面文件
│
├return_url.php ┈┈┈┈┈┈┈┈┈┈┈┈┈頁面跳轉同步通知文件
│
├key┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈私鑰公鑰文件夾(用法見下方※注意※)
│ │
│ ├rsa_private_key.pem┈┈┈┈┈┈┈┈┈商戶的私鑰文件
│ │
│ └alipay_public_key.pem┈┈┈┈┈┈┈┈支付寶的公鑰文件
│
├openssl┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈缺省dll文件(用法見下方※注意※)
│ │
│ ├libeay32.dll
│ │
│ ├ssleay32.dll
│ │
│ └php_openssl.dll
│
├cacert.pem ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈用於CURL中校驗SSL的CA證書文件
│
└readme.txt ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈使用說明文本
3. 把必需的文件整合到框架裡(我當時用的是thinkPHP框架)
(1)在 裡新建一個文件夾叫AliMobilePay,
把上圖裡的4個文件拷貝到AliMobilePay文件夾裡,
對以上文件進行重命名,
alipay_core.function.php重命名為:Corefunction.php
alipay_notify.class.php重命名為:Notify.php
alipay_rsa.function.php重命名為:Rsafunction.php
alipay_submit.class.php重命名為:Submit.php
打開Notify.php,去掉一下代碼,
require_once("alipay_core.function.php");
require_once("alipay_rsa.function.php");
同樣的道理去掉其他3個文件裡的包含文件。
(2)在根目錄下建立一個文件夾key
在key文件夾裡放入商戶的私鑰文件、支付寶的公鑰文件、CA證書文件
如何生成RSA密鑰:https://cshall.alipay.com/enterprise/help_detail.htm?help_id=474010&keyword=%C8%E7%BA%CE%C9%FA%B3%C9%B9%AB%CB%BD%D4%BF&sToken=s-5d0c889ac47741fd8094b26d4862696b&from=search&flag=0 (此文中描述的rsa_private_key.pem就是商家的私鑰文件)
◆商戶的私鑰
1、不需要對剛生成的(原始的)私鑰做pkcs8編碼
2、不需要去掉去掉“-----BEGIN RSA PRIVATE KEY-----”、“-----END RSA PRIVATE KEY-----”
簡言之,只要維持用openssl工具剛生成出來的私鑰的內容即可。
◆支付寶公鑰
1、須保留“-----BEGIN PUBLIC KEY-----”、“-----END PUBLIC KEY-----”這兩條文字。
簡言之,支付寶公鑰只需要維持demo裡的原樣即可。
(3)alipay_config.php 配置文件
把alipay_config.php 配置文件整合到thinkPHP框架的配置文件裡
<?php /** * Created by PhpStorm. * User: zhangxiaoliu * Date: 16/4/15 * Time: 上午10:39 */ //支付寶商家服務中心鏈接:https://b.alipay.com/login.htm?goto=https://b.alipay.com:443/newIndex.htm return array( 'ALIMOBILEPAY_CONFIG'=>array( //合作身份者id,以2088開頭的16位純數字, (合作身份者id的查看鏈接:https://b.alipay.com/order/pidAndKey.htm) 'partner' => '2088XXXXXXXXXXXX', //收款支付寶賬號,與partner的值一樣 'seller_id' => '2088XXXXXXXXXXXX', //商戶的私鑰(後綴是.pem)文件相對路徑 'private_key_path'=> NEW_PORTAL_DOMAIN.'key/rsa_private_key.pem', //支付寶公鑰(後綴是.pem)文件相對路徑 'ali_public_key_path'=> NEW_PORTAL_DOMAIN.'key/alipay_public_key.pem', //簽名方式 不需修改 'sign_type' => strtoupper('RSA'), //字符編碼格式 目前支持 gbk 或 utf-8 'input_charset'=> 'utf-8', //ca證書路徑地址,用於curl中ssl校驗 'cacert' => NEW_PORTAL_DOMAIN.'key/cacert.pem', //訪問模式,根據自己的服務器是否支持ssl訪問,若支持請選擇https;若不支持請選擇http 'transport' => 'http', //這裡是異步通知頁面url,提交到項目的Payment控制器的notifyurl方法; //需http://格式的完整路徑,不能加?id=123這類自定義參數 'notify_url'=> NEW_PORTAL_DOMAIN.'portal.php/AliMobilePay/notify_url.php', //這裡是頁面跳轉通知url,提交到項目的Payment控制器的returnurl方法; //需http://格式的完整路徑,不能加?id=123這類自定義參數 'return_url'=> NEW_PORTAL_DOMAIN.'portal.php/AliMobilePay/return_url.php', //支付成功跳轉到的頁面 'successpage'=>NEW_PORTAL_DOMAIN.'portal.php/Success/index', //支付失敗跳轉到的頁面 'errorpage'=>NEW_PORTAL_DOMAIN.'portal.php/Error/index', //商品展示地址 'product_url'=>NEW_PORTAL_DOMAIN.'portal.php/Product/index', ) );
(4)支付寶幫助中心
https://cshall.alipay.com/enterprise/index.htm
4.調用支付寶接口
(1)新建一個AliMobilePay控制器
<?php namespace Portal\Controller; use Common\Component\FilterComponent; use Portal\Service\LogPaycallbacksService; use Portal\Service\GuozhanOrderService; use Portal\Model\Pengwifi\Guozhan\OrderModel; use Portal\Service\TokenService; use Portal\Service\UserService; use Portal\Service\SetMotoRadiusService; use Common\Model\Radius\RadcheckModel; /* * 購買上網卡的手機頁面支付寶接口 */ class AliMobilePayController extends CommonController{ protected $_order_model=null; protected $_order_service=null; protected $_token_service = null; protected $_Set_MotoRadius_service=null; protected $_RadcheckModel=null; protected $_log_pay_callbacks = null; protected function afterInit() { parent::afterInit(); vendor('AliMobilePay.Corefunction'); vendor('AliMobilePay.Rsafunction'); vendor('AliMobilePay.Notify'); vendor('AliMobilePay.Submit'); $this->_order_model= new OrderModel(); $this->_order_service= new GuozhanOrderService(); $this->_log_pay_callbacks = new LogPaycallbacksService(); $this->_service = new UserService(); $this->_token_service = new TokenService(); $this->_RadcheckModel = new RadcheckModel(); $this->_Set_MotoRadius_service = new SetMotoRadiusService(); } /** * 執行新增訂單 */ protected function _post(){ if(isset($this->params['name']) && ($this->params['name']=="notify_url")){ $this->notify_url('notify_url'); die; } $this->insert_order(); } protected function _get(){ /* *根據配置文件裡的路由規則: *':'.$var_controller.'/[:name]/[:action]'=> ':1/_index?', //匹配控制器後緊跟字符串,表示name * 例如:http://portal_v2.com/portal.php/Payment/Return.html * $notify_url會返回Return */ $notify_url = isset($this->params['name']) ? FilterComponent::getString($this->params['name']) : 'Unknown'; switch($notify_url){ case 'return_url': $this->return_url($notify_url); break; default: $this->_log_pay_callbacks->update(array('request_from'=>'Unknown'), false); exit('Wrong request url'); } } //服務器異步通知頁面方法 private function notify_url($notify_url){ $alipay_config = C('ALIMOBILEPAY_CONFIG'); //計算得出通知驗證結果 $alipayNotify = new \AlipayNotify($alipay_config); $verify_result = $alipayNotify->verifyNotify(); if($verify_result) {//驗證成功 //商戶訂單號 $order_sn = $this->params['out_trade_no']; //支付寶交易號 //$trade_no = $this->params['trade_no']; //交易狀態 $trade_status = $this->params['trade_status']; $this->_log_pay_callbacks->update(array('request_from'=>$notify_url, 'order_sn'=>$order_sn, 'response_status'=>$trade_status), false); if (in_array($trade_status,array('TRADE_SUCCESS','TRADE_FINISHED'))) { //判斷該筆訂單是否在商戶網站中已經做過處理 //如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序 //如果有做過處理,不執行商戶的業務程序 if(!$this->checkorderstatus($order_sn)){ $result=$this->orderhandle($order_sn); if($result==true){ echo "success"; }else{ echo "fail"; } } }else{ echo "fail"; } }else { //驗證失敗 echo "fail"; } } //頁面跳轉同步通知 private function return_url($notify_url){ $alipay_config=C('ALIMOBILEPAY_CONFIG'); //計算得出通知驗證結果 $alipayNotify = new \AlipayNotify($alipay_config); $verify_result = $alipayNotify->verifyReturn(); if($verify_result) {//驗證成功 //商戶訂單號 $order_sn = $this->params['out_trade_no']; //支付寶交易號 //$trade_no = $this->params['trade_no']; //交易狀態 $trade_status = $this->params['trade_status']; $this->_log_pay_callbacks->update(array('request_from'=>$notify_url, 'order_sn'=>$order_sn, 'response_status'=>$trade_status), false); if (in_array($trade_status,array('TRADE_SUCCESS','TRADE_FINISHED'))) { //判斷該筆訂單是否在商戶網站中已經做過處理 //如果沒有做過處理,根據訂單號(out_trade_no)在商戶網站的訂單系統中查到該筆訂單的詳細,並執行商戶的業務程序 //如果有做過處理,不執行商戶的業務程序 if(!$this->checkorderstatus($order_sn)){ $result=$this->orderhandle($order_sn); //——請根據您的業務邏輯來編寫程序(以上代碼僅作參考)—— if($result==true){ header("Location:".C('ALIMOBILEPAY_CONFIG.successpage'));//跳轉到配置項中配置的支付成功頁面; }else{ header("Location:".C('ALIMOBILEPAY_CONFIG.errorpage'));//跳轉到配置項中配置的支付失敗頁面; } } }else { header("Location:".C('ALIMOBILEPAY_CONFIG.errorpage'));//跳轉到配置項中配置的支付失敗頁面; } }else { //支付寶頁面“返回商戶”按鈕的鏈接,商品頁面 header("Location:".C('ALIMOBILEPAY_CONFIG.product_url')); } } //在線交易訂單支付處理函數 //函數功能:根據支付接口傳回的數據判斷該訂單是否已經支付成功; //返回值:如果訂單已經成功支付,返回true,否則返回false; private function checkorderstatus($order_sn){ $status=$this->_order_model->where("order_sn='$order_sn'")->getField('order_status'); if($status == OrderModel::ORDER_STATUS_PAYED){ return true; }else{ return false; } } //處理訂單函數 //更新訂單狀態,寫入訂單支付後返回的數據 private function orderhandle($order_sn){ try{ //開啟事務 $this->_order_model->startTrans(); $data['order_status']=OrderModel::ORDER_STATUS_PAYED; $affected_row=$this->_order_model->where("order_sn='$order_sn'")->save($data); $find=$this->_order_model->where("order_sn='$order_sn'")->field('location_id,goods_id,mobile,goods_number')->find(); //根據goods_id查找card_name對應的上網時長 $goods_model=M('goods'); $card_model=M('card'); $card_name=$goods_model->where("id={$find['goods_id']}")->getField('card_name'); $duration=$card_model->where("location_id={$find['location_id']} and card_name='$card_name'")->order('id desc')->getField('duration'); $incre_time=($find['goods_number']) * $duration; $user_model=M('user'); $mobile=$find['mobile']; $user_info=$user_model->where("user_name='{$mobile}'")->field('id,end_time')->find(); $affected_row2=$user_model->where("user_name='{$mobile}'")->setInc('usable_time',$incre_time); //如果end_time 大於當前的時間戳就累計,否則就更新:使用當前時間戳 加上 $incre_time if($user_info['end_time'] >= time()){ $user_model->where("user_name='{$mobile}'")->setInc('end_time',$incre_time); }else{ $update_data['end_time']=time()+$incre_time; $user_model->where("user_name='{$mobile}'")->save($update_data); } if(empty($affected_row)){ $this->_log_pay_callbacks->setException(L('ERROR_FAILED_UPDATE_ORDER'), $this->_log_pay_callbacks->getException('code')); throw new \Exception(); } if(empty($affected_row2)){ $this->_log_pay_callbacks->setException(L('ERROR_FAILED_UPDATE_USABLETIME'), $this->_log_pay_callbacks->getException('code')); throw new \Exception(); } //提交更新 if($affected_row && $affected_row2) { $this->_order_model->commit(); return true; } }catch(\Exception $e){ $this->_order_model->rollback(); return false; } } private function insert_order(){ $gw_id = isset($this->params['gw_id']) ? FilterComponent::get($this->params['gw_id']) : ''; if (empty($gw_id)) { exit('400_EMPTY_GWID'); } $router=M('router'); $location_id=$router->where("gw_id='$gw_id'")->getField('supplier_location_id'); $goods_number = isset($this->params['goods_number']) ? FilterComponent::get($this->params['goods_number'],'int') : ''; if (empty($goods_number)) { exit('400_EMPTY_GOODSNUMBER'); } $mobile = isset($this->params['mobile']) ? FilterComponent::get($this->params['mobile']) : ''; if (!preg_match('/^1[0-9]{10}$/',$mobile)) { exit('400_ERROR_MOBILE'); } $user=M('user'); //查詢充值號碼是否存在 $user_name=$user->where("user_name='$mobile'")->getField('user_name'); if(!$user_name){ exit('400_EMPTY_USERNAME'); } $goods_id = isset($this->params['goods_id']) ? FilterComponent::get($this->params['goods_id'],'int') : ''; if (empty($goods_id)) { exit('400_EMPTY_GOODSID'); } $goods=M('goods'); $unit_price=$goods->where("id=$goods_id")->getField('unit_price'); $this->params['WIDtotal_fee']=$unit_price * $goods_number; $data['location_id']=$location_id; $data['mobile']=$mobile; $data['goods_id']=$goods_id; $data['goods_type']=1;//1代表充值卡 $data['goods_number']=$goods_number; $data['total_price']=$this->params['WIDtotal_fee']; $data['pay_type']=OrderModel::PAY_TYPE_ALIPAY;//支付寶 //執行添加操作 $insert_id=$this->_order_service->update($data,false); // var_dump($this->_order_service->getError()); // var_dump($this->_order_service->model->getError()); // var_dump($this->_order_service->model->getlastsql());die; if($insert_id){ $this->params['WIDout_trade_no']=$this->_order_model->where("id=$insert_id")->getField('order_sn'); /**************************請求參數**************************/ //支付類型 $payment_type = "1"; //必填,不能修改 //商戶訂單號 $out_trade_no = $this->params['WIDout_trade_no']; //商戶網站訂單系統中唯一訂單號,必填 $this->params['WIDsubject']='pengwifi_card'; //訂單名稱 $subject = $this->params['WIDsubject']; //必填 //付款金額 $total_fee = $this->params['WIDtotal_fee']; //必填 //$this->params['WIDshow_url']=trim(C('ALIMOBILEPAY_CONFIG.product_url')); $this->params['WIDshow_url']=$_SERVER['HTTP_REFERER']; //商品展示地址 $show_url = $this->params['WIDshow_url']; //必填,需以http://開頭的完整路徑,例如:http://www.商戶網址.com/myorder.html //訂單描述 $body = $this->params['WIDbody']; //選填 //超時時間 $it_b_pay = $this->params['WIDit_b_pay']; //選填 //錢包token $extern_token = $this->params['WIDextern_token']; //選填 /************************************************************/ //構造要請求的參數數組,無需改動 $parameter = array( "service" => "alipay.wap.create.direct.pay.by.user", "partner" => trim(C('ALIMOBILEPAY_CONFIG.partner')), "seller_id" => trim(C('ALIMOBILEPAY_CONFIG.seller_id')), "payment_type" => $payment_type, "notify_url" => trim(C('ALIMOBILEPAY_CONFIG.notify_url')), "return_url" => trim(C('ALIMOBILEPAY_CONFIG.return_url')), "out_trade_no" => $out_trade_no, "subject" => $subject, "total_fee" => $total_fee, "show_url" => $show_url, "body" => $body, "it_b_pay" => $it_b_pay, "extern_token" => $extern_token, "_input_charset" => trim(strtolower(C('input_charset'))) ); $alipay_config=C('ALIMOBILEPAY_CONFIG'); //建立請求 $alipaySubmit = new \AlipaySubmit($alipay_config); //建立請求,以表單HTML形式構造(默認),經測試post方法不行 $html_text = $alipaySubmit->buildRequestForm($parameter,"get", "確認"); echo $html_text; }else{ echo 'fail'; } } }