Apache设置允许跨域

1、首先找到 Apache 配置文件 httpd.conf ,开启 Apache 头信息自定义模块:

LoadModule headers_module modules/mod_headers.so

把以上这一行的#注释符去掉(也有可能已经默认去掉了)

2、找到对应的虚拟主机配置文件,也就是需要被跨域的网站所对应的主机配置文件,具体内容示例如下:

<VirtualHost *:80>
DocumentRoot /www/web/ken_01h_net/public_html
ServerName ken.01h.net
</VirtualHost>
<Directory /www/web/ken_01h_net>
    Options FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

添加一行: Header set Access-Control-Allow-Origin *

<VirtualHost *:80>
DocumentRoot /www/web/ken_01h_net/public_html
ServerName ken.01h.net
</VirtualHost>
<Directory /www/web/ken_01h_net>
    Options FollowSymLinks
    AllowOverride All
    Require all granted
    Header set Access-Control-Allow-Origin * #对所有域名开放
    #Header set Access-Control-Allow-Origin http://www.01h.net #对指定域名开放
</Directory>

支付宝退款接口返回空值问题

通过跟踪调试,最终追溯到支付宝SDK文件 alipay_core.function.php 中的getHttpResponsePOST 方法:

/**
 * 远程获取数据,POST模式
 * 注意:
 * 1.使用Crul需要修改服务器中php.ini文件的设置,找到php_curl.dll去掉前面的";"就行了
 * 2.文件夹中cacert.pem是SSL证书请保证其路径有效,目前默认路径是:getcwd().'\\cacert.pem'
 * @param $url 指定URL完整路径地址
 * @param $cacert_url 指定当前工作目录绝对路径
 * @param $para 请求的数据
 * @param $input_charset 编码格式。默认值:空值
 * return 远程输出的数据
 */
function getHttpResponsePOST($url, $cacert_url, $para, $input_charset = '') {

	if (trim($input_charset) != '') {
		$url = $url."_input_charset=".$input_charset;
	}
	$curl = curl_init($url);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); //SSL证书认证
	curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); //严格认证
	curl_setopt($curl, CURLOPT_CAINFO, $cacert_url); //证书地址
	curl_setopt($curl, CURLOPT_HEADER, 0 ); //过滤HTTP头
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); //显示输出结果
	curl_setopt($curl, CURLOPT_POST, true); //post传输数据
	curl_setopt($curl, CURLOPT_POSTFIELDS, $para); //post传输数据
	$responseText = curl_exec($curl);
	//var_dump( curl_error($curl) ); //如果执行curl过程中出现异常,可打开此开关,以便查看异常内容
	curl_close($curl);
	
	return $responseText;
}

将SSL证书认证那一行中的 true 设为 false 就正常了。

支付宝退款与微信退款的PHP代码示例

1、支付宝退款有老版新版两种接口,老版接口叫“即时到账批量退款有密接口”,接口名称叫“refund_fastpay_by_platform_pwd”,也就是说调用退款接口的时候,会跳转到支付宝那边去,然后需输入商家账号的支付密码确认才能完成退款操作,显然,这跟直接登陆支付宝商家账号操作退款差不多,都需要人工来处理,无法通过纯代码来实现退款了。老版有密退款的PHP代码示例如下:

	//退款接口
	function apply_refund($refund_info) {
        //配置信息
		$alipay_config['partner'] = '合作者身份ID';
		$alipay_config['seller_user_id'] = $alipay_config['partner'];
		$alipay_config['key'] = '支付密钥';
		$alipay_config['notify_url'] = 'http://ken.01h.net/alipay_refund_notify.php';  //退款通知url地址
		$alipay_config['sign_type'] = strtoupper('MD5');
		$alipay_config['refund_date'] = date("Y-m-d H:i:s",time());  //退款日期 时间格式 yyyy-MM-dd HH:mm:ss
		$alipay_config['service'] = 'refund_fastpay_by_platform_pwd';  //退款接口名
		$alipay_config['input_charset'] = strtolower('utf-8');
		$alipay_config['cacert'] = '/payments/alipay/lib' . '\\cacert.pem';
		$alipay_config['transport'] = 'http';
        require_once("/payments/alipay/lib/alipay_submit.class.php");  //此文件在支付宝旧版接口的SDK中
		
		//批次号,必填,格式:当天日期[8位]+序列号[3至24位],如:201908171000001
		$batch_no = $refund_info['WIDbatch_no'];
		//退款笔数,必填,参数detail_data的值中,“#”字符出现的数量加1,最大支持1000笔(即“#”字符出现的数量999个)
		$batch_num = $refund_info['WIDbatch_num'];
		//退款详细数据,必填,格式(支付宝交易号^退款金额^备注),多笔请用#隔开
		$detail_data = $refund_info['WIDdetail_data'];
		
		//构造要请求的参数数组,无需改动
		$parameter = array(
			"service" => trim($alipay_config['service']),
			"partner" => trim($alipay_config['partner']),
			"notify_url"	=> trim($alipay_config['notify_url']),
			"seller_user_id"	=> trim($alipay_config['seller_user_id']),
			"refund_date"	=> trim($alipay_config['refund_date']),
			"batch_no"	=> $batch_no,
			"batch_num"	=> $batch_num,
			"detail_data"	=> $detail_data,
			"_input_charset"	=> trim(strtolower($alipay_config['input_charset']))
		);
		
		//建立请求
		$alipaySubmit = new AlipaySubmit($alipay_config);
		$html_text = $alipaySubmit->buildRequestForm($parameter,"get", "确认");
        header("Content-type: text/html; charset=utf-8");
        echo $html_text;
        exit;
	}

支付宝有密退款只有异步通知,所以还得配一份接受异步通知的代码:

	//退款通知
	function alipay_refund_notify() {
        //配置信息
		$alipay_config['partner'] = '合作者身份ID';
		$alipay_config['seller_user_id'] = $alipay_config['partner'];
		$alipay_config['key'] = '支付密钥';
		$alipay_config['sign_type'] = strtoupper('MD5');
		$alipay_config['refund_date'] = date("Y-m-d H:i:s",time());  //退款日期 时间格式 yyyy-MM-dd HH:mm:ss
		$alipay_config['service'] = 'refund_fastpay_by_platform_pwd';  //退款接口名
		$alipay_config['input_charset'] = strtolower('utf-8');
		$alipay_config['cacert'] = '/payments/alipay/lib' . '\\cacert.pem';
		$alipay_config['transport'] = 'http';
        require_once("/payments/alipay/lib/alipay_submit.class.php");  //此文件在支付宝旧版接口的SDK中
		
		//计算得出通知验证结果
		$alipayNotify = new AlipayNotify($alipay_config);
		$verify_result = $alipayNotify->verifyNotify();
		
		if($verify_result) {//验证成功
			//批次号
			$batch_no = $_POST['batch_no'];
			//批量退款数据中转账成功的笔数
			$success_num = $_POST['success_num'];
			//批量退款数据中的详细信息
			$result_details = $_POST['result_details'];
			$notify_result = array(
				'batch_no' => $batch_no,
				'success_num' => $success_num,
				'result_details' => $result_details,
				'trade_status' => '1',
			);
		}else{
			$notify_result = array(
				'trade_status' => '0',
			);
		}
		return $notify_result;
	}

