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 相关文章推荐
一个多文件上传的例子(原创)
Oct 09 PHP
php中使用cookie来保存用户登录信息的实现代码
Mar 08 PHP
PHP中鲜为人知的10个函数
Feb 28 PHP
ThinkPHP3.1新特性之命名范围的使用
Jun 19 PHP
php创建和删除目录函数介绍和递归删除目录函数分享
Nov 18 PHP
分享十款最出色的PHP安全开发库中文详细介绍
Mar 22 PHP
php利用事务处理转账问题
Apr 22 PHP
PHP版本如何选择?应该使用哪个版本?
May 13 PHP
使用PHP实现微信摇一摇周边红包
Jan 04 PHP
解析WordPress中控制用户登陆和判断用户登陆的PHP函数
Mar 01 PHP
php验证身份证号码正确性的函数
Jul 20 PHP
PHP 实现从数据库导出到.csv文件方法
Jul 06 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 api函数实现数组的交换排序示例
2014/04/13 PHP
PHP中使用curl伪造IP的简单方法
2015/08/07 PHP
zend framework中使用memcache的方法
2016/03/04 PHP
php获得刚插入数据的id 的几种方法总结
2018/05/31 PHP
PHP通过get方法获得form表单数据方法总结
2018/09/12 PHP
fireworks菜单生成器mm_menu.js在 IE 7.0 显示问题的解决方法
2009/10/20 Javascript
jquery 添加节点的几种方法介绍
2013/09/04 Javascript
再谈JavaScript线程
2015/07/10 Javascript
JavaScript中的this使用详解
2016/07/27 Javascript
BootStrap Validator 版本差异问题导致的submitHandler失效问题的解决方法
2016/12/01 Javascript
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
2016/12/14 Javascript
微信小程序版翻牌小游戏
2018/01/26 Javascript
解决vue 引入子组件报错的问题
2018/09/06 Javascript
js实现鼠标拖拽缩放div实例代码
2019/03/25 Javascript
js实现移动端轮播图滑动切换
2020/12/21 Javascript
在Python中编写数据库模块的教程
2015/04/29 Python
深入学习python的yield和generator
2016/03/10 Python
解决python读取几千万行的大表内存问题
2018/06/26 Python
对python列表里的字典元素去重方法详解
2019/01/21 Python
浅谈keras 的抽象后端(from keras import backend as K)
2020/06/16 Python
Python 排序最长英文单词链(列表中前一个单词末字母是下一个单词的首字母)
2020/12/14 Python
python 实现网易邮箱邮件阅读和删除的辅助小脚本
2021/03/01 Python
HTML5 对各个标签的定义与规定:body的介绍
2012/06/21 HTML / CSS
Canvas与Image互相转换示例代码
2013/08/09 HTML / CSS
如何在Canvas中添加事件的方法示例
2019/05/21 HTML / CSS
加拿大领先的时尚和体育零售商:Sporting Life
2019/12/15 全球购物
开工庆典邀请函范文
2014/01/16 职场文书
简单的辞职信范文
2014/01/18 职场文书
七年级音乐教学反思
2014/01/26 职场文书
婚纱摄影师求职信范文
2014/04/17 职场文书
居委会四风问题个人对照检查材料
2014/09/25 职场文书
党的群众路线教育实践活动党员个人剖析材料
2014/10/08 职场文书
优秀少先队辅导员事迹材料
2014/12/24 职场文书
婚礼答谢词范文
2015/09/29 职场文书
用python基于appium模块开发一个自动收取能量的小助手
2021/09/25 Python
MyBatis XPathParser解析器使用范例详解
2022/07/15 Java/Android