您好,登錄后才能下訂單哦!
小編給大家分享一下PHP后端銀聯(lián)支付及退款的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
聲明:此文以當(dāng)前銀聯(lián)官方最新SDK(2016-08-09 5.1.0版)進(jìn)行說明,若出現(xiàn)包不相同的情況請(qǐng)檢查是否是此版本
近期遇到銀聯(lián)支付以及相關(guān)退款(此文僅以手機(jī)控件支付作為前提)操作,下面會(huì)依次寫出期間遇到的問題以及基本流程,在此之前通過官方的一張圖片了解一個(gè)支付中,對(duì)于后端人員的我們需要做到的一些事情
由此圖可以看出,后端在此負(fù)責(zé)1、平臺(tái)訂單生成;2、銀聯(lián)全渠道平臺(tái)訂單推送;3、返回tn碼給前端進(jìn)行支付;4、處理前臺(tái)通知以及全渠道平臺(tái)的異步通知。
此間難點(diǎn)有三,訂單推送、異步通知處理、訂單狀態(tài)查詢。
通過官方的郵件說明下載相關(guān)的包并放入后端php代碼中,(支付控件去下載你看到的估計(jì)只有IOS,安卓版的SDK,對(duì)于后端來說,隨便下載一個(gè)即可,PHP的代碼在里面都有放置);然后仔細(xì)閱讀SDK中的readme.txt文件,此后進(jìn)行以下步驟:
一、相關(guān)參數(shù)配置
對(duì)接過程中使用在sdk的assets文件夾中測(cè)試環(huán)境配置文件及證書,放置到sdk文件夾中,并配置/sdk/SDKconfig.php文件已正確的讀取acp_sdk.ini配置文件。
在acp_sdk.ini文件中配置好acpsdk.signCert.path、acpsdk.encryptCert.path、acpsdk.rootCert.path、acpsdk.middleCert.path四個(gè)文件的絕對(duì)地址(自定義文件路徑即可)。
因項(xiàng)目開發(fā)過程中會(huì)出現(xiàn)系統(tǒng)不同或項(xiàng)目地址不同導(dǎo)致的證書絕對(duì)地址等錯(cuò)誤,尤其在實(shí)際生產(chǎn)環(huán)境中,極易出現(xiàn)項(xiàng)目部署文件地址不同,不可能在開發(fā)后每次更新都要更換證書地址,在此修改了一下SDK中的SDKconfig.php已兼容不同文件地址較長(zhǎng),這里還請(qǐng)點(diǎn)擊展開查看
<?php namespace com\unionpay\acp\sdk;; include_once 'log.class.php'; include_once 'common.php'; class SDKConfig { private static $_config = null; public static function getSDKConfig(){ if (SDKConfig::$_config == null ) { SDKConfig::$_config = new SDKConfig(); } return SDKConfig::$_config; } private $frontTransUrl; private $backTransUrl; private $singleQueryUrl; private $batchTransUrl; private $fileTransUrl; private $appTransUrl; private $cardTransUrl; private $jfFrontTransUrl; private $jfBackTransUrl; private $jfSingleQueryUrl; private $jfCardTransUrl; private $jfAppTransUrl; private $qrcBackTransUrl; private $qrcB2cIssBackTransUrl; private $qrcB2cMerBackTransUrl; private $signMethod; private $version; private $ifValidateCNName; private $ifValidateRemoteCert; private $signCertPath; private $signCertPwd; private $validateCertDir; private $encryptCertPath; private $rootCertPath; private $middleCertPath; private $frontUrl; private $backUrl; private $secureKey; private $logFilePath; private $logLevel; function __construct(){ //如果想把a(bǔ)cp_sdk.ini挪到其他路徑的話,請(qǐng)修改下面這行指定絕對(duì)路徑。 $configFilePath = dirname(__FILE__) . "/acp_sdk.ini"; $certsFilePath = dirname(dirname(__FILE__)) . "/certs/"; if(!file_exists($configFilePath)){ $logger = LogUtil::getLogger(); $logger->LogError("配置文件加載失敗,文件路徑:[" . $configFilePath . "].請(qǐng)檢查啟動(dòng)php的用戶是否有讀權(quán)限。"); return; } $ini_array = parse_ini_file($configFilePath, true); $sdk_array = $ini_array["acpsdk"]; $this->frontTransUrl = array_key_exists("acpsdk.frontTransUrl", $sdk_array)?$sdk_array["acpsdk.frontTransUrl"] : null; $this->backTransUrl = array_key_exists("acpsdk.backTransUrl", $sdk_array)?$sdk_array["acpsdk.backTransUrl"] : null; $this->singleQueryUrl = array_key_exists("acpsdk.singleQueryUrl", $sdk_array)?$sdk_array["acpsdk.singleQueryUrl"] : null; $this->batchTransUrl = array_key_exists("acpsdk.batchTransUrl", $sdk_array)?$sdk_array["acpsdk.batchTransUrl"] : null; $this->fileTransUrl = array_key_exists("acpsdk.fileTransUrl", $sdk_array)?$sdk_array["acpsdk.fileTransUrl"] : null; $this->appTransUrl = array_key_exists("acpsdk.appTransUrl", $sdk_array)?$sdk_array["acpsdk.appTransUrl"] : null; $this->cardTransUrl = array_key_exists("acpsdk.cardTransUrl", $sdk_array)?$sdk_array["acpsdk.cardTransUrl"] : null; $this->jfFrontTransUrl = array_key_exists("acpsdk.jfFrontTransUrl", $sdk_array)?$sdk_array["acpsdk.jfFrontTransUrl"] : null; $this->jfBackTransUrl = array_key_exists("acpsdk.jfBackTransUrl", $sdk_array)?$sdk_array["acpsdk.jfBackTransUrl"] : null; $this->jfSingleQueryUrl = array_key_exists("acpsdk.jfSingleQueryUrl", $sdk_array)?$sdk_array["acpsdk.jfSingleQueryUrl"] : null; $this->jfCardTransUrl = array_key_exists("acpsdk.jfCardTransUrl", $sdk_array)?$sdk_array["acpsdk.jfCardTransUrl"] : null; $this->jfAppTransUrl = array_key_exists("acpsdk.jfAppTransUrl", $sdk_array)?$sdk_array["acpsdk.jfAppTransUrl"] : null; $this->qrcBackTransUrl = array_key_exists("acpsdk.qrcBackTransUrl", $sdk_array)?$sdk_array["acpsdk.qrcBackTransUrl"] : null; $this->qrcB2cIssBackTransUrl = array_key_exists("acpsdk.qrcB2cIssBackTransUrl", $sdk_array)?$sdk_array["acpsdk.qrcB2cIssBackTransUrl"] : null; $this->qrcB2cMerBackTransUrl = array_key_exists("acpsdk.qrcB2cMerBackTransUrl", $sdk_array)?$sdk_array["acpsdk.qrcB2cMerBackTransUrl"] : null; $this->signMethod = array_key_exists("acpsdk.signMethod", $sdk_array)?$sdk_array["acpsdk.signMethod"] : null; $this->version = array_key_exists("acpsdk.version", $sdk_array)?$sdk_array["acpsdk.version"] : null; $this->ifValidateCNName = array_key_exists("acpsdk.ifValidateCNName", $sdk_array)?$sdk_array["acpsdk.ifValidateCNName"] : "true"; $this->ifValidateRemoteCert = array_key_exists("acpsdk.ifValidateRemoteCert", $sdk_array)?$sdk_array["acpsdk.ifValidateRemoteCert"] : "false"; $this->signCertPath = $certsFilePath . (array_key_exists("acpsdk.signCert.path", $sdk_array)?$sdk_array["acpsdk.signCert.path"]: null); $this->signCertPwd = array_key_exists("acpsdk.signCert.pwd", $sdk_array)?$sdk_array["acpsdk.signCert.pwd"]: null; $this->validateCertDir = array_key_exists("acpsdk.validateCert.dir", $sdk_array)? $sdk_array["acpsdk.validateCert.dir"]: null; $this->encryptCertPath = $certsFilePath . (array_key_exists("acpsdk.encryptCert.path", $sdk_array)? $sdk_array["acpsdk.encryptCert.path"]: null); $this->rootCertPath = $certsFilePath . (array_key_exists("acpsdk.rootCert.path", $sdk_array)? $sdk_array["acpsdk.rootCert.path"]: null); $this->middleCertPath = $certsFilePath . (array_key_exists("acpsdk.middleCert.path", $sdk_array)?$sdk_array["acpsdk.middleCert.path"]: null); $this->frontUrl = array_key_exists("acpsdk.frontUrl", $sdk_array)?$sdk_array["acpsdk.frontUrl"]: null; $this->backUrl = array_key_exists("acpsdk.backUrl", $sdk_array)?$sdk_array["acpsdk.backUrl"]: null; $this->secureKey = array_key_exists("acpsdk.secureKey", $sdk_array)?$sdk_array["acpsdk.secureKey"]: null; $this->logFilePath = array_key_exists("acpsdk.log.file.path", $sdk_array)?$sdk_array["acpsdk.log.file.path"]: null; $this->logLevel = array_key_exists("acpsdk.log.level", $sdk_array)?$sdk_array["acpsdk.log.level"]: null; } public function __get($property_name) { if(isset($this->$property_name)) { return($this->$property_name); } else { return(NULL); } } }
二、全渠道商品訂單推送
相關(guān)代碼請(qǐng)點(diǎn)擊查看
use com\unionpay\acp\sdk\AcpService; use com\unionpay\acp\sdk\LogUtil; use com\unionpay\acp\sdk\SDKConfig; /** * 銀聯(lián)支付下單 * * @param $orders * @param $orders_type * @return array */ public function unionPay($orders, $orders_type = 0) { include_once dirname(dirname(dirname(__FILE__))) . '/Model/unionpay-sdk/sdk/acp_service.php'; $config = new SDKConfig(); $AcpService = new AcpService(); $log = LogUtil::getLogger(); $time = date('YmdHis', time()); $params = array( //以下信息非特殊情況不需要改動(dòng) 'version' => $config->getSDKConfig()->version, //版本號(hào) 'encoding' => 'utf-8', //編碼方式 'txnType' => '01', //交易類型 'txnSubType' => '01', //交易子類 'bizType' => '000201', //業(yè)務(wù)類型 'frontUrl' => $config->getSDKConfig()->frontUrl, //前臺(tái)通知地址 'backUrl' => $this->getURL('api_pay_unionpay_call_back'), //后臺(tái)通知地址 'signMethod' => $config->getSDKConfig()->signMethod, //簽名方法 'channelType' => '08', //渠道類型,07-PC,08-手機(jī) 'accessType' => '0', //接入類型 'currencyCode' => '156', //交易幣種,境內(nèi)商戶固定156 //TODO 以下信息需要填寫 'merId' => $this->getParameter('mer_id'), //商戶代碼,請(qǐng)改自己的測(cè)試商戶號(hào) 'orderId' => $orders["order_no"], //商戶訂單號(hào),8-32位數(shù)字字母,不能含“-”或“_” 'txnTime' => $time, //訂單發(fā)送時(shí)間,格式為YYYYMMDDhhmmss,取北京時(shí)間 'txnAmt' => $orders['total_price'] * 100, //交易金額,單位分 ); $AcpService->sign ( $params ); // 簽名 $url = $config->getSDKConfig()->appTransUrl; $result_arr = $AcpService->post ($params, $url); if(count($result_arr)<=0) { //沒收到200應(yīng)答的情況 $log->LogInfo('沒收到200應(yīng)答的情況'); } // $this->printResult ($url, $params, $result_arr ); //頁面打印請(qǐng)求應(yīng)答數(shù)據(jù) if (!$AcpService->validate ($result_arr) ){ $log->LogInfo('應(yīng)答報(bào)文驗(yàn)簽失敗'); } if ($result_arr["respCode"] == "00"){ //成功 return array('txn_time'=>$time, 'tn'=>$result_arr["tn"]); // echo "后續(xù)請(qǐng)將此tn傳給手機(jī)開發(fā),由他們用此tn調(diào)起控件后完成支付。 \n"; // echo "手機(jī)端demo默認(rèn)從仿真獲取tn,仿真只返回一個(gè)tn,如不想修改手機(jī)和后臺(tái)間的通訊方式,【此頁面請(qǐng)修改代碼為只輸出tn】。 \n"; } else { //其他應(yīng)答碼做以失敗處理 return array('txn_time'=>$time, 'tn'=>0); //echo "失敗:" . $result_arr["respMsg"] . "。 \n"; } }
在此注意txnTime格式不要傳錯(cuò),測(cè)試環(huán)境下應(yīng)該不會(huì)出現(xiàn)什么問題,將得到的tn返回APP進(jìn)行支付即可
三、異步通知處理以及訂單交易狀態(tài)查詢
這一步主要作用為處理銀聯(lián)交易成功信息,并盡可能避免出現(xiàn)回調(diào)未處理導(dǎo)致問題。
先說異步通知處理,此步驟為訂單狀態(tài)修改的主要依據(jù)。無實(shí)際難點(diǎn),保證相關(guān)參數(shù)無問題即可
/** * 銀聯(lián)回調(diào) * * @param Request $request * @return array|Response */ public function unionPayCallBackAction(Request $request) { if ($request->get('type') == 1){//前臺(tái)通知-進(jìn)行訂單狀態(tài)查詢 $query = $this->unionPayQuery($request, array(), 1); return new JsonResponse($query); } require_once dirname(dirname(dirname(__FILE__))) . "/Model/unionpay-sdk/sdk/acp_service.php"; $log = LogUtil::getLogger(); $AcpService = new AcpService(); if ($request->request->has('signature') && $AcpService->validate($_POST)) { $order_no = $request->request->get('orderId'); $respCode = $request->request->get('respCode'); $total = $request->request->get('txnAmt'); // 交易金額 if ($respCode === '00' || $respCode === 'A6') { $trade_no = $request->request->get('origQryId')?:'UN' . date('YmdHis', time()) . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8); $this->dispose($order_no, $trade_no, 4);//訂單交易處理-請(qǐng)根據(jù)實(shí)際情況自行編寫 } } else { if (!$request->request->has('signature')) { $log->LogInfo('簽名為空'); } else { $log->LogInfo('驗(yàn)簽失敗'); } } exit; }
訂單交易狀態(tài)查詢
do{//循環(huán)查詢,直到獲取到退款訂單的queryID sleep($number * 2); $query = $this->unionPayQuery('', $orders); $number += 1; }while($query['errorCode'] != 0 || empty($query['result_arr']["queryId"])); public function unionPayQuery($request, $orders) { require_once dirname(dirname(dirname(__FILE__))) . "/Model/unionpay-sdk/sdk/acp_service.php"; $config = new SDKConfig(); $AcpService = new AcpService(); $log = LogUtil::getLogger(); $params = array( //以下信息非特殊情況不需要改動(dòng) 'version' => $config->getSDKConfig()->version, //版本號(hào) 'encoding' => 'utf-8', //編碼方式 'signMethod' => $config->getSDKConfig()->signMethod, //簽名方法 'txnType' => '00', //交易類型 'txnSubType' => '00', //交易子類 'bizType' => '000000', //業(yè)務(wù)類型 'accessType' => '0', //接入類型 'channelType' => '07', //渠道類型 //TODO 以下信息需要填寫 'orderId' => $orders['order_no'], //請(qǐng)修改被查詢的交易的訂單號(hào),8-32位數(shù)字字母,不能含“-”或“_” 'merId' => $this->getParameter('mer_id'), //商戶代碼,請(qǐng)改自己的測(cè)試商戶號(hào) 'txnTime' => date('YmdHis', time()), //請(qǐng)修改被查詢的交易的訂單發(fā)送時(shí)間,格式為YYYYMMDDhhmmss ); $AcpService->sign ( $params ); // 簽名 $url = $config->getSDKConfig()->singleQueryUrl; $result_arr = $AcpService->post ( $params, $url); if(count($result_arr)<=0) { //沒收到200應(yīng)答的情況 $log->LogInfo('沒收到200應(yīng)答的情況'); } if (!$AcpService->validate ($result_arr) ){ $log->LogInfo('應(yīng)答報(bào)文驗(yàn)簽失敗'); } if ($result_arr["respCode"] == "00"){ if ($result_arr["origRespCode"] == "00"){ //交易成功 $trade_no = 'UN' . date('YmdHis', time()) . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8); $this->dispose($orders['order_no'], $trade_no, 4); $result = array('errorCode'=>0, 'message'=>'交易成功', 'result_arr'=>$result_arr); } else if ($result_arr["origRespCode"] == "03" || $result_arr["origRespCode"] == "04" || $result_arr["origRespCode"] == "05"){ //后續(xù)需發(fā)起交易狀態(tài)查詢交易確定交易狀態(tài) $result = array('errorCode'=>2, 'message'=>'交易處理中', 'result_arr'=>$result_arr); } else { //其他應(yīng)答碼做以失敗處理 echo "交易失?。?quot; . $result_arr["origRespMsg"] . "。 \n"; $result = array('errorCode'=>1, 'message'=>"交易失敗:" . $result_arr["origRespMsg"] . ".", 'result_arr'=>$result_arr); } } else if ($result_arr["respCode"] == "03" || $result_arr["respCode"] == "04" || $result_arr["respCode"] == "05" ){ //后續(xù)需發(fā)起交易狀態(tài)查詢交易確定交易狀態(tài) $result = array('errorCode'=>2, 'message'=>"處理超時(shí),請(qǐng)稍后查詢.", 'result_arr'=>$result_arr); } else { //其他應(yīng)答碼做以失敗處理 $result = array('errorCode'=>1, 'message'=>"失?。?quot; . $result_arr["respMsg"] . ".", 'result_arr'=>$result_arr); } return $result; }
到此為止,若是項(xiàng)目沒有訂單線上退款就完成了。
訂單退款相關(guān)
public function refundUnionPay($orders) { require_once(dirname(dirname(__FILE__)) . "/Model/unionpay-sdk/sdk/acp_service.php"); set_time_limit(100); $config = new SDKConfig(); $AcpService = new AcpService(); $log = LogUtil::getLogger(); $number = 0; do{//循環(huán)查詢,直到獲取到退款訂單的queryID sleep($number * 2); $query = $this->unionPayQuery('', $orders); $number += 1; }while($query['errorCode'] != 0 || empty($query['result_arr']["queryId"])); if ($query['errorCode'] != 0) { return array('errorCode'=>1, 'message'=>'訂單未成交,無法退款'); } $params = array( //以下信息非特殊情況不需要改動(dòng) 'version' => $config->getSDKConfig()->version, //版本號(hào) 'encoding' => 'utf-8', //編碼方式 'signMethod' => $config->getSDKConfig()->signMethod, //簽名方法 'txnType' => '04', //交易類型 'txnSubType' => '00', //交易子類 'bizType' => '000201', //業(yè)務(wù)類型 'accessType' => '0', //接入類型 'channelType' => '07', //渠道類型 'backUrl' => $config->getSDKConfig()->backUrl, //后臺(tái)通知地址 //TODO 以下信息需要填寫 'orderId' => "T" . $orders['order_no'], //商戶訂單號(hào),8-32位數(shù)字字母,不能含“-”或“_”,可以自行定制規(guī)則,重新產(chǎn)生-此處為在退款訂單前拼接 T 'merId' => $this->getParameter('mer_id'), //商戶代碼,請(qǐng)改成自己的商戶號(hào) 'origQryId' => $query['result_arr']["queryId"], //原消費(fèi)的queryId,可以從查詢接口或者通知接口中獲取 'txnTime' => date('YmdHis', time()), //訂單發(fā)送時(shí)間,格式為YYYYMMDDhhmmss,重新產(chǎn)生,不同于原消費(fèi) 'txnAmt' => $orders['total_price'] * 100, //交易金額,退貨總金額需要小于等于原消費(fèi) ); $AcpService->sign ( $params ); // 簽名 $url = $config->getSDKConfig()->backTransUrl; $result_arr = $AcpService->post ( $params, $url); if(count($result_arr)<=0) { //沒收到200應(yīng)答的情況 return array('errorCode'=>1, 'message'=>"沒收到應(yīng)答."); } if (!$AcpService->validate ($result_arr) ){ return array('errorCode'=>1, 'message'=>"應(yīng)答報(bào)文驗(yàn)簽失敗."); } if ($result_arr["respCode"] == "00"){ //交易已受理,等待接收后臺(tái)通知更新訂單狀態(tài),如果通知長(zhǎng)時(shí)間未收到也可發(fā)起交易狀態(tài)查詢 return array('errorCode'=>0, 'message'=>"受理成功."); } else if ($result_arr["respCode"] == "03" || $result_arr["respCode"] == "04" || $result_arr["respCode"] == "05" ){ //后續(xù)需發(fā)起交易狀態(tài)查詢交易確定交易狀態(tài) return array('errorCode'=>1, 'message'=>"處理超時(shí),請(qǐng)稍微查詢."); } else { //其他應(yīng)答碼做以失敗處理 return array('errorCode'=>1, 'message'=>"失?。?quot; . $result_arr["respMsg"] . "."); } }
依據(jù)返回狀態(tài)值進(jìn)行相關(guān)操作即可
以上是“PHP后端銀聯(lián)支付及退款的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。