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 相关文章推荐
动态网站web开发 PHP、ASP还是ASP.NET
Oct 09 PHP
解析thinkphp import 文件内容变量失效的问题
Jun 20 PHP
php发送post请求的三种方法
Feb 11 PHP
PHP中比较时间大小实例
Aug 21 PHP
php pthreads多线程的安装与使用
Jan 19 PHP
简单介绍PHP非阻塞模式
Mar 03 PHP
浅谈PHP安全防护之Web攻击
Jan 03 PHP
PHP去除空数组且数组键名重置的讲解
Feb 28 PHP
php使用redis的几种常见操作方式和用法示例
Feb 20 PHP
php设计模式之适配器模式实例分析【星际争霸游戏案例】
Apr 07 PHP
laravel数据库查询结果自动转数组修改实例
Feb 27 PHP
PHP7新特性
Mar 09 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
ThinkPHP3.1新特性之多层MVC的支持
2014/06/19 PHP
php+mysqli使用面向对象方式查询数据库实例
2015/01/29 PHP
利用 fsockopen() 函数开放端口扫描器的实例
2017/08/19 PHP
laravel 去掉index.php伪静态的操作方法
2019/10/12 PHP
JavaScript入门之基本函数详解
2011/10/21 Javascript
jQuery获取样式中的背景颜色属性值/颜色值
2012/12/17 Javascript
js控制input输入字符解析
2013/12/27 Javascript
关于js数组去重的问题小结
2014/01/24 Javascript
jquery 实现两Select 标签项互调示例代码
2014/09/25 Javascript
jQuery中[attribute!=value]选择器用法实例
2014/12/31 Javascript
jQuery动画与特效详解
2015/02/01 Javascript
JavaScript跨平台的开源框架NativeScript
2015/03/24 Javascript
浅析javascript函数表达式
2016/02/10 Javascript
jQuery简单实现列表隐藏和显示效果示例
2016/09/12 Javascript
详解JavaScript 中getElementsByName在IE中的注意事项
2017/02/21 Javascript
vue复合组件实现注册表单功能
2017/11/06 Javascript
vue2.0 路由模式mode=&quot;history&quot;的作用
2018/10/18 Javascript
Vue.js组件间通信方式总结【推荐】
2018/11/23 Javascript
layer弹出层显示在top顶层的方法
2019/09/11 Javascript
vue动态路由:路由参数改变,视图不更新问题的解决
2019/11/05 Javascript
解决vant框架做H5时踩过的坑(下拉刷新、上拉加载等)
2020/11/11 Javascript
[02:08]什么藏在DOTA2 TI9“小紫本”里?斧王历险记告诉你!
2019/05/17 DOTA
Python输出PowerPoint(ppt)文件中全部文字信息的方法
2015/04/28 Python
python之验证码生成(gvcode与captcha)
2019/01/02 Python
python的sorted用法详解
2019/06/25 Python
Tensorflow中tf.ConfigProto()的用法详解
2020/02/06 Python
工厂厂长的职责
2013/12/12 职场文书
请假条的格式
2014/04/11 职场文书
情况说明书格式范文
2014/05/06 职场文书
党政领导班子四风问题对照检查材料思想汇报
2014/10/02 职场文书
2014年党风廉政工作总结
2014/12/03 职场文书
2015年青年志愿者工作总结
2015/05/20 职场文书
创业计划书之服装
2019/10/07 职场文书
Django程序的优化技巧
2021/04/29 Python
Spring Cache和EhCache实现缓存管理方式
2021/06/15 Java/Android
Go gorilla/sessions库安装使用
2022/08/14 Golang