PHP接入银联在线网关支付


网站支付:一般接入微信支付、支付宝支付、银联支付,本文介绍的是如何接入中国银联在线网关支付

银联介绍

银联在线支付网关是中国银联联合各商业银行为持卡人提供的集成化、综合性互联网支付工具,主要支持输入卡号付款、用户登录支付、网银支付、迷你付(IC卡支付)等多种支付方式,为持卡人提供境内外网上购物、水电煤缴费、商旅预订等支付服务。

网站支付:一般接入微信支付、支付宝支付、银联支付,本文介绍的是如何接入中国银联在线网关支付

银联介绍

银联在线支付网关是中国银联联合各商业银行为持卡人提供的集成化、综合性互联网支付工具,主要支持输入卡号付款、用户登录支付、网银支付、迷你付(IC卡支付)等多种支付方式,为持卡人提供境内外网上购物、水电煤缴费、商旅预订等支付服务。

具体流程如下图:

SDK&Demo下载

开放平台服务端SDK
下图为下载好的sdk

ps:官方给的demo都是php文件中写html,没有分离,网上找了各种资料,没有找到分离过后的demo,所以自己总结了demo,希望我的文章可以帮助到大家快速接入银联

异常应答说明

  1. 不返回报文体的情况:
    版本号,交易类型、子类,签名方法,签名值等关键域未上送,返回“Invalid request.”; 交易类型和请求地址校验有误,返回“Invalid request URI.”
  2. 返回全部的请求报文,附加应答码和应答描述(包括的应答码有:01、02、03、10、11、32):

验证签名失败;
报文格式错(包含,必填域缺失,上送银联报文未定义的域,报文域的格式非法,交易时间超出范围);
签名失败;
超时等其他系统异常

友情提示

中间如遇到问题:请登录商家后台进行人工咨询,什么qq客服已停用。
基本错误的话文档都会有介绍,如果不知道debug到了哪一步,客服会告诉你调用接口时返回的错误


登录步骤:开放平台在线咨询:https://open.unionpay.com/tjweb/index 用已有账号登录或者注册新用户登录,选择“普通用户”身份登录,登录后页面点击“前往商户测试中心”,界面上点击客服图标后,点击“确定登录”,再点击“商户咨询”进入咨询。
业务问题与生产问题请拨打4007795516电话咨询。

银联在线网关支付接口地址

https://open.unionpay.com/tjweb/api/dictionary?apiSvcId=448

修改公共参数配置

制作签名需要配置参数,由于sdk中用到的大部分是ini配置文件,所以我将部分配置信息放到了php的配置文件中,修改了原有的配置文件

下图要特别注意:消费和查询交易,三个值必须统一,则退款不需要

/sdk/SDKConfig.php文件

// __construct方法中的配置文件添加
$this->env = 'test';
$this->signCertPwd = '000000';
//如果想把acp_sdk.ini挪到其他路径的话,请修改下面这行指定绝对路径。
$configFilePath = dirname(__FILE__) . "/acp_sdk_test.ini";
if(env('APP_ENV') == 'production'){
    $this->env = 'prod';
    $this->signCertPwd = env('UNION_SIGN_CERT_PWD','000000');
    $configFilePath = dirname(__FILE__) . "/acp_sdk.ini";
}
// __construct方法中的配置文件修改
/*
 * 重新定义配置文件路径   ---- 自定义
    $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->signCertPath = array_key_exists("acpsdk.signCert.path", $sdk_array)?$sdk_array["acpsdk.signCert.path"]: null;

    $this->encryptCertPath = array_key_exists("acpsdk.encryptCert.path", $sdk_array)? $sdk_array["acpsdk.encryptCert.path"]: null;
    $this->rootCertPath = array_key_exists("acpsdk.rootCert.path", $sdk_array)? $sdk_array["acpsdk.rootCert.path"]: null;
    $this->middleCertPath =  array_key_exists("acpsdk.middleCert.path", $sdk_array)?$sdk_array["acpsdk.middleCert.path"]: 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;
    $this->signCertPwd = array_key_exists("acpsdk.signCert.pwd", $sdk_array)?$sdk_array["acpsdk.signCert.pwd"]: null;
*/
// 由于是laravel框架,所以放到了env中,也可以放到config中,其它框架放到配置文件中即可
$this->backUrl =  env('HOST_FAST').env('UNION_PAY_BACKURL','');
$this->frontUrl =  env('HOST_EDU').env('UNION_PAY_FRONTURL','/');
$this->signCertPath = dirname(dirname(dirname(__FILE__))).'/union_pay/assets/'.$this->env.'/acp_sign.pfx';
$this->encryptCertPath = dirname(dirname(dirname(__FILE__))).'/union_pay/assets/'.$this->env.'/acp_enc.cer';
$this->middleCertPath = dirname(dirname(dirname(__FILE__))).'/union_pay/assets/'.$this->env.'/acp_middle.cer';
$this->rootCertPath = dirname(dirname(dirname(__FILE__))).'/union_pay/assets/'.$this->env.'/acp_root.cer';
$this->logFilePath = storage_path(env("UNION_PAY_LOGFILE_PATH"));
$this->logLevel = env("UNION_PAY_LOG_LEVEL",null);

