目前只使用到jsapi支付,后续使用到了其他支付再更新代码
加载guzzlehttp和wechatpay的composer包
在composer.json 添加包信息 然后执行composer update
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/uri-template": "^1.0",
"wechatpay/wechatpay": "^1.4"
composter手册
案例
<?php
namespace plugins\wxapp\service;
use think\Cache;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;
use think\DB;
class WxpayService
{
public $merchantId = '';
public $merchantPrivateKeyFilePath = '';
public $merchantPrivateKeyInstance = '';
public $merchantCertificateSerial = '';
public $platformCertificateFilePath = '';
public $platformPublicKeyInstance = '';
public $platformCertificateSerial = '';
public $appid = '';
public $secret = '';
public $paySecret = '';
public $v3Key = '';
public $payTest = 0;
public $instance;
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
function __construct()
{
$config = $this->getConfig();
$this->appid = $config['appid'];
$this->merchantId = $config['merchantId'];
$this->merchantPrivateKeyFilePath = trim($config['merchantPrivateKeyFilePath']);
$Rsa = new Rsa();
$this->merchantPrivateKeyInstance = $Rsa::from($this->merchantPrivateKeyFilePath, $Rsa::KEY_TYPE_PRIVATE);
$this->merchantCertificateSerial = $config['merchantCertificateSerial'];
$this->secret = $config['secret'];
$this->paySecret = $config['paySecret'];
$this->v3Key = $config['v3Key'];
$this->payTest = $config['payTest'];
$this->platformCertificateFilePath = trim($config['platformCertificateFilePath']);
$this->platformPublicKeyInstance = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
$this->platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
$this->instance = Builder::factory([
'mchid' => $this->merchantId,
'serial' => $this->merchantCertificateSerial,
'privateKey' => $this->merchantPrivateKeyInstance,
'certs' => [
$this->platformCertificateSerial => $this->platformPublicKeyInstance,
],
]);
}
public function doPay($params)
{
$trade_type = $params['trade_type'] ?? 'h5';
$amount = (int)(bcmul("{$params['WIDtotal_amount']}","100"));
$requestData = [
'json' => [
'appid' => $this->appid,
'mchid' => $this->merchantId,
'description' => $params['WIDsubject'],
'out_trade_no' => $params['WIDout_trade_no'],
'notify_url' => $params['notifyUrl'],
'amount' => [
'total' => !$this->payTest ? $amount : 1,
'currency' => 'CNY'
],
'scene_info' => [
'payer_client_ip' => get_client_ip()
],
'attach' => $params['attach'] ?? ''
],
'headers' => ['Accept' => 'application/json']
];
if ($trade_type == 'jsapi') {
if (!isset($params['openid']) || empty($params['openid'])) {
return ['code' => 0, 'message' => 'Openid不能为空'];
}
$requestData['json']['payer'] = [
'openid' => $params['openid']
];
}
if ($trade_type == 'h5') {
$requestData['json']['scene_info']['h5_info']['type'] = "Wap";
}
try{
$resp = $this->instance
->chain("v3/pay/transactions/{$trade_type}")
->post($requestData);
$content = $resp->getBody();
$data = json_decode($content, true);
$ret = $this->getTypeInfo($trade_type,$data);
$info['code'] = $resp->getStatusCode();
$info['data'] = $ret;
return $info;
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$code = $r->getStatusCode();
$data = json_decode($r->getBody(),true);
if(isset($data['prepay_id'])){
$info['code'] = 200;
$ret = $this->getTypeInfo($trade_type,$data);
$info['data'] = $ret;
return $info;
}
}
return false;
}
}
public function getTypeInfo($trade_type,$data){
switch ($trade_type) {
case 'app':
$ret = ['orderInfo' => $this->getOrderInfo($data['prepay_id'])];
break;
case 'jsapi':
$ret = ['jsApiInfo' => $this->getJsApiInfo($data['prepay_id'])];
break;
case 'h5':
$ret = ['h5_url' => $data['h5_url']];
break;
case 'native':
$ret = ['code_url' => $data['code_url']];
break;
default:
break;
}
return $ret;
}
public function getJsApiInfo($prepay_id){
$info = [
"appId"=>$this->appid,
"timeStamp"=>time(),
"nonceStr"=>$this->getNonceStr(16),
"package"=>"prepay_id=$prepay_id",
];
$paySign = $this->RSAsign($info,$this->merchantPrivateKeyFilePath);
return $paySign;
}
public static function MD5sign($array, $wx_key) {
ksort($array);
$stringA = urldecode(http_build_query($array));
$stringSignTemp="$stringA&key=".$wx_key;
return strtoupper(md5($stringSignTemp));
}
public static function RSAsign($params,$merchantPrivateKeyInstance) {
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance
), 'signType' => 'RSA'];
return $params;
}
public function getOrderInfo($prepay_id)
{
$nonceStr = $this->getNonceStr(16);
$package = "Sign=WXPay";
$timestamp = time();
$paySign = $this->paySign($this->appid, $timestamp, $nonceStr, $package);
return [
'appid' => $this->appid,
'noncestr' => $nonceStr,
'package' => $package,
'partnerid' => $this->merchantId,
'prepayid' => $prepay_id,
'timestamp' => $timestamp,
'sign' => $paySign,
];
}
public static function getNonceStr($length = 16)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str ="";
for ( $i = 0; $i < $length; $i++ ) {
$str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
public function getAccessToken($code,$user_id){
$accessToken = Cache::get('accessToken:'.$user_id);
if(!isset($accessToken['errcode'])){
if($accessToken) return $accessToken;
}
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=$this->appid&secret=$this->secret&code=$code&grant_type=authorization_code";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ch);
curl_close($ch);
$arr = json_decode($res, true);
if(isset($arr['errcode'])){
return false;
}
Cache::set('accessToken:'.$user_id,$arr,'7200');
return $arr;
}
public function notify($post_data)
{
if ($post_data['event_type'] == 'TRANSACTION.SUCCESS') {
$nonceStr = $post_data['resource']['nonce'];
$associatedData = $post_data['resource']['associated_data'];
$ciphertext = $post_data['resource']['ciphertext'];
$ciphertext = base64_decode($ciphertext);
if (strlen($ciphertext) <= 12) {
return;
}
$result = $this->wechartDecrypt($ciphertext,$associatedData,$nonceStr);
return $result;
}
return ;
}
public function wechartDecrypt($ciphertext,$associatedData, $nonceStr) {
$key = $this->v3Key;
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
$str = \ sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $key);
return json_decode($str,true);
}
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\ crypto_aead_aes256gcm_is_available()) {
$str = \ Sodium\ crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $key);
return json_decode($str,true);
}
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);
$str = \ openssl_decrypt($ctext, 'aes-256-gcm', $key, \OPENSSL_RAW_DATA, $nonceStr,
$authTag, $associatedData);
return json_decode($str,true);
}
return '';
throw new\ RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
public function getConfig(){
$payconfig = DB::name('plugin')->where('id',8)->value('config');
return json_decode($payconfig,true);
}
}
?>
|