微信JSAPI支付V3版本(主要用于分账使用)(PHP)

定义微信配置

    protected $sp_appid = '';//服务商APPID(目前只有公众号)
    protected $sp_mchid = '';//服务商商户号
    protected $sub_appid = '';//小程序APPID
    protected $sub_mchid = '';//商户号
    protected $apiV3Key = '';//支付秘钥(服务商V3级别)
    protected $privateKeyPath = '';//证书物理路径(私钥)
    protected $serialNo = '';//证书序列号
    protected $app_secret = '';//小程序通信秘钥
    protected $notify_url = '';//支付回调地址

初始化加载配置

    public function _initialize()
    {
        parent::_initialize();
        $wechatconf = config('wechat');//可以写在配置文件,也可写在数据库
        $this->sp_appid = $wechatconf['sp_appid'];
        $this->sp_mchid = $wechatconf['sp_mchid'];
        $this->sub_appid = $wechatconf['sub_appid'];
        $this->sub_mchid = $wechatconf['sub_mchid'];
        $this->apiV3Key = $wechatconf['apiV3Key'];
        $this->privateKeyPath = $wechatconf['privateKeyPath'];
        $this->serialNo = $wechatconf['serialNo'];
        $this->app_secret = $wechatconf['app_secret'];
        $this->notify_url = $wechatconf['notify_url'];
    }

统一下单(注意signType不参与签名,package必须是下面写的prepay_id=)

    public function unifyPay(){
        $param = $this->request->param();
        if(!$param['uid'] || !$param['orderno']){
            return $this->json_result(400,'缺少参数');
        }
        $orderinfo = Db::name('service_order')->where(['orderno'=>$param['orderno'],'uid'=>$param['uid']])->find();
        if(empty($orderinfo)){
            return $this->json_result(400,'订单不存在');
        }
        $userinfo = Db::name('user')->where('id',$param['uid'])->find();
        if(empty($userinfo)){
            return $this->json_result(400,'用户不存在');
        }
        ;
        //这个数组里所有的数据都是必填的
        $unifydata = [
            'sp_appid' => $this->sp_appid,
            'sp_mchid' => $this->sp_mchid,
            'sub_appid' => $this->sub_appid,
            'sub_mchid' => $orderinfo['sub_mchid'],
            'description' => '服务',
            'out_trade_no' => $param['orderno'],//订单号
            'notify_url' => $this->notify_url,//回调地址
            'settle_info'=>['profit_sharing'=>true],
            'amount'=>['total'=>(int)bcmul(0.01, '100',0),'currency'=>'CNY'],
            'payer'=>['sub_openid'=>$userinfo['username']],
            'scene_info'=>['payer_client_ip'=>$_SERVER["REMOTE_ADDR"]],
        ];
        $orderResult = $this->create_pay_order($unifydata);
        $timeStamp = time();
        $paydata = [
            'appId' => $this->sub_appid,
            'timeStamp' => "$timeStamp",
            'nonceStr' => md5(rand(100000,999999)),
            'package' => 'prepay_id=' . $orderResult['prepay_id'],
        ];
        $sign = $this->getSign($paydata);//给小程序生成验证签名
        $paydata['signType'] = 'RSA';
        $paydata['paySign'] = $sign;
        return $this->json_result(200,'统一下单成功',$paydata);
    }

调用微信下单接口

    public function create_pay_order($inputData){
        $inputData = json_encode($inputData);//v3微信传递参数需要json格式
        $headers = $this->getV3Sign('https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi','POST',$inputData);//将签名放在报头里
        $result = $this -> post_data('https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi',$inputData,$headers);//下单
        return json_decode($result,true);
    }

获取签名

    public function getV3Sign($url,$http_method,$body) {
        //商户号
        $mchid = $this->sp_mchid;
        //随机字符串
        $nonce = strtoupper($this -> getNoncestr());
        //商户序列号
        $serialNo = $this->serialNo;
        //时间戳
        $timestamp = time();
        //url
        $url_parts = parse_url($url);
        //获取绝对路径
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        //密钥key
        $private_key = $this->getPrivateKey($this->privateKeyPath);
        //拼接参数
        $message = $http_method."\n".
            $canonical_url."\n".
            $timestamp."\n".
            $nonce."\n".
            $body."\n";
        //计算签名值
        openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption');
        $sign   = base64_encode($raw_sign);
//        $token  = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',$mchid, $nonce, $timestamp, $serial_no, $sign);
        $token = sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $mchid, $nonce, $timestamp, $serialNo, $sign);
        $headers = [
            'Accept: application/json',
            'User-Agent: */*',
            'Content-Type: application/json; charset=utf-8',
            'Authorization: '.$token,
        ];
        return $headers;
    }

生成32位随机字符串

    public 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 function getPrivateKey($filepath) {
        return openssl_get_privatekey(file_get_contents($filepath));
    }

