tp3.2 企业转账到零钱

  • 不同于企业付款到零钱,转账功能更丰富化,支持批量付款,付款可以手机上再确定下
  • php5.6 7.2均可
  • action
            $res = D('WxTransToPerson')->tixian($trade_no,$openid,(int)$flag['withdraw_money']);
            
            if($res['num'] <= 0){
                $maindb->rollback();
                $this->json->err('微信打款出错-' . $res['desc']);    
            }

  • WxTransToPersonModel
<?php

namespace Common\Model;
use Think\Model;

use Vendor\Func\Http;

/**
 * 小程序之企业转账到零钱
 */
class WxTransToPersonModel extends Model
{

    //其中 wx_ciphertext 方法是获取平台证书和平台证书序列号使用,获取平台证书需要安装php7.1以上
    /***
     * out_trade_no 订单号
     * money 支付金额,单位:分
     */
    public function tixian($out_trade_no,$openid,$money){
        setlog([$out_trade_no,$openid,$money],[],'','trans_start.log');

        if(!$out_trade_no || !$openid){
            $result_arr = [
                'num'     =>      -999,
                'desc'   =>      '缺少参数',
            ];

            return $result_arr;
        }

        if($money < 30){
            $result_arr = [
                'num'     =>      -99,
                'desc'   =>      '最低0.3元',
            ];

            return $result_arr;
        }

        if(10000 < $money){
            $result_arr = [
                'num'     =>      -939,
                'desc'   =>      '最多一次100元',
            ];

            return $result_arr;
        }

        //查询用户授权记录
        Vendor('wxpay3.WxPayJsApiPay');
        $input = new \JsApiPay();
        $url='https://api.mch.weixin.qq.com/v3/transfer/batches';

        $body['appid'] = C('WX_APP_ID');
        $body['out_batch_no'] = 'Y' . $out_trade_no;// 批次单号由单个单号演变而来
        $body['batch_name'] = '用户提现';
        $body['batch_remark'] = '提现到账';
        $body['total_amount'] = $money;
        $body['total_num'] = 1;

        //订单风险金
        $detail = [
            'out_detail_no'     =>  $out_trade_no,
            'transfer_amount'   =>  $money,
            'transfer_remark'   =>  '用户提现到账',
            'openid'            =>  $openid,
            //'user_name'       =>  $input->getEncrypt($user_name),  //noted zb敏感信息加密
        ];

        $body['transfer_detail_list'][] = $detail;
        $responseData = $input->Unite($url,'POST',json_encode($body));
        $res = json_decode($responseData,true);
        setlog([$out_trade_no,$openid,$money],$res,'','trans.log');

        if($res['httpCode'] != 200){
            $result_arr = [
                'num'     =>      -299,
                'desc'   =>      '发生错误-' . $res['message'],
            ];
            return $result_arr;
        }

        $result_arr = [
            'num'           =>      1,
            'desc'          =>      '处理成功-',
            'batch_id'      =>      $res['batch_id'],
            'out_batch_no'  =>      $res['out_batch_no'],
        ];

        return $result_arr;
    }
    /*
    *array(3) {
        ["code"] => string(10) "NOT_ENOUGH"
        ["message"] => string(30) "账户余额不足,请充值"
        ["httpCode"] => int(403)
    }
    *
    array(4) {
        ["batch_id"] => string(40) "1030000005401361716282022060500845809117"// 微信明细单号
        ["create_time"] => string(25) "2022-06-05T12:54:50+08:00"
        ["out_batch_no"] => string(15) "Y20220605125450" // 微信批次单号
        ["httpCode"] => int(200)
    }
    https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
    */


}

  • WxPayJsApiPay.php
<?php
class JsApiPay
{
    public $data = null;
    public $serial_no = '****************************************';  //商户证书序列号 和 平台证书序列号 是两回事---由函数根据参数抓取
    public $mchid = '*****';   //商户号
    public $mch_key_v3 = '*******';   //v3秘钥

    public function Unite($url,$http_method='GET',$body){
        $Authorization = $this->getSign($url,$http_method,$body);
        return $this->curl_request($url, $body, $http_method, $Authorization);
    }

    public function JsapiSign($prepay_id,$appid,$timestamp,$nonce){
        $mch_private_key = file_get_contents('./apiclient_key.pem'); //获取密钥文件内容
        $message = $appid."\n".
        $timestamp."\n".
        $nonce."\n".
        $prepay_id."\n";
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        return $sign;
    }
    
    public function getSign($url,$http_method='GET',$body){
        $serial_no=$this->serial_no;
        $merchant_id=$this->mchid;
        $timestamp=time();
        $nonce=$this->getNonceStr();
        $mch_private_key = file_get_contents('./apiclient_key.pem'); //获取密钥文件内容
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $message = $http_method."\n".
        $canonical_url."\n".
        $timestamp."\n".
        $nonce."\n".
        $body."\n";
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        $schema = 'WECHATPAY2-SHA256-RSA2048';
        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
        $merchant_id, $nonce, $timestamp, $serial_no, $sign);
        return $schema.' '.$token;
    }

    /*验证签名*/
    public function verifysign($serial_no,$TIMESTAMP,$NONCE,$BODY,$SIGNATURE){
        $SIGNATURE = base64_decode($SIGNATURE);
        $message = $TIMESTAMP."\n".
        $NONCE."\n".
        $BODY."\n";

        $mch_private_key = $this->wx_ciphertext($serial_no);
        $pubkeyid = openssl_pkey_get_public($mch_private_key);
        $ok = openssl_verify($message, $SIGNATURE, $pubkeyid,OPENSSL_ALGO_SHA256);
        openssl_free_key($pubkeyid);
        if($ok == 1){
            return true;
        }else{
            return false;
        }
    }

    /*获取微信支付平台证书
      serial_no:平台证书序列号
    */
    public function wx_ciphertext(){
        $url = 'https://api.mch.weixin.qq.com/v3/certificates';
        $certificates = $this->Unite($url,'GET','');
        $certificates = json_decode($certificates,true);
    
        // foreach ($certificates['data'] as $key => $value) {
        //     if($value['serial_no']==$this->serial_no){
        //         $associatedData=$value['encrypt_certificate']['associated_data'];
        //         $nonceStr=$value['encrypt_certificate']['nonce'];
        //         $ciphertext=$value['encrypt_certificate']['ciphertext'];
        //         break;
        //     }
        // }

        $data = $certificates['data'][0]['encrypt_certificate'];
        $associatedData = $data['associated_data'];
        $nonceStr = $data['nonce'];
        $ciphertext = $data['ciphertext'];
        $AesUtil = new \AesUtil($this->mch_key_v3);
        return $AesUtil->decryptToString($associatedData, $nonceStr, $ciphertext);  //获取平台证书。复制到文本里改为pem后缀
    }

    /**
     * 产生随机字符串,不长于32位
     * @param int $length
     * @return 产生的随机字符串
     */
    public static function getNonceStr($length = 32) 
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";  
        $str = "";
        for ($i = 0; $i < $length; $i++ )  {  
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);  
        }
        return $str;
    }


    /**获取平台证书序列号**/
    public static function getCertificate($filepath) {
        $cert = openssl_x509_read(file_get_contents($filepath));
        $cert_data = openssl_x509_parse($cert);
        dump($cert_data);
        array_walk($cert_data, 'print_element');

        // Free the resource
        openssl_x509_free($cert);
        dump($cert);
        // return openssl_x509_read(file_get_contents($filepath));  
    }

    /**
     * @Description: curl请求
     * @Author: Yang
     * @param $url
     * @param null $data
     * @param string $method
     * @param array $header
     * @param bool $https
     * @param int $timeout
     * @return mixed
     */
    function curl_request($url,$data=null,$method='get',$Authorization,$https=true,$timeout = 5){
        $header = array(
            'Content-Type: application/json',
            'Accept: application/json',
            'Wechatpay-Serial:******',//平台序列号
            'Authorization: '.$Authorization
        );
        
        $user_agent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36";

        $method = strtoupper($method);
        $ch = curl_init();//初始化
        curl_setopt($ch, CURLOPT_URL, $url);//访问的URL
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);//只获取页面内容,但不输出
        if($https){
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);//https请求 不验证证书
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//https请求 不验证HOST
        }

        if ($method != "GET") {
            if($method == 'POST'){
                curl_setopt($ch, CURLOPT_POST, true);//请求方式为post请求
            }

            if ($method == 'PUT' || strtoupper($method) == 'DELETE') {
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); //设置请求方式
            }
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//请求数据
        }

        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header); //模拟的header头
        curl_setopt($ch, CURLOPT_USERAGENT,$user_agent);
        //curl_setopt($ch, CURLOPT_HEADER, false);//设置不需要头信息
        $result = curl_exec($ch);//执行请求
        $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
        if($result){
//            file_put_contents('./text.txt', '请求返回:'.$result.PHP_EOL, FILE_APPEND);
            $result=json_decode($result,true);
            $result['httpCode']=$httpCode;
            $result = json_encode($result,JSON_UNESCAPED_UNICODE);
        } else {
            $result = json_encode(['httpCode'=>$httpCode]);
        }

        curl_close($ch);//关闭curl,释放资源
        return $result;
    }

    /**
     * 敏感信息加密算法
     * @param $str
     * @return string
     */
    public function getEncrypt($str)
    {
        //$str是待加密字符串
        $public_key = file_get_contents('../vendor/wxpay3/cacert/pingtai_cert.pem'); //此处证书文件为平台证书
        $encrypted  = '';
        openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING);//这里 类型要设置为OPENSSL_PKCS1_OAEP_PADDING
        //base64编码
        $sign = base64_encode($encrypted);
        return $sign;
    }
}


class AesUtil{
    /**
    * AES key
    * @var string
    */
    private $aesKey;
    const KEY_LENGTH_BYTE = 32;
    const AUTH_TAG_LENGTH_BYTE = 16;

    /**
    * Constructor
    */
    public function __construct($aesKey)
    {
        if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
            throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
        }
        $this->aesKey = $aesKey;
    }

    /**
    * Decrypt AEAD_AES_256_GCM ciphertext
    *
    * @param string    $associatedData     AES GCM additional authentication data
    * @param string    $nonceStr           AES GCM nonce
    * @param string    $ciphertext         AES GCM cipher text
    *
    * @return string|bool      Decrypted string on success or FALSE on failure
    */
      public function decryptToString($associatedData, $nonceStr, $ciphertext)
      {
          $ciphertext = \base64_decode($ciphertext);
          if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
              return false;
          }

          // ext-sodium (default installed on >= PHP 7.2)
          if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
              return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
          }

          // ext-libsodium (need install libsodium-php 1.x via pecl)
          if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
              return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
          }

          // openssl (PHP >= 7.1 support AEAD)
          if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
              $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
              $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

              return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,$authTag, $associatedData);
          }

          throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
      }
}

posted @ 2022-06-08 21:35  盘思动  阅读(128)  评论(0编辑  收藏  举报