1,请求接口签名方式——RSA,用到了API证书序列号,后缀为key.pem的密钥文件,cert文件要用java自己生成,具体参考:https://developers.weixin.qq.com/community/develop/doc/000e4a0d5dc1486acc19c6fd15bc00?_at=1569021781371
2,返给小程序或者H5的支付参数签名也要RSA加密,而且仅支持RSA加密。
3,支付成功回调的数据要解密,根据php版本有的需要安装libsodium-php扩展:http://pecl.php.net/package/libsodium 我公司的服务器是php7.0的
wget http://pecl.php.net/get/libsodium-2.0.10.tgz
pecl install libsodium-2.0.10.tgz
php.ini 文件添加扩展,重启php就可以了
extension=sodium.so
composer require wechatpay/wechatpay-guzzle-middleware
<?php
namespace app\store\controller\api;
use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Client;
/**
*
* Class PayV3
* @package app\store\controller\api
*/
class PayV3
{
// 商户相关配置
public $spAppId;
public $subAppId;
public $merchantId; // 商户号
public $merchantSerialNumber = ''; // 商户API证书序列号
public $merchantPrivateKey ; // 商户证书私钥
public $wechatpayCertificateV3; // 微信支付平台证书
public $key ;
public $keyV3 ;
public $out_trade_no;
public function __construct()
{
//服务商appid
$this->spAppId = 'wxxxxxxxxxxxxxxxxx';
//特约商户appid
$this->subAppId = 'wxxxxxxxxxxxxxxxxx';
//服务商商户号
$this->merchantId = '160xxxxxxx';
//特约商户商户号(测试用项目里做成变量)
$this->subMerchantId = '160xxxxxxx';
$this->key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
//apiV3 key
$this->keyV3 = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
//微信商户号查看
$this->merchantSerialNumber = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
//微信商户号下载
$this->merchantPrivateKey = PemUtil::loadPrivateKey('config/01jh_cert/apiclient_key.pem');
//此文件是java环境生成的不是商户号下载的,具体自作参考:https://developers.weixin.qq.com/community/develop/doc/000e4a0d5dc1486acc19c6fd15bc00?_at=1569021781371
//注意java版本 我之前的是1.8.0_131 不好使跟新为 1.8.0_291 生成成功了
$this->wechatpayCertificateV3 = PemUtil::loadCertificate('config/01jh_cert/v3_cert.pem');
}
private function client() {
$wechatpayMiddleware = WechatPayMiddleware::builder()
->withMerchant($this->merchantId, $this->merchantSerialNumber, $this->merchantPrivateKey) // 传入商户相关配置
->withWechatPay([ $this->wechatpayCertificateV3 ]) // 可传入多个微信支付平台证书,参数类型为array
->build();
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
// 创建Guzzle HTTP Client时,将HandlerStack传入
return new Client(['handler' => $stack]);
}
private function request($url,$data,$method='POST') {
$client = $this->client();
// 接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
try {
$resp = $client->request($method, $url, [
'headers' => ['Accept' => 'application/json', 'Content-Type' => 'application/json'],
'json' => $data,
]);
//echo $resp->getStatusCode().' '.$resp->getReasonPhrase()."\n";
//echo $resp->getBody()."\n";
if($resp->getReasonPhrase() == 'OK') {
return $resp->getBody();
}
return false;
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage();
// echo $e->getMessage()."\n";
// if ($e->hasResponse()) {
// echo $e->getResponse()->getStatusCode().' '.$e->getResponse()->getReasonPhrase()."\n";
// echo $e->getResponse()->getBody();
// }
}
}
//小程序获取支付参数(使用时直接调取这个方法就ok了)
public function jsapiParams($data) {
$this->out_trade_no = $data['out_trade_no'];
//这可以设置默认的参数,也可以不设置
$d = [
'sp_appid' => $this->spAppId ,
'sp_mchid' => $this->merchantId,
'sub_appid' => $this->subAppId,
//支付到特约商户上
'sub_mchid' => $this->subMerchantId,
'description' =>'自定义商品描述',
'out_trade_no' => $this->out_trade_no,
'notify_url' => 'https://xxxxx.xxxxx.com/ygyAdmin/public/store/api.v3pay/notify',
'amount' => ['total' => 1000],
//'payer' => ['sp_openid' => 'o8MCu5UvKGRs8gJsP_VNFLvZj_fM'],
'payer' => ['sub_openid' => 'ogD1q5Hz0bNdoPac0DYfV6jLcR0s'],
//是否分账
//'settle_info' =>['profit_sharing' => true]
];
foreach ($data as $k=>$v) $d[$k] = $v;
$body = $this->request('https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi',$d);
if($body === false) return false;
$nonce_str = $this->getNoncestr();
$prepay_id = json_decode($body,true)['prepay_id'];
$time = time();
//在哪个小程序下
//$tmp['appId'] = $this->appid;
$tmp['appId'] = $this->subAppId;
$tmp['timeStamp'] = $time;
$tmp['nonceStr'] = $nonce_str;
$tmp['package'] = 'prepay_id='.$prepay_id;
$data = array();
$data['state'] = 200;
$data['timeStamp'] = "$time";
$data['nonceStr'] = $nonce_str;
$data['package'] = 'prepay_id='.$prepay_id;//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
$data['paySign'] = $this->sign($tmp);
$data['out_trade_no'] = $this->out_trade_no;
return $data;
}
//签名 $data要先排好顺序
private function sign($data){
$string = '';
foreach ($data as $key=>$value){
if(!$value) continue;
$string .= $value."\n";
}
$private_key = $this->merchantPrivateKey;
openssl_sign($string, $raw_sign, $private_key, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
return $sign;
}
/**
* 获取随机数
*/
public function getNoncestr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
/**
* 支付回调数据解密
* @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->aesKeyV3);
}
// 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->aesKeyV3);
}
// 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->aesKeyV3, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
}