银联支付

银联支付

商户入驻

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);
  }

}

1、getAuthorization为除H5支付外的支付签名方法

2、getH5Authorization为H5支付签名算法

3、ChinaUMSRequest为支付请求方法

4、ChinaUMSH5Request为H5支付请求方法

5、ChinaUMSBusinessRequest为银联商户入驻请求方法

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。