由于支付宝有密退款接口是通过传递支付宝交易号这个参数作为必要条件的,但一般我们的平台中只保存了外部订单号,所以还得有一个查询接口,即通过外部订单号查询支付宝交易号,示例代码如下:

	//查询接口,因为有密退款接口需要支付宝交易号,而平台中只保存了外部订单号,需通过外部订单号查询支付宝交易号
	function trade_query($out_trade_no) {
        //配置信息
		$alipay_config['partner'] = '合作者身份ID';
		$alipay_config['key'] = '支付密钥';
		$alipay_config['sign_type'] = strtoupper('MD5');
		$alipay_config['input_charset'] = strtolower('utf-8');
		$alipay_config['cacert'] = '/payments/alipay/lib' . '\\cacert.pem';
		$alipay_config['transport'] = 'http';
        require_once("/payments/alipay/lib/alipay_submit.class.php");

		//构造要请求的参数数组,无需改动
		$parameter = array(
			"service" => "single_trade_query",
			"partner" => trim($alipay_config['partner']),
			//"trade_no"	=> $trade_no,
			"out_trade_no"	=> $out_trade_no,
			"_input_charset"	=> trim(strtolower($alipay_config['input_charset']))
		);

		//建立请求
		$alipaySubmit = new AlipaySubmit($alipay_config);
		$html_text = $alipaySubmit->buildRequestHttp($parameter);

		//解析XML
		//注意:该功能PHP5环境及以上支持,需开通curl、SSL等PHP配置环境。建议本地调试时使用PHP开发软件
		$doc = new DOMDocument();
		$doc->loadXML($html_text);
		if( ! empty($doc->getElementsByTagName( "alipay" )->item(0)->nodeValue) ) {
			return $doc->getElementsByTagName( "trade_no" )->item(0)->nodeValue;
		}else{
			return '';
		}
	}

以上看来,支付宝老版有密退款接口不仅操作上比较麻烦,代码实现上也相对麻烦一些。而新版接口就简单多了,但新版接口需要配置支付宝开放平台对应的APPID下的RSA密钥对,而且,支付宝新版接口的SDK也跟之前老版的完全不一样了,示例代码如下:

    /**
     *    退款接口
     */
	function apply_refund($refund_info) {
		//引入配置,以下所引用的文件都在支付宝新版接口的SDK中
		require_once dirname(__FILE__).'/config.php';
		require_once dirname(__FILE__).'/pagepay/service/AlipayTradeService.php';
		require_once dirname(__FILE__).'/pagepay/buildermodel/AlipayTradeRefundContentBuilder.php';
		
		//构造参数
		$RequestBuilder=new AlipayTradeRefundContentBuilder();
		$RequestBuilder->setOutTradeNo($refund_info['order_sn']);  //外部订单号,可选,与支付宝交易号二选一
		$RequestBuilder->setTradeNo($refund_info['trade_no']);  //支付宝交易号,可选,与外部订单号二选一
		$RequestBuilder->setRefundAmount($refund_info['refund_fee']);  //需要退款的金额,该金额不能大于订单金额,必填
		$RequestBuilder->setOutRequestNo($refund_info['refund_sn']);  //标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传
		$RequestBuilder->setRefundReason($refund_info['refund_reason']);  //退款的原因说明
		
		$aop = new AlipayTradeService($config);
		
		/**
		 * alipay.trade.refund (统一收单交易退款接口)
		 * @param $builder 业务参数,使用buildmodel中的对象生成。
		 * @return $response 支付宝返回的信息
		 */
		$response = $aop->Refund($RequestBuilder);
		return $response;  //json对象
		/* 返回结果示例
{"code": "10000", "msg": "Success", "buyer_logon_id": "159****5620", "buyer_user_id": "2088101117955611", "fund_change": "Y", "gmt_refund_pay": "2019-08-17 15:45:57", "out_trade_no": "1920707058", "refund_fee": 88.88, "send_back_fee": "0.00", "trade_no": "2019081722001413990559810225"}
		*/
	}

配置文件config.php内容示例:

$config = array (	
		//应用ID,您的APPID。
		'app_id' => PAYMENT_ALIPAY_APPID,

		//商户私钥
		'merchant_private_key' => MERCHANT_PRIVATE_KEY,
		
		//异步通知地址
		'notify_url' => "",
		
		//同步跳转
		'return_url' => "",

		//编码格式
		'charset' => "UTF-8",

		//签名方式
		'sign_type'=>"RSA2",

		//支付宝网关
		'gatewayUrl' => "https://openapi.alipay.com/gateway.do",

		//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
		'alipay_public_key' => ALIPAY_PUBLIC_KEY,
);

