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

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

CrazyPanda发表于:2023-12-01 21:51:12浏览:1271次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滑动验证码的实现原理。一、滑动验证码的定义与应用滑动验证码是一种人机交互的验证码形式,它的基本原理是在界面上展示一张包含某些图片或图形的滑块,用户需要按住滑块进行拖动,直到把滑块拼接到验证码图形相应位置,以此进行验证。该类型的验证码多用于广告、登录、注册和评论等需要用户身份识别的场景。二、php滑动验证码的实现原理ph
发表于:2023-12-28 浏览:317 TAG:
【PHP】PHP Websocket开发指南,实现实时交通信息查询功能
PHP Websocket开发指南,实现实时交通信息查询功能前言Websocket是一种在Web上实现双向通信的技术,它能够实现实时更新数据,适用于需要实时交互的场景。本篇文章将介绍如何使用PHP开发一个实时交通信息查询的功能,并提供相应的代码示例。准备工作在开始开发之前,需要准备以下工作:一台安装了PHP和Apache服务器的主机,具备基础的PHP编程知识一个支持Websocket的浏览器,如Chrome、Firefox等安装Composer,用于安装相关依赖库开始开发3.1 安装W
发表于:2023-12-04 浏览:571 TAG:
【PHP】在vscode中要用php需安装什么
在Vscode中使用Php需安装什么?随着Php的使用范围逐渐扩大,越来越多的人开始在Vscode中使用Php进行开发。但是,要在Vscode中使用Php需要安装一些必要的扩展和插件。本篇文章将为大家讲解在Vscode中使用Php需要安装的扩展和插件。PHP Extension PackPHP Extension Pack是由Microsoft开发的一个扩展包,其中包括了一些必要的Php扩展,比如Php Debug、Php IntelliSense、Php DocBlocker等。使用
发表于:2023-12-19 浏览:329 TAG:
【PHP】linux系统php怎么安装curl扩展
有时候安装好php后,后面需要一些扩展需要添加进来,又不能影响当前php的工作,我们就只能通过动态添加扩展来实现。php安装curl扩展的方法:一.首先明确一些目录1.源码包目录,就是放从网上下载的源码包文件的目录,里面有下载的php,mysql,http等源码例如:/usr/local/src/&nbsp; 2.PHP扩展包路径,就是PHP用来支持扩展服务的软件包,一般在php源码包的ext目录例如:/usr/local/src/php-7.0.12/ext/二. 开始动手安装1.安装curl
发表于:2024-03-19 浏览:281 TAG:
【PHP】Composer配置中国全量镜像
查看当前的镜像源:composer config -g -l repo.packagist切换国内镜像阿里云 composer&nbsp;config&nbsp;-g&nbsp;repos.packagist&nbsp;composer&nbsp;https://mirrors.aliyun.com/composer/腾讯云 composer&nbsp;config&nbsp;-g&nbsp;repos.packagist&nbsp;composer&nbsp;https://mirrors.c
发表于:2023-12-18 浏览:378 TAG:
【PHP】Composer创建一个library
1、首先创建一个空的项目文件夹mkdir&nbsp;mylibrary&nbsp;&amp;&nbsp;cd&nbsp;mylibrary2、执行composer命令//开始创建composer&nbsp;包 sh-4.2#&nbsp;composer&nbsp;init &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;nbs
发表于:2024-08-28 浏览:228 TAG:
【PHP】PHP 框架中大型项目代码维护挑战与解决方法
在大型 php 项目中,代码维护面临主要挑战,包括依赖管理、代码重构、代码风格和文档。解决方法包括:使用依赖项管理器管理版本冲突。使用unittest框架在重构时保持代码完整性。建立并强制执行代码风格指南。建立文档策略并使用phpdoc生成文档。PHP 框架中大型项目代码维护挑战与解决方法在大型 PHP 项目中,代码维护可能是一个艰巨的任务。随着项目规模的增长,代码库会变得庞大且复杂,从而难以管理和更新。以下是 PHP 框架中代码维护面临的主要挑战以及解决这些挑战的一些方法:挑战 1:依赖管理依
发表于:2024-05-30 浏览:268 TAG:
【PHP】php在数组后面增加一列
在 PHP 中,要在数组后面增加一列,我们可以采用 array_push() 函数或直接赋值的方式来实现。下面我们来介绍一下这两种方法。方法一:使用 array_push() 函数array_push() 函数可将一个或多个元素添加到数组末尾,我们可以利用此函数在数组后面增加一列。具体实现的方式如下:&lt;?php //&nbsp;定义一个数组 $oldArr&nbsp;=&nbsp;array( &nbsp;&nbsp;&nbsp;&nbsp;array(&#39;name&amp;#39
发表于:2023-12-18 浏览:325 TAG:
【PHP】设计模式的六大原则
设计模式的六大原则是指导软件开发设计的核心原则,它们帮助开发人员创建可维护、可扩展和灵活的软件系统。这些原则包括:&nbsp;‌&nbsp;&nbsp;&nbsp;&nbsp;单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起它变化的原因。这有助于减少类与类之间的耦合,提高类的内聚性和可读性。&nbsp;‌&nbsp;&nbsp;&nbsp;&nbsp;开闭原则(Open-Closed Principle, OCP):软件实体(如类、
发表于:2024-07-16 浏览:266 TAG:
【PHP】thinkphp利用缓存提高数据库查询性能
ThinkPHP是一款十分受欢迎的PHP框架,它提供了许多便捷的功能和优化的设计,使得开发者可以更高效地进行Web应用程序的开发。其中,利用缓存提高数据库查询性能是一个常见的优化手段。本文将分享一些关于如何在ThinkPHP中利用缓存提高数据库查询性能的经验。一、什么是缓存?缓存是指将经常查询的数据存储在快速访问的存储介质中,以提高数据的访问速度。在Web应用程序中,数据库是最常用的数据存储介质之一。而经常查询数据库会带来一定的性能压力。因此,利用缓存可以避免频繁地查询数据库,从而提高
发表于:2023-12-08 浏览:421 TAG: