PHP接入微信官方支付(native·APIv3)
本文引用于 猫玖云 https://blog.csdn.net/qq_63573498
一、项目介绍
两个文件实现微信官方支付(native·APIv3)的发起支付和回调应答功能
注意:
- notify.php中需要再次填写你的APIv3秘钥
- 在交易成功的if语句中,填写最终支付成功的相关业务逻辑
二、准备资料
商户号:需要使用到营业执照注册商户
appid:小程序或者订阅号的appid
APIv3秘钥:32位秘钥,APIv2秘钥为16位,不要混淆
证书序号: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);
}
}
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);
}
}
大部分编程语言(较新版本)都支持了AEAD_AES_256_GCM
。开发者可以参考下列的示例,了解如何使用您的编程语言实现解密。
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');
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2022-07-27 WPF Canvas image高分辨率打印
2022-07-27 WPF技巧-Canvas转为位图
2022-07-27 WPF把Canvas保存为图片,文本文件,xps文件
2022-07-27 WPF 的Canvas画图区整体缩放与平移
2022-07-27 WPF Canvas 以鼠标指针为中心缩放
2022-07-27 【WPF】图片等可视化元素缩放变换及中心点设置