【银联支付】php接入银联支付

SDK文件夹 :链接:https://pan.baidu.com/s/16b5RtA_CqV6wHX4ilE3yYA
提取码:gkby
复制这段内容后打开百度网盘手机App,操作更方便哦

银联支付需要配置的比较多,还要注意当前版本,证书签名方式

银联支付配置

;;;;;;;;;;;;;;SDK配置文件(证书方式签名);;;;;;;;;;;;;;;;
; 说明:
; 1. 使用时请删除后缀的“.证书”,并将此文件复制到根文件夹下替换原来的acp_sdk.ini。
; 2. 具体配置项请根据注释修改。
; 3. sdk默认读取的配置文件路径为sdk文件夹的acp_sdk.ini文件,如果需修改路径,请自行到sdk/SDKConfig.php中修改。
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

[acpsdk]
;;;;;;;;;;;;;;;;;;;;;;;;;;入网测试环境交易发送地址(线上测试需要使用生产环境交易请求地址);;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;交易请求地址
;acpsdk.frontTransUrl=https://gateway.test.95516.com/gateway/api/frontTransReq.do
;acpsdk.backTransUrl=https://gateway.test.95516.com/gateway/api/backTransReq.do
;acpsdk.singleQueryUrl=https://gateway.test.95516.com/gateway/api/queryTrans.do
;acpsdk.batchTransUrl=https://gateway.test.95516.com/gateway/api/batchTrans.do
;acpsdk.fileTransUrl=https://filedownload.test.95516.com/
;acpsdk.appTransUrl=https://gateway.test.95516.com/gateway/api/appTransReq.do
;acpsdk.cardTransUrl=https://gateway.test.95516.com/gateway/api/cardTransReq.do

;以下缴费产品使用,其余产品用不到
;acpsdk.jfFrontTransUrl=https://gateway.test.95516.com/jiaofei/api/frontTransReq.do
;acpsdk.jfBackTransUrl=https://gateway.test.95516.com/jiaofei/api/backTransReq.do
;acpsdk.jfSingleQueryUrl=https://gateway.test.95516.com/jiaofei/api/queryTrans.do
;acpsdk.jfCardTransUrl=https://gateway.test.95516.com/jiaofei/api/cardTransReq.do
;acpsdk.jfAppTransUrl=https://gateway.test.95516.com/jiaofei/api/appTransReq.do

;;交易请求地址
acpsdk.frontTransUrl=https://gateway.95516.com/gateway/api/frontTransReq.do
acpsdk.backTransUrl=https://gateway.95516.com/gateway/api/backTransReq.do
acpsdk.singleQueryUrl=https://gateway.95516.com/gateway/api/queryTrans.do
acpsdk.batchTransUrl=https://gateway.95516.com/gateway/api/batchTrans.do
acpsdk.fileTransUrl=https://filedownload.95516.com/
acpsdk.appTransUrl=https://gateway.95516.com/gateway/api/appTransReq.do
acpsdk.cardTransUrl=https://gateway.95516.com/gateway/api/cardTransReq.do

;以下缴费产品使用,其余产品用不到
acpsdk.jfFrontTransUrl=https://gateway.95516.com/jiaofei/api/frontTransReq.do
acpsdk.jfBackTransUrl=https://gateway.95516.com/jiaofei/api/backTransReq.do
acpsdk.jfSingleQueryUrl=https://gateway.95516.com/jiaofei/api/queryTrans.do
acpsdk.jfCardTransUrl=https://gateway.95516.com/jiaofei/api/cardTransReq.do
acpsdk.jfAppTransUrl=https://gateway.95516.com/jiaofei/api/appTransReq.do
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; 报文版本号,固定5.1.0,请勿改动
acpsdk.version=5.1.0

; 签名方式,证书方式固定01,请勿改动
acpsdk.signMethod=01

; 是否验证验签证书的CN,测试环境请设置false,生产环境请设置true。非false的值默认都当true处理。
;acpsdk.ifValidateCNName=false
acpsdk.ifValidateCNName=true
; 是否验证https证书,测试环境请设置false,生产环境建议优先尝试true,不行再false。非true的值默认都当false处理。
;acpsdk.ifValidateRemoteCert=false
acpsdk.ifValidateRemoteCert=true

