您的当前位置:首页>全部文章>文章详情

【PHP】PHP接入微信官方支付(native·APIv3)

CrazyPanda发表于:2023-12-01 21:51:12浏览:1293次TAG:

一、项目介绍

两个文件实现微信官方支付(native·APIv3)的发起支付和回调应答功能

二、准备资料

  1. 商户号:需要使用到营业执照注册商户

  2. appid:小程序或者订阅号的appid

  3. APIv3秘钥:32位秘钥,APIv2秘钥为16位,不要混淆

  4. 证书序号:apiclient_key.pem文件中的秘钥,需要将该文件改为txt后缀,然后获取其中的秘钥

三、支付代码

1.index.php文件

<?php
//支付配置
$mchid = '';//微信支付商户号 PartnerID
$appid = '';//公众号APPID
$apiKey = '';//APIv3密钥
$serialNumber = '';     //证书序列号
$privateKey = '';       //apiclient_key.pem的文件内容,可以先将后缀名改为txt,然后获取里面内容//商品信息,可以由前端获取
$payAmount = '';        //付款金额,单位:元
$orderName = '';        //订单标题//默认配置
$outTradeNo = date('YmdHis').uniqid();     //订单号,采用时间加微秒计ID
$notifyUrl = 'https://***/notify.php';     //付款成功后的通知地址,需要用https协议//这里可以填写预下单业务逻辑//发起支付
$wxPay = new IndexService($mchid, $appid, $apiKey,$privateKey,$serialNumber);
$wxPay->setTotalFee($payAmount);
$wxPay->setOutTradeNo($outTradeNo);
$wxPay->setOrderName($orderName);
$wxPay->setNotifyUrl($notifyUrl);
$result = $wxPay->doPay();
$url = 'https://wenhairu.com/static/api/qr/?size=300&text=' . $result['code_url'];
echo "<img src='{$url}' style='width:100px;'><br>";echo '二维码内容:' . $result['code_url'];
//IndexService类
class IndexService
{
    protected $mchid;
    protected $appid;
    protected $apiKey;
    protected $privateKey;
    protected $serialNumber;
    protected $totalFee;
    protected $outTradeNo;
    protected $orderName;
    protected $notifyUrl;
    protected $auth;
    protected $gateWay='https://api.mch.weixin.qq.com/v3';
    public function __construct($mchid, $appid, $apikey, $privateKey, $serialNumber)
    {
        $this->mchid = $mchid;
        $this->appid = $appid;
        $this->apiKey = $apikey;
        $this->privateKey = $privateKey;
        $this->serialNumber = $serialNumber;
    }
    
    public function setTotalFee($totalFee)
    {
        $this->totalFee = floatval($totalFee);
    }
    
    public function setOutTradeNo($outTradeNo)
    {
        $this->outTradeNo = $outTradeNo;
    }
    
    public function setOrderName($orderName)
    {
        $this->orderName = $orderName;
    }
    
    public function setNotifyUrl($notifyUrl)
    {
        $this->notifyUrl = $notifyUrl;
    }
    
    /**     
     * 发起支付     
     */
     public function doPay()
     {
         $reqParams = array(
         'appid' => $this->appid,        //公众号或移动应用appid
         'mchid' => $this->mchid,        //商户号
         'description' => $this->orderName,     //商品描述
         'attach' => 'pay',              //附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
         'notify_url' => $this->notifyUrl,       //通知URL必须为直接可访问的URL,不允许携带查询串。
         'out_trade_no' => $this->outTradeNo,      //商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。特殊规则:最小字符长度为6
         'amount'=>array(
             'total'=> floatval($this->totalFee) * 100, //订单总金额,单位为分
             'currency'=> 'CNY', //CNY:人民币,境内商户号仅支持人民币
         ),
         'scene_info'=>array(        
             //支付场景描述
             'payer_client_ip'=>'127.0.0.1'   //调用微信支付API的机器IP
         )
     );
     $reqUrl = $this->gateWay.'/pay/transactions/native';
     $this->getAuthStr($reqUrl,$reqParams);
     $response = $this->curlPost($reqUrl,$reqParams);
     return json_decode($response,true);
     }
     
