搜索

查看: 3071|回复: 11

[PHP] PHP JSAPI调支付API实现微信支付功能详解

[复制链接]
发表于 2023-5-4 17:07:50 | 显示全部楼层 |阅读模式
Editor 2023-5-4 17:07:50 3071 11 看全部
目录
  • 一、首先我们来填个坑
  • 二、代码示例
  • 1.请求参数配置
  • 2.统一下单API
  • 3.MakeSign 签名
  • 4.ToXml 数组参数转xml
  • 5.postXmlCurl 发送请求
  • 6.FromXml 结果xml参数转数组
  • 总结
    一、首先我们来填个坑
    支付验签失败
    这个问题折磨了我两天,官方文档比较含糊不清。各种百度下来的方法试过之后也不尽人意,最后发现问题是没有二次签名
    二次签名需要参数(代码会展示在哪里二次签名):
    appId:      商户申请的公众号对应的appid(I大写)
    nonceStr: 随机字符串(注意是JSAPI下单接口中返回的 nonce_str、不是重新生成)
    package:  统一下单接口返回的prepay_id参数值 ,(注意格式prepay_id=wx.....)
    signType: 签名类型、(官方文档)仅支持RSA。
    (我的签名类型是 HMAC-SHA256 也是可以的,必须和下单使用的签名类型保持一致)
    timeStamp:时间戳(这里要把 time() 转成字符串类型)
    注明:使用这五个参数生成的 paySign 签名才是需要返给前端的(

    2022111709333219.png

    2022111709333219.png


    官方文档实例要计算签名也给我整的蒙圈,最后发现直接将五个必须参数生成的签名返给前端就可以直接调取API了

    二、代码示例
    1.请求参数配置
                    $oInput    = [
                            'body'         => '测试商品',          // 商品说明                                                
                            'attach'       => '测试场景',          // 自定义参数:可以用来做回调后场景区分                                             
                            'out_trade_no' => '测试单号' . time(), // 自定义订单号                                       
                            'total_fee'    => 1 * 100,           // 付款金额:记得*100 微信官方是以分为单位                                          
                            'goods_tag'    => '',                // 优惠券相关参数                                   
                            'notify_url'   => 'http://...',     // 回调通知地址
                            'trade_type'   => 'JSAPI',          // 支付方式                                       
                            'openid'       => $openid,          // 付款用户openid   
                // 'profit_sharing' => 'Y',         // 是否分账的标识                                                                                                                              
                    ];
                    $res = $this->unifiedOrder($oInput);     // 这里我调用的统一下单
                    return $res;                             // 返给前端带APPID等参数给前端去调用支付
    2.统一下单API
            public function unifiedOrder($inputObj, $timeOut = 6)
            {
                    $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
                    // 首次签名参数
                    $oValues = [
                            'body'                                 => $inputObj['body'],                                // 设置商品或支付单简要描述
                            'attach'                         => $inputObj['attach'],                                // 设置附加数据,用于商户携带订单的自定义数据
                            'out_trade_no'                 => $inputObj['out_trade_no'],                 // 设置商户系统内部的订单号,transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no
                            'total_fee'                 => $inputObj['total_fee'],                         // 设置订单总金额,只能为整数,单位:分
                            'time_start'                 => date("YmdHis"),                                         // 设置订单生成时间
                            'time_expire'                 => date("YmdHis", time() + 600),         // 设置订单失效时间
                            'goods_tag'                 => $inputObj['goods_tag'],                         // 设置商品标记,代金券或立减优惠功能的参数
                            'notify_url'                 => $inputObj['notify_url'],                 // 获取接收微信支付异步通知回调地址的值
                            'trade_type'                 => $inputObj['trade_type'],                 // JSAPI,NATIVE,APP
                            'openid'                         => $inputObj['openid'],                         // 用户在商户appid下的唯一标识
                            //'profit_sharing'         => $inputObj['profit_sharing'],                // 是否需要分账
                            'appid'                         => 'appid',                                     // app_id:替换真实的
                            'mch_id'                         => 'mchid',                                     // 商户号:替换真实的
                            'spbill_create_ip'         => $_SERVER['REMOTE_ADDR'],                 // 终端ip
                            'nonce_str'                 => '自定义生成',                                 // 随机32位字符串
                            'sign_type'                 => 'HMAC-SHA256',                                         // 签名类型,自行替换
                    ];
                    // 首次签名
                    ksort($oValues);
                    $oValues['sign'] = $this->MakeSign($oValues);                 // 调用签名
                    $xml = $this->ToXml($oValues);                      // 数字转xml类型
                    $response = self::postXmlCurl($xml, $url, false, $timeOut); // 请求
                    $result   = $this->FromXml($response);              // 请求结果从xml转成数组类型
            // 二次签名参数
                    $oResult    = [
                            'appId'     => $result['appid'],                   // 首次请求中的appid
                            'nonceStr'  => $result['nonce_str'],               // 首次请求中的nonce_str
                            'package'   => 'prepay_id=' . $result['prepay_id'],// 首次请求中的prepay_id
                            'signType'  => 'HMAC-SHA256',   // 跟首次签名中的签名类型参数保持一致
                            'timeStamp' => (string)(time()),// 时间戳转字符串类型
                    ];
            // 二次签名
                    $oResult['paySign'] = $this->MakeSign($oResult);    // 调用签名
                    $result = json_encode($oResult); // encode数组
                    return $result;                  // 直接返回
            }
    3.MakeSign 签名
            /**
             * 生成签名
             * @param bool $needSignType  是否需要补signtype
             * @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
             */
            public function MakeSign($values, $needSignType = true)
            {
                    if ($needSignType) {
                            $sSignType = 'HMAC-SHA256'; // 可以在文档开头用枚举定义: 所有签名类型必须一致
                    }
                    $sKey = 'key';                   // 获取支付参数key
                    // 签名步骤一:按字典序排序参数
                    ksort($values);
                    $string = $this->ToUrlParams($values);
                    // 签名步骤二:在string后加入KEY
                    $string = $string . "&key=" . $sKey;
                    // 签名步骤三:MD5加密或者HMAC-SHA256
                    if ($sSignType == "MD5") {
                            $string = md5($string);
                    } else if ($sSignType == "HMAC-SHA256") {
                            $string = hash_hmac("sha256", $string, $sKey);
                    } else {
                            return "签名类型不支持!";
                    }
                    // 签名步骤四:所有字符转为大写
                    $result = strtoupper($string);
                    return $result;
            }
    4.ToXml 数组参数转xml
            public function ToXml($values)
            {
                    if (!is_array($values) || count($values) ";
                    foreach ($values as $key => $val) {
                            if (is_numeric($val)) {
                                    $xml .= "" . $val . "";
                            } else {
                                    $xml .= "";
                            }
                    }
                    $xml .= "";
                    return $xml;
            }
    5.postXmlCurl 发送请求
            /**
             * 以post方式提交xml到对应的接口url
             *
             * @param WxPayConfigInterface $config  配置对象
             * @param string         $xml                  需要post的xml数据
             * @param string         $url                  url
             * @param bool                 $useCert         是否需要证书,默认不需要
             * @param int                 $second           url执行超时时间,默认30s
             */
            private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
            {
                    $ch                         = curl_init();
                    $curlVersion         = curl_version();
                    $ua                         = "WXPaySDK/" . self::VERSION . " (" . PHP_OS . ") PHP/" . PHP_VERSION . " CURL/" . $curlVersion['version'] . " " . $aWxpayParam['mchid'];
                    //设置超时
                    curl_setopt($ch, CURLOPT_TIMEOUT, $second);
                    $proxyHost = "0.0.0.0";
                    $proxyPort = 0;
                    // 如果有配置代理这里就设置代理
                    if ($proxyHost != "0.0.0.0" && $proxyPort != 0) {
                            curl_setopt($ch, CURLOPT_PROXY, $proxyHost);
                            curl_setopt($ch, CURLOPT_PROXYPORT, $proxyPort);
                    }
                    curl_setopt($ch, CURLOPT_URL, $url);
                    // curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
                    // curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
                    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
                    curl_setopt($ch, CURLOPT_USERAGENT, $ua);
                    // 设置header
                    curl_setopt($ch, CURLOPT_HEADER, FALSE);
                    // 要求结果为字符串且输出到屏幕上
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
                    if ($useCert == true) {
                            // 设置证书
                            // 使用证书:cert 与 key 分别属于两个.pem文件
                            // 证书文件请放入服务器的非web目录下
                            $sslCertPath         = 'sslCertPath';        // 证书路径
                            $sslKeyPath         = 'sslKeyPath';         // 证书路径
                            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
                            curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath);
                            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
                            curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath);
                    }
                    // post提交方式
                    curl_setopt($ch, CURLOPT_POST, TRUE);
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
                    // 运行curl
                    $data = curl_exec($ch);
                    // 返回结果
                    if ($data) {
                            curl_close($ch);
                            return $data;
                    } else {
                            $error = curl_errno($ch);
                            curl_close($ch);
                            throw new WxPayException("curl出错,错误码:$error");
                    }
            }
    6.FromXml 结果xml参数转数组
            /**
             * 将xml转为array
             * @param string $xml
             * @throws WxPayException
             */
            public function FromXml($xml)
            {
                    if (!$xml) {
                            return "xml数据异常!";
                    }
                    //将XML转为array
                    //禁止引用外部xml实体
                    libxml_disable_entity_loader(true);
                    $res = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
                    return $res;
            }
    总结
    注意统一下单中五个调用方法别忘了:
    getNonceStr:我没贴出来,这个要自己写(0.0)

    2022111709333220.png

    2022111709333220.png


    MakeSign: 这里面的key要记得替换成自己真实的参数
    ToXml
    postXmlCurl : 注意这里面的证书要改成自己真实的哈

    2022111709333221.png

    2022111709333221.png


    FromXml
    到此这篇关于PHP JSAPI调支付API实现微信支付功能详解的文章就介绍到这了,更多相关PHP微信支付内容请搜索知鸟论坛以前的文章或继续浏览下面的相关文章希望大家以后多多支持知鸟论坛
  • 回复

    使用道具 举报

    发表于 2023-6-28 19:34:17 | 显示全部楼层
    我是的十八簿 2023-6-28 19:34:17 看全部
    其实我一直觉得楼主的品味不错!呵呵!知鸟论坛太棒了!
    回复

    使用道具 举报

    发表于 2023-6-28 22:15:48 | 显示全部楼层
    墙和鸡蛋 2023-6-28 22:15:48 看全部
    我看不错噢 谢谢楼主!知鸟论坛越来越好!
    回复

    使用道具 举报

    发表于 2023-6-28 23:40:48 | 显示全部楼层
    123456848 2023-6-28 23:40:48 看全部
    楼主,大恩不言谢了!知鸟论坛是最棒的!
    回复

    使用道具 举报

    发表于 2023-6-29 01:20:31 | 显示全部楼层
    啤酒瓶空了缓 2023-6-29 01:20:31 看全部
    这个帖子不回对不起自己!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    发表于 2023-6-29 07:22:50 | 显示全部楼层
    胡37 2023-6-29 07:22:50 看全部
    楼主,大恩不言谢了!知鸟论坛是最棒的!
    回复

    使用道具 举报

    发表于 2023-6-29 11:49:23 | 显示全部楼层
    米老鼠和蓝精鼠v 2023-6-29 11:49:23 看全部
    楼主太厉害了!楼主,I*老*虎*U!我觉得知鸟论坛真是个好地方!
    回复

    使用道具 举报

    发表于 2023-6-29 15:42:10 | 显示全部楼层
    落败的青春阳落s 2023-6-29 15:42:10 看全部
    楼主太厉害了!楼主,I*老*虎*U!我觉得知鸟论坛真是个好地方!
    回复

    使用道具 举报

    发表于 2023-6-29 16:03:25 | 显示全部楼层
    六翼天使494 2023-6-29 16:03:25 看全部
    既然你诚信诚意的推荐了,那我就勉为其难的看看吧!知鸟论坛不走平凡路。
    回复

    使用道具 举报

    发表于 2023-6-29 18:51:02 | 显示全部楼层
    李志敏 2023-6-29 18:51:02 看全部
    这个帖子不回对不起自己!我想我是一天也不能离开知鸟论坛
    回复

    使用道具 举报

    • 您可能感兴趣
    点击右侧快捷回复 【请勿灌水】
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则 返回列表

    RSS订阅| SiteMap| 小黑屋| 知鸟论坛
    联系邮箱E-mail:zniao@foxmail.com
    快速回复 返回顶部 返回列表