;后台通知地址,填写接收银联后台通知的地址,必须外网能访问
acpsdk.backUrl=http://baidu.com/pay/unionpay/notify
acpsdk.partnerBackUrl=http://apitest.baidu.com/and/v2.0.0/partnerpay/unionpay/notify
;acpsdk.backUrl=http://apitest.baidu.com/pay/unionpay/notify
;前台通知地址,填写处理银联前台通知的地址,必须外网能访问
acpsdk.frontUrl=http://localhost:8086/upacp_demo_app/demo/api_05_app/FrontReceive.php

;;;;;;;;;;;;;;;;;;;;;;;;;入网测试环境签名证书配置 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 多证书的情况证书路径为代码指定,可不对此块做配置。
; 签名证书路径,必须使用绝对路径,如果不想使用绝对路径,可以自行实现相对路径获取证书的方法;测试证书所有商户共用开发包中的测试签名证书,生产环境请从cfca下载得到。
; 测试环境证书位于assets/测试环境证书/文件夹下,请复制到d:/certs文件夹。生产环境证书由业务部门邮件发送。
; windows样例:
;acpsdk.signCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/acp_test_sign.pfx
acpsdk.signCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/mclandian.pfx
; linux样例(注意:在linux下读取证书需要保证证书有被应用读的权限)(后续其他路径配置也同此条说明)
;acpsdk.signCert.path=/SERVICE01/usr/ac_frnas/conf/ACPtest/acp_test_sign.pfx

; 签名证书密码,测试环境固定000000,生产环境请修改为从cfca下载的正式证书的密码,正式环境证书密码位数需小于等于6位,否则上传到商户服务网站会失败
acpsdk.signCert.pwd=00000

;;;;;;;;;;;;;;;;;;;;;;;;;;加密证书配置;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 敏感信息加密证书路径(商户号开通了商户对敏感信息加密的权限,需要对 卡号accNo,pin和phoneNo,cvn2,expired加密(如果这些上送的话),对敏感信息加密使用)
;acpsdk.encryptCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/acp_test_enc.cer
acpsdk.encryptCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/acp_prod_enc.cer


;;;;;;;;;;;;;;;;;;;;;;;;;;验签证书配置;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 验签中级证书(证书位于assets/测试环境证书/文件夹下,请复制到d:/certs文件夹)
;acpsdk.middleCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/acp_test_middle.cer
acpsdk.middleCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/acp_prod_middle.cer
; 验签根证书(证书位于assets/测试环境证书/文件夹下,请复制到d:/certs文件夹)
;acpsdk.rootCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/acp_test_root.cer
acpsdk.rootCert.path=/home/www/lazyshop_api/app/Sdk/Unionpay/certs/acp_prod_root.cer

;;;;;;;;;;;;;;;;;;;;;;;;;;日志配置;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 日志打印路径,linux注意要有写权限
acpsdk.log.file.path=/home/www/lazyshop_api/storage/logs
; 日志级别,debug级别会打印密钥,生产请用info或以上级别
acpsdk.log.level=INFO

核心文件(该文件负责验证报文的有效性和解析返回报文):

<?php
namespace App\Sdk\Unionpay;
//header ( 'Content-type:text/html;charset=utf-8' );
//include_once 'PhpLog.php';
//include_once 'SDKConfig.php';
//include_once 'LogUtil.php';
//include_once 'CertUtil.php';

use App\Sdk\Unionpay\PhpLog;
use App\Sdk\Unionpay\SDKConfig;
use App\Sdk\Unionpay\LogUtil;
use App\Sdk\Unionpay\CertUtil;
class AcpService {

