php银联网页支付实现方法


Posted in PHP onMarch 04, 2015

本文实例讲述了php银联网页支付实现方法。分享给大家供大家参考。具体分析如下:
这里介绍的银联WAP支付功能,仅限消费功能。

1. PHP代码如下:

<?php

namespace common\services;

class UnionPay

{

    /**

     * 支付配置

     * @var array

     */

    public $config = [];

    /**

     * 支付参数,提交到银联对应接口的所有参数

     * @var array

     */

    public $params = [];

    /**

     * 自动提交表单模板

     * @var string

     */

    private $formTemplate = <<<'HTML'

<!DOCTYPE HTML>

<html>

<head>

    <meta charset="utf-8">

    <title>支付</title>

</head>

<body>

    <div style="text-align:center">跳转中...</div>

    <form id="pay_form" name="pay_form" action="%s" method="post">

        %s

    </form>

    <script type="text/javascript">

        document.onreadystatechange = function(){

            if(document.readyState == "complete") {

                document.pay_form.submit();

            }

        };

    </script>

</body>

</html>

HTML;

/**

* 构建自动提交HTML表单

* @return string

*/

public function createPostForm()

{

        $this->params['signature'] = $this->sign();

        $input = '';

        foreach($this->params as $key => $item) {

            $input .= "\t\t<input type=\"hidden\" name=\"{$key}\" value=\"{$item}\">\n";

        }

        return sprintf($this->formTemplate, $this->config['frontUrl'], $input);

}

/**

* 验证签名

* 验签规则:

* 除signature域之外的所有项目都必须参加验签

* 根据key值按照字典排序,然后用&拼接key=value形式待验签字符串;

* 然后对待验签字符串使用sha1算法做摘要;

* 用银联公钥对摘要和签名信息做验签操作

* 

* @throws \Exception

* @return bool

*/

public function verifySign()

{

        $publicKey = $this->getVerifyPublicKey();

        $verifyArr = $this->filterBeforSign();

        ksort($verifyArr);

        $verifyStr = $this->arrayToString($verifyArr);

        $verifySha1 = sha1($verifyStr);

        $signature = base64_decode($this->params['signature']);

        $result = openssl_verify($verifySha1, $signature, $publicKey);

        if($result === -1) {

            throw new \Exception('Verify Error:'.openssl_error_string());

        }

        return $result === 1 ? true : false;

}

/**

* 取签名证书ID(SN)

* @return string

*/

public function getSignCertId()

{

        return $this->getCertIdPfx($this->config['signCertPath']);

}   

/**

* 签名数据

* 签名规则:

* 除signature域之外的所有项目都必须参加签名

* 根据key值按照字典排序,然后用&拼接key=value形式待签名字符串;

* 然后对待签名字符串使用sha1算法做摘要;

* 用银联颁发的私钥对摘要做RSA签名操作

* 签名结果用base64编码后放在signature域

* 

* @throws \InvalidArgumentException

* @return multitype|string

*/

private function sign() {

        $signData = $this->filterBeforSign();

        ksort($signData);

        $signQueryString = $this->arrayToString($signData);

        if($this->params['signMethod'] == 01) {

            //签名之前先用sha1处理

            //echo $signQueryString;exit;

            $datasha1 = sha1($signQueryString);

            $signed = $this->rsaSign($datasha1);

        } else {

            throw new \InvalidArgumentException('Nonsupport Sign Method');

        }

        return $signed;

}

/**

* 数组转换成字符串

* @param array $arr

* @return string

*/

private function arrayToString($arr)

{

        $str = '';

        foreach($arr as $key => $value) {

            $str .= $key.'='.$value.'&';

        }

        return substr($str, 0, strlen($str) - 1);

}

/**

* 过滤待签名数据

* signature域不参加签名

* 

* @return array

*/

private function filterBeforSign()

{

        $tmp = $this->params;

        unset($tmp['signature']);

        return $tmp;

}

/**

* RSA签名数据,并base64编码

* @param string $data 待签名数据

* @return mixed

*/

private function rsaSign($data)

{

        $privatekey = $this->getSignPrivateKey();

        $result = openssl_sign($data, $signature, $privatekey);

        if($result) {

            return base64_encode($signature);

        }

        return false;

}

/**

* 取.pfx格式证书ID(SN)

* @return string

*/

private function getCertIdPfx($path)

{

        $pkcs12certdata = file_get_contents($path);

        openssl_pkcs12_read($pkcs12certdata, $certs, $this->config['signCertPwd']);

        $x509data = $certs['cert'];

        openssl_x509_read($x509data);

        $certdata = openssl_x509_parse($x509data);

        return $certdata['serialNumber'];

}

/**

* 取.cer格式证书ID(SN)

* @return string

*/

private function getCertIdCer($path)

{

        $x509data = file_get_contents($path);

        openssl_x509_read($x509data);

        $certdata = openssl_x509_parse($x509data);

        return $certdata['serialNumber'];

}

/**

* 取签名证书私钥

* @return resource

*/

private function getSignPrivateKey()

{

        $pkcs12 = file_get_contents($this->config['signCertPath']);

        openssl_pkcs12_read($pkcs12, $certs, $this->config['signCertPwd']);

        return $certs['pkey'];

}

/**

* 取验证签名证书

* @throws \InvalidArgumentException

* @return string

*/

private function getVerifyPublicKey()

{

        //先判断配置的验签证书是否银联返回指定的证书是否一致

        if($this->getCertIdCer($this->config['verifyCertPath']) != $this->params['certId']) {

            throw new \InvalidArgumentException('Verify sign cert is incorrect');

        }

        return file_get_contents($this->config['verifyCertPath']);       

    }

}

