适用语言:PHP(建议7.2以上,本人8.0)、tp6
官方文档:
微信支付开发者文档
说明:微信支付有多种实现方式,此处我使用两种方式来实现,一种是使用官方sdk改写(建议使用),一种是直接调用接口。
- 目录结构
- cert目录:存放证书目录(如果是Linux服务器,需赋予创建文件权限)
- config目录:配置文件目录
- controller目录:
- Demo.php 请求示例demo
- AesUtil.php 解密方法
- GetCert.php 下载微信支付证书方法
- Notify.php 微信支付成功回调示例
- Wechat.php 微信支付下单示例
- route目录:路由
二、代码说明
- 直接调用
说明:直接调用比较简单,url地址填写navicate下单地址即可,请求参数安装文档填写必须参数即可,此处需注意:下单接口文档并没有提及请求header的内容,但是此处是必须要添加请求header!!!具体header组成如下图代码所示。特别是'Authorization在文章下面我写有生成的该参数值的方法。
/**
*
* navicate支付,直接调用
*/
public function navicate2()
{
$uri = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native';
$data = [
"amount" => [
"total" => 1,
"currency" => "CNY",
],
"mchid" => "商户号",
"description" => "微信支付测试",
"notify_url" => "回调地址",
"out_trade_no" => "订单号",
"appid" => "appid",
];
$header = [
'Authorization: WECHATPAY2-SHA256-RSA2048 mchid="商户号",nonce_str="随机字符串",timestamp="时间戳",serial_no="证书序列表",signature="签名"', // 身份认证信息
'Content-Type:application/json',
'Accept:application/json',
'User-Agent:' . $_SERVER['HTTP_USER_AGENT']
];
$res = curlPost($uri, $data, 2, $header);
halt($res);
}
- Sdk使用(我用的是开发者版本的sdk)
文档地址:
微信支付-开发者文档
第一步:composer安装
composer require wechatpay/wechatpay-guzzle-middleware
第二步:开始编写代码,此处需要注意的是需要两个密钥文件都需要放在可访问目录下(商户私钥和微信支付平台证书),商户密钥是从微信支付平台下载的,微信支付平台证书是通过代码进行下载的,下载网址为:https://api.mch.weixin.qq.com/v3/certificates具体方法在下方展示。此处代码为navicate下单部分关键代码,一些作用类的代码在下方。
2.1 WeChat.php代码如下
<?php
declare (strict_types=1);
namespace app\pay\controller;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
class Wechat
{
????/**
?????*
?????* 微信支付,此处需要有创建文件权限
?????*/
????private $client; ???// ?微信支付client
????private $merchantId; ???// ?商户号
????private $apiclient_key; // ?商户私钥地址
????private $wechat_key; // ?微信支付平台证书地址
????private $resArr; ???// ?返回错误信息
????private $appid; // ?公众号/小程序/app - appid
????const TYPE = 'wechat';
????public function __construct($type = 1)
????{
????????????$this->resArr = [
????????????'code' => 0,
????????????'msg' => 'success',
????????????'data' => ''
????????];
????????$this->apiclient_key = root_path() . '/app/pay/cert/apiclient_key.pem';
????????$this->wechat_key = root_path() . '/app/pay/cert/wechat.pem';
????????$this->appid = config('wechat.appid');
????????// 商户相关配置
????????$this->merchantId = config('wechat.merchantId'); // 商户号
????????$merchantSerialNumber = config('wechat.merchantSerialNumber'); // 商户API证书序列号
????????$merchantPrivateKey = PemUtil::loadPrivateKey($this->apiclient_key); // 商户私钥
????????// ?检测微信支付平台证书是否存在,不存在重新生成
????????if (!file_exists($this->wechat_key)) {
????????????$cert = new GetCert($this->apiclient_key);
????????????$res = $cert->makeCert();
????????????if (!$res) {
????????????????$this->resArr['code'] = -1;
????????????????$this->resArr['msg'] = '证书下载失败,请检查是否相关目录是否赋予权限!';
????????????????return $this->resArr;
????????????}
????????}
????????// 微信支付平台配置
????????$wechatpayCertificate = PemUtil::loadCertificate($this->wechat_key); // 微信支付平台证书
????????// 构造一个WechatPayMiddleware
????????$wechatpayMiddleware = WechatPayMiddleware::builder()
????????????->withMerchant($this->merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
????????????->withWechatPay([$wechatpayCertificate]) // 可传入多个微信支付平台证书,参数类型为array
????????????->build();
????????// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
????????$stack = HandlerStack::create();
????????$stack->push($wechatpayMiddleware, 'wechatpay');
????????// 创建Guzzle HTTP Client时,将HandlerStack传入
????????$this->client = new Client(['handler' => $stack]);
????}
????/**
?????* navicate下单
?????* $total:金额,单位(分)
?????* $out_trade_no:订单号
?????* $notify_url:回调地址
?????*/
????public function navicate($total = 1, $out_trade_no = '', $notify_url = '')
????{
????????try {
????????????$resp = $this->client->request(
????????????????'POST',
????????????????'https://api.mch.weixin.qq.com/v3/pay/transactions/native', // ?请求URL
????????????????[
????????????????????// JSON请求体
????????????????????'json' => [
????????????????????????"amount" => [
????????????????????????????"total" => $total,
????????????????????????????"currency" => "CNY",
????????????????????????],
????????????????????????"mchid" => $this->merchantId,
????????????????????????"description" => "微信支付测试",
????????????????????????"notify_url" => $notify_url,
????????????????????????"out_trade_no" => $out_trade_no,
????????????????????????"appid" => $this->appid,
????????????????????],
????????????????????'headers' => ['Accept' => 'application/json']
????????????????]
????????????);
????????????$statusCode = $resp->getStatusCode();
????????????if ($statusCode == 200) { //处理成功
????????????????$res = json_decode($resp->getBody()->getContents());
????????????????$this->resArr['data'] = $res->code_url;
????????????????return $this->resArr; ?// ?返回微信二维码地址
????????????} else if ($statusCode == 204) { //处理成功,无返回Body
????????????????$this->resArr['msg'] = '暂无body';
????????????????return $this->resArr;;
????????????}
????????} catch (RequestException $e) {
????????????// 进行错误处理
????????????echo $e->getMessage() . "\n";
????????????$this->resArr['code'] = -1;
????????????$this->resArr['msg'] = $e->getMessage();
????????????if ($e->hasResponse()) {
????????????????$this->resArr['msg'] = "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
????????????}
????????????return $this->resArr;
????????}
????}
???????/**
?????* 查询订单-通过微信支付订单
?????*/
????public function findByTransactionId($id)
????{
????????try {
????????????$resp = $this->client->request(
????????????????'GET',
????????????????'https://api.mch.weixin.qq.com/v3/pay/transactions/id/' . $id . '?mchid=' . $this->merchantId // ?请求URL
????????????);
????????????$statusCode = $resp->getStatusCode();
????????????if ($statusCode == 200) { //处理成功
????????????????$res = json_decode($resp->getBody()->getContents());
????????????????$this->resArr['data'] = $res;
????????????????return $this->resArr; ?// ?返回微信二维码地址
????????????} else if ($statusCode == 204) { //处理成功,无返回Body
????????????????$this->resArr['msg'] = '暂无body';
????????????????return $this->resArr;;
????????????}
????????} catch (RequestException $e) {
????????????// 进行错误处理
????????????echo $e->getMessage() . "\n";
????????????$this->resArr['msg'] = $e->getMessage();
????????????if ($e->hasResponse()) {
????????????????$this->resArr['msg'] = "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
????????????}
????????????return $this->resArr;
????????}
????}
????/**
?????* 查询订单-通过商户订单号
?????*/
????public function findByTransactionOrder($orderId)
????{
????????try {
????????????$resp = $this->client->request(
????????????????'GET',
????????????????'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $orderId . '?mchid=' . $this->merchantId // ?请求URL
????????????);
????????????$statusCode = $resp->getStatusCode();
????????????if ($statusCode == 200) { //处理成功
????????????????$res = json_decode($resp->getBody()->getContents());
????????????????$this->resArr['data'] = $res;
????????????????return $this->resArr; ?// ?返回微信二维码地址
????????????} else if ($statusCode == 204) { //处理成功,无返回Body
????????????????$this->resArr['msg'] = '暂无body';
????????????????return $this->resArr;;
????????????}
????????} catch (RequestException $e) {
????????????// 进行错误处理
????????????echo $e->getMessage() . "\n";
????????????$this->resArr['msg'] = $e->getMessage();
????????????if ($e->hasResponse()) {
????????????????$this->resArr['msg'] = "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
????????????}
????????????return $this->resArr;
????????}
????}
}
2.2 Demo.php 代码如下
<?php
namespace app\pay\controller;
class Demo
{
????/**
?????* 说明:
?????* 1.可自行选择安装sodium扩展
?????* 2.参数请自行配置,所需参数:商户密钥文件、商户号、appid、v3密钥、证书序列号等
?????* 3.wechat控制器:封装支付、查询等接口方法
?????* 4.Notify控制器:回调通知示例
?????* 5.GetCert控制器:封装下载证书方法,参数无误可直接使用
?????* 6.AesUtil控制器:封装解密方法
????*/
????/**
?????* navicate支付
?????* 参数:{ total:金额,单位(分),out_trade_no:订单号,notify_url }
????*/
???function navicate(){
???????$pay = new Wechat('wechat');
???????// ??参数请看上面注释
???????$res = $pay->navicate(1, 'br' . time() . '1654', 'https://api.open.sdbaizhi.com/open/site/not');
???????halt($res);
???}
}
2.3 AesUtil.php 代码如下
<?php
namespace app\pay\controller;
class AesUtil
{
????/**
?????* AES key
?????*
?????* @var string
?????*/
????private $aesKey;
????const KEY_LENGTH_BYTE = 32;
????const AUTH_TAG_LENGTH_BYTE = 16;
????/**
?????* Constructor
?????*/
????public function __construct($aesKey)
????{
????????if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
????????????throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
????????}
????????$this->aesKey = $aesKey;
????}
????/**
?????* 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($associatedData, $nonceStr, $ciphertext)
????{
????????$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, $this->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, $this->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', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
????????????$authTag, $associatedData);
??????}
????????throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
????}
}
2.4 GetCert.php 代码如下
<?php
declare (strict_types=1);
namespace app\pay\controller;
class GetCert
{
????/**
?????* 通过接口获取微信支付平台证书
?????*
?????*/
????private $apiclient_key; // ?商户私钥文件目录
????public function __construct($apiclient_key)
????{
????????$this->apiclient_key = !empty($apiclient_key) ?? root_path() . '/app/pay/cert/apiclient_key.pem';
????}
????/**
?????* 下载证书
?????*/
????public function makeCert()
????{
????????// ?获取平台证书列表url地址
????????$uri = 'https://api.mch.weixin.qq.com/v3/certificates';
????????// ?请求头部
????????$header = [
????????????'Authorization:' . $this->makeAuthorization(), ?// ?身份认证信息
????????????'Content-Type:application/json',
????????????'Accept:application/json',
????????????'User-Agent:' . $_SERVER['HTTP_USER_AGENT']
????????];
????????// ?获取请求结果
????????$res = json_decode(curlGet($uri, $header), true);
????????$associated_data = '';
????????$nonce = '';
????????$ciphertext = '';
????????foreach ($res['data'] as $k => $v) {
????????????$associated_data = $v['encrypt_certificate']['associated_data'];
????????????$nonce = $v['encrypt_certificate']['nonce'];
????????????$ciphertext = $v['encrypt_certificate']['ciphertext'];
????????}
????????$aesUitl = new AesUtil(config('wechat.aesKey'));
????????// ?证书解密
????????$certText = $aesUitl->decryptToString($associated_data, $nonce, $ciphertext);
????????// ?生成证书文件
????????$filePath = root_path() . '/app/pay/cert/wechat.pem';
????????$file = fopen($filePath, 'a');
????????fwrite($file, $certText);
????????fclose($file);
????????if (file_exists($filePath)) return true;
????????return false;
????}
????/**
?????* 生成身份认证信息
?????*/
????protected function makeAuthorization($http_method = 'GET')
????{
????????$url = 'https://api.mch.weixin.qq.com/v3/certificates'; // ?请求地址
????????$timestamp = time(); ???// ?时间戳
????????$nonce = getRandStr(); ?// ?随机字符串
????????$body = ''; // ?请求体
????????$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";
????????// ?读取商户私钥
????????$mch_private_key = file_get_contents($this->apiclient_key);
????????// ?使用商户私钥对待签名串进行SHA256 with RSA签名
????????openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
????????// ?对签名结果进行Base64编码得到签名值
????????$sign = base64_encode($raw_sign);
????????// ?组合身份认证信息
????????$schema = 'WECHATPAY2-SHA256-RSA2048';
????????$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
????????????$this->merchantId, $nonce, $timestamp, config('wechat.merchantSerialNumber'), $sign);
????????return $schema . ' ' . $token;
????}
}
2.5 Notify.php 代码如下
<?php
declare (strict_types=1);
namespace app\pay\controller;
use app\common\model\Order;
class Notify
{
????/**
?????*
?????* 回调通知
?????* 此处只选择我认为可用部分,其他内容请根据文档自行定义
?????* 微信签名暂未认证
????*/
????public function notify()
????{
????????// ?接收微信回调通知并转成数组,注意:v3版本以json格式回调,不再使用xml格式;以post请求
????????$postData = input('post.');
????????if ($postData['summary'] != '支付成功') return false;
????????$sign = request()->header('Wechatpay-Signature'); ??// ?接收微信签名
????????// ?信息解密
????????$associated_data = $postData['resource']['associated_data'];
????????$nonce = $postData['resource']['nonce'];
????????$ciphertxt = $postData['resource']['ciphertext'];
????????$aes = new AesUtil(config('wechat.aesKey'));
????????$res = json_decode($aes->decryptToString($associated_data, $nonce, $ciphertxt), true); ?// ?解密文件并生成数组
????????if (empty($res)) return false;
????????// ?判断appid与商户id是否一致
????????if ($res['appid'] != config('wechat.appid') && $res['mchid'] != config('wechat.merchantId')) return false;
????????// ?其他信息暂未列出,具体根据自己实际情况使用
????????$orderData = [
????????????'out_trade_no' => $res['out_trade_no'], ??// ?订单号
????????????'transaction_id' => $res['transaction_id'], ??// ?微信支付订单号
????????????'trade_type' => $res['trade_type'], ??// ?交易类型
????????????'total' => $res['amount']['total'], ??// ?订单总金额,单位(分)
????????????'payer_total' => $res['amount']['payer_total'], ??// ?用户支付金额,单位(分)
????????????'openid' => $res['payer']['openid'], ??// ?支付者openid
????????????'success_time' => $res['success_time'] ?// ?支付完成时间
????????];
????????$order = new Order();
????????try {
????????????$saveData = $order->save($orderData);
????????????if ($saveData !== false) {
????????????????json(['code' => 'SUCCESS', 'message' => '成功']);
????????????}
????????????json(['code' => 'ERRPR', 'message' => '失败']);
????????} catch (\Exception $e) {
????????????echo $e->getMessage();
????????????exit();
????????}
????}
}
2.6 config里面参数如下
<?php
// ?微信配置
return [
????'merchantId' => '商户号', ??// 微信支付商户号
????'merchantSerialNumber' => '证书序列号', ??// ?证书序列号
????'appid' => 'appid', ???// ?公众号appid
????'aesKey' => 'v3密钥', ???// ?v3密钥
];
更多信息:柏知网
|