	/**
	 *
	 * 更新证书
	 *
	 * Enter description here ...
	 */
	public static function updateEncryptCert(&$params)
	{
        $logger = LogUtil::getLogger();
		// 取得证书
		$strCert = $params['encryptPubKeyCert'];
		$certType = $params['certType'];
		openssl_x509_read($strCert);
		$certInfo = openssl_x509_parse($strCert);
		if($certType === "01"){
			$logger->LogInfo ('原证书certId:'.CertUtil::getEncryptCertId().',新证书certId:'.$certInfo['serialNumber']);
			// 更新敏感信息加密公钥
			if (CertUtil::getEncryptCertId() != $certInfo['serialNumber']) {
				$newFileName = getBackupFileName(SDKConfig::getSDKConfig()->encryptCertPath);
				// 将原证书备份重命名
				if(!copy(SDKConfig::getSDKConfig()->encryptCertPath, $newFileName)){
					$logger->LogError ('原证书备份失败');
					return -1;
				}
				// 更新证书
				if(!file_put_contents(SDKConfig::getSDKConfig()->encryptCertPath, $strCert)){
					$logger->LogError ('更新证书失败');
					return -1;
				}
				$logger->LogInfo ('证书更新成功');
				return 1;
			} else {						
				$logger->LogInfo ('证书无需更新');
				return 0;
			}
		} else if($certType === "02"){
			return 0;
		} else {						
			$logger->LogError ('unknown cerType: '. $certType);
			return -1;
		}
	}

	/**
	 * 签名
	 * @param req 请求要素
	 * @param resp 应答要素
	 * @return 是否成功
	 */
	static function sign(&$params) {
		if($params['signMethod']=='01')	{
			return AcpService::signByCertInfo($params, SDKConfig::getSDKConfig()->signCertPath, SDKConfig::getSDKConfig()->signCertPwd);
		} else {
			return AcpService::signBySecureKey($params, SDKConfig::getSDKConfig()->secureKey);
		} 
	}
	
	static function signByCertInfo(&$params, $cert_path, $cert_pwd) {

		$logger = LogUtil::getLogger();
		$logger->LogInfo ( '=====签名报文开始======' );
		if(isset($params['signature'])){
			unset($params['signature']);
		}
		
		$result = false;
		
		if($params['signMethod']=='01') {
			//证书ID
			$params ['certId'] = CertUtil::getSignCertIdFromPfx($cert_path, $cert_pwd);
			$private_key = CertUtil::getSignKeyFromPfx( $cert_path, $cert_pwd );
			// 转换成key=val&串
			$params_str = createLinkString ( $params, true, false );
			$logger->LogInfo ( "签名key=val&...串 >" . $params_str );
			if($params['version']=='5.0.0'){
				$params_sha1x16 = sha1 ( $params_str, FALSE );
				$logger->LogInfo ( "摘要sha1x16 >" . $params_sha1x16 );
				// 签名
				$result = openssl_sign ( $params_sha1x16, $signature, $private_key, OPENSSL_ALGO_SHA1);
		
				if ($result) {
					$signature_base64 = base64_encode ( $signature );
					$logger->LogInfo ( "签名串为 >" . $signature_base64 );
					$params ['signature'] = $signature_base64;
				} else {
					$logger->LogInfo ( ">>>>>签名失败<<<<<<<" );
				}
			} else if($params['version']=='5.1.0'){
				//sha256签名摘要
				$params_sha256x16 = hash( 'sha256',$params_str);
				$logger->LogInfo ( "摘要sha256x16 >" . $params_sha256x16 );
				// 签名
				$result = openssl_sign ( $params_sha256x16, $signature, $private_key, 'sha256');
				if ($result) {
					$signature_base64 = base64_encode ( $signature );
					$logger->LogInfo ( "签名串为 >" . $signature_base64 );
					$params ['signature'] = $signature_base64;
				} else {
					$logger->LogInfo ( ">>>>>签名失败<<<<<<<" );
				}
			} else {
				$logger->LogError ( "wrong version: " + $params['version'] );
				$result = false;
			}
		} else {
			$logger->LogError ( "signMethod不正确");
			$result = false;
		}
		$logger->LogInfo ( '=====签名报文结束======' );
		return $result;
	}
	
