当前位置: 首页 - 编程技术 - 文章正文

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

xiaoqihv

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.doacpsdk.backTransUrl=https://gateway.95516.com/gateway/api/backTransReq.doacpsdk.singleQueryUrl=https://gateway.95516.com/gateway/api/queryTrans.doacpsdk.batchTransUrl=https://gateway.95516.com/gateway/api/batchTrans.doacpsdk.fileTransUrl=https://filedownload.95516.com/acpsdk.appTransUrl=https://gateway.95516.com/gateway/api/appTransReq.doacpsdk.cardTransUrl=https://gateway.95516.com/gateway/api/cardTransReq.do;以下缴费产品使用,其余产品用不到acpsdk.jfFrontTransUrl=https://gateway.95516.com/jiaofei/api/frontTransReq.doacpsdk.jfBackTransUrl=https://gateway.95516.com/jiaofei/api/backTransReq.doacpsdk.jfSingleQueryUrl=https://gateway.95516.com/jiaofei/api/queryTrans.doacpsdk.jfCardTransUrl=https://gateway.95516.com/jiaofei/api/cardTransReq.doacpsdk.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=falseacpsdk.ifValidateCNName=true; 是否验证https证书,测试环境请设置false,生产环境建议优先尝试true,不行再false。非true的值默认都当false处理。;acpsdk.ifValidateRemoteCert=falseacpsdk.ifValidateRemoteCert=true;后台通知地址,填写接收银联后台通知的地址,必须外网能访问acpsdk.backUrl=http://baidu.com/pay/unionpay/notifyacpsdk.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.pfxacpsdk.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.ceracpsdk.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.ceracpsdk.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.ceracpsdk.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

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

<?phpnamespace 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 ); // 不验证HOSTcurl_setopt ( $ch, CURLOPT_SSLVERSION, 1 ); // http://php.net/manual/en/function.curl-setopt.php页面搜CURL_SSLVERSION_TLSv1curl_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 ); // 不验证HOSTcurl_setopt ( $ch, CURLOPT_SSLVERSION, 1 ); // http://php.net/manual/en/function.curl-setopt.php页面搜CURL_SSLVERSION_TLSv1curl_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; } }
文章地址:https://wenmayi.cn/post/126.html