程序框架:ThinkPHP 5.1
PHP版本:7.4.33
官方SDK文档:https://pay.weixin.qq.com/doc/v3/partner/4012083112
使用 Composer 安装最新版本微信支付SDK:
composer require wechatpay/wechatpay
官方文档中还提到了Guzzle扩展

使用 Composer 安装最新版本 Guzzle 7:
扩展地址:https://packagist.org/packages/guzzlehttp/guzzle
composer require guzzlehttp/guzzle
程序项目中微信支付配置文件,根据项目你们自行创建,我这里用的是ThinkPHP 5.1版本
创建 config/applets.php 文件
注意:这里的公、私钥证书路径,必须带有 ‘file://’,否则会报错
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Author: 自然 <c1516k@gmail.com>
// +----------------------------------------------------------------------
// +----------------------------------------------------------------------
// | 微信小程序设置
// +----------------------------------------------------------------------
return [
// 小程序appid
'appId' => '',
// 小程序appsecret
'appSecret' => '',
//------- 微信支付 微信商户平台 账户中心 > API安全 里获取 -------
// 商户号
'mchId' => '',
// API密钥 v2版本,v3版本用不到可忽略
'apiKey' => '',
//「商户API私钥」路径
'merchantPrivateKeyFilePath' => 'file://' . ROOT_PATH . 'data' . DIRECTORY_SEPARATOR . 'wxpay_cert' . DIRECTORY_SEPARATOR. 'apiclient_key.pem',
//「商户API证书」序列号
'merchantCertificateSerial' => '',
//「微信支付公钥」路径
'platformPublicKeyFilePath' => 'file://' . ROOT_PATH . 'data' . DIRECTORY_SEPARATOR . 'wxpay_cert' . DIRECTORY_SEPARATOR . 'pub_key.pem',
//「微信支付公钥ID」
'platformPublicKeyId' => '',
// 微信支付回调密钥
'payCallbackPrivateKey' => ''
];
创建客户端实例:library/WxPay.php,根据项目你们自定义
<?php
/*
* 微信支付 v3 操作
*/
namespace app\api\library;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
class WxPay
{
function initialize()
{
}
//微信支付配置获取
static public function getConfig()
{
$data = config('applets.');
//私钥解析
if ( !empty($data['merchantPrivateKeyFilePath']) ) {
$data['merchantPrivateKeyInstance'] = Rsa::from($data['merchantPrivateKeyFilePath'], Rsa::KEY_TYPE_PRIVATE);
}
//公钥解析
if ( !empty($data['platformPublicKeyFilePath']) ) {
$data['twoPlatformPublicKeyInstance'] = Rsa::from($data['platformPublicKeyFilePath'], Rsa::KEY_TYPE_PUBLIC);
}
return $data;
}
// APIv3 客户端实例
static public function getWxpayInstance()
{
$config = self::getConfig();
$instance = Builder::factory([
'mchid' => $config['mchId'],
'serial' => $config['merchantCertificateSerial'],
'privateKey' => $config['merchantPrivateKeyInstance'],
'certs' => [
$config['platformPublicKeyId'] => $config['twoPlatformPublicKeyInstance'],
],
]);
return $instance;
}
}
创建微信支付控制器:controller/Wxpay.php
<?php
// +----------------------------------------------------------------------
// | 微信支付 v3
// +----------------------------------------------------------------------
namespace app\api\controller;
use think\Db;
use util\AesUtil;
use app\common\controller\Api;
use app\api\library\WxPay AS WxPayLibrary;
class Wxpay extends Api
{
private $requestUrl = 'https://api.mch.weixin.qq.com/';
public $config = [];
//日志目录
public $logs_path = ROOT_PATH . 'data' . DIRECTORY_SEPARATOR . 'wxpay_logs' . DIRECTORY_SEPARATOR;
protected function initialize()
{
parent::initialize();
$this->WxPayLibrary = new WxPayLibrary;
$this->get_config();
}
/*
* 发起 JSAPI小程序支付
* 文档:https://pay.weixin.qq.com/doc/v3/partner/4012738519
*/
public function get_jsapi()
{
$order_sn = $this->request->post('order_sn/s', null);
if (!$order_sn) {
$this->error('支付失败');
}
//订单
$order_info = Db::name('order')->where(['order_sn'=>$order_sn])->find();
//非待支付订单
if ($order_info['status'] != 2) {
$this->error('支付失败');
}
//订单金额,单位为分
$order_amount = (float) $order_info['order_amount'];
//下单数据
$data = [
'appid' => $this->config['appId'],
'mchid' => $this->config['mchId'],
'description' => '订单标题【自定义】',
'out_trade_no' => $order_sn,
'time_expire' => getTimestampRFC3339($order_info['endtime']),
'notify_url' => SITE_URL . '/api/wxpay/callback',
'amount' => [
'total' => ($order_amount * 100),
'currency' => 'CNY'
],
'payer' => [
'openid' => $this->openid
]
];
$timestamp = time();
$nonceStr = self::createNonceStr();
$apiUrl = 'v3/pay/transactions/jsapi';
$url = $this->requestUrl.$apiUrl;
$url_parts = parse_url($url);
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
//支付
$instance = WxPayLibrary::getWxpayInstance();
$resp = $instance -> chain($apiUrl) -> post(['json' => $data,
'headers' => [
'Authorization' => $this->getAuthorizationSign($nonceStr, $timestamp, $canonical_url, json_encode($data)),
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'User-Agent' => 'application/json'
],
]);
$res = json_decode((string) $resp->getBody(), true);
if ( isset($res['prepay_id']) )
{
//success
$data = $this->get_request_param($timestamp, $nonceStr, $res['prepay_id']);
$this->success('拉起支付成功', $data);
}
else
{
$this->error('支付失败');
}
}
/*
* 支付回调
* 文档:https://pay.weixin.qq.com/doc/v3/partner/4012085146
*/
public function callback()
{
$getCallBackData = file_get_contents('php://input');
//将接收到的数据存入callBack.json文件中
file_put_contents($this->logs_path.'callBack.json', $getCallBackData."\n\r",FILE_APPEND);
//将回调密钥赋值给 解密类,回调密钥在“微信商户平台 > 账户中心 > API安全”
$getData = new AesUtil($this->config['payCallbackPrivateKey']);
//将变量由json类型数据转换为数组
$disposeReturnData = json_decode($getCallBackData, true);
//获取associated_data数据,附加数据
$associatedData = $disposeReturnData['resource']['associated_data'];
//获取nonce数据,加密使用的随机串
$nonceStr = $disposeReturnData['resource']['nonce'];
//获取ciphertext数据,base64编码后的数据密文
$ciphertext = $disposeReturnData['resource']['ciphertext'];
//解密得到数组
$result = $getData->decryptToString($associatedData, $nonceStr, $ciphertext);
$data = json_decode($result, true);
//记录回调解密数组
file_put_contents($this->logs_path.'callBackData.json', $result."\n\r",FILE_APPEND);
//支付成功
if ( $data['trade_state'] === 'SUCCESS' )
{
//处理回调业务逻辑
}
else
{
// 支付异常
return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
}
}
//获取配置
private function get_config()
{
$config = WxPayLibrary::getConfig();
return $this->config = $config;
}
//发起支付参数
private function get_request_param($timeStamp, $nonceStr, $prepay_id)
{
$data = [
'appId' => $this->config['appId'],
'timeStamp' => $timeStamp,
'nonceStr' => $nonceStr,
'package' => 'prepay_id='.$prepay_id,
'signType' => 'RSA'
];
$data['paySign'] = $this->getSign($data['appId'], $data['timeStamp'], $data['nonceStr'], $data['package']);
return $data;
}
//随机字符串
static function createNonceStr($length = 16)
{
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$str = '';
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
//微信支付签名
private function getSign($appid = null, $time = null, $randStr = null, $prepay_id = null)
{
$str = $appid . "\n" . $time . "\n" . $randStr . "\n" . $prepay_id . "\n";
$privateKeyStr = file_get_contents( str_replace('file://', '', $this->config['merchantPrivateKeyFilePath']) );
$data = getSha256WithRSA($str, $privateKeyStr);
return $data;
}
//公共签名
private function getAuthorizationSign($nonceStr, $timeStamp, $canonical_url, $data){
$key = openssl_get_privatekey(file_get_contents( str_replace('file://', '', $this->config['merchantPrivateKeyFilePath']) ));//这里是打开商户私钥证书
$method = 'POST'; //请求的方式
$message = $method . "\n" .$canonical_url. "\n" .$timeStamp . "\n" . $nonceStr . "\n" . $data . "\n"; //组合成一字符串 \n 需要每行携带,官方要求的
openssl_sign($message, $rawSign, $key,'sha256WithRSAEncryption'); //sha256WithRSA 加密
$sign = base64_encode($rawSign); //收到上面加密值做base64
return 'WECHATPAY2-SHA256-RSA2048 mchid='.$this->config['mchId'].',nonce_str="'.$nonceStr.'",timestamp="'.$timeStamp.'",serial_no='.$this->config['merchantCertificateSerial'].',signature="'.$sign.'"';//最后这一步,组合头部参数字符串,
}
}
这里官方给提供了一个回调密文解密类,文档:https://pay.weixin.qq.com/doc/v3/partner/4012082320
<?php
//微信支付解密
namespace util;
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');
}
}
回调密文(解密前)
{
"id": "EV-2018022511223320873",
"create_time": "2015-05-20T13:29:35+08:00",
"resource_type": "encrypt-resource",
"event_type": "TRANSACTION.SUCCESS",
"summary": "支付成功",
"resource": {
"original_type": "transaction",
"algorithm": "AEAD_AES_256_GCM",
"ciphertext": "",
"associated_data": "",
"nonce": ""
}
}
回调密文(解密后)
{
"sp_appid" : "wx8888888888888888",
"sp_mchid" : "1230000109",
"sub_appid" : "wxd678efh567hg6999",
"sub_mchid" : "1900000109",
"out_trade_no" : "1217752501201407033233368018",
"transaction_id" : "1217752501201407033233368018",
"trade_type" : "JSAPI",
"trade_state" : "SUCCESS",
"trade_state_desc" : "支付成功",
"bank_type" : "CMC",
"attach" : "自定义数据",
"success_time" : "2018-06-08T10:34:56+08:00",
"payer" : {
"sp_openid" : "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o\t",
"sub_openid" : "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o\t"
},
"amount" : {
"total" : 100,
"payer_total" : 100,
"currency" : "CNY",
"payer_currency" : "CNY"
},
"scene_info" : {
"device_id" : "013467007045764"
},
"promotion_detail" : [
{
"coupon_id" : "109519",
"name" : "单品惠-6",
"scope" : "GLOBAL",
"type" : "CASH",
"amount" : 100,
"stock_id" : "931386",
"wechatpay_contribute" : 0,
"merchant_contribute" : 0,
"other_contribute" : 0,
"currency" : "CNY",
"goods_detail" : [
{
"goods_id" : "M1006",
"quantity" : 1,
"unit_price" : 100,
"discount_amount" : 1,
"goods_remark" : "商品备注信息"
}
]
}
]
}
支付控制器还涉及到两个自定义函数,我给贴到下面,自行使用
/**
* 生成SHA256WithRSA签名
* @param string $data 待签名数据
* @param string $privateKey 私钥(PEM格式)
* @return string Base64编码的签名
*/
function getSha256WithRSA($data, $privateKey)
{
$key = openssl_pkey_get_private($privateKey);
if (!$key) {
throw new Exception("私钥无效");
}
openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256);
openssl_free_key($key);
return base64_encode($signature);
}
//生成 RFC 3339 时间格式 2018-06-08T10:34:56+08:00"
function getTimestampRFC3339($timestamp)
{
// 创建一个DateTime对象,使用时间戳初始化
$dateTime = new DateTime();
$dateTime->setTimestamp($timestamp);
// 格式化为RFC 3339
$rfc3339String = $dateTime->format(DateTime::RFC3339);
return $rfc3339String;
}


&spm=1001.2101.3001.5002&articleId=155265754&d=1&t=3&u=b37174131f624e8384e0c1267cc24182)
2903

被折叠的 条评论
为什么被折叠?