备注:支付宝新版退款接口是可以通过传递外部订单号这个参数的,当然也可以传递支付宝交易号,二选一,且无需异步通知。

2、接下来再说说微信的退款接口,其实跟支付宝新版的接口逻辑上是一样的,只不过多了个异步通知而已,示例代码如下:

	//退款接口
	function apply_refund($refund_info) {
		require_once ("/payments/wxjsapi/lib/WxPay.Api.php");
		$input = new WxPayRefund();  //申请退款
		$input->SetOut_trade_no($refund_info['order_sn']); //商户订单号
		$input->SetOut_refund_no($refund_info['refund_sn']); //退款编号
		$input->SetTotal_fee(intval(bcmul($refund_info['total_fee'], 100 ,0))); //总金额
		$input->SetRefund_fee(intval(bcmul($refund_info['refund_fee'], 100 ,0))); //退款金额
		$input->SetOp_user_id(MCHID); //操作员,默认为商户号
		$input->SetNotify_url('http://ken.01h.net/wx_refund_notify.php');  //异步通知url地址
		$result = WxPayApi::refund($input);
		return $result;
		/* 返回结果示例
Array ( [appid] => wxe344e3******a730 [cash_fee] => 27 [cash_refund_fee] => 27 [coupon_refund_count] => 0 [coupon_refund_fee] => 0 [mch_id] => 133***3701 [nonce_str] => ONKuZw5SSNgRquH4 [out_refund_no] => 201926720674 [out_trade_no] => 1920645422 [refund_channel] => Array ( ) [refund_fee] => 27 [refund_id] => 50000601022019080211155839761 [result_code] => SUCCESS [return_code] => SUCCESS [return_msg] => OK [sign] => F65D86BDD42F47F1308431575A76AF35 [total_fee] => 27 [transaction_id] => 4200000345201907268893222782 )
		*/
	}

配置文件WxPay.Config.php的内容示例如下:

/**
* 	配置账号信息
*/
class WxPayConfig
{
	//=======【基本信息设置】=====================================
	//
	/**
	 * TODO: 修改这里配置为您自己申请的商户信息
	 * 微信公众号信息配置
	 * 
	 * APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
	 * 
	 * MCHID:商户号(必须配置,开户邮件中可查看)
	 * 
	 * KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置)
	 * 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
	 * 
	 * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置),
	 * 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&amp;t=advanced/dev&amp;token=2005451881&amp;lang=zh_CN
	 * @var string
	 */
        
	const APPID = APPID;
	const MCHID = MCHID;
	const KEY = KEY;
	const APPSECRET = APPSECRET;
	
	//=======【证书路径设置】=====================================
	/**
	 * TODO:设置商户证书路径
	 * 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
	 * API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
	 * @var path
	 */
	const SSLCERT_PATH = SSLCERT_PATH;  //API证书文件(apiclient_cert.pem)路径
	const SSLKEY_PATH = SSLKEY_PATH;  //API证书文件(apiclient_key.pem)路径
	
	//=======【curl代理设置】===================================
	/**
	 * TODO:这里设置代理机器,只有需要代理的时候才设置,不需要代理,请设置为0.0.0.0和0
	 * 本例程通过curl使用HTTP POST方法,此处可修改代理服务器,
	 * 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
	 * @var unknown_type
	 */
	const CURL_PROXY_HOST = "0.0.0.0";//"10.152.18.220";
	const CURL_PROXY_PORT = 0;//8080;
	
	//=======【上报信息配置】===================================
	/**
	 * TODO:接口调用上报等级,默认紧错误上报(注意:上报超时间为【1s】,上报无论成败【永不抛出异常】,
	 * 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
	 * 开启错误上报。
	 * 上报等级,0.关闭上报; 1.仅错误出错上报; 2.全量上报
	 * @var int
	 */
	const REPORT_LEVENL = 1;
}