     public function curlPost($url = '', $postData = array(), $options = array())
     {
         if (is_array($postData)) 
         {
             $postData = json_encode($postData);
         }
         $ch = curl_init();
         curl_setopt($ch, CURLOPT_URL, $url);
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
         curl_setopt($ch, CURLOPT_POST, 1);
         curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
         curl_setopt($ch, CURLOPT_HTTPHEADER, array(
             'Authorization:'.$this->auth,
             'Content-Type:application/json',
             'Accept:application/json',
             'User-Agent:'.$_SERVER['HTTP_USER_AGENT']
             )
         );
         curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
         if (!empty($options)) 
         {
             curl_setopt_array($ch, $options);
         }
         //https请求 不验证证书和host
         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
         curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
         $data = curl_exec($ch);
         curl_close($ch);
         return $data;
     }
     
     private function getSchema(): string
     {
         return 'WECHATPAY2-SHA256-RSA2048';
     }
     
     public function getAuthStr($requestUrl,$reqParams=array()): string
     {
         $schema = $this->getSchema();
         $token = $this->getToken($requestUrl,$reqParams);
         $this->auth = $schema.' '.$token;return $this->auth;
     }
     
     private function getNonce()
     {
         static $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
         $charactersLength = strlen($characters);
         $randomString = '';
         for ($i = 0; $i < 32; $i++) 
         {
             $randomString .= $characters[rand(0, $charactersLength - 1)];
         }
         
         return $randomString;
     }
     
     public function getToken($requestUrl,$reqParams=array()): string
     {
         $body = $reqParams ?  json_encode($reqParams) : '';
         $nonce = $this->getNonce();
         $timestamp = time();
         $message = $this->buildMessage($nonce, $timestamp, $requestUrl,$body);
         $sign = $this->sign($message);
         $serialNo = $this->serialNumber;
         return sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',$this->mchid, $nonce, $timestamp, $serialNo, $sign);
     }
     
     private function buildMessage($nonce, $timestamp, $requestUrl, $body = ''): string
     {
         $method = 'POST';
         $urlParts = parse_url($requestUrl);
         $canonicalUrl = ($urlParts['path'] . (!empty($urlParts['query']) ? "?{$urlParts['query']}" : ""));
         return strtoupper($method) . "\n" .$canonicalUrl . "\n" .$timestamp . "\n" .$nonce . "\n" .$body . "\n";
     }
     
     private function sign($message): string
     {
         if (!in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) 
         {
             throw new \RuntimeException("当前PHP环境不支持SHA256withRSA");
         }
         
         $res = $this->privateKey;
         if (!openssl_sign($message, $sign, $res, 'sha256WithRSAEncryption')) 
         {
             throw new \UnexpectedValueException("签名验证过程发生了错误");
         }
         
         return base64_encode($sign);
     }
}

说明:

  1. 该微信支付属于native的APIv3版本,需要用到商户证书的相关内内容,不要和微信的其他支付方式或者APIv2相混淆

  2. index.php中的支付成功通知地址需要用https协议,该文件内容,下面会给出

  3. 仅需要填写IndexService类上边的配置即可,IndexService类的内容不需要修改

 2.notify.php