	static function signBySecureKey(&$params, $secureKey) {
		
		$logger = LogUtil::getLogger();
		$logger->LogInfo ( '=====签名报文开始======' );
		
		if($params['signMethod']=='11') {
			// 转换成key=val&串
			$params_str = createLinkString ( $params, true, false );
			$logger->LogInfo ( "签名key=val&...串 >" . $params_str );
			$params_before_sha256 = hash('sha256', $secureKey);
			$params_before_sha256 = $params_str.'&'.$params_before_sha256;
			$logger->LogDebug( "before final sha256: " . $params_before_sha256);
			$params_after_sha256 = hash('sha256',$params_before_sha256);
			$logger->LogInfo ( "签名串为 >" . $params_after_sha256 );
			$params ['signature'] = $params_after_sha256;
			$result = true;
		} else if($params['signMethod']=='12') {
			//TODO SM3
			$logger->LogError ( "signMethod=12未实现");
			$result = false;
		} else {
			$logger->LogError ( "signMethod不正确");
			$result = false;
		}
		$logger->LogInfo ( '=====签名报文结束======' );
		return $result;
	}

	/**
	 * 验签
	 * @param $params 应答数组
	 * @return 是否成功
	 */
	static function validate($params) {

		$logger = LogUtil::getLogger();

		$isSuccess = false;

		if($params['signMethod']=='01')
		{
			$signature_str = $params ['signature'];
			unset ( $params ['signature'] );
			$params_str = createLinkString ( $params, true, false );
			$logger->LogInfo ( '报文去[signature] key=val&串>' . $params_str );
			$logger->LogInfo ( '签名原文>' . $signature_str );
			if($params['version']=='5.0.0'){

				// 公钥
				$public_key = CertUtil::getVerifyCertByCertId ( $params ['certId'] );
				$signature = base64_decode ( $signature_str );
				$params_sha1x16 = sha1 ( $params_str, FALSE );
				$logger->LogInfo ( 'sha1>' . $params_sha1x16 );
				$isSuccess = openssl_verify ( $params_sha1x16, $signature, $public_key, OPENSSL_ALGO_SHA1 );
				$logger->LogInfo ( $isSuccess ? '验签成功' : '验签失败' );

			} else if($params['version']=='5.1.0'){

				$strCert = $params['signPubKeyCert'];
				$strCert = CertUtil::verifyAndGetVerifyCert($strCert);
				if($strCert == null){
                	$logger->LogError ("validate cert err: " + $params["signPubKeyCert"]);
					$isSuccess = false;
				} else {
					$params_sha256x16 = hash('sha256', $params_str);
					$logger->LogInfo ( 'sha256>' . $params_sha256x16 );
					$signature = base64_decode ( $signature_str );
					$isSuccess = openssl_verify ( $params_sha256x16, $signature,$strCert, "sha256" );
					$logger->LogInfo ( $isSuccess ? '验签成功' : '验签失败' );
				}

			} else {
				$logger->LogError ( "wrong version: " + $params['version'] );
				$isSuccess = false;
			}
		} else {
			$isSuccess = AcpService::validateBySecureKey($params, SDKConfig::getSDKConfig()->secureKey);
		} 
		return $isSuccess;
	}

	static function validateBySecureKey($params, $secureKey) { 
		
		$logger = LogUtil::getLogger();
		$isSuccess = false;
		
		$signature_str = $params ['signature'];
		unset ( $params ['signature'] );
		$params_str = createLinkString ( $params, true, false );
		$logger->LogInfo ( '报文去[signature] key=val&串>' . $params_str );
		$logger->LogInfo ( '签名原文>' . $signature_str );
		
		if($params['signMethod']=='11') {
			
			$params_before_sha256 = hash('sha256', $secureKey);
			$params_before_sha256 = $params_str.'&'.$params_before_sha256;
			$params_after_sha256 = hash('sha256',$params_before_sha256);
			$isSuccess = $params_after_sha256 == $signature_str;
			$logger->LogInfo ( $isSuccess ? '验签成功' : '验签失败' );
		} else if($params['signMethod']=='12') {
			//TODO SM3
			$logger->LogError ( "sm3没实现");
			$isSuccess = false;
		} else {
			$logger->LogError ( "signMethod不正确");
			$isSuccess = false;
		}

		return $isSuccess;
		
	}
	