2. 配置示例    
//银联支付设置

 'unionpay' => [

     //测试环境参数

     'frontUrl' => 'https://101.231.204.80:5000/gateway/api/frontTransReq.do', //前台交易请求地址

     //'singleQueryUrl' => 'https://101.231.204.80:5000/gateway/api/queryTrans.do', //单笔查询请求地址

     'signCertPath' => __DIR__.'/../keys/unionpay/test/sign/700000000000001_acp.pfx', //签名证书路径

     'signCertPwd' => '000000', //签名证书密码

     'verifyCertPath' => __DIR__.'/../keys/unionpay/test/verify/verify_sign_acp.cer', //验签证书路径

     'merId' => 'xxxxxxx',

     //正式环境参数

     //'frontUrl' => 'https://101.231.204.80:5000/gateway/api/frontTransReq.do', //前台交易请求地址

     //'singleQueryUrl' => 'https://101.231.204.80:5000/gateway/api/queryTrans.do', //单笔查询请求地址

     //'signCertPath' => __DIR__.'/../keys/unionpay/test/sign/PM_700000000000001_acp.pfx', //签名证书路径

     //'signCertPwd' => '000000', //签名证书密码

     //'verifyCertPath' => __DIR__.'/../keys/unionpay/test/verify/verify_sign_acp.cer', //验签证书路径

     //'merId' => 'xxxxxxxxx', //商户代码

 ],

3. 支付示例    
$unionPay = new UnionPay();

$unionPay->config = Yii::$app->params['unionpay'];//上面的配置

$unionPay->params = [

    'version' => '5.0.0', //版本号

    'encoding' => 'UTF-8', //编码方式

    'certId' => $unionPay->getSignCertId(), //证书ID

    'signature' => '', //签名

    'signMethod' => '01', //签名方式

    'txnType' => '01', //交易类型

    'txnSubType' => '01', //交易子类

    'bizType' => '000201', //产品类型

    'channelType' => '08',//渠道类型

    'frontUrl' => Url::toRoute(['payment/unionpayreturn'], true), //前台通知地址

    'backUrl' => Url::toRoute(['payment/unionpaynotify'], true), //后台通知地址

    //'frontFailUrl' => Url::toRoute(['payment/unionpayfail'], true), //失败交易前台跳转地址

    'accessType' => '0', //接入类型

    'merId' => Yii::$app->params['unionpay']['merId'], //商户代码

    'orderId' => $orderNo, //商户订单号

    'txnTime' => date('YmdHis'), //订单发送时间

    'txnAmt' => $sum * 100, //交易金额,单位分

    'currencyCode' => '156', //交易币种

];

$html = $unionPay->createPostForm();

4. 异步通知示例
$unionPay = new UnionPay();

$unionPay->config = Yii::$app->params['unionpay'];

$unionPay->params = Yii::$app->request->post(); //银联提交的参数

if(empty($unionPay->params)) {

    return 'fail!';

}

if($unionPay->verifySign() && $unionPay->params['respCode'] == '00') {

    //.......

}

希望本文所述对大家的php程序设计有所帮助。