<?php
    //获取返回json数据
    $getCallBackJson = file_get_contents('php://input');
    //转化为关联数组
    $getCallBackArray = json_decode($getCallBackJson, true);
    //获取需要解密字段
    $associatedData = $getCallBackArray['resource']['associated_data'];
    $nonceStr = $getCallBackArray['resource']['nonce'];
    $ciphertext = $getCallBackArray['resource']['ciphertext'];
    //执行解密
    $apiKey = '';   
    //这里需要填写APIv3秘钥
    $getData = new NotifyService($apiKey);
    $resultJson = $getData->decryptToString($associatedData, $nonceStr, $ciphertext);
    //解密结果,为关联数组格式$resultArray = json_decode($resultJson, true);
    //交易成功
    if ($resultArray['trade_state'] === 'SUCCESS') 
    {    
        //这里填写交易成功的相关业务,如更新账单状态,其中可能需要用到的参数如下
        //$resultArray['out_trade_no']       商户订单号
        //$resultArray['transaction_id']     订单号
        //$resultArray['amount']['total']    订单金额
    }
    //NotifyService类
    class NotifyService
    {
        protected $apiKey;
        const AUTH_TAG_LENGTH_BYTE = 16;
        public function __construct($apiKey)
        {
            $this->apiKey = $apiKey;
        }
        /**     
         * 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(string $associatedData, string $nonceStr, string $ciphertext)
         {
             $ciphertext = \base64_decode($ciphertext);
             if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) 
             {
                 return false;
             }
             $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->apiKey, \OPENSSL_RAW_DATA, $nonceStr,$authTag, $associatedData);
         }
     }

注意:

  1. notify.php中需要再次填写你的APIv3秘钥

  2. 在交易成功的if语句中,填写最终支付成功的相关业务逻辑

猜你喜欢

【PHP】php 判断是否是数组
在PHP中,判断一个变量是否是数组可以使用多种方式。以下将介绍四种判断方法:1. is_array()函数is_array()函数是PHP中判断变量是否是数组的最常用方法。该函数接受一个变量作为参数,如果参数是一个数组,则返回true;反之,返回false。下面是一个使用is_array()函数的例子:$arr&nbsp;=&nbsp;array(&quot;apple&quot;,&quot;banana&quot;,&quot;cherry&quot;); if(is_array(
发表于:2023-12-20 浏览:309 TAG:
【PHP】ThinkPHP 集成 jwt 技术 token 验证
ThinkPHP 集成 jwt 技术 token 验证一、思路流程二、安装 firebase/php-jwt三、封装token类四、创建中间件,检验Token校验时效性五、配置路由中间件六、写几个测试方法,通过postman去验证一、思路流程客户端使用用户名和密码请求登录服务端收到请求,验证用户名和密码验证成功后,服务端会签发一个token,再把这个token返回给客户端客户端收到token后可以把它存储起来,比如放到cookie中客户端每次向服务端请求资源时需要携带服务端签发的token,可以
发表于:2024-08-08 浏览:282 TAG: #php #jwt #token
【PHP】CI,ThinkPHP,YII,Laravel框架比较
用过其中的yii TP CI框架。大概整理了这些框架的优点和缺点,有些错误的地方还希望大家指正。各个框架各有所长,针对的应用场景不同。一、Ci框架推崇简单就是美这一原则,没有花哨的设计模式,没有华丽的对象结构,一切就是那么简单。优点:框架的入门槛很低,极易学,极易用,框架很小,静态化非常容易配置简单,全部的配置使用php脚本来配置,执行效率高缺点:架构略简单,只能满足小型应用,略微不太能够满足中型应用需要大型项目扩展能力差,有些功能需要自己写扩展数据库类的扩展 ci给用户提供了一个名为call_
发表于:2024-07-14 浏览:313 TAG: #php #框架
【PHP】9个适用于PHP的最佳自动化测试框架
您是否花了很长时间调试您PHP代码? 好吧,对于大多数程序员来说,这可能不是最迷人的挑战,但是有一种解决方案可以帮助我们缩短这项繁琐的任务。 自动化测试通过允许预先编写的测试来驱动开发过程,可以显着改善PHP开发的工作流程 。在这篇文章中,我们将尝试了解为什么自动化测试如此酷 ,它如何工作以及您可以从中开始的最佳测试框架是什么。为什么测试很重要新手PHP开发人员倾向于不为他们的代码编写测试。 我们大多数人的职业生涯都是通过逐一测试刚在浏览器窗口中编写的新特性和功能开始的,当出现问题时,我们一无所
发表于:2024-06-22 浏览:276 TAG: #测试
【PHP】从零搭建php8环境
从零搭建php环境-php8一、下载1、https://www.php.net/distributions/php-8.0.0.tar.gz下载到本地,文件传输上传到 /usr/local/src/2、wget -P /usr/local/src/ https://www.php.net/distributions/php-8.0.0.tar.gz二、解压、编译、安装1、解压缩&gt;&nbsp;cd&nbsp;/usr/local/src/ &gt;&nbsp;tar&nbsp;xzf&amp;nbs
发表于:2024-01-01 浏览:274 TAG:
【PHP】thinkphp5.1+workman+jsonRpc
1.下载jsonRpc包放到vendor目录下 &nbsp;2.启动文件 &nbsp;3.启动效果:启动命令php allserver.php start &nbsp;4.RpcClient Rpc客户端+RpcClient Rpc服务端 访问:http://localhost/product/public/index/RpcTest/rpctest 报错:stream_socket_client(): unable to connect to tcp://127.0.0.1:2015 (由于目标计算机积极拒绝,无法连接。
发表于:2024-05-27 浏览:341 TAG:
【PHP】php如何将utf8转gbk编码
随着互联网的发展,跨语言交流和国际化成为了越来越普遍的需求。由于不同的编码方式,网站之间的数据传输和处理也变得越来越复杂。在这个过程中,一些老旧的编码方式依然在使用,比如GBK编码。为了兼容各种编码方式,php提供了一些内置函数来进行编码转换,本文将介绍如何将utf8编码转换成GBK编码。一、了解编码首先,我们需要了解utf8和GBK编码分别是什么。utf8是一种可变长度的字符编码,是国际标准化组织ISO的一部分,也是Unicode字符集的一种实现方式。utf8编码可以容纳所有Unic
发表于:2023-12-20 浏览:327 TAG:
【PHP】PHP 框架在大型项目中微服务的最佳实践
微服务在 php 框架中的优点包括模块化、可扩展性和容错性。实战案例展示了使用 laravel 创建微服务架构,包括创建用户、产品和订单微服务。与微服务的集成最佳实践建议使用消息队列实现异步通信、采用 api 网关处理身份验证和流量管理,以及运用 devops 实践简化开发和部署流程。PHP 框架在大型项目中微服务的最佳实践近年来,微服务已成为大型项目架构的流行选择。微服务架构在 PHP 框架中可以带来诸多好处,包括:模块化: 微服务允许您将项目分解成较小的、独立的组件,便于维护和迭代。可扩展性
发表于:2024-05-30 浏览:378 TAG:
【PHP】PHP8中如何使用Stringable Interface更方便地处理字符串操作
PHP8中如何使用Stringable Interface更方便地处理字符串操作?PHP8是PHP语言的最新版本,带来了许多新特性和改进。其中一项令开发者欢欣鼓舞的改进之一就是Stringable Interface的加入。Stringable Interface是一个用于处理字符串操作的接口,它提供了一种更方便的方式来处理和操作字符串。本文将详细介绍如何使用Stringable Interface来提升字符串操作的便捷性,并提供具体的代码示例。首先,让我们了解一下Stringable Inte
发表于:2023-12-30 浏览:350 TAG:
【PHP】PHP中的堆和栈的概念及其应用
hp作为一门非常流行的编程语言,其对于数据结构的处理和使用具有非常重要的作用。而在php中,堆和栈是两种非常重要的数据结构,它们在程序设计和实现中有着重要的应用价值。本文将从概念和应用两方面介绍php中的堆和栈。一、堆和栈的概念堆堆是一种数据结构,它是一种特殊的树形结构。在PHP中,堆是由节点和边组成的一种图形式的数据结构。堆中每个节点都有一个值,并且每个节点的值都满足一定的关系,即父节点的值大于等于子节点的值(大根堆)或父节点的值小于等于子节点的值(小根堆)。在PHP中,堆通常是用来进行高效
发表于:2024-07-29 浏览:283 TAG: