21 PHP微信合单支付Demo

warning: 这篇文章距离上次修改已过1210天,其中的内容可能已经有所变动。
阿里云2核2G3M不限新老用户99元(续费同价)
华为新用户2核1G2M1年29元(首年特惠/年抛)
服务器9.9元/年(10个固定IPV6可当挂机宝)
免费GPT3.5,也有收费的GPT-4
免费CDN带WAF防火墙不限流量


kxn27cxr.png

  1. 获取API v3证书

    /**
     * 获取证书
     * @return mixed
     */public function getCert(){
     $url = 'https://api.mch.weixin.qq.com/v3/certificates';
    
     $timestamp = time();
     $nonce = $this->nonce_str();
     $body = '';
     $sign = $this->sign($url, 'GET', $timestamp, $nonce, $body, $this->getPrivateKey($this->private_key), $this->mch_id,        $this->serial_no);
    
     $header = [        'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign,        'Accept:application/json',        'User-Agent:' . $this->mch_id,
     ];
    
     $result = $this->curl($url, '', $header, 'GET');
     $result = json_decode($result, true);    return $result['data']['0']['serial_no'];
    }
  2. 支付请求

    /**
     * @return bool|mixed
     */public function pay(){
     $url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/app';
    
     $requestData = [        'combine_appid' => $this->app_id,        'combine_mchid' => $this->mch_id,        'combine_out_trade_no' => 'app_pay_' . time(),        'scene_info' => [            'device_id' => 'pay_device_id',            'payer_client_ip' => '127.0.0.1',
         ],        'time_start' => date('c', time()),        'time_expire' => date('c', time() + 7200),        'notify_url' => 'https://wangsheng.cc/notify.php',        'sub_orders' => [
             [                'mchid' => $this->mch_id,                'attach' => 'notify with attach',                'amount' => [                    'total_amount' => 100,                    'currency' => 'CNY',
                 ],                'out_trade_no' => 'sub_order_' . time(),                'sub_mchid' => $this->sub_mch_id, // 二级商户号 需要进件系统生成
                 'profit_sharing' => true, // 分账
                 'description' => '描述',
             ],
         ],
     ];
    
     $header = $this->getCurlHeader($url, json_encode($requestData), 'POST');
    
     $result = $this->curl($url, json_encode($requestData), $header, 'POST');    return json_decode($result, true);
    }
  3. 支付查询

    /**
     * @return mixed
     */public function payQuery(){
     $url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/';
     $url = $url . ''; // 支付单号
    
     $method = 'GET';
     $data = '';
     $header = $this->getCurlHeader($url, $data, $method);
    
     $result = $this->curl($url, $data, $header, $method);    return json_decode($result, true);
    }
  4. 退款

    /**
     * @return mixed
     */public function refund(){
     $requestData = [        'sp_appid' => $this->app_id,        'sub_mchid' => $this->sub_mch_id,        'transaction_id' => '',// 支付三方流水
         'out_refund_no' => '',// 退款单号
    
         'notify_url' => 'http://www.ttglad.com/notify_refund.php',        'amount' => [            'refund' => 100,            'total' => 100,            'currency' => 'CNY',
         ]
     ];
    
     $header = $this->getCurlHeader($this->refundUrl, json_encode($requestData), 'POST');
     $result = $this->curl($this->refundUrl, json_encode($requestData), $header, 'POST');    return json_decode($result, true);
    }
  5. 退款查询

    /**
     * @return mixed
     */public function refundQuery(){
     $url = 'https://api.mch.weixin.qq.com/v3/ecommerce/refunds/id/' . '' . '?sub_mchid=' . $this->sub_mch_id;
    
     $method = 'GET';
     $data = '';
     $header = $this->getCurlHeader($url, $data, $method);
    
     $result = $this->curl($url, $data, $header, $method);    return json_decode($result, true);
    }
  6. 支付通知

    public function notify(){
     $header = $this->getHeaders();
     $body = $GLOBALS['HTTP_RAW_POST_DATA'];    if (empty($header) || empty($body)) {        throw new Exception('通知参数为空', 2001);
     }
    
     $timestamp = $header['WECHATPAY-TIMESTAMP'];
     $nonce = $header['WECHATPAY-NONCE'];
     $signature = $header['WECHATPAY-SIGNATURE'];
     $serialNo = $header['WECHATPAY-SERIAL'];    if (empty($timestamp) || empty($nonce) || empty($signature) || empty($serialNo)) {        throw new Exception('通知头参数为空', 2002);
     }
    
     $cert = $this->getCertBySerialNo($serialNo);
    
     $message = "$timestamp\n$nonce\n$body\n";    //校验签名
     if (!$this->verify($message, $signature, $cert['plainCertificate'])) {        throw new Exception('验签失败', 2005);
     }
    
     $decodeBody = json_decode($body, true);    if (empty($decodeBody) || !isset($decodeBody['resource'])) {        throw new Exception('通知参数内容为空', 2003);
     }
     $decodeBodyResource = $decodeBody['resource'];
     $decodeData = $this->decryptToString($decodeBodyResource['associated_data'], $decodeBodyResource['nonce'],
         $decodeBodyResource['ciphertext'], '');
    
     $decodeData = json_decode($decodeData, true);    if (empty($decodeData)) {        throw new Exception('通知参数解密发生错误', 2004);
     }    // todo 业务逻辑}
  7. 其他方法

    /**
     * 初始化参数
     */public function __construct(){    parent::__construct();    // 微信支付 商户号
     $this->mch_id = '';    // 二级商户号,需要走进件系统生成
     $this->sub_mch_id = '';    // 微信支付 商户号绑定的appid
     $this->app_id = '';    // 商户私钥
     $this->private_key = '';    // 商户证书序列号
     // 如何查看证书序列号:https://wechatpay-api.gitbook.io/wechatpay-api-v3/chang-jian-wen-ti/zheng-shu-xiang-guan#ru-he-cha-kan-zheng-shu-xu-lie-hao
     $this->serial_no = '';    // apiv3秘钥:https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/api-v3-mi-yao
     $this->mch_key = '';
    }/**
     * @param $serialNo
     * @return mixed
     */private function getCertBySerialNo($serialNo){
     $url = 'https://api.mch.weixin.qq.com/v3/certificates';
    
     $timestamp = time();
     $nonce = $this->nonce_str();
     $body = '';
     $sign = $this->sign($url, 'GET', $timestamp, $nonce, $body, $this->getPrivateKey($this->private_key), $this->mch_id,        $this->serial_no);
    
     $header = [        'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign,        'Accept:application/json',        'User-Agent:' . $this->mch_id,
     ];
    
     $result = $this->curl($url, '', $header, 'GET');
     $cert = json_decode($result, true);
    
     $return = [];    if (!empty($cert['data'])) {        foreach ($cert['data'] as $item) {            if ($serialNo == $item['serial_no']) {
                 $return = $item;                break;
             }
         }
     }    return $return;
    }/**
     * @param $url
     * @param $body
     * @param $method
     * @return array
     */protected function getCurlHeader($url, $body, $method){
     $timestamp = time();
     $nonce = $this->nonce_str();
     $sign = $this->sign($url, $method, $timestamp, $nonce, $body, $this->getPrivateKey($this->private_key), $this->mch_id,        $this->serial_no);    return [        'Authorization: WECHATPAY2-SHA256-RSA2048 ' . $sign,        'Accept:application/json',        'User-Agent:' . $this->mch_id,        'Content-Type:application/json',        'Wechatpay-Serial:' . $this->getCert(),
     ];
    }/**
     * @return string
     */protected function nonce_str(){    static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
     $charactersLength = strlen($characters);
     $randomString = '';    for ($i = 0; $i < 32; $i++) {
         $randomString .= $characters[rand(0, $charactersLength - 1)];
     }    return $randomString;
    }/**
     * @param $key
     * @return bool|resource
     */protected function getPrivateKey($key){    return openssl_get_privatekey($key);
    }/**
     * @param $key
     * @return resource
     */protected function getPublicKey($key){    return openssl_get_publickey($key);
    }/**
     * @param $url
     * @param $http_method
     * @param $timestamp
     * @param $nonce
     * @param $body
     * @param $mch_private_key
     * @param $merchant_id
     * @param $serial_no
     * @return string
     */protected function sign($url, $http_method, $timestamp, $nonce, $body, $mch_private_key, $merchant_id, $serial_no){
     $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 $token;
    }/**
     * @param $message
     * @param $signature
     * @param $merchantPublicKey
     * @return bool|int
     */private function verify($message, $signature, $merchantPublicKey){    if (empty($merchantPublicKey)) {        return false;
     }    if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {        throw new \RuntimeException("当前PHP环境不支持SHA256withRSA");
     }
     $signature = base64_decode($signature);    return openssl_verify($message, $signature, $this->getPublicKey($merchantPublicKey), 'sha256WithRSAEncryption');
    }/**
     * @param $url
     * @param array $data
     * @param $header
     * @param string $method
     * @param int $time_out
     * @return mixed
     */private function curl($url, $data = [], $header, $method = 'POST', $time_out = 3){
     $curl = curl_init();    // 设置curl允许执行的最长秒数
    
     curl_setopt($curl, CURLOPT_URL, $url);
     curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
     curl_setopt($curl, CURLOPT_HEADER, false);
     curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
     curl_setopt($curl, CURLOPT_TIMEOUT, $time_out);
     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);    if ($method == 'POST') {
         curl_setopt($curl, CURLOPT_POST, true);
         curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
     }    // 执行操作
     $result = curl_exec($curl);
    
     curl_close($curl);    return $result;
    }/**
     * @param $associatedData
     * @param $nonceStr
     * @param $ciphertext
     * @param $aesKey
     * @return bool|string
     */private function decryptToString($associatedData, $nonceStr, $ciphertext, $aesKey = ''){    if (empty($aesKey)) {
         $aesKey = $this->mch_key;
     }
     $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, $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, $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', $aesKey, \OPENSSL_RAW_DATA, $nonceStr,
             $authTag, $associatedData);
     }    throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }/**
     * @return array
     */private function getHeaders(){
     $headers = array();    foreach ($_SERVER as $key => $value) {        if ('HTTP_' == substr($key, 0, 5)) {
             $headers[str_replace('_', '-', substr($key, 5))] = $value;
         }        if (isset($_SERVER['PHP_AUTH_DIGEST'])) {
             $header['AUTHORIZATION'] = $_SERVER['PHP_AUTH_DIGEST'];
         } elseif (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
             $header['AUTHORIZATION'] = base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']);
         }        if (isset($_SERVER['CONTENT_LENGTH'])) {
             $header['CONTENT-LENGTH'] = $_SERVER['CONTENT_LENGTH'];
         }        if (isset($_SERVER['CONTENT_TYPE'])) {
             $header['CONTENT-TYPE'] = $_SERVER['CONTENT_TYPE'];
         }
     }    return $headers;
    }

本文地址:https://wangsheng.cc/post/21.html

打赏名单

不分博文,不定期整理近期数据,感谢各位打赏,如有需求请在扫码后的备注填写

网名 打赏金额(元) 网名 打赏金额(元)
火舞艳阳☀ 50 *帆(喝奶茶) 7
*宇 1 杨*8 5
乖乖鲁 1 linyi 2
*人 2 a*9 1
张九今 500
none
最后修改于:2021年12月26日 17:42

评论已关闭