POST调用API

    public function post_data($url,$data=[],$headers=[]){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header头
        curl_setopt($ch, CURLOPT_HTTPHEADER,$headers);
        // POST数据
        curl_setopt($ch, CURLOPT_POST, 1);
        // 把post的变量加上
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $output = curl_exec($ch);
        curl_close($ch);
        return $output;
    }
给小程序生成验证签名(V3不再用MD5用SHA256)
    protected function getSign($data) {
        $tmpstr = $data['appId'] . "\n" . $data['timeStamp'] . "\n" . $data['nonceStr'] . "\n" . $data['package'] . "\n";
        $privateKey         = file_get_contents($this->privateKeyPath);
        $binary_signature   = "";
        $algo               = "SHA256";
        openssl_sign($tmpstr, $binary_signature, $privateKey, $algo);
        $sign               = base64_encode($binary_signature);
        return $sign;
    }

支付回调 v3必须大于php7.2

    public function notify(){
        $result = $this->request->param();
        if($result){
            $text = base64_decode($result['resource']['ciphertext']); //解密
            /* =========== 使用V3支付需要PHP7.2.6安装sodium扩展才能进行解密参数  ================ */
            $str = sodium_crypto_aead_aes256gcm_decrypt($text, $result['resource']['associated_data'], $result['resource']['nonce'], $this->apiV3Key);
            $res = json_decode($str, true);
            //如果成功返回了
            if($res['trade_state'] == 'SUCCESS'){
                Db::startTrans();
                try {
                    Db::name('service_order')->where('orderno',$res['out_trade_no'])->update([
                        'pay_state'=>1,
                        'transaction_id'=>$res['transaction_id'],
                        'pay_time'=>time(),
                        'status'=>1
                    ]);
                    $lid = Db::name('service_order')->where('orderno',$res['out_trade_no'])->value('lid');
                    if($lid){
                        Db::name('service_list')->where('id',$lid)->setInc('sales',1);
                    }
                    Db::commit();
                    return $this->json_result(200,'支付成功');
                }catch (DbException $e){
                    return $this->json_result(400,$e->getMessage());
                }
            }
        }
        return $this->json_result(400,'支付失败');
    }

### 关于微信 JSAPI 支付 v3 商户私钥 &#39;Illegal base64 character 2e&#39; 错误的解决方案 在微信 JSAPI 支付 v3 版本中,商户私钥用于签名生成和验证。如果在获取或使用商户私钥时出现 `Illegal base64 character 2e` 错误,通常是因为私钥格式不正确或编码问题引起的。以下是可能的原因及解决方法: #### 1. 私钥格式错误 商户私钥必须是符合标准的 PEM 格式。如果私钥文件中包含多余的空格、换行符或其他非法字符,则可能导致解码失败[^1]。 **解决方法:** 确保商户私钥以以下格式保存: ```text -----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ... -----END PRIVATE KEY----- ``` 注意: - 确保 `-----BEGIN PRIVATE KEY-----` 和 `-----END PRIVATE KEY-----` 之间的内容为连续的 Base64 编码字符串。 - 移除所有非必要的空格和换行符。 #### 2. Base64 解码问题 `Illegal base64 character 2e` 错误表明 Base64 解码器检测到一个非法字符(如 `.` 或其他非 Base64 字符)。这可能是由于私钥被修改或损坏[^2]。 **解决方法:** 检查私钥是否被意外修改。可以通过以下代码验证私钥是否可以成功解码: ```python import base64 # 替换为实际的私钥内容 private_key_content = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ..." try: # 去掉头部和尾部标记 key_body = private_key_content.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "") # 解码 Base64 decoded_key = base64.b64decode(key_body) print("解码成功:", decoded_key[:20]) # 打印前20字节验证 except Exception as e: print("解码失败:", str(e)) ``` #### 3. 私钥来源问题 商户私钥应从微信支付平台下载,而不是手动生成或编辑。如果私钥来源不可靠,可能会导致格式或内容错误[^3]。 **解决方法:** 重新从微信支付商户平台下载私钥文件,并严格按照官方文档要求进行配置。 #### 4. 编程语言或框架限制 某些编程语言或框架对 Base64 解码有特定要求。例如,Java 中的 `Base64.getDecoder().decode()` 方法对输入字符串的格式非常严格[^4]。 **解决方法:** 确保使用的编程语言或框架支持标准的 Base64 解码。如果不确定,可以参考官方文档或切换到其他实现方式。 --- ### 示例代码:商户私钥加载与验证 以下是一个完整的 Python 示例,展示如何加载和验证商户私钥: ```python from cryptography.hazmat.primitives import serialization # 替换为实际的私钥路径 private_key_path = "path/to/your/private_key.pem" try: with open(private_key_path, "rb") as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, # 如果私钥加密,请提供密码 ) print("私钥加载成功") except Exception as e: print("私钥加载失败:", str(e)) ``` --- ### 总结 `Illegal base64 character 2e` 错误通常是由于私钥格式不正确或被意外修改导致的。通过检查私钥格式、验证 Base64 解码结果以及重新下载官方提供的私钥,可以有效解决问题。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值