农业银行直连支付接入

一客户要做多家银行的直连支付,注定纠结。各家银行规定不同,做过银行支付接入的同学都应该有这种感觉:银行提供的开发文档真的不太好现解,让人抓狂。

这两天,农行的正式证书已经下来了,B2B、B2C支付接口已正式开通。由于农行总行规定必须完成B2B(需测试:下载交易记录、直接支付、查询交易结果)、B2C(需测试:支付请求、查询订单记录、退款、下载交易对帐单)规定的接口测试后才给正式开通,而B2B接口没有PHP版的,只提供了JAVA版的。原本以为农行接入做不了了,最后,还是一不小心给整出来了。

后期会将农行直连支付接入的开发流程等总结出来。

001

002

 

003

xmlrpc demo

<?php

/**
 * 函数:提供给RPC客户端调用的函数
 * 参数:
 * $method 客户端需要调用的函数
 * $params 客户端需要调用的函数的参数数组
 * 返回:返回指定调用结果
 */
function lifecycle($method, $params) {
    switch ($params[0]) {
        case 'p1':
            $reply = '接收参数为p1执行的业务...';
            break;
        case 'guo1':
            $reply = '您好,天南,欢迎您的到来!<br />您传递了两个参数:' . $params[0] . ' -- ' . $params[1];
            break;
        default:
            $reply = '未知的参数!';
    }
    return $reply;
}
$output_options = array(
    "output_type" => "xml",
    "verbosity" => "pretty",
    "escaping" => array("markup"), //这个设置是关键 
    "version" => "xmlrpc",
    "encoding" => "UTF-8"
);

//产生一个XML-RPC的服务器端
$server = xmlrpc_server_create();

//注册一个服务,客户端调用 cycle函数,实际调用服务端的 lifecycle函数
xmlrpc_server_register_method($server, "cycle", "lifecycle");
$request = $HTTP_RAW_POST_DATA; //接收客户端POST来的数据

$response = xmlrpc_server_call_method($server, $request, null, $output_options); //呼叫注册的方法,当第4个参数不指定时,默认xmlrpc默认编码为 iso-8859-1,在客户端分割结果数据时,要注意xml编码与此处一致
header('Content-Type: text/xml');
print $response;

//销毁XML-RPC服务器端资源 
xmlrpc_server_destroy($server);

 

<?php

/**
 * 函数:提供给客户端进行连接XML-RPC服务器端的函数,需开启 php_sockets 、php_xmlrpc 扩展
 * 参数:
 * $host 需要连接的主机
 * $port 连接主机的端口
 * $request 封装的XML请求信息
 * 返回:连接成功成功返回由服务器端返回的XML信息
 */
function do_call($host, $port, $host_url, $request) {
    //打开指定的服务器端
    $fp = fsockopen($host, $port, $errno, $errstr);

    //构造需要进行通信的XML-RPC服务器端的查询POST请求信息
    $query = "POST {$host_url} HTTP/1.0\nUser_Agent: My Egg Client\nHost: " . $host . "\nContent-Type: text/xml\nContent-Length: " . strlen($request) . "\n\n" . $request . "\n";

    //把构造好的HTTP协议发送给服务器,失败返回false
    if (!fputs($fp, $query, strlen($query))) {
        $errstr = "Write error";
        return 0;
    }

    //获取从服务器端返回的所有信息,包括HTTP头和XML信息
    $contents = '';
    while (!feof($fp)) {//如果不是文件最后一行 ,将文件内容逐行读入
        $contents .= fgets($fp);
    }

    //关闭连接资源后返回获取的内容
    fclose($fp);
    return $contents;
}
//构造连接RPC服务器端的信息
$host = '127.0.0.1';
$port = 1401;
$host_url = '/guo_demo_xmlrpc/server.php';

//把需要发送的XML请求进行编码成XML,需要调用的方法是cycle,参数是guo1 guo2,多个参数时要以数组形式组组,一个参数可以直接写
$request = xmlrpc_encode_request('cycle', array('guo1', 'guo2'));

//调用do_call函数把所有请求发送给XML-RPC服务器端后获取信息
$response = do_call($host, $port, $host_url, $request);
//var_dump($response);exit;

//分析从服务器端返回的XML,去掉HTTP头信息,并且把XML转为PHP能识别的字符串
$split = '<?xml version="1.0" encoding="UTF-8"?>';
$xml = explode($split, $response);
$xml = $split . array_pop($xml);
$response = xmlrpc_decode($xml);

//输出从RPC服务器端获取的信息
print_r($response);

 

FPM/FastCGI下的fastcgi_finish_request()函数

当PHP以FPM/FastCGI方式运行时,有一个给力函数fastcgi_finish_request(),它是干嘛的?官方对此函数的描述是:用于在请求完成和刷新数据后,继续在后台执行耗时的工作(录入视频转换、统计处理等)。

下面我们看个例子:

<?php

echo '天南测试...';
fastcgi_finish_request();
echo '看我能不能输出'; //通过浏览器运行后,此句并不会在浏览器上输出
file_put_contents('log.txt', 'Hello World.'); //确保目录有w权限后,会在当前目录写入log.txt

在调用fastcgi_finish_request()函数后,客户端响应就已经结束了,但服务端脚本依旧继续运行直到结束。

总结:有人说利用此函数实现“异步”,这并不可靠。一方面,长时间脚本存在超时现象(当然,你可以设置set_time_limit,不过,这不科学);另一方面,这种“异步”没有返回值,无法得知最终的结果状态。

opcache

Zend Opcache已集成在PHP5.5里了,编译安装时加上 –enable-opcache 就OK了(win server则不需要),然后在php.ini配置文件中找到[opcache]配置节,添加以下配置:

zend_extension = "E:\php-5.5.13-nts-Win32-VC11-x64\ext\php_opcache.dll"
opcache.memory_consumption=1024
opcache.optimization_level=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4096
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable=1
opcache.enable_cli=1

保存重启服务后,通过phpinfo()就能看到 Zend OPcache 已开启了。

天南总结了一下:

1、缓存的是 .php 文件,此PHP文件中的HTML等也会被缓存;
2、include、require进来的文件也会被缓存;这在一定程度上也减小了I/O开销;事实上真的如此吗?测试时用get_included_files()发现文件还是正常被包含的;
3、开发中不要使用,项目上线后由于程序较少改动,所以建议开启;

IIS上PHP版本选择

Windows版的PHP从版本5.2.1开始有Thread Safe(线程安全)和None Thread Safe(NTS,非线程安全)之分,这两者不同在于何处?到底应该用哪种?这里做一个简单的介绍。

从2000年10月20日发布的第一 个Windows版的PHP3.0.17开始的都是线程安全的版本,这是由于与Linux/Unix系统是采用多进程的工作方式不同的是Windows系 统是采用多线程的工作方式。如果在IIS下以CGI方式运行PHP会非常慢,这是由于CGI模式是建立在多进程的基础之上的,而非多线程。一般我们会把 PHP配置成以ISAPI的方式来运行,ISAPI是多线程的方式,这样就快多了。但存在一个问题,很多常用的PHP扩展是以Linux/Unix的多进 程思想来开发的,这些扩展在ISAPI的方式运行时就会出错搞垮IIS。因此在IIS下CGI模式才是PHP运行的最安全方式,但CGI模式对于每个 HTTP请求都需要重新加载和卸载整个PHP环境,其消耗是巨大的。

为了兼顾IIS下PHP的效率和安全,微软给出了FastCGI的解 决方案。FastCGI可以让PHP的进程重复利用而不是每一个新的请求就重开一个进程。同时FastCGI也可以允许几个进程同时执行。这样既解决了 CGI进程模式消耗太大的问题,又利用上了CGI进程模式不存在线程安全问题的优势。

因此,如果是使用ISAPI的方式来运行PHP就必须用Thread Safe(线程安全)的版本;而用FastCGI模式运行PHP的话就没有必要用线程安全检查了,用None Thread Safe(NTS,非线程安全)的版本能够更好的提高效率

curl demo

$cookfile = dirname(__FILE__).'/cookie.txt';
$ip=$_SERVER['SERVER_ADDR'];
$uname="帐号";
$upass="密码";
// 设置URL和相应的选项
$HTTP_REQUEST_HEADER = array(
		"method"	 => 	 "POST",
		"timeout"	 =>	 30,
		"Content-Type"	=>	 "application/x-www-form-urlencoded; charset=gb2312",
		"Referer"	 =>	 "http://www.51.la/user/notice.asp",
		"Client-IP"	 =>	 $ip,
		"X-Forwarded-For"	=>	$ip,
		"Host"	 =>	 "http://www.51.la/"
);

$url = "http://www.51.la/login.asp";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //这行是设定curl是否跟随header发送的location,重要
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($ch, CURLOPT_HTTPHEADER, $HTTP_REQUEST_HEADER);
curl_setopt($ch, CURLOPT_POSTFIELDS, "uname=$uname&upass=$upass");
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookfile);   // 保存cookie
//curl_setopt($ch, CURLOPT_COOKIEFILE, $cookfile);	// 发送 cookie
curl_setopt($ch, CURLOPT_COOKIESESSION, 1);
//以下为SSL设置
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
//curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,  2);

$res = curl_exec($ch);
echo curl_error($ch);
curl_close($ch);
if($res===false){
exit('false');
}elseif(strpos($res,$uname)>0){//查找用户名是否存在,存在则登录正确
echo '登录成功';
}else{
echo $res;
}

 

$cookfile = dirname(__FILE__).'/cookie.txt';
$ip=$_SERVER['SERVER_ADDR'];
// 设置URL和相应的选项
$HTTP_REQUEST_HEADER = array(
		"method"	 => 	 "POST",
		"timeout"	 =>	 30,
		"Content-Type"	=>	 "application/x-www-form-urlencoded; charset=gb2312",
		"Referer"	 =>	 "http://www.51.la/user/notice.asp",
		"Client-IP"	 =>	 $ip,
		"X-Forwarded-For"	=>	$ip,
		"Host"	 =>	 "http://www.51.la/"
);
$url = "http://www.51.la/report/1_main.asp?id=2157050";
$ch1 = curl_init();
curl_setopt($ch1, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //这行是设定curl是否跟随header发送的location,重要
curl_setopt($ch1, CURLOPT_COOKIEFILE, $cookfile);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookfile);   // 保存cookie
curl_setopt($ch, CURLOPT_COOKIESESSION, 1);
$res = curl_exec($ch1);
//关闭cURL资源,并且释放系统资源
curl_close($ch1);
if($html===false){
<span style="white-space:pre">	</span>exit('false');
}
$html = str_replace(array("\r", "\n"), '', $html);
$html=cut_html($html,'<!--Ajiang Stat 2.0--><img','</div><div style="width:550px;');
$html=iconv("GB2312//IGNORE","UTF-8",$html);
$html=str_replace(array(' ','&nbsp;'),'',$html);
preg_match_all('/\>(.*)IP/i', $html, $a);
$ip=$a[1];
preg_match_all('/IP\/(.*)UV\//i', $html, $a);
$uv=$a[1];
preg_match_all('/pv"\/\>(.*)PV/i', $html, $a);
$pv=$a[1];