PHP 相关文章推荐
PHP聊天室技术
Oct 09 PHP
echo(),print(),print_r()之间的区别?
Nov 19 PHP
弄了个检测传输的参数是否为数字的Function
Dec 06 PHP
php 3行代码的分页算法(求起始页和结束页)
Oct 21 PHP
PHP下编码转换函数mb_convert_encoding与iconv的使用说明
Dec 16 PHP
php模拟asp中的XmlHttpRequest实现http请求的代码
Mar 24 PHP
php的XML文件解释类应用实例
Sep 22 PHP
php数组查找函数总结
Nov 18 PHP
laravel实现批量更新多条记录的方法示例
Oct 22 PHP
tp5(thinkPHP5框架)使用DB实现批量删除功能示例
May 28 PHP
Yii框架实现对数据库的CURD操作示例
Sep 03 PHP
laravel http 自定义公共验证和响应的方法
Sep 29 PHP
php随机抽奖实例分析
Mar 04 #PHP
php二维数组合并及去重复的方法
Mar 04 #PHP
php中get_cfg_var()和ini_get()的用法及区别
Mar 04 #PHP
php用ini_get获取php.ini里变量值的方法
Mar 04 #PHP
浅谈PHP中单引号和双引号到底有啥区别呢?
Mar 04 #PHP
php查询mysql大量数据造成内存不足的解决方法
Mar 04 #PHP
在win系统安装配置 Memcached for PHP 5.3 图文教程
Mar 03 #PHP
You might like
PHP将整个网站生成HTML纯静态网页的方法总结
2012/02/05 PHP
php session 写入数据库
2016/02/13 PHP
php实现转换html格式为文本格式的方法
2016/05/16 PHP
thinkphp在php7环境下提示Cannot use ‘String’ as class name as it is reserved的解决方法
2016/09/30 PHP
PHP与SQL语句写一句话木马总结
2019/10/11 PHP
jQuery 中关于CSS操作部分使用说明
2007/06/10 Javascript
javascript options属性集合操作代码
2009/12/28 Javascript
Js组件的一些写法
2010/09/10 Javascript
Tab页界面 用jQuery及Ajax技术实现(php后台)
2011/10/12 Javascript
javascrpt绑定事件之匿名函数无法解除绑定问题
2012/12/06 Javascript
Ajax执行顺序流程及回调问题分析
2012/12/10 Javascript
防止xss和sql注入:JS特殊字符过滤正则
2013/04/18 Javascript
原生JS操作网页给p元素添加onclick事件及表格隔行变色
2013/12/01 Javascript
jquery实现LED广告牌旋转系统图片切换效果代码分享
2015/08/26 Javascript
jQuery插件实现表格隔行变色及鼠标滑过高亮显示效果代码
2016/02/25 Javascript
什么是JavaScript注入攻击?
2016/09/14 Javascript
详解使用vue脚手架工具搭建vue-webpack项目
2017/05/10 Javascript
vue轮播图插件vue-awesome-swiper
2017/11/27 Javascript
jquery手机触屏滑动拼音字母城市选择器的实例代码
2017/12/11 jQuery
微信小程序实现限制用户转发功能的实例代码
2020/02/22 Javascript
[02:56]DOTA2上海特锦赛小组赛解说FreeAgain采访花絮
2016/02/27 DOTA
Python二维码生成库qrcode安装和使用示例
2014/12/16 Python
使用Python脚本将绝对url替换为相对url的教程
2015/04/24 Python
python实现文件批量编码转换及注意事项
2019/10/14 Python
Python实现RGB与HSI颜色空间的互换方式
2019/11/27 Python
Python OpenCV实现测量图片物体宽度
2020/05/27 Python
Richards网上商店:当代时尚,遍布巴西
2019/11/03 全球购物
英国时尚首饰品牌:Missoma
2020/06/29 全球购物
UNIX文件名称有什么规定
2013/03/25 面试题
项目副经理岗位职责
2013/12/30 职场文书
集体婚礼策划方案
2014/02/22 职场文书
2015年英语教研组工作总结
2015/05/23 职场文书
教师节主题班会方案
2015/08/17 职场文书
《少年闰土》教学反思
2016/02/18 职场文书
强烈推荐:小学生:暑假作息时间表(值得收藏)
2019/07/09 职场文书
vue实现列表垂直无缝滚动
2022/04/08 Vue.js