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"; } }

在此注意txnTime格式不要传错,测试环境下应该不会出现什么问题,将得到的tn返回APP进行支付即可

三、异步通知处理以及订单交易状态查询

这一步主要作用为处理银联交易成功信息,并尽可能避免出现回调未处理导致问题。

先说异步通知处理,此步骤为订单状态修改的主要依据。无实际难点,保证相关参数无问题即可

/** * 银联回调 * * @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"] . "."); } }

依据返回状态值进行相关操作即可,实际逻辑代码请自行实现

切换生产环境

项目关系暂无法进行-后续补充

未完待续。。。。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-11-14 12:01:26

PHP后端银联支付及退款实例代码的相关文章

php版本银联支付接口开发实例

银联支付,首先要注意二重要的部分: PHP运行环境是5.4.18以上 开了扩展openss 开发手册上面的列子只做参考,因为基本都是错的.你可以试着去官网下一个demo...注意现在银联开发,没有测试密钥提供,只能在正式环境开发[20151219] 下面是我用ThinkPHP编写的一个支付类 /** * 银联支付 v0.1 * @auther:Summer<dengwz7788@gmail.com>; * @date:20151202 * **/   class NetPayAction ex

Laravel 使用银联支付进行支付开发实例

我们接下来要讲的银联支付还是使用这个Laravel包,并集成OmniPay提供的银联支付(UnionPay)接口--UmniPay UnionPay. 1.安装银联支付依赖包 废话不多说,下面直接切入正题.由于我们在上一节已经安装了ignited/laravel-omnipay这个包,这里我们只需要安装银联支付包即可: composer require lokielse/omnipay-unionpay dev-master 2.获取银联支付配置信息 到银联支付开发者中心注册账户并提交商户资料进

JS传递对象数组为参数给后端,后端获取的实例代码_javascript技巧

前端JS代码: var conditons = []; var test1 = new Object(); test1.name="1"; test1.id="2"; var test2 = new Object(); test2.name="1"; test2.id="2"; conditons.push(test1); conditons.push(test2); $(function(){ $.ajax({ async:

初次接触银联支付

1.注册 银联开发平台 https://open.unionpay.com 平台分为三个角色 我是商户 我是机构 我是开发者 第一反应我是开发者就直接申请我是开发者的角色.但并没有如愿以偿如下图: 暂不支持申请.暂不支持测试 这就尴尬了.为啥各种不支持呢?正在开发中? 带着这个疑问以及想占个坑的思想就注册了一个账号同时也提交了一个工单但客服没有处理可能是过节的原因吧. 生活不能永停留在无尽的等待当中需要我们去勇敢的尝试新的事物.so 新的事物新的探索就开始了.我就申请我是商户的角色 注册可以使用

android银联支付,请求报文错误[8100008]

问题描述 android银联支付,请求报文错误[8100008] 之前搞得银联支付,并且试用了没有问题. 但今天再用就在跳转时弹出对话框,显示: 请求报文错误[8100008] 我没有动过这一块的代码. 请大神指导 解决方案 Android-银联支付android银联支付android银联支付 解决方案二: 问下银联是不是改什么东西了?或者确认一下这个代码的意思 解决方案三: iOS同样遇到这个问题,楼主解决了吗 解决方案四: 是你们服务端改东西了吧 流程图说明: (1)用户在客户端中点击购买商

Ajax上传图片及上传前先预览功能实例代码

手头上有几个小项目用到了easyUI,一开始决定使用easyUI就注定了项目整体上前后端分离,基本上所有的请求都采用Ajax来完成.在文件上传的时候用到了Ajax上传文件,以及图片在上传之前的预览效果,解决了这两个小问题,和小伙伴们分享下. 上传之前的预览 方式一 先来说说图片上传之前的预览问题.这里主要采用了HTML5中的FileReader对象来实现,关于FileReader对象,如果小伙伴们不了解,可以查看这篇文章HTML5学习之FileReader接口.我们来看看实现方式: <!DOCT

银联支付接口+支付宝接口统一支付功能

这个demo发出来已经比较久了,后面发现陆续还有同学找到我,我这里说下现在的情况.这个接口中的银联支付接口实际也是走的支付宝接口,不需要单独申请银联了.但是!支付宝的这个接口已经下架了,以前已经接入的此接口还可以照常使用,新接入的不能再使用银联接口.===================这个model将支付宝和银行支付统一到一起,摆脱了对SDK的依赖,我尽量封在了PayAction: <?php  class PayAction extends Action{     //支付跳转     fu

asp.net网站,无法跳转到银联支付页面

问题描述 网站以前都是可以正常跳转到银联支付页面支付的.近期不可以跳转了.能帮我分析一下是什么原因么?谢谢了 解决方案 解决方案二:这问题问了,谁知道你在问什么问题!~~解决方案三:呵呵你猜?解决方案四:引用1楼jrl5365的回复: 这问题问了,谁知道你在问什么问题!~~ 因为以前没接触过这个.所以不懂怎么问,能帮我分析一下吗?之所以跳转不过去,是什么原因呢?解决方案五:引用2楼liaoxing168的回复: 呵呵你猜? 因为以前没接触过这个.所以不懂怎么问,能帮我分析一下吗?之所以跳转不过去

重写 ajax 实现 session 超时跳转到登录页面实例代码

问题:使用window.location.href来跳转页面的时候,后端只需实现一个过滤器就可以在session超时的情况下重定向到登陆页面.但是使用ajax呢?使用ajax来执行会发生302错误,并且页面不可能跳转.下面就针对这个问题来贴上我的前后端代码. 1.session过滤器 import java.io.IOException; <p style="text-align: center"><img alt=""import javax.s