点击登录
  • 欢迎访问无限星辰技术博客,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站 QQ群
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏无限星辰吧
  • 好集导航开张了,传送门:好集导航

微信支付V3api demo

PHP学习笔记 crx349 926次浏览 0个评论 扫描二维码
class Formatter
{
    /**
     * Generate a random BASE62 string aka `nonce`, similar as `random_bytes`.
     *
     * @param int $size - Nonce string length, default is 32.
     *
     * @return string - base62 random string.
     */
    public static function nonce(int $size = 32): string
    {
        if ($size < 1) {
            throw new Exception('Size must be a positive integer.');
        }
        return implode('', array_map(static function(string $c): string {
            return '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[ord($c) % 62];
        }, str_split(random_bytes($size))));
    }

    /**
     * Retrieve the current `Unix` timestamp.
     *
     * @return int - Epoch timestamp.
     */
    public static function timestamp(): int
    {
        return time();
    }

    /**
     * Formatting for the heading `Authorization` value.
     *
     * @param string $mchid - The merchant ID.
     * @param string $nonce - The Nonce string.
     * @param string $signature - The base64-encoded `Rsa::sign` ciphertext.
     * @param string $timestamp - The `Unix` timestamp.
     * @param string $serial - The serial number of the merchant public certification.
     *
     * @return string - The APIv3 Authorization `header` value
     */
    public static function authorization(string $mchid, string $nonce, string $signature, string $timestamp, string $serial): string
    {
        return sprintf(
            'WECHATPAY2-SHA256-RSA2048 mchid="%s",serial_no="%s",timestamp="%s",nonce_str="%s",signature="%s"',
            $mchid, $serial, $timestamp, $nonce, $signature
        );
    }

    /**
     * Formatting this `HTTP::request` for `Rsa::sign` input.
     *
     * @param string $method - The HTTP verb, must be the uppercase sting.
     * @param string $uri - Combined string with `URL::pathname` and `URL::search`.
     * @param string $timestamp - The `Unix` timestamp, should be the one used in `authorization`.
     * @param string $nonce - The `Nonce` string, should be the one used in `authorization`.
     * @param string $body - The playload string, HTTP `GET` should be an empty string.
     *
     * @return string - The content for `Rsa::sign`
     */
    public static function request(string $method, string $uri, string $timestamp, string $nonce, string $body = ''): string
    {
        return static::joinedByLineFeed($method, $uri, $timestamp, $nonce, $body);
    }

    /**
     * Formatting this `HTTP::response` for `Rsa::verify` input.
     *
     * @param string $timestamp - The `Unix` timestamp, should be the one from `response::headers[Wechatpay-Timestamp]`.
     * @param string $nonce - The `Nonce` string, should be the one from `response::headers[Wechatpay-Nonce]`.
     * @param string $body - The response payload string, HTTP status(`201`, `204`) should be an empty string.
     *
     * @return string - The content for `Rsa::verify`
     */
    public static function response(string $timestamp, string $nonce, string $body = ''): string
    {
        return static::joinedByLineFeed($timestamp, $nonce, $body);
    }

    /**
     * Joined this inputs by for `Line Feed`(LF) char.
     *
     * @param string|float|int|bool $pieces - The scalar variable(s).
     *
     * @return string - The joined string.
     */
    public static function joinedByLineFeed(...$pieces): string
    {
        return implode("\n", array_merge($pieces, ['']));
    }

    /**
     * Sort an array by key with `SORT_STRING` flag.
     *
     * @param array $thing - The input array.
     *
     * @return array - The sorted array.
     */
    public static function ksort(array $thing = []): array
    {
        ksort($thing, SORT_STRING);

        return $thing;
    }

    /**
     * Like `queryString` does but without the `sign` and `empty value` entities.
     *
     * @param array $thing - The input array.
     *
     * @return string - The `key=value` pair string whose joined by `&` char.
     */
    public static function queryStringLike(array $thing = []): string
    {
        $data = [];

        foreach ($thing as $key => $value) {
            if ($key === 'sign' || is_null($value) || $value === '') {
                continue;
            }
            $data[] = implode('=', [$key, $value]);
        }

        return implode('&', $data);
    }
    public static function curlPostWithWx($params,$authorization,$url)
    {
        $paramsString = json_encode($params);
        // 初始化curl
        $ch = curl_init();
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // post数据
        curl_setopt($ch, CURLOPT_POST, 1);
        // post的变量
        curl_setopt($ch, CURLOPT_POSTFIELDS, $paramsString);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($paramsString),
                'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
                'Accept: application/json',
                'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
            )
        );
        // 运行curl,结果以jason形式返回
        $res = curl_exec($ch);
        curl_close($ch);
        // 取出数据
        $data = json_decode($res, true);
        return $data;
    }

    /**
     * get请求
     * @param $url
     * @param $authorization
     * @return mixed
     */
    public static function curlGetWithWx($url, $authorization,$params=[])
    {
        $paramsString = json_encode($params);
        // 初始化curl
        $ch = curl_init();
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($paramsString),
                'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
                'Accept: application/json',
                'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
            )
        );
        // 运行curl,结果以jason形式返回
        $res = curl_exec($ch);
        curl_close($ch);
        // 取出数据
        $data = json_decode($res, true);
        //返回
        return $data;
    }
    public static function getAuthorization($url, $body,$mch_id, $priKey, $serial, $http_method = "POST")
    {
        // Authorization:  
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $timestamp = static::timestamp();
        $nonce = static::nonce();
        $message = static::request($http_method, $canonical_url, $timestamp, $nonce, json_encode($body));
        openssl_sign($message, $raw_sign, $priKey, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        return static::authorization($mch_id, $nonce, $sign, $timestamp, $serial);
    }

}
$mch_id = '1630****';//商户号
$priKey = '';//私钥文本
$serial = '';//证书序号
$queryUrl = 'https://api.mch.weixin.qq.com/v3/bill/tradebill';//账单接口
$param = ['bill_date'=>'2022-09-01','sub_mchid'=>$mch_id,'bill_type'=>'ALL','tar_type'=>'GZIP'];
$authorization = Formatter::getAuthorization($queryUrl, $param, $mch_id, $priKey, $serial);//生成签名
$result = Formatter::curlGetWithWx($param, $authorization, $queryUrl);//接口请求
var_dump($result);            
//通知
header('Content-type:text/html; Charset=utf-8');
/** 请填写以下配置信息 **/
$publicKeyPath = getcwd() . '/cert/public_key.pem';    //微信支付公钥证书文件路径,可以到 https://www.dedemao.com/wx/wx_publickey_download.php 生成
$apiKey = 'xxxxx';   //https://pay.weixin.qq.com 帐户中心-安全中心-API安全-APIv3密钥-设置密钥
/** 配置结束 **/

