银联支付
商户入驻
1. 填写商户基础信息
2. 提交信息后,跳转银联签约页面完成签约
3. 签约完成后,如果是对公账户,需要等待银联打款(交易附言可能会有交易码),在完成对公账户打款验证
4. 等待银联审核完成,下发商户号
支付
主从商户模式
-
支付宝
-
APP支付
-
通过APP支付接口下单,成功后得到支付要素(js串),通过在APP内集成银联提供的SDK,吊起APP支付
- 支付成功后回调下单时上送的回调地址,进行对应回调处理
-
-
生活窗支付
-
下单后,得到支付要素,通过支付要素、单号,在浏览器中跳转到自己的收银页面,调起支付宝收银台
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GBK"> <title>跳转支付中...</title> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0"> <script src="<?php echo env('WEB_SERVER');?>/web/js/jquery.min.js" type="text/javascript"></script> <script> var tradeNo = <?php echo json_encode($tradeNo);?>; var return_url = '<?php echo $return_url;?>'; // 调试时可以通过在页面定义一个元素,打印信息,使用alert方法不够优雅 function log(obj) { $("#result").append(obj).append(" ").append("<br />"); } $(document).ready(function(){ // 页面载入完成后即唤起收银台 // 此处${tradeNO}为模板语言语法,实际调用样例类似为tradePpay("2016072621001004200000000752") tradePay(tradeNo); // 点击payButton按钮后唤起收银台 $("#payButton").click(function() { tradePay(tradeNo); }); // 通过jsapi关闭当前窗口,仅供参考,更多jsapi请访问 // /aod/54/104510 $("#closeButton").click(function() { AlipayJSBridge.call('closeWebview'); }); }); // 由于js的载入是异步的,所以可以通过该方法,当AlipayJSBridgeReady事件发生后,再执行callback方法 function ready(callback) { if (window.AlipayJSBridge) { callback && callback(); } else { document.addEventListener('AlipayJSBridgeReady', callback, false); } } function tradePay(tradeNO) { ready(function(){ // 通过传入交易号唤起快捷调用方式(注意tradeNO大小写严格) AlipayJSBridge.call("tradePay", { tradeNO: tradeNO }, function (data) { if ("9000" == data.resultCode) { log("支付成功"); window.location.href=return_url; } }); }); } </script> </head> <body> <p id="result">result: </p> </body> </html>
- 支付成功后回调下单时上送的回调地址,进行对应回调处理
-
-
H5支付
-
下单后,得到一个url,在浏览器中跳转后,会调起手机支付宝APP
- 支付成功后回调下单时上送的回调地址,进行对应回调处理
-
-
-
微信
-
APP支付
-
通过APP支付接口下单,成功后得到支付要素(js串),通过在APP内集成银联提供的SDK,吊起APP支付
- 支付成功后回调下单时上送的回调地址,进行对应回调处理
-
-
公众号支付
- 下单后,得到支付要素,通过支付要素、单号,在微信客户端内跳转到自己的收银页面,调起微信收银台
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GBK"> <title>跳转支付中...</title> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0"> <script src="<?php echo env('WEB_SERVER');?>/web/js/jquery.min.js" type="text/javascript"></script> <script> var json = <?php echo json_encode($data);?>; var return_url = '<?php echo $return_url;?>'; $(function(){ onBridgeReady(json); }); function onBridgeReady(content){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', content, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { window.location.href=return_url; } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); } if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', function(){ onBridgeReady(json) }, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', function(){ onBridgeReady(json) }); document.attachEvent('onWeixinJSBridgeReady', function(){ onBridgeReady(json) }); } }else{ onBridgeReady(json); } </script> </head> <body> </body> </html> ``` - 支付成功后回调下单时上送的回调地址,进行对应回调处理
小程序支付
-
-
银联
- B扫C支付(条码支付)
- C扫B支付(主动扫码支付)
独立商户模式
-
支付宝
- APP支付
- 生活窗支付
- H5支付
-
微信
- APP支付
- 公众号支付
- 小程序支付
-
银联
- B扫C支付(条码支付)
- C扫B支付(主动扫码支付)
结算
自动清分
- 在商户进件时,设置好清分比例,每笔订单支付成功后,银联按设置比例自动清分
指令清分
- 每日通过上送清分指令文件到银联SFTP服务器,告诉银联每笔订单如何进行分账
对账
每天14点以后,银联将前一日的订单对账文件推送至服务器,用户通过SFTP方式,到银联服务器下载每日的对账文件
部分代码
<?php
/**
* Created by PhpStorm.
* User: isesame
* Date: 2019/11/05
* Time: 19:08
* 银联支付服务
*/
namespace App\Http\Services\PayClass;
use App\Http\Services\BaseService;
use App\Http\Services\Params\ChinaUMSPay\Exceptions\CurlException;
use App\Http\Services\Params\Params;
class ChinaUMSPayService extends BaseService
{
public $notifyUrl; //支付回调地址
public function __construct()
{
$this->notifyUrl = env('WEB_SERVER').'/chinaums/pay/getNotify';
}
public function getAuthorization($content)
{
$arr['appId'] = config('chinaumspay.AppId');
$arr['timestamp'] = date('YmdHis');//yyyyMMddHHmmss
$arr['nonce'] = md5(uniqid(microtime(true),true));
$arr['content'] = json_encode($content);
$strA=bin2hex(hash('sha256', $arr['content'], true));
$appKey = config('chinaumspay.AppKey');
$signature = base64_encode(hash_hmac('sha256',$arr['appId'].$arr['timestamp'].$arr['nonce'].$strA, $appKey, true));
$Authorization="OPEN-BODY-SIG AppId=\"".$arr['appId']."\", Timestamp=\"".$arr['timestamp']."\", Nonce=\"".$arr['nonce']."\", Signature=\"".$signature."\"";
return $Authorization;
}
public function getH5Authorization($content)
{
$arr['appId'] = config('chinaumspay.AppId');
$arr['authorization'] = 'OPEN-FORM-PARAM';
$arr['timestamp'] = date('YmdHis');//yyyyMMddHHmmss
$arr['nonce'] = md5(uniqid(microtime(true),true));
$arr['content'] = json_encode($content);
$strA=bin2hex(hash('sha256', $arr['content'], true));
$appKey = config('chinaumspay.AppKey');
$signature = base64_encode(hash_hmac('sha256',$arr['appId'].$arr['timestamp'].$arr['nonce'].$strA, $appKey, true));
$arr['signature'] = $signature;
return $arr;
}
/**
* @param Params $params
* @return mixed
* @throws \Exception
*/
public function ChinaUMSRequest(Params $params)
{
$param = array_filter($params->toArray(), function ($item) {
return $item !== "" && !is_null($item);
}); //object to array
$request_url = array_pull($param,'request_url');
$Authorization = $this->getAuthorization($param);
$header[]='AUTHORIZATION:'.$Authorization;
app('myLog')->lumenLog('银联转发接口请求参数,trade:' . json_encode($param), 'chinaums_trade');
// 请求银联
try {
$result = $this->postUmsCurl($request_url, $param, $header);
} catch (\Exception $e) {
throw new \Exception('请求转发银联接口失败请稍后重试!', 602);
}
app('myLog')->lumenLog('银联接口请求成功,return:' . json_encode($result), 'chinaums_trade_success');
return json_decode(json_encode($result),true);
}
/**
* @param Params $params
* @return mixed
* @throws \Exception
*/
public function ChinaUMSH5Request(Params $params)
{
$param = array_filter($params->toArray(), function ($item) {
return $item !== "" && !is_null($item);
}); //object to array
$request_url = array_pull($param,'request_url');
app('myLog')->lumenLog('银联转发接口请求参数,trade:' . json_encode($param), 'chinaums_trade');
$param = $this->getH5Authorization($param);
$request_url = $request_url .'?'. http_build_query($param);
return $request_url;
}
/**
* 请求商户接口
* @param Params $params
* @return mixed
* @throws \Exception
*/
public function ChinaUMSBusinessRequest(Params $params)
{
if (env('APP_ENV') == 'dev') {
$key = 'udik876ehjde32dU61edsxsf';
$business_accesser_id = '100004';
} else {
$key = config('chinaumspay.business_key');
$business_accesser_id = config('chinaumspay.business_accesser_id');
}
$param = array_filter($params->toArray(), function ($item) {
return $item !== "" && !is_null($item);
}); //object to array
$request_url = array_pull($param,'request_url');
$json_str = json_encode($param);
$sign_data = hash('sha256', $json_str);
$json_data = self::encrypt($json_str, $key);
$arr = [
'json_data' => $json_data,
'sign_data' => $sign_data,
'accesser_id' => $business_accesser_id
];
app('myLog')->lumenLog('银联转发接口请求参数,trade:' . json_encode($param), 'chinaums_trade');
try {
$result = postCurl($request_url, $arr);
} catch (\Exception $e) {
throw new \Exception('请求转发银联接口失败请稍后重试!', 602);
}
app('myLog')->lumenLog('银联接口请求成功,return:' . json_encode($result), 'chinaums_trade_success');
return json_decode($result,true);
}
/**
* 请求UMS
* @param $url
* @param array $body
* @param array $header
* @param string $method
* @return bool|mixed
* @throws CurlException
*/
public function postUmsCurl($url, $body = array(), $header = array(), $method = 'POST')
{
$curl = curl_init(); //初始化
$body = json_encode($body);
curl_setopt($curl, CURLOPT_URL,$url); //设置url
array_push($header, 'Accept:application/json');
array_push($header, 'Content-Type:application/json;charset=utf-8');
array_push($header, 'Content-Length:'.strlen($body));
curl_setopt($curl, CURLOPT_HTTPHEADER,$header);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); // 从证书中检查SSL加密算法是否存在 设为0表示不检查证书 设为1表示检查证书中是否有CN(common name)字段 设为2表示在1的基础上校验当前的域名是否与CN匹配
// curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
curl_setopt($curl, CURLOPT_POSTFIELDS,$body);
curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
$return = curl_exec($curl);
$err = curl_error($curl);
$errno = curl_errno($curl);
curl_close($curl);
if ($errno) {
app('log')->error(sprintf(
'postUmsCurl %s %s error: %s[%s] body: %s%s',
strtoupper($method),
$url,
$err,
$errno,
json_encode(
$body,
JSON_UNESCAPED_SLASHES
| JSON_UNESCAPED_UNICODE
| JSON_PRETTY_PRINT
| JSON_FORCE_OBJECT
),
PHP_EOL
));
return false;
} else {
app('log')->error(sprintf(
'postUmsCurl %s %s body: %s%s response: %s%s',
strtoupper($method),
$url,
json_encode(
$body,
JSON_UNESCAPED_SLASHES
| JSON_UNESCAPED_UNICODE
| JSON_PRETTY_PRINT
| JSON_FORCE_OBJECT
),
PHP_EOL,
$return,
PHP_EOL
));
}
if($return === false){
throw new CurlException('ChinaUms请求失败', 501);
}
$return=json_decode($return,true);
return $return;
}
// 加密算法
public static function encrypt($str, $key)
{
$desJsonStr = self::pkcs5Pad($str, 8);
if (strlen($desJsonStr) % 8) {
$desJsonStr = str_pad($desJsonStr, strlen($desJsonStr) + 8 - strlen($desJsonStr) % 8, "\0");
}
$method = 'DES-EDE3';
return bin2hex(openssl_encrypt($desJsonStr, $method, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING));
}
public static function pkcs5Pad($text, $byteLen)
{
$pad = $byteLen - (strlen($text) % $byteLen);
return $text . str_repeat(chr($pad), $pad);
}
}