	/**
	 * @deprecated 5.1.0开发包已删除此方法,请直接参考5.1.0开发包中的VerifyAppData.php验签。
	 * 对控件支付成功返回的结果信息中data域进行验签
	 * @param $jsonData json格式数据,例如:{"sign" : "J6rPLClQ64szrdXCOtV1ccOMzUmpiOKllp9cseBuRqJ71pBKPPkZ1FallzW18gyP7CvKh1RxfNNJ66AyXNMFJi1OSOsteAAFjF5GZp0Xsfm3LeHaN3j/N7p86k3B1GrSPvSnSw1LqnYuIBmebBkC1OD0Qi7qaYUJosyA1E8Ld8oGRZT5RR2gLGBoiAVraDiz9sci5zwQcLtmfpT5KFk/eTy4+W9SsC0M/2sVj43R9ePENlEvF8UpmZBqakyg5FO8+JMBz3kZ4fwnutI5pWPdYIWdVrloBpOa+N4pzhVRKD4eWJ0CoiD+joMS7+C0aPIEymYFLBNYQCjM0KV7N726LA==",  "data" : "pay_result=success&tn=201602141008032671528&cert_id=68759585097"}
	 * @return 是否成功
	 */
	static function validateAppResponse($jsonData) {

		$data = json_decode($jsonData);
		$sign = $data->sign;
		$data = $data->data;
		$dataMap = parseQString($data);
		$public_key = CertUtil::getVerifyCertByCertId( $dataMap ['cert_id'] );
		$signature = base64_decode ( $sign );
		$params_sha1x16 = sha1 ( $data, FALSE );
		$isSuccess = openssl_verify ( $params_sha1x16, $signature,$public_key, OPENSSL_ALGO_SHA1 );
		return $isSuccess;
	}