/sdk/acp_service.php文件

createAutoFormHtml方法 直接返回参数

static function createAutoFormHtml($params, $reqUrl) {
    return $params;
}

配置信息

.env文件

UNION_PAY_TEST_MERID=测试商户号
UNION_PAY_MERID=线上商户号
UNION_SIGN_CERT_PWD=线上商户号密码
UNION_PAY_BACKURL=
UNION_PAY_FRONTURL=
UNION_PAY_LOGFILE_PATH=logs/union_pay
UNION_PAY_LOG_LEVEL=DEBUG

控制器

  1. consume方法为给前端返回的所有配置信息
  2. union_validate是去验证支付参数是否正确(后台通知地址)
  3. queryTrans交易状态查询
  4. handle_result处理银联的回调
  5. union_refund退款

实战demo地址

https://github.com/WXiangQian/laravel-api

接入消费接口

merId = env('UNION_PAY_TEST_MERID'); // 测试号
        if(env('APP_ENV') == 'production'){
            $this->merId = env('UNION_PAY_MERID');;
        }
    }

    /**
     * 银联消费的使用参数
     * @return \Illuminate\Http\JsonResponse
     * User: https://github.com/WXiangQian
     */
    public function consume()
    {

        /**
         * 重要:联调测试时请仔细阅读注释!
         *
         * 产品:跳转网关支付产品
* 交易:消费:前台跳转,有前台通知应答和后台通知应答
* 日期: 2015-09
* 版权: 中国银联
* 说明:以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码性能规范性等方面的保障
* 提示:该接口参考文档位置:open.unionpay.com帮助中心 下载 产品接口规范 《网关支付产品接口规范》,
* 《平台接入接口规范-第5部分-附录》(内包含应答码接口规范,全渠道平台银行名称-简码对照表)
* 《全渠道平台接入接口规范 第3部分 文件接口》(对账文件格式说明)
* 测试过程中的如果遇到疑问或问题您可以:1)优先在open平台中查找答案: * 调试过程中的问题或其他问题请在 https://open.unionpay.com/ajweb/help/faq/list 帮助中心 FAQ 搜索解决方案 * 测试过程中产生的7位应答码问题疑问请在https://open.unionpay.com/ajweb/help/respCode/respCodeList 输入应答码搜索解决方案 * 2) 咨询在线人工支持: open.unionpay.com注册一个用户并登陆在右上角点击“在线客服”,咨询人工QQ测试支持。 * 交易说明:1)以后台通知或交易状态查询交易确定交易成功,前台通知不能作为判断成功的标准. * 2)交易状态查询交易(Form_6_5_Query)建议调用机制:前台类交易建议间隔(5分、10分、30分、60分、120分)发起交易查询,如果查询到结果成功,则不用再查询。(失败,处理中,查询不到订单均可能为中间状态)。也可以建议商户使用payTimeout(支付超时时间),过了这个时间点查询,得到的结果为最终结果。 */ $channelType = $this->request->input('channelType','07'); $merId = $this->merId; $riskRateInfo = $this->request->input('riskRateInfo',''); $orderId = $this->request->input('orderId',0); $txnAmt = $this->request->input('txnAmt',1); $txnAmt = $txnAmt * 100; $txnTime = $this->request->input('txnTime',0); if ($txnTime == 0) { $txnTime = date('YmdHis'); } // 在生产环境测试的时候,交易金额请勿小于1角。 if(env('APP_ENV') == 'production' && $txnAmt <= 10){ return $this->response->tag('PARAM_ERROR')->response(); } // todo 查询数据库中的实际付款金额 $order_txnAmt = 0; // 没有查到则定金异常 if (!isset($order_txnAmt)) { return $this->response->tag('ORDER_NOT_EXIST')->response(); } $order_txnAmt = $order_txnAmt * 100; // 不一样则认为恶意修改金额 返回错误 if ($txnAmt != $order_txnAmt) { return $this->response->tag('ORDER_EXCEPTION')->response(); } $params = array( //以下信息非特殊情况不需要改动 'version' => SDKConfig::getSDKConfig()->version, //版本号 'encoding' => 'utf-8', //编码方式 'txnType' => '01', //交易类型 'txnSubType' => '01', //交易子类 'bizType' => '000201', //业务类型 'frontUrl' => SDKConfig::getSDKConfig()->frontUrl.'?order_id='.$orderId, //前台通知地址 'backUrl' => SDKConfig::getSDKConfig()->backUrl.$txnTime, //后台通知地址 'signMethod' => SDKConfig::getSDKConfig()->signMethod, //签名方法 'channelType' => $channelType, //渠道类型,07-PC,08-手机 'accessType' => '0', //接入类型 'currencyCode' => '156', //交易币种,境内商户固定156 //TODO 以下信息需要填写 'merId' => $merId, //商户代码,请改自己的测试商户号,此处默认取demo演示页面传递的参数 'orderId' => $orderId, //商户订单号,8-32位数字字母,不能含“-”或“_”,此处默认取demo演示页面传递的参数,可以自行定制规则 'txnTime' => $txnTime, //订单发送时间,格式为YYYYMMDDhhmmss,取北京时间,此处默认取demo演示页面传递的参数 'txnAmt' => $txnAmt, //交易金额,单位分,此处默认取demo演示页面传递的参数 // 订单超时时间。 // 超过此时间后,除网银交易外,其他交易银联系统会拒绝受理,提示超时。 跳转银行网银交易如果超时后交易成功,会自动退款,大约5个工作日金额返还到持卡人账户。 // 此时间建议取支付时的北京时间加15分钟。 // 超过超时时间调查询接口应答origRespCode不是A6或者00的就可以判断为失败。 'payTimeout' => date('YmdHis', strtotime('+15 minutes')), 'riskRateInfo' =>'{commodityName='.$riskRateInfo.'}', // 请求方保留域, // 透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。 // 出现部分特殊字符时可能影响解析,请按下面建议的方式填写: // 1. 如果能确定内容不会出现&={}[]"'等符号时,可以直接填写数据,建议的方法如下。 // 'reqReserved' =>'透传信息1|透传信息2|透传信息3', // 2. 内容可能出现&={}[]"'符号时: // 1) 如果需要对账文件里能显示,可将字符替换成全角&={}【】“‘字符(自己写代码,此处不演示); // 2) 如果对账文件没有显示要求,可做一下base64(如下)。 // 注意控制数据长度,实际传输的数据长度不能超过1024位。 // 查询、通知等接口解析时使用base64_decode解base64后再对数据做后续解析。 // 'reqReserved' => base64_encode('任意格式的信息都可以'), //TODO 其他特殊用法请查看 special_use_purchase.php ); AcpService::sign ( $params ); $uri = SDKConfig::getSDKConfig()->frontTransUrl; $html_form = AcpService::createAutoFormHtml( $params, $uri ); $data['url'] = $uri; foreach ($html_form as $key=>$value) { $data['data'][] = ['name'=>$key,'value'=>$value]; } return $this->response->data($data)->response(); } /** * 验证支付参数是否正确 * @param $txnTime 订单发送时间 * @return \Illuminate\Http\JsonResponse * User: https://github.com/WXiangQian */ public function union_validate($txnTime) { if (isset ( $_POST ['signature'] )) { // 验签失败 if (!AcpService::validate ($_POST)) { return $this->response->tag('PARAM_ERROR')->response(); } $orderId = $_POST ['orderId']; //其他字段也可用类似方式获取 $respCode = $_POST ['respCode']; //判断respCode=00、A6后,对涉及资金类的交易,请再发起查询接口查询,确定交易成功后更新数据库。 if ($respCode == 00 || $respCode == 'A6') { // todo 将下单时间存到redis key:order_id value:txnTime 查询交易的时候需要使用 // todo 调用api项目的回调地址 return $this->response->response(); } else { return $this->response->tag('OPERATION_FAILED')->response(); } } return $this->response->tag('PARAM_LACK')->response(); } }

接入交易状态查询接口

/**
 * 交易状态查询
 * @param $channelType
 * @param $merId
 * @param $orderId
 * @param $txnTime
 * @return mixed
 * @throws LogicException
 * User: https://github.com/WXiangQian
 */
public function queryTrans($channelType,$merId,$orderId,$txnTime)
{
    $params = array(
        //以下信息非特殊情况不需要改动
        'version' => SDKConfig::getSDKConfig()->version,          //版本号
        'encoding' => 'utf-8',          //编码方式
        'signMethod' => SDKConfig::getSDKConfig()->signMethod,          //签名方法
        'txnType' => '00',              //交易类型
        'txnSubType' => '00',          //交易子类
        'bizType' => '000000',          //业务类型
        'accessType' => '0',          //接入类型
        'channelType' => $channelType,          //渠道类型

        //TODO 以下信息需要填写
        'orderId' => $orderId,    //请修改被查询的交易的订单号,8-32位数字字母,不能含“-”或“_”,此处默认取demo演示页面传递的参数
        'merId' => $merId,        //商户代码,请改自己的测试商户号,此处默认取demo演示页面传递的参数
        'txnTime' => $txnTime,    //请修改被查询的交易的订单发送时间,格式为YYYYMMDDhhmmss,此处默认取demo演示页面传递的参数
    );

    AcpService::sign ( $params ); // 签名
    $url = SDKConfig::getSDKConfig()->singleQueryUrl;
    $result_arr = $this->handle_result($params,$url);

    return $result_arr;
}

/**
 * 处理银联的回调
 * @param $params
 * @param $url
 * @throws LogicException
 * @return mixed
 * User: https://github.com/WXiangQian
 */
public function handle_result($params,$url)
{
    $result_arr = AcpService::post ( $params, $url);
    if(count($result_arr)<=0) { //没收到200应答的情况
        throw new LogicException('REFUND_FAIL');
    }

    if (!AcpService::validate ($result_arr) ){
        throw new LogicException('CHECK_SIGN_FAIL');
    }

    if ($result_arr["respCode"] == "00"){
        //交易已受理,等待接收后台通知更新订单状态,如果通知长时间未收到也可发起交易状态查询
        //TODO
        return $result_arr;
    } else if ($result_arr["respCode"] == "03"
        || $result_arr["respCode"] == "04"
        || $result_arr["respCode"] == "05" ){
        //后续需发起交易状态查询交易确定交易状态
        //TODO
        throw new LogicException('HADNLE_OVERTIME');
    } else {
        //其他应答码做以失败处理
        //TODO
        throw new LogicException('失败:'.$result_arr["respMsg"],14004);
    }
}

接入退货接口

/**
 * 银联退款
 * @throws LogicException
 * @return \Illuminate\Http\JsonResponse
 * User: https://github.com/WXiangQian
 */
public function union_refund()
{
    $channelType = $this->request->input('channelType','07');
    $merId = $this->merId;
    $oid = $this->request->input('orderId',0);
    $txnAmt = $this->request->input('txnAmt',1);
    $txnAmt2 = $txnAmt * 100;
    $txnTime = date('YmdHis');
    // todo 查询订单信息 判断订单是否存在
    $order_info =  ['plat_oid'=>11111111];
    $origQryId = $order_info['plat_oid'];
    $orderId = time().rand(1111, 9999); // 生成退款订单号

    /**
     * 重要:联调测试时请仔细阅读注释!
     *
     * 产品:跳转网关支付产品
* 交易:退货交易:后台资金类交易,有同步应答和后台通知应答
* 日期: 2015-09
* 版权: 中国银联
* 说明:以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己需要,按照技术文档编写。该代码仅供参考,不提供编码性能规范性等方面的保障
* 该接口参考文档位置:open.unionpay.com帮助中心 下载 产品接口规范 《网关支付产品接口规范》
* 《平台接入接口规范-第5部分-附录》(内包含应答码接口规范,全渠道平台银行名称-简码对照表)
* 测试过程中的如果遇到疑问或问题您可以:1)优先在open平台中查找答案: * 调试过程中的问题或其他问题请在 https://open.unionpay.com/ajweb/help/faq/list 帮助中心 FAQ 搜索解决方案 * 测试过程中产生的7位应答码问题疑问请在https://open.unionpay.com/ajweb/help/respCode/respCodeList 输入应答码搜索解决方案 * 2) 咨询在线人工支持: open.unionpay.com注册一个用户并登陆在右上角点击“在线客服”,咨询人工QQ测试支持。 * 交易说明: 1)以后台通知或交易状态查询交易(Form_6_5_Query)确定交易成功,建议发起查询交易的机制:可查询N次(不超过6次),每次时间间隔2N秒发起,即间隔1,2,4,8,16,32S查询(查询到03,04,05继续查询,否则终止查询) * 2)退货金额不超过总金额,可以进行多次退货 * 3)退货能对11个月内的消费做(包括当清算日),支持部分退货或全额退货,到账时间较长,一般1-10个清算日(多数发卡行5天内,但工行可能会10天),所有银行都支持 */ $params = array( //以下信息非特殊情况不需要改动 'version' => SDKConfig::getSDKConfig()->version, //版本号 'encoding' => 'utf-8', //编码方式 'signMethod' => SDKConfig::getSDKConfig()->signMethod, //签名方法 'txnType' => '04', //交易类型 'txnSubType' => '00', //交易子类 'bizType' => '000201', //业务类型 'accessType' => '0', //接入类型 'channelType' => $channelType, //渠道类型 'backUrl' => 'http://www.specialUrl.com', //后台通知地址 //TODO 以下信息需要填写 'orderId' => $orderId, //商户订单号,8-32位数字字母,不能含“-”或“_”,可以自行定制规则,重新产生,不同于原消费,此处默认取demo演示页面传递的参数 'merId' => $merId, //商户代码,请改成自己的测试商户号,此处默认取demo演示页面传递的参数 'origQryId' => $origQryId, //原消费的queryId,可以从查询接口或者通知接口中获取,此处默认取demo演示页面传递的参数 'txnTime' => $txnTime, //订单发送时间,格式为YYYYMMDDhhmmss,重新产生,不同于原消费,此处默认取demo演示页面传递的参数 'txnAmt' => $txnAmt2, //交易金额,退货总金额需要小于等于原消费 // 请求方保留域, // 透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。 // 出现部分特殊字符时可能影响解析,请按下面建议的方式填写: // 1. 如果能确定内容不会出现&={}[]"'等符号时,可以直接填写数据,建议的方法如下。 // 'reqReserved' =>'透传信息1|透传信息2|透传信息3', // 2. 内容可能出现&={}[]"'符号时: // 1) 如果需要对账文件里能显示,可将字符替换成全角&={}【】“‘字符(自己写代码,此处不演示); // 2) 如果对账文件没有显示要求,可做一下base64(如下)。 // 注意控制数据长度,实际传输的数据长度不能超过1024位。 // 查询、通知等接口解析时使用base64_decode解base64后再对数据做后续解析。 // 'reqReserved' => base64_encode('任意格式的信息都可以'), ); AcpService::sign ( $params ); // 签名 $url = SDKConfig::getSDKConfig()->backTransUrl; $this->handle_result($params,$url); // todo 退款成功-需要将退款信息存到自己的业务表中 return $this->response->response(); }

测试环境测试银联支付

测试商户号为:777290058110048。测试环境只能使用银联给的银行卡信息:测试环境的测试卡信息

测试流程截图

注意事项

注:在生产环境测试的时候,交易金额请勿小于1角。

从测试配置切到线上配置时,线上必须修改UNION_SIGN_CERT_PWD线上商户号密码

遇到报错信息:The each() function is deprecated. This message will be suppressed on further calls

php7.2+已将each函数废除,则需要自己换为foreach

解决方案

// 在sdk/common.php中的createLinkString方法进行修改
//    while ( list ( $key, $value ) = each ( $para ) ) {
//        if ($encode) {
//            $value = urlencode ( $value );
//        }
//        $linkString .= $key . "=" . $value . "&";
//    }
    // php7.2后废弃each
    foreach ($para as $key => $value) {
        if ($encode) {
            $value = urlencode ( $value );
        }
        $linkString .= $key . "=" . $value . "&";
    }

结束语

真枪实战php接入中国银联在线网关支付消费和交易状态查询以及银联退款接口。

掘金地址


文章作者: WXiangQian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 WXiangQian !
 上一篇
mac重启php-fpm mac重启php-fpm
查看php-fpm端口是否在被php-fpm使用sudo lsof -i:9000 一般修改 php.ini 文件后经常需要重启php-fpmsudo killall php-fpm // 关闭 再输入 sudo lsof -i:
2020-02-17
下一篇 
EasyWeChat在laravel框架中的使用技巧 EasyWeChat在laravel框架中的使用技巧
EasyWeChat在laravel框架中的使用技巧 laravel框架实战:EasyWeChat EasyWeChat公众号配置protected $wechat; public function __construct () {
2019-12-25
  目录