PHP實現後端銀聯支付及退款實例詳解
本篇文章主要介紹PHP實作後端銀聯支付及退款實例詳解,有興趣的朋友參考下,希望對大家有幫助。
聲明:此文以當前銀聯官方最新SDK(2016-08-09 5.1.0版)進行說明,若出現包不相同的情況請檢查是否是此版本
#近期遇到銀聯支付以及相關退款(此文僅以手機控制支付作為前提)操作,下面會依次寫出期間遇到的問題以及基本流程,在此之前通過官方的一張圖片了解一個支付中,對於後端人員的我們需要做到的一些事
由此圖可以看出,後端在此負責1、平台訂單產生;2、銀聯全通路平台訂單推播;3、返回tn碼給前端支付;4、處理前台通知以及全通路平台的非同步通知。
此間難點有三,訂單推播、非同步通知處理、訂單狀態查詢。
透過官方的郵件說明下載相關的套件並放入後端php程式碼中,(支付控制項去下載你看到的估計只有IOS,安卓版的SDK,對於後端來說,隨便下載一個即可,PHP的程式碼在裡面都有放置);然後仔細閱讀SDK中的readme.txt文件,此後進行以下步驟:
一、相關參數配置
對接過程中使用在sdk的assets資料夾中測試環境設定檔及證書,放置到sdk資料夾中,並設定/sdk/SDKconfig.php檔案已正確的讀取acp_sdk.ini設定檔。
在acp_sdk.ini檔案中設定好acpsdk.signCert.path、acpsdk.encryptCert.path、acpsdk.rootCert.path、acpsdk.middleCert.path四個檔案的絕對位址(自訂檔案路徑即可)。
因專案開發過程中會出現系統不同或專案位址不同導致的憑證絕對位址等錯誤,尤其在實際生產環境中,極易出現專案部署檔案位址不同,不可能在開發後每次更新都要更換憑證位址,在此修改了一下SDK中的SDKconfig.php已相容於不同檔案位址較長,這裡也請點擊展開檢視
<?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(){ //如果想把acp_sdk.ini挪到其他路径的话,请修改下面这行指定绝对路径。 $configFilePath = dirname(__FILE__) . "/acp_sdk.ini"; $certsFilePath = dirname(dirname(__FILE__)) . "/certs/"; if(!file_exists($configFilePath)){ $logger = LogUtil::getLogger(); $logger->LogError("配置文件加载失败,文件路径:[" . $configFilePath . "].请检查启动php的用户是否有读权限。"); 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); } } }
二、全通路商品訂單推送
相關程式碼請點選查看
##
use com\unionpay\acp\sdk\AcpService; use com\unionpay\acp\sdk\LogUtil; use com\unionpay\acp\sdk\SDKConfig; /** * 银联支付下单 * * @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( //以下信息非特殊情况不需要改动 'version' => $config->getSDKConfig()->version, //版本号 'encoding' => 'utf-8', //编码方式 'txnType' => '01', //交易类型 'txnSubType' => '01', //交易子类 'bizType' => '000201', //业务类型 'frontUrl' => $config->getSDKConfig()->frontUrl, //前台通知地址 'backUrl' => $this->getURL('api_pay_unionpay_call_back'), //后台通知地址 'signMethod' => $config->getSDKConfig()->signMethod, //签名方法 'channelType' => '08', //渠道类型,07-PC,08-手机 'accessType' => '0', //接入类型 'currencyCode' => '156', //交易币种,境内商户固定156 //TODO 以下信息需要填写 'merId' => $this->getParameter('mer_id'), //商户代码,请改自己的测试商户号 'orderId' => $orders["order_no"], //商户订单号,8-32位数字字母,不能含“-”或“_” 'txnTime' => $time, //订单发送时间,格式为YYYYMMDDhhmmss,取北京时间 'txnAmt' => $orders['total_price'] * 100, //交易金额,单位分 ); $AcpService->sign ( $params ); // 签名 $url = $config->getSDKConfig()->appTransUrl; $result_arr = $AcpService->post ($params, $url); if(count($result_arr)<=0) { //没收到200应答的情况 $log->LogInfo('没收到200应答的情况'); } // $this->printResult ($url, $params, $result_arr ); //页面打印请求应答数据 if (!$AcpService->validate ($result_arr) ){ $log->LogInfo('应答报文验签失败'); } if ($result_arr["respCode"] == "00"){ //成功 return array('txn_time'=>$time, 'tn'=>$result_arr["tn"]); // echo "后续请将此tn传给手机开发,由他们用此tn调起控件后完成支付。 \n"; // echo "手机端demo默认从仿真获取tn,仿真只返回一个tn,如不想修改手机和后台间的通讯方式,【此页面请修改代码为只输出tn】。 \n"; } else { //其他应答码做以失败处理 return array('txn_time'=>$time, 'tn'=>0); //echo "失败:" . $result_arr["respMsg"] . "。 \n"; } }
三、非同步通知處理以及訂單交易狀態查詢
/** * 银联回调 * * @param Request $request * @return array|Response */ public function unionPayCallBackAction(Request $request) { if ($request->get('type') == 1){//前台通知-进行订单状态查询 $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);//订单交易处理-请根据实际情况自行编写 } } else { if (!$request->request->has('signature')) { $log->LogInfo('签名为空'); } else { $log->LogInfo('验签失败'); } } exit; }
do{//循环查询,直到获取到退款订单的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( //以下信息非特殊情况不需要改动 'version' => $config->getSDKConfig()->version, //版本号 'encoding' => 'utf-8', //编码方式 'signMethod' => $config->getSDKConfig()->signMethod, //签名方法 'txnType' => '00', //交易类型 'txnSubType' => '00', //交易子类 'bizType' => '000000', //业务类型 'accessType' => '0', //接入类型 'channelType' => '07', //渠道类型 //TODO 以下信息需要填写 'orderId' => $orders['order_no'], //请修改被查询的交易的订单号,8-32位数字字母,不能含“-”或“_” 'merId' => $this->getParameter('mer_id'), //商户代码,请改自己的测试商户号 'txnTime' => date('YmdHis', time()), //请修改被查询的交易的订单发送时间,格式为YYYYMMDDhhmmss ); $AcpService->sign ( $params ); // 签名 $url = $config->getSDKConfig()->singleQueryUrl; $result_arr = $AcpService->post ( $params, $url); if(count($result_arr)<=0) { //没收到200应答的情况 $log->LogInfo('没收到200应答的情况'); } if (!$AcpService->validate ($result_arr) ){ $log->LogInfo('应答报文验签失败'); } 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"){ //后续需发起交易状态查询交易确定交易状态 $result = array('errorCode'=>2, 'message'=>'交易处理中', 'result_arr'=>$result_arr); } else { //其他应答码做以失败处理 echo "交易失败:" . $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" ){ //后续需发起交易状态查询交易确定交易状态 $result = array('errorCode'=>2, 'message'=>"处理超时,请稍后查询.", 'result_arr'=>$result_arr); } else { //其他应答码做以失败处理 $result = array('errorCode'=>1, 'message'=>"失败:" . $result_arr["respMsg"] . ".", 'result_arr'=>$result_arr); } return $result; }
到此為止,若是項目沒有訂單線上退款就完成了。 訂單退款相關
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{//循环查询,直到获取到退款订单的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( //以下信息非特殊情况不需要改动 'version' => $config->getSDKConfig()->version, //版本号 'encoding' => 'utf-8', //编码方式 'signMethod' => $config->getSDKConfig()->signMethod, //签名方法 'txnType' => '04', //交易类型 'txnSubType' => '00', //交易子类 'bizType' => '000201', //业务类型 'accessType' => '0', //接入类型 'channelType' => '07', //渠道类型 'backUrl' => $config->getSDKConfig()->backUrl, //后台通知地址 //TODO 以下信息需要填写 'orderId' => "T" . $orders['order_no'], //商户订单号,8-32位数字字母,不能含“-”或“_”,可以自行定制规则,重新产生-此处为在退款订单前拼接 T 'merId' => $this->getParameter('mer_id'), //商户代码,请改成自己的商户号 'origQryId' => $query['result_arr']["queryId"], //原消费的queryId,可以从查询接口或者通知接口中获取 'txnTime' => date('YmdHis', time()), //订单发送时间,格式为YYYYMMDDhhmmss,重新产生,不同于原消费 'txnAmt' => $orders['total_price'] * 100, //交易金额,退货总金额需要小于等于原消费 ); $AcpService->sign ( $params ); // 签名 $url = $config->getSDKConfig()->backTransUrl; $result_arr = $AcpService->post ( $params, $url); if(count($result_arr)<=0) { //没收到200应答的情况 return array('errorCode'=>1, 'message'=>"没收到应答."); } if (!$AcpService->validate ($result_arr) ){ return array('errorCode'=>1, 'message'=>"应答报文验签失败."); } if ($result_arr["respCode"] == "00"){ //交易已受理,等待接收后台通知更新订单状态,如果通知长时间未收到也可发起交易状态查询 return array('errorCode'=>0, 'message'=>"受理成功."); } else if ($result_arr["respCode"] == "03" || $result_arr["respCode"] == "04" || $result_arr["respCode"] == "05" ){ //后续需发起交易状态查询交易确定交易状态 return array('errorCode'=>1, 'message'=>"处理超时,请稍微查询."); } else { //其他应答码做以失败处理 return array('errorCode'=>1, 'message'=>"失败:" . $result_arr["respMsg"] . "."); } }
切換生產環境
PHP後端銀聯支付及退款實例詳解
銀聯支付及退款實例
##
以上是PHP實現後端銀聯支付及退款實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP 8.4 帶來了多項新功能、安全性改進和效能改進,同時棄用和刪除了大量功能。 本指南介紹如何在 Ubuntu、Debian 或其衍生版本上安裝 PHP 8.4 或升級到 PHP 8.4

Visual Studio Code,也稱為 VS Code,是一個免費的原始碼編輯器 - 或整合開發環境 (IDE) - 可用於所有主要作業系統。 VS Code 擁有大量針對多種程式語言的擴展,可以輕鬆編寫

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

字符串是由字符組成的序列,包括字母、數字和符號。本教程將學習如何使用不同的方法在PHP中計算給定字符串中元音的數量。英語中的元音是a、e、i、o、u,它們可以是大寫或小寫。 什麼是元音? 元音是代表特定語音的字母字符。英語中共有五個元音,包括大寫和小寫: a, e, i, o, u 示例 1 輸入:字符串 = "Tutorialspoint" 輸出:6 解釋 字符串 "Tutorialspoint" 中的元音是 u、o、i、a、o、i。總共有 6 個元

本教程演示瞭如何使用PHP有效地處理XML文檔。 XML(可擴展的標記語言)是一種用於人類可讀性和機器解析的多功能文本標記語言。它通常用於數據存儲

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。
