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 print类函数使用总结
Jun 25 PHP
php 数组动态添加实现代码(最土团购系统的价格排序)
Dec 30 PHP
PHP三元运算符的结合性介绍
Jan 10 PHP
Window 7/XP 安装Apache 2.4与PHP 5.4 的过程详解
Jun 02 PHP
PHP FTP操作类代码( 上传、拷贝、移动、删除文件/创建目录)
May 10 PHP
浅谈PHP正则表达式中修饰符/i, /is, /s, /isU
Oct 21 PHP
PHP实现全角字符转为半角方法汇总
Jul 09 PHP
在Mac OS上自行编译安装Apache服务器和PHP解释器
Dec 24 PHP
PHP操作redis实现的分页列表,新增,删除功能封装类与用法示例
Aug 04 PHP
PHP+Redis开发的书签案例实战详解
Jul 09 PHP
PHP如何防止用户重复提交表单
Dec 09 PHP
open_basedir restriction in effect. 原因与解决方法
Mar 14 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
咖啡因含量是由谁决定的?低因咖啡怎么来?低因咖啡适合什么人喝
2021/03/06 新手入门
PHP技术开发技巧分享
2010/03/23 PHP
Zend Studio使用技巧两则
2016/04/01 PHP
PHP ADODB实现事务处理功能示例
2018/05/25 PHP
用jscript启动sqlserver
2007/06/21 Javascript
JS中toFixed()方法引起的问题如何解决
2012/11/20 Javascript
jQuery筛选器children()案例详解(图文)
2013/02/17 Javascript
使用Jquery获取带特殊符号的ID 标签的方法
2014/04/30 Javascript
js检测用户输入密码强度
2015/10/22 Javascript
基于jQuery实现的仿百度首页滑动选项卡效果代码
2015/11/16 Javascript
探讨JavaScript语句的执行过程
2016/01/28 Javascript
TypeScript Type Innference(类型判断)
2016/03/10 Javascript
Javascript中常用类型的格式化方法小结
2016/12/26 Javascript
webpack入门+react环境配置
2017/02/08 Javascript
微信小程序之页面跳转和参数传递的实现
2017/09/29 Javascript
集成vue到jquery/bootstrap项目的方法
2018/02/10 jQuery
如何为vue的项目添加单元测试
2018/12/19 Javascript
js设计模式之代理模式及订阅发布模式实例详解
2019/08/15 Javascript
Python的净值数据接口调用示例分享
2016/03/15 Python
python实现字典(dict)和字符串(string)的相互转换方法
2017/03/01 Python
解决Django数据库makemigrations有变化但是migrate时未变动问题
2018/05/30 Python
在Python中表示一个对象的方法
2019/06/25 Python
pytorch加载自定义网络权重的实现
2020/01/07 Python
Pytorch 实现数据集自定义读取
2020/01/18 Python
四方通行旅游网:台湾订房、出国旅游
2017/09/20 全球购物
英国网络托管和域名领导者:Web Hosting UK
2017/10/15 全球购物
美国巧克力喷泉品牌:Sephra
2019/05/05 全球购物
会话Bean的种类
2013/11/07 面试题
List、Map、Set三个接口,存取元素时,各有什么特点?
2015/09/27 面试题
EJB的几种类型
2012/08/15 面试题
2014年最新版离婚协议书范本
2014/11/25 职场文书
2014年社区卫生工作总结
2014/12/18 职场文书
教师听课评语大全
2014/12/31 职场文书
2015年党员创先争优承诺书
2015/01/22 职场文书
企业法律事务工作总结
2015/08/11 职场文书
《吃水不忘挖井人》教学反思
2016/02/22 职场文书