处理退款异步通知的示例代码如下:

    /**
     * 微信退款通知
     */
    function wx_refund_notify() {
        if($GLOBALS['HTTP_RAW_POST_DATA']){
			$data = json_decode(json_encode(simplexml_load_string($GLOBALS['HTTP_RAW_POST_DATA'], 'SimpleXMLElement', LIBXML_NOCDATA)), true);  //将XML转为array
			$xml = $this->refund_decrypt($data['req_info'], MD5('商户密钥'));  //解码req_info信息,参看http://ken.01h.net/%e5%be%ae%e4%bf%a1%e9%80%80%e6%ac%be%e7%bb%93%e6%9e%9c%e9%80%9a%e7%9f%a5req_info%e8%a7%a3%e5%af%86%ef%bc%88php%e7%89%88%ef%bc%89/
			$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);  //将XML转为array
			//……
		}
		echo "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";  //告诉微信不要再发通知了
        exit;
    }

至此结束,正好写完吃饭!

php索引数组与关联数组的区别

在大多数编程语言里面,数组一般是指有序的元素序列,所谓“有序”,表现为其下标按数字0,1,2,3,4……这种递增的方式排列。还有另一种叫键值对的数据结构,与数组不同,它没有下标,而是通过键名(key)来赋值取值,这种键值对结构一般都被当作对象来处理,有的语言里面叫Dict,有的语言里面叫Map,而在PHP里面仍叫数组,只不过加上了个修饰词,叫“关联”数组,而真正的数组在PHP里面被称之为“索引”数组。这两种数据结构在PHP里面都以Array来定义,由于其定义的方式都一样,所以比较容易产生混淆,混淆主要表现在将数据转成json操作的时候。

$arr = array('a','b','c');
$json = json_encode($arr);
echo $json;  //["a","b","c"]
$arr = json_decode($json);
print_r($arr);  //Array ( [0] => a [1] => b [2] => c )

对于真正的数组,以上的互转是没有问题的。

$arr = array('x'=>'a','y'=>'b','z'=>'c');
$json = json_encode($arr);
echo $json;  //{"x":"a","y":"b","z":"c"}
$obj = json_decode($json);
print_r($obj);  //stdClass Object ( [x] => a [y] => b [z] => c ) 

而对于键值对结构的数据,以上互转就有点小问题了,转过去然后再转回来,数据结构发生了改变,之前是Array,后面变成了stdClass Object,由此也说明,在PHP里面,由Array定义的数据其实是数组和对象(键值对)的结合体,在应该是数组的时候就是数组,在应该是对象(键值对)的时候就是对象(键值对),好像是自适应的。

//PHP判断数据结构是索引数组还是关联数组
if(array_keys($arr) === range(0, count($arr)-1)){
    echo '索引数组';
}else{
    echo '关联数组';
}

php将数组存入cookie

最新在做公众号 H5 页面的微信授权登录功能时,需要将获取到的微信用户信息先存入 cookie,转入另一个页面后还需再次使用。实际上,cookie 是不能存数组的,因此首先想到的是把数组转成 JSON 字串,再存入 cookie ,但后来发现使用起来并不方便,存入 cookie 后再取出来时,中文变成了unicode 编码,链接被加上了反斜杠,后来才想到用序列化函数 serialize() ,中文和链接都可以维持原样。示例如下:

//存入cookie
$wxinfo['openid'] = 'ot03vvmOUAPDhsjABHr8lMsAWH6U';
$wxinfo['nickname'] = 'Ken';
$wxinfo = serialize($wxinfo);
setcookie('wxinfo',$wxinfo);
//取出数据
$wxinfo = str_replace('\"', '"', $_COOKIE['wxinfo']);
$wxinfo = unserialize($wxinfo);