	/**
	 * 后台交易 HttpClient通信
	 *
	 * @param unknown_type $params
	 * @param unknown_type $url
	 * @return mixed
	 */
	static function post($params, $url) {
		$logger = LogUtil::getLogger();

		$opts = createLinkString ( $params, false, true );
		$logger->LogInfo ( "后台请求地址为>" . $url );
		$logger->LogInfo ( "后台请求报文为>" . $opts );

		$ch = curl_init ();
		curl_setopt ( $ch, CURLOPT_URL, $url );
		curl_setopt ( $ch, CURLOPT_POST, 1 );
		curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, false ); // 不验证证书
		curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, false ); // 不验证HOST
		curl_setopt ( $ch, CURLOPT_SSLVERSION, 1 ); // http://php.net/manual/en/function.curl-setopt.php页面搜CURL_SSLVERSION_TLSv1
		curl_setopt ( $ch, CURLOPT_HTTPHEADER, array (
				'Content-type:application/x-www-form-urlencoded;charset=UTF-8' 
				) );
				curl_setopt ( $ch, CURLOPT_POSTFIELDS, $opts );
				curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
				$html = curl_exec ( $ch );
				$logger->LogInfo ( "后台返回结果为>" . $html );

				if(curl_errno($ch)){
					$errmsg = curl_error($ch);
					curl_close ( $ch );
					$logger->LogInfo ( "请求失败,报错信息>" . $errmsg );
					return null;
				}
				if( curl_getinfo($ch, CURLINFO_HTTP_CODE) != "200"){
					$errmsg = "http状态=" . curl_getinfo($ch, CURLINFO_HTTP_CODE);
					curl_close ( $ch );
					$logger->LogInfo ( "请求失败,报错信息>" . $errmsg );
					return null;
				}
				curl_close ( $ch );
				$result_arr = convertStringToArray ( $html );
				return $result_arr;
	}

	/**
	 * 后台交易 HttpClient通信
	 *
	 * @param unknown_type $params
	 * @param unknown_type $url
	 * @return mixed
	 */
	static function get($params, $url) {

		$logger = LogUtil::getLogger();

		$opts = createLinkString ( $params, false, true );
		$logger->LogDebug( "后台请求地址为>" . $url ); //get的日志太多而且没啥用,设debug级别
		$logger->LogDebug ( "后台请求报文为>" . $opts );

		$ch = curl_init ();
		curl_setopt ( $ch, CURLOPT_URL, $url );
		curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, false ); // 不验证证书
		curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, false ); // 不验证HOST
		curl_setopt ( $ch, CURLOPT_SSLVERSION, 1 ); // http://php.net/manual/en/function.curl-setopt.php页面搜CURL_SSLVERSION_TLSv1
		curl_setopt ( $ch, CURLOPT_HTTPHEADER, array (
		'Content-type:application/x-www-form-urlencoded;charset=UTF-8'
		) );
		curl_setopt ( $ch, CURLOPT_POSTFIELDS, $opts );
		curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
		$html = curl_exec ( $ch );
		$logger->LogInfo ( "后台返回结果为>" . $html );
		if(curl_errno($ch)){
			$errmsg = curl_error($ch);
			curl_close ( $ch );
			$logger->LogDebug ( "请求失败,报错信息>" . $errmsg );
			return null;
		}
		if( curl_getinfo($ch, CURLINFO_HTTP_CODE) != "200"){
			$errmsg = "http状态=" . curl_getinfo($ch, CURLINFO_HTTP_CODE);
			curl_close ( $ch );
			$logger->LogDebug ( "请求失败,报错信息>" . $errmsg );
			return null;
		}
		curl_close ( $ch );
		return $html;
	}

	static function createAutoFormHtml($params, $reqUrl) {
		// <body οnlοad="javascript:document.pay_form.submit();">
		$encodeType = isset ( $params ['encoding'] ) ? $params ['encoding'] : 'UTF-8';
		$html = <<<eot
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset={$encodeType}" />
</head>
<body οnlοad="javascript:document.pay_form.submit();">
    <form id="pay_form" name="pay_form" action="{$reqUrl}" method="post">
	
eot;
		foreach ( $params as $key => $value ) {
			$html .= "    <input type=\"hidden\" name=\"{$key}\" id=\"{$key}\" value=\"{$value}\" />\n";
		}
		$html .= <<<eot
   <!-- <input type="submit" type="hidden">-->
    </form>
</body>
</html>
eot;
		$logger = LogUtil::getLogger();
		$logger->LogInfo ( "自动跳转html>" . $html );
		return $html;
	}



	static function getCustomerInfo($customerInfo) {
		if($customerInfo == null || count($customerInfo) == 0 )
		return "";
		return base64_encode ( "{" . createLinkString ( $customerInfo, false, false ) . "}" );
	}

	/**
	 * map转换string,按新规范加密
	 *
	 * @param
	 *        	$customerInfo
	 */
	static function getCustomerInfoWithEncrypt($customerInfo) {
		if($customerInfo == null || count($customerInfo) == 0 )
		return "";
		$encryptedInfo = array();
		foreach ( $customerInfo as $key => $value ) {
			if ($key == 'phoneNo' || $key == 'cvn2' || $key == 'expired' ) {
				//if ($key == 'phoneNo' || $key == 'cvn2' || $key == 'expired' || $key == 'certifTp' || $key == 'certifId') {
				$encryptedInfo [$key] = $customerInfo [$key];
				unset ( $customerInfo [$key] );
			}
		}
		if( count ($encryptedInfo) > 0 ){
			$encryptedInfo = createLinkString ( $encryptedInfo, false, false );
			$encryptedInfo = AcpService::encryptData ( $encryptedInfo, SDKConfig::getSDKConfig()->encryptCertPath );
			$customerInfo ['encryptedInfo'] = $encryptedInfo;
		}
		return base64_encode ( "{" . createLinkString ( $customerInfo, false, false ) . "}" );
	}


	/**
	 * 解析customerInfo。
	 * 为方便处理,encryptedInfo下面的信息也均转换为customerInfo子域一样方式处理,
	 * @param unknown $customerInfostr
	 * @return array形式ParseCustomerInfo
	 */
	static function parseCustomerInfo($customerInfostr) {
		$customerInfostr = base64_decode($customerInfostr);
		$customerInfostr = substr($customerInfostr, 1, strlen($customerInfostr) - 2);
		$customerInfo = parseQString($customerInfostr);
		if(array_key_exists("encryptedInfo", $customerInfo)) {
			$encryptedInfoStr = $customerInfo["encryptedInfo"];
			unset ( $customerInfo ["encryptedInfo"] );
			$encryptedInfoStr = AcpService::decryptData($encryptedInfoStr);
			$encryptedInfo = parseQString($encryptedInfoStr);
			foreach ($encryptedInfo as $key => $value){
				$customerInfo[$key] = $value;
			}
		}
		return $customerInfo;
	}

	static function getEncryptCertId() {
		$cert_path=SDKConfig::getSDKConfig()->encryptCertPath;
		return CertUtil::getEncryptCertId($cert_path);
	}

	/**
	 * 加密数据
	 * @param string $data数据
	 * @param string $cert_path 证书配置路径
	 * @return unknown
	 */
	static function encryptData($data, $cert_path=null) {
		if( $cert_path == null ) {
			$cert_path = SDKConfig::getSDKConfig()->encryptCertPath;
		}
		$public_key = CertUtil::getEncryptKey( $cert_path );
		openssl_public_encrypt ( $data, $crypted, $public_key );
		return base64_encode ( $crypted );
	}

	/**
	 * 解密数据
	 * @param string $data数据
	 * @param string $cert_path 证书配置路径
	 * @return unknown
	 */
	static function decryptData($data, $cert_path=null, $cert_pwd=null) {

		if( $cert_path == null ) {
			$cert_path = SDKConfig::getSDKConfig()->signCertPath;
			$cert_pwd = SDKConfig::getSDKConfig()->signCertPwd;
		}
		
		$data = base64_decode ( $data );
		$private_key = CertUtil::getSignKeyFromPfx ( $cert_path, $cert_pwd);
		openssl_private_decrypt ( $data, $crypted, $private_key );
		return $crypted;
	}


	/**
	 * 处理报文中的文件
	 *
	 * @param unknown_type $params
	 */
	static function deCodeFileContent($params, $fileDirectory) {
		$logger = LogUtil::getLogger();
		if (isset ( $params ['fileContent'] )) {
			$logger->LogInfo ( "---------处理后台报文返回的文件---------" );
			$fileContent = $params ['fileContent'];

			if (empty ( $fileContent )) {
				$logger->LogInfo ( '文件内容为空' );
				return false;
			} else {
				// 文件内容 解压缩
				$content = gzuncompress ( base64_decode ( $fileContent ) );
				$filePath = null;
				if (empty ( $params ['fileName'] )) {
					$logger->LogInfo ( "文件名为空" );
					$filePath = $fileDirectory . $params ['merId'] . '_' . $params ['batchNo'] . '_' . $params ['txnTime'] . '.txt';
				} else {
					$filePath = $fileDirectory . $params ['fileName'];
				}
				$handle = fopen ( $filePath, "w+" );
				if (! is_writable ( $filePath )) {
					$logger->LogInfo ( "文件:" . $filePath . "不可写,请检查!" );
					return false;
				} else {
					file_put_contents ( $filePath, $content );
					$logger->LogInfo ( "文件位置 >:" . $filePath );
				}
				fclose ( $handle );
			}
			return true;
		} else {
			return false;
		}
	}


	static function enCodeFileContent($path){

		$file_content_base64 = '';
		if(!file_exists($path)){
			echo '文件没找到';
			return false;
		}

		$file_content = file_get_contents ( $path );
		//UTF8 去掉文本中的 bom头
		$BOM = chr(239).chr(187).chr(191);
		$file_content = str_replace($BOM,'',$file_content);
		$file_content_deflate = gzcompress ( $file_content );
		$file_content_base64 = base64_encode ( $file_content_deflate );
		return $file_content_base64;
	}

}


代码实现

  • 发起支付
 use App\Sdk\Unionpay\AcpService;
 use App\Sdk\Unionpay\SDKConfig;
 /**
 * @param 外部订单号
 * @param 支付金额
 */
 public static function unionPayOrderString($orderId,$payTotalPrice,$partner = false)
    {

        $payTotalPrice = $payTotalPrice * 100;
        $params = array(

            //以下信息非特殊情况不需要改动
            'version' => SDKConfig::getSDKConfig()->version,                 //版本号
            'encoding' => 'utf-8',				  //编码方式
            'txnType' => '01',				      //交易类型
            'txnSubType' => '01',				  //交易子类
            'bizType' => '000201',				  //业务类型
            'frontUrl' =>  SDKConfig::getSDKConfig()->frontUrl,  //前台通知地址
            'backUrl' => $partner ? SDKConfig::getSDKConfig()->partnerBackUrl : SDKConfig::getSDKConfig()->backUrl,	  //后台通知地址
            'signMethod' => SDKConfig::getSDKConfig()->signMethod,	              //签名方法
            'channelType' => '08',	              //渠道类型,07-PC,08-手机
            'accessType' => '0',		          //接入类型
            'currencyCode' => '156',	          //交易币种,境内商户固定156

            //TODO 以下信息需要填写
            'merId' => 1111111111,		//商户代码,请改自己的测试商户号,此处默认取demo演示页面传递的参数
            'orderId' => $orderId,	//商户订单号,8-32位数字字母,不能含“-”或“_”,此处默认取demo演示页面传递的参数,可以自行定制规则
            'txnTime' => date('YmdHis',time()),	//订单发送时间,格式为YYYYMMDDhhmmss,取北京时间,此处默认取demo演示页面传递的参数
            //todo 支付金额
            'txnAmt' => $payTotalPrice,	//交易金额,单位分,此处默认取demo演示页面传递的参数

            // 请求方保留域,
            // 透传字段,查询、通知、对账文件中均会原样出现,如有需要请启用并修改自己希望透传的数据。
            // 出现部分特殊字符时可能影响解析,请按下面建议的方式填写:
            // 1. 如果能确定内容不会出现&={}[]"'等符号时,可以直接填写数据,建议的方法如下。
            //    'reqReserved' =>'透传信息1|透传信息2|透传信息3',
            // 2. 内容可能出现&={}[]"'符号时:
            // 1) 如果需要对账文件里能显示,可将字符替换成全角&={}【】“‘字符(自己写代码,此处不演示);
            // 2) 如果对账文件没有显示要求,可做一下base64(如下)。
            //    注意控制数据长度,实际传输的数据长度不能超过1024位。
            //    查询、通知等接口解析时使用base64_decode解base64后再对数据做后续解析。
            //    'reqReserved' => base64_encode('任意格式的信息都可以'),

            //TODO 其他特殊用法请查看 pages/api_05_app/special_use_purchase.php
        );

        AcpService::sign ( $params ); // 签名
        $url = SDKConfig::getSDKConfig()->appTransUrl;

        $result_arr = AcpService::post ($params,$url);

        if(count($result_arr)<=0) { //没收到200应答的情况
            throw new \Exception('发起支付失败');
            //Order::saveLog('error:unionpay:order_id:'.$orderId.':user_id:'.$userId.':get order string failed,not receive response');
        }


        if (!AcpService::validate ($result_arr) ){
            //Order::saveLog('error:unionpay:order_id:'.$orderId.':user_id:'.$userId.':get order string failed,response validate failed');
            throw new \Exception('发起支付失败');
        }
        if ($result_arr["respCode"] == "00"){
            return ['tn'=>$result_arr["tn"]];
        } else {
            //其他应答码做以失败处理
            //Order::saveLog('error:unionpay:order_id:'.$orderId.':user_id:'.$userId.':get order string failed,'.$result_arr["respMsg"]);
            throw new \Exception('发起支付失败');
        }
    }
  • 支付回调
	public function unionPayNotify(Request $request)
    {
        if(PayService::unionPayValidate($request->all()))
        {
            $orderNo = $request->input('orderId');
            $tradeNo = $request->input('queryId');
            $totalPrice = $request->input('txnAmt')/100;//微信返回的是分 存的元
			//todo 自己的订单逻辑 更改订单状态等
          
            return $res ? 'success':'failed';
        }else{
            return 'failed';
        }
    }
    
    public static function unionPayValidate($input)
    {
        if(AcpService::validate($input) && $input['respCode'] == '00') {
            return true;
        }else{
            return  false;
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值