$wxPay = new WxpayService($apiKey, $publicKeyPath);
$result = $wxPay->validate();
if($result===false){
    //验证签名失败
    exit('sign error');
}
$result = $wxPay->notify();
if ($result === false) {
    exit('pay error');
}
if ($result['trade_state'] == 'SUCCESS') {
    //支付成功,完成你的逻辑
    //例如连接数据库,获取付款金额$result['amount']['total'],获取订单号$result['out_trade_no']修改数据库中的订单状态等;
    //订单总金额,单位为分:$result['amount']['total']
    //用户支付金额,单位为分:$result['amount']['payer_total']
    //商户订单号:$result['out_trade_no']
    //微信支付订单号:$result['transaction_id']
    //银行类型:$result['bank_type']
    //支付完成时间:$result['success_time'] 格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
    //用户标识:$result['payer']['openid']
    //交易状态:$result['trade_state']
    //具体详细请看微信文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_11.shtml
    echo 'success';
}


class WxpayService
{
    protected $apiKey;
    protected $publicKeyPath;
    protected $publicKey;

    public function __construct($apikey, $publicKeyPath)
    {
        $this->apiKey = $apikey;
        $this->publicKeyPath = $publicKeyPath;
    }

    public function getHeader($key = '')
    {
        $headers = getallheaders();
        if ($key) {
            return $headers[$key];
        }
        return $headers;
    }

    public function validate()
    {
        $serialNo = $this->getHeader('Wechatpay-Serial');
        $sign = $this->getHeader('Wechatpay-Signature');
        $timestamp = $this->getHeader('Wechatpay-Timestamp');
        $nonce = $this->getHeader('Wechatpay-Nonce');
        if (!isset($serialNo, $sign, $timestamp, $nonce)) {
            return false;
        }
//        if (!$this->checkTimestamp($timestamp)) {
//            return false;
//        }
        $body = file_get_contents('php://input');
        $message = "$timestamp\n$nonce\n$body\n";

        $certificate = openssl_x509_read(file_get_contents($this->publicKeyPath));
        $_serialNo = $this->parseSerialNo($certificate);
        if ($serialNo !== $_serialNo) return false;
        $this->publicKey = openssl_get_publickey($certificate);
        return $this->verify($message, $sign);
    }

    private function verify($message, $signature)
    {
        if (!$this->publicKey) {
            return false;
        }
        if (!in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
            exit("当前PHP环境不支持SHA256withRSA");
        }
        $signature = base64_decode($signature);
        return (bool)openssl_verify($message, $signature, $this->publicKey, 'sha256WithRSAEncryption');
    }

    private function parseSerialNo($certificate)
    {
        $info = openssl_x509_parse($certificate);
        if (!isset($info['serialNumber']) && !isset($info['serialNumberHex'])) {
            exit('证书格式错误');
        }

        $serialNo = '';
        if (isset($info['serialNumberHex'])) {
            $serialNo = $info['serialNumberHex'];
        } else {
            if (strtolower(substr($info['serialNumber'], 0, 2)) == '0x') { // HEX format
                $serialNo = substr($info['serialNumber'], 2);
            } else { // DEC format
                $value = $info['serialNumber'];
                $hexvalues = ['0', '1', '2', '3', '4', '5', '6', '7',
                    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
                while ($value != '0') {
                    $serialNo = $hexvalues[bcmod($value, '16')] . $serialNo;
                    $value = bcdiv($value, '16', 0);
                }
            }
        }

        return strtoupper($serialNo);
    }

    protected function checkTimestamp($timestamp)
    {
        return abs((int)$timestamp - time()) <= 120;
    }

    public function notify()
    {
        $postStr = file_get_contents('php://input');
        $postData = json_decode($postStr, true);
        if ($postData['resource']) {
            $data = $this->decryptToString($postData['resource']['associated_data'], $postData['resource']['nonce'], $postData['resource']['ciphertext']);
            $data = json_decode($data, true);
            return is_array($data) ? $data : false;
        }
        return false;
    }

    public function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = base64_decode($ciphertext);
        if (strlen($ciphertext) <= 16) {
            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->apiKey);
        }

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

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

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

        exit('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }
}

无限星辰 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明微信支付V3api demo!
喜欢 (0)
[]
分享 (0)

您必须 登录 才能发表评论!