775 lines
29 KiB
PHP
775 lines
29 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
namespace app\controller;
|
|||
|
|
|
|||
|
|
|
|||
|
|
use think\facade\Db;
|
|||
|
|
use think\facade\Log;
|
|||
|
|
|
|||
|
|
class WechatPay
|
|||
|
|
{
|
|||
|
|
//微信支付
|
|||
|
|
const KEY_LENGTH_BYTE = 32;
|
|||
|
|
const AUTH_TAG_LENGTH_BYTE = 16;
|
|||
|
|
|
|||
|
|
public function config($openid, $money)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
// 商户相关配置
|
|||
|
|
$merchantId = config('wx.merchantId'); // 商户号
|
|||
|
|
$merchantSerialNumber = config('wx.merchantSerialNumber');
|
|||
|
|
$filepath = __DIR__ . config('wx.apiclientKey'); //私钥在本地的位置
|
|||
|
|
$file = file_get_contents($filepath);
|
|||
|
|
$mch_private_key = openssl_get_privatekey($file);
|
|||
|
|
|
|||
|
|
$appid = config('wx.AppID'); //小程序appid
|
|||
|
|
$out_trade_no = $this->generate_recharge(6);
|
|||
|
|
|
|||
|
|
$data = [
|
|||
|
|
"appid" => $appid,
|
|||
|
|
"mchid" => $merchantId,
|
|||
|
|
"description" => '个人钱包充值',
|
|||
|
|
'out_trade_no' => $out_trade_no,
|
|||
|
|
'notify_url' => 'https://' . $_SERVER['HTTP_HOST'] . '/sharenotify', //回调地址
|
|||
|
|
"amount" => [
|
|||
|
|
"total" => $money * 100,
|
|||
|
|
"currency" => "CNY"
|
|||
|
|
],
|
|||
|
|
"payer" => [
|
|||
|
|
"openid" => $openid //用户openid
|
|||
|
|
]
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$timestamp = time();
|
|||
|
|
$nonce = date('YmdHis', time()) . rand(1000, 9999);
|
|||
|
|
$url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';
|
|||
|
|
$url_parts = parse_url($url);
|
|||
|
|
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
|
|||
|
|
|
|||
|
|
$data = json_encode($data);
|
|||
|
|
$message = 'POST' . "\n" .
|
|||
|
|
$canonical_url . "\n" .
|
|||
|
|
$timestamp . "\n" .
|
|||
|
|
$nonce . "\n" .
|
|||
|
|
$data . "\n";
|
|||
|
|
|
|||
|
|
openssl_sign($message, $signature, $mch_private_key, "sha256WithRSAEncryption");
|
|||
|
|
$sign = base64_encode($signature);
|
|||
|
|
$schema = 'WECHATPAY2-SHA256-RSA2048';
|
|||
|
|
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchantId, $nonce, $timestamp, $merchantSerialNumber, $sign);
|
|||
|
|
|
|||
|
|
$header = "Authorization: " . $schema . " " . $token;
|
|||
|
|
|
|||
|
|
$res = $this->http_post($url, $header, $data);
|
|||
|
|
$arr = json_decode($res, true);
|
|||
|
|
|
|||
|
|
$time = time();
|
|||
|
|
$str = time() . round('1000', '9999');
|
|||
|
|
$prepay = 'prepay_id=' . $arr['prepay_id'];
|
|||
|
|
|
|||
|
|
$message1 = $appid . "\n" .
|
|||
|
|
$time . "\n" .
|
|||
|
|
$str . "\n" .
|
|||
|
|
$prepay . "\n";
|
|||
|
|
$prepay_id = $arr['prepay_id'];
|
|||
|
|
|
|||
|
|
openssl_sign($message1, $signature, $mch_private_key, "sha256WithRSAEncryption");
|
|||
|
|
$sign1 = base64_encode($signature);
|
|||
|
|
|
|||
|
|
$data = array();
|
|||
|
|
$data['appId'] = $appid;
|
|||
|
|
$data['timeStamp'] = (string)$time;
|
|||
|
|
$data['nonceStr'] = $str;
|
|||
|
|
$data['package'] = 'prepay_id=' . $arr['prepay_id'];
|
|||
|
|
$data['signType'] = 'RSA';
|
|||
|
|
|
|||
|
|
$data['paySign'] = $sign1;
|
|||
|
|
|
|||
|
|
return json($data);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
function http_post($url, $header, $data)
|
|||
|
|
{
|
|||
|
|
$headers[] = "Accept:application/json";
|
|||
|
|
$headers[] = "Content-Type:application/json";
|
|||
|
|
$headers[] = "User-Agent:application/json";
|
|||
|
|
$headers[] = $header;
|
|||
|
|
|
|||
|
|
$curl = curl_init(); // 启动一个CURL会话
|
|||
|
|
curl_setopt($curl, CURLOPT_URL, $url);
|
|||
|
|
curl_setopt($curl, CURLOPT_HEADER, 0);
|
|||
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
|||
|
|
curl_setopt($curl, CURLOPT_POST, 1);
|
|||
|
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
|||
|
|
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
|
|||
|
|
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
|
|||
|
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
|||
|
|
$tmpInfo = curl_exec($curl);
|
|||
|
|
//关闭URL请求
|
|||
|
|
curl_close($curl);
|
|||
|
|
return $tmpInfo;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//支付回调
|
|||
|
|
public function sharenotify()
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
//code...
|
|||
|
|
|
|||
|
|
$header = $this->getHeaders(); //读取http头信息 见下文
|
|||
|
|
$body = file_get_contents('php://input'); //读取微信传过来的信息,是一个json字符串
|
|||
|
|
|
|||
|
|
if (empty($header) || empty($body)) {
|
|||
|
|
throw new \Exception('通知参数为空', 2001);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$timestamp = $header['WECHATPAY-TIMESTAMP'];
|
|||
|
|
$nonce = $header['WECHATPAY-NONCE'];
|
|||
|
|
$signature = $header['WECHATPAY-SIGNATURE'];
|
|||
|
|
$serialNo = $header['WECHATPAY-SERIAL'];
|
|||
|
|
if (empty($timestamp) || empty($nonce) || empty($signature) || empty($serialNo)) {
|
|||
|
|
throw new \Exception('通知头参数为空', 2002);
|
|||
|
|
}
|
|||
|
|
$cert = $this->getzhengshuDb(1);
|
|||
|
|
|
|||
|
|
if ($cert != $serialNo) {
|
|||
|
|
throw new \Exception('验签失败', 2005);
|
|||
|
|
}
|
|||
|
|
$message = "$timestamp\n$nonce\n$body\n";
|
|||
|
|
|
|||
|
|
//校验签名
|
|||
|
|
if (!$this->verify($message, $signature, __DIR__ . config('wx.pingtai_public_key_path'))) {
|
|||
|
|
throw new \Exception('验签失败', 2005);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$decodeBody = json_decode($body, true);
|
|||
|
|
if (empty($decodeBody) || !isset($decodeBody['resource'])) {
|
|||
|
|
throw new \Exception('通知参数内容为空', 2003);
|
|||
|
|
}
|
|||
|
|
$decodeBodyResource = $decodeBody['resource'];
|
|||
|
|
$decodeData_res = $this->decryptToString($decodeBodyResource['associated_data'], $decodeBodyResource['nonce'], $decodeBodyResource['ciphertext'], ''); //解密resource
|
|||
|
|
$decodeData = json_decode($decodeData_res, true);
|
|||
|
|
Log::error('用户充值: ' . $decodeData_res);
|
|||
|
|
|
|||
|
|
//返回结果格式
|
|||
|
|
//array (
|
|||
|
|
// 'mchid' => 'xxx',
|
|||
|
|
// 'appid' => 'xxxxxxx',
|
|||
|
|
// 'out_trade_no' => '1217752501201407033233368026',
|
|||
|
|
// 'transaction_id' => '4200001336202201037507057791',
|
|||
|
|
// 'trade_type' => 'NATIVE',
|
|||
|
|
// 'trade_state' => 'SUCCESS',
|
|||
|
|
// 'trade_state_desc' => '支付成功',
|
|||
|
|
// 'bank_type' => 'OTHERS',
|
|||
|
|
// 'attach' => '',
|
|||
|
|
// 'success_time' => '2022-01-03T19:43:05+08:00',
|
|||
|
|
// 'payer' =>
|
|||
|
|
// array (
|
|||
|
|
// 'openid' => 'ovs326bgwfA4o8jlFQXMEma2JZek',
|
|||
|
|
// ),
|
|||
|
|
// 'amount' =>
|
|||
|
|
// array (
|
|||
|
|
// 'total' => 1,
|
|||
|
|
// 'payer_total' => 1,
|
|||
|
|
// 'currency' => 'CNY',
|
|||
|
|
// 'payer_currency' => 'CNY',
|
|||
|
|
// ),
|
|||
|
|
// )
|
|||
|
|
//执行自己的代码start
|
|||
|
|
$openid = $decodeData['payer']['openid'];
|
|||
|
|
|
|||
|
|
$data = [
|
|||
|
|
'out_trade_no' => $decodeData['out_trade_no'],
|
|||
|
|
'transaction_id' => $decodeData['transaction_id'],
|
|||
|
|
'trade_state' => $decodeData['trade_state'],
|
|||
|
|
'success_time' => date('Y-m-d H:i:s', time()),
|
|||
|
|
'total' => $decodeData['amount']['total'] / 100,
|
|||
|
|
'payer_total' => $decodeData['amount']['payer_total'] / 100,
|
|||
|
|
'openid' => $openid
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
|
|||
|
|
Db::table('zxc_recharge')->save($data);
|
|||
|
|
Db::table('zxc_user')
|
|||
|
|
->where('openid', $openid)
|
|||
|
|
->update([
|
|||
|
|
'account' => Db::raw('account+' . ($decodeData['amount']['total'] / 100))
|
|||
|
|
]);
|
|||
|
|
\app\model\User::addMoneyLog($openid, $decodeData['amount']['total'] / 100, 2, '用户充值');
|
|||
|
|
|
|||
|
|
|
|||
|
|
//执行自己的代码end
|
|||
|
|
|
|||
|
|
exit();
|
|||
|
|
|
|||
|
|
} catch (\Exception $e) {
|
|||
|
|
Log::error($e->getMessage());
|
|||
|
|
$arr = array("code" => "ERROR", "message" => $e->getMessage());
|
|||
|
|
echo json_encode($arr);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function getHeaders()
|
|||
|
|
{
|
|||
|
|
$header = array();
|
|||
|
|
|
|||
|
|
foreach ($_SERVER as $key => $value) {
|
|||
|
|
if ('HTTP_' == substr($key, 0, 5)) {
|
|||
|
|
$header[str_replace('_', '-', substr($key, 5))] = $value;
|
|||
|
|
}
|
|||
|
|
if (isset($_SERVER['PHP_AUTH_DIGEST'])) {
|
|||
|
|
$header['AUTHORIZATION'] = $_SERVER['PHP_AUTH_DIGEST'];
|
|||
|
|
} elseif (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
|
|||
|
|
$header['AUTHORIZATION'] = base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW']);
|
|||
|
|
}
|
|||
|
|
if (isset($_SERVER['CONTENT_LENGTH'])) {
|
|||
|
|
$header['CONTENT-LENGTH'] = $_SERVER['CONTENT_LENGTH'];
|
|||
|
|
}
|
|||
|
|
if (isset($_SERVER['CONTENT_TYPE'])) {
|
|||
|
|
$header['CONTENT-TYPE'] = $_SERVER['CONTENT_TYPE'];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $header;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//获取平台证书序列号
|
|||
|
|
public function getzhengshuDb($getNew = 0)
|
|||
|
|
{
|
|||
|
|
if ($getNew !== 1) {
|
|||
|
|
dump(file_get_contents(__DIR__ . config('wx.pingtai_public_key_path')));
|
|||
|
|
}
|
|||
|
|
$url = "https://api.mch.weixin.qq.com/v3/certificates";
|
|||
|
|
$timestamp = time(); //时间戳
|
|||
|
|
$nonce = $this->nonce_str(); //获取一个随机数
|
|||
|
|
$body = "";
|
|||
|
|
$mch_private_key = $this->getPrivateKey(); //读取商户api证书私钥
|
|||
|
|
$merchant_id = config('wx.merchantId'); //服务商商户号
|
|||
|
|
$serial_no = config('wx.merchantSerialNumber'); //在API安全中获取
|
|||
|
|
$sign = $this->sign($url, 'GET', $timestamp, $nonce, $body, $mch_private_key, $merchant_id, $serial_no); //签名
|
|||
|
|
|
|||
|
|
$header = [
|
|||
|
|
'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $sign,
|
|||
|
|
'Accept:application/json',
|
|||
|
|
'User-Agent:' . $merchant_id
|
|||
|
|
];
|
|||
|
|
$result = $this->curl($url, '', $header, 'GET');
|
|||
|
|
$result = json_decode($result, true);
|
|||
|
|
$serial_no = $result['data'][0]['serial_no'];
|
|||
|
|
file_put_contents(__DIR__ . '/../../Secret/serial_no.txt', $serial_no);
|
|||
|
|
|
|||
|
|
$encrypt_certificate = $result['data'][0]['encrypt_certificate'];
|
|||
|
|
$sign_key = config('wx.apiV3key'); //在API安全中设置
|
|||
|
|
$result = $this->decryptToString($encrypt_certificate['associated_data'], $encrypt_certificate['nonce'], $encrypt_certificate['ciphertext'], $sign_key); //解密
|
|||
|
|
|
|||
|
|
file_put_contents(__DIR__ . config('wx.pingtai_public_key_path'), $result);
|
|||
|
|
|
|||
|
|
return $serial_no;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//生成随机字符串
|
|||
|
|
public function nonce_str($length = 32)
|
|||
|
|
{
|
|||
|
|
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|||
|
|
$str = "";
|
|||
|
|
for ($i = 0; $i < $length; $i++) {
|
|||
|
|
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
|
|||
|
|
}
|
|||
|
|
return $str;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//读取商户api证书私钥
|
|||
|
|
public function getPrivateKey()
|
|||
|
|
{
|
|||
|
|
return openssl_get_privatekey(file_get_contents(__DIR__ . config('wx.apiclientKey'))); //微信商户平台中下载下来,保存到服务器直接读取
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//签名
|
|||
|
|
public function sign($url, $http_method, $timestamp, $nonce, $body, $mch_private_key, $merchant_id, $serial_no)
|
|||
|
|
{
|
|||
|
|
$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";
|
|||
|
|
openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
|
|||
|
|
$sign = base64_encode($raw_sign);
|
|||
|
|
$schema = 'WECHATPAY2-SHA256-RSA2048';
|
|||
|
|
$token = sprintf(
|
|||
|
|
'mchid="%s",nonce_str="%s",signature="%s",timestamp="%d",serial_no="%s"',
|
|||
|
|
$merchant_id,
|
|||
|
|
$nonce,
|
|||
|
|
$sign,
|
|||
|
|
$timestamp,
|
|||
|
|
$serial_no
|
|||
|
|
);
|
|||
|
|
return $token;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//curl提交
|
|||
|
|
public function curl($url, $data = [], $header, $method = 'POST')
|
|||
|
|
{
|
|||
|
|
$curl = curl_init();
|
|||
|
|
curl_setopt($curl, CURLOPT_URL, $url);
|
|||
|
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
|
|||
|
|
curl_setopt($curl, CURLOPT_HEADER, false);
|
|||
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
|||
|
|
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
|||
|
|
if ($method == "POST") {
|
|||
|
|
curl_setopt($curl, CURLOPT_POST, TRUE);
|
|||
|
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
|
|||
|
|
}
|
|||
|
|
$result = curl_exec($curl);
|
|||
|
|
curl_close($curl);
|
|||
|
|
return $result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private function decryptToString($associatedData, $nonceStr, $ciphertext, $aesKey = '')
|
|||
|
|
{
|
|||
|
|
if (empty($aesKey)) {
|
|||
|
|
$aesKey = config('wx.apiV3key'); //微信商户平台 api安全中设置获取
|
|||
|
|
}
|
|||
|
|
$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, $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, $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',
|
|||
|
|
$aesKey,
|
|||
|
|
\OPENSSL_RAW_DATA,
|
|||
|
|
$nonceStr,
|
|||
|
|
$authTag,
|
|||
|
|
$associatedData
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//签名验证操作
|
|||
|
|
private function verify($message, $signature, $merchantPublicKey)
|
|||
|
|
{
|
|||
|
|
if (!in_array('sha256WithRSAEncryption', \openssl_get_md_methods(true))) {
|
|||
|
|
throw new \RuntimeException("当前PHP环境不支持SHA256withRSA");
|
|||
|
|
}
|
|||
|
|
$signature = base64_decode($signature);
|
|||
|
|
$a = openssl_verify($message, $signature, $this->getWxPublicKey($merchantPublicKey), 'sha256WithRSAEncryption');
|
|||
|
|
return $a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//获取平台公钥 获取平台证书序列号时存起来的cert.pem文件
|
|||
|
|
protected function getWxPublicKey($key)
|
|||
|
|
{
|
|||
|
|
$public_content = file_get_contents($key);
|
|||
|
|
$a = openssl_get_publickey($public_content);
|
|||
|
|
return $a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected function generate_recharge($length)
|
|||
|
|
{
|
|||
|
|
$chars = '0123456789';
|
|||
|
|
$time = time();
|
|||
|
|
$password = 'DZZS' . $time . 'RE';
|
|||
|
|
for ($i = 0; $i < $length; $i++) {
|
|||
|
|
$password .= $chars[mt_rand(0, strlen($chars) - 1)];
|
|||
|
|
}
|
|||
|
|
return $password;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 企业充值
|
|||
|
|
public function EnterpriseConfig()
|
|||
|
|
{
|
|||
|
|
$params = input();
|
|||
|
|
// 商户相关配置
|
|||
|
|
$merchantId = config('wx.merchantId'); // 商户号
|
|||
|
|
$merchantSerialNumber = config('wx.merchantSerialNumber');
|
|||
|
|
$filepath = __DIR__ . config('wx.apiclientKey'); //私钥在本地的位置
|
|||
|
|
$file = file_get_contents($filepath);
|
|||
|
|
$mch_private_key = openssl_get_privatekey($file);
|
|||
|
|
|
|||
|
|
$appid = config('wx.AppID'); //小程序appid
|
|||
|
|
$out_trade_no = $this->generate_recharge(6);
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 创建企业充值订单
|
|||
|
|
Db::table('enterprise_recharge')->save([
|
|||
|
|
'user_id' => $params['enterprise_user_id'],
|
|||
|
|
'enterprise_id' => $params['enterprise_id'],
|
|||
|
|
'recharge_no' => $out_trade_no,
|
|||
|
|
'money' => $params['money'],
|
|||
|
|
'create_time' => time()
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
|
|||
|
|
$data = [
|
|||
|
|
"appid" => $appid,
|
|||
|
|
"mchid" => $merchantId,
|
|||
|
|
"description" => '充值企业余额',
|
|||
|
|
'out_trade_no' => $out_trade_no,
|
|||
|
|
'notify_url' => 'https://' . $_SERVER['HTTP_HOST'] . '/enterpriseChargeNotify', //回调地址
|
|||
|
|
"amount" => [
|
|||
|
|
"total" => $params['money'] * 100,
|
|||
|
|
"currency" => "CNY"
|
|||
|
|
],
|
|||
|
|
"payer" => [
|
|||
|
|
"openid" => $params['openid'] //用户openid
|
|||
|
|
]
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$timestamp = time();
|
|||
|
|
$nonce = date('YmdHis', time()) . rand(1000, 9999);
|
|||
|
|
$url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';
|
|||
|
|
$url_parts = parse_url($url);
|
|||
|
|
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
|
|||
|
|
|
|||
|
|
$data = json_encode($data);
|
|||
|
|
$message = 'POST' . "\n" .
|
|||
|
|
$canonical_url . "\n" .
|
|||
|
|
$timestamp . "\n" .
|
|||
|
|
$nonce . "\n" .
|
|||
|
|
$data . "\n";
|
|||
|
|
|
|||
|
|
openssl_sign($message, $signature, $mch_private_key, "sha256WithRSAEncryption");
|
|||
|
|
$sign = base64_encode($signature);
|
|||
|
|
$schema = 'WECHATPAY2-SHA256-RSA2048';
|
|||
|
|
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchantId, $nonce, $timestamp, $merchantSerialNumber, $sign);
|
|||
|
|
|
|||
|
|
$header = "Authorization: " . $schema . " " . $token;
|
|||
|
|
|
|||
|
|
$res = $this->http_post($url, $header, $data);
|
|||
|
|
$arr = json_decode($res, true);
|
|||
|
|
|
|||
|
|
$time = time();
|
|||
|
|
$str = time() . round('1000', '9999');
|
|||
|
|
$prepay = 'prepay_id=' . $arr['prepay_id'];
|
|||
|
|
|
|||
|
|
$message1 = $appid . "\n" .
|
|||
|
|
$time . "\n" .
|
|||
|
|
$str . "\n" .
|
|||
|
|
$prepay . "\n";
|
|||
|
|
$prepay_id = $arr['prepay_id'];
|
|||
|
|
$key = 'ZuXingZhiYeYouXianGongSi15182231';
|
|||
|
|
// $paySign = strtoupper(MD5("appId=$appid&nonceStr=$str&package=prepay_id=$prepay_id&signType=MD5&timeStamp=$time&key=$key"));
|
|||
|
|
openssl_sign($message1, $signature, $mch_private_key, "sha256WithRSAEncryption");
|
|||
|
|
$sign1 = base64_encode($signature);
|
|||
|
|
|
|||
|
|
$data = array();
|
|||
|
|
$data['appId'] = $appid;
|
|||
|
|
$data['timeStamp'] = (string)$time;
|
|||
|
|
$data['nonceStr'] = $str;
|
|||
|
|
$data['package'] = 'prepay_id=' . $arr['prepay_id'];
|
|||
|
|
$data['signType'] = 'RSA';
|
|||
|
|
// $data['signType'] = $arr['prepay_id'];
|
|||
|
|
$data['paySign'] = $sign1;
|
|||
|
|
|
|||
|
|
return json($data);
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
// 企业充值回调
|
|||
|
|
//支付回调
|
|||
|
|
public function enterpriseChargeNotify()
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
//code...
|
|||
|
|
|
|||
|
|
$header = $this->getHeaders(); //读取http头信息 见下文
|
|||
|
|
$body = file_get_contents('php://input'); //读取微信传过来的信息,是一个json字符串
|
|||
|
|
|
|||
|
|
if (empty($header) || empty($body)) {
|
|||
|
|
throw new \Exception('通知参数为空', 2001);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$timestamp = $header['WECHATPAY-TIMESTAMP'];
|
|||
|
|
$nonce = $header['WECHATPAY-NONCE'];
|
|||
|
|
$signature = $header['WECHATPAY-SIGNATURE'];
|
|||
|
|
$serialNo = $header['WECHATPAY-SERIAL'];
|
|||
|
|
if (empty($timestamp) || empty($nonce) || empty($signature) || empty($serialNo)) {
|
|||
|
|
throw new \Exception('通知头参数为空', 2002);
|
|||
|
|
}
|
|||
|
|
$cert = $this->getzhengshuDb(1);
|
|||
|
|
|
|||
|
|
if ($cert != $serialNo) {
|
|||
|
|
throw new \Exception('验签失败', 2005);
|
|||
|
|
}
|
|||
|
|
$message = "$timestamp\n$nonce\n$body\n";
|
|||
|
|
|
|||
|
|
//校验签名
|
|||
|
|
if (!$this->verify($message, $signature, __DIR__ . config('wx.pingtai_public_key_path'))) {
|
|||
|
|
throw new \Exception('验签失败', 2005);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$decodeBody = json_decode($body, true);
|
|||
|
|
if (empty($decodeBody) || !isset($decodeBody['resource'])) {
|
|||
|
|
throw new \Exception('通知参数内容为空', 2003);
|
|||
|
|
}
|
|||
|
|
$decodeBodyResource = $decodeBody['resource'];
|
|||
|
|
$decodeData_res = $this->decryptToString($decodeBodyResource['associated_data'], $decodeBodyResource['nonce'], $decodeBodyResource['ciphertext'], ''); //解密resource
|
|||
|
|
$decodeData = json_decode($decodeData_res, true);
|
|||
|
|
Log::error('企业充值返回数据: ' . $decodeData_res);
|
|||
|
|
Db::table('charge_logo')->save(['name' => '企业充值返回数据', 'mark' => $decodeData_res]);
|
|||
|
|
|
|||
|
|
//返回结果格式
|
|||
|
|
//array (
|
|||
|
|
// 'mchid' => 'xxx',
|
|||
|
|
// 'appid' => 'xxxxxxx',
|
|||
|
|
// 'out_trade_no' => '1217752501201407033233368026',
|
|||
|
|
// 'transaction_id' => '4200001336202201037507057791',
|
|||
|
|
// 'trade_type' => 'NATIVE',
|
|||
|
|
// 'trade_state' => 'SUCCESS',
|
|||
|
|
// 'trade_state_desc' => '支付成功',
|
|||
|
|
// 'bank_type' => 'OTHERS',
|
|||
|
|
// 'attach' => '',
|
|||
|
|
// 'success_time' => '2022-01-03T19:43:05+08:00',
|
|||
|
|
// 'payer' =>
|
|||
|
|
// array (
|
|||
|
|
// 'openid' => 'ovs326bgwfA4o8jlFQXMEma2JZek',
|
|||
|
|
// ),
|
|||
|
|
// 'amount' =>
|
|||
|
|
// array (
|
|||
|
|
// 'total' => 1,
|
|||
|
|
// 'payer_total' => 1,
|
|||
|
|
// 'currency' => 'CNY',
|
|||
|
|
// 'payer_currency' => 'CNY',
|
|||
|
|
// ),
|
|||
|
|
// )
|
|||
|
|
//执行自己的代码start
|
|||
|
|
$info = Db::table('enterprise_recharge')->where('recharge_no', $decodeData['out_trade_no'])->where('status', 0)->find();
|
|||
|
|
if ($info && $decodeData['trade_state'] == 'SUCCESS') {
|
|||
|
|
// 充值成功,修改状态 增加余额
|
|||
|
|
Db::table('enterprise_recharge')->where('id', $info['id'])->update(['status' => 1]);
|
|||
|
|
|
|||
|
|
Db::table('enterprise')->where('id', $info['enterprise_id'])->inc('money', $info['money'])->update();
|
|||
|
|
|
|||
|
|
\app\model\User::addMoneyLog($decodeData['payer']['openid'], $decodeData['amount']['total']/100, 2, '用户进行企业充值');
|
|||
|
|
}
|
|||
|
|
echo 'OK';
|
|||
|
|
} catch (\Exception $e) {
|
|||
|
|
Log::error($e->getMessage());
|
|||
|
|
$arr = array("code" => "ERROR", "message" => $e->getMessage());
|
|||
|
|
echo json_encode($arr);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 即充即退支付信息
|
|||
|
|
public function directlyConfig($out_trade_no, $openid, $money)
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
// 商户相关配置
|
|||
|
|
$merchantId = config('wx.merchantId'); // 商户号
|
|||
|
|
$merchantSerialNumber = config('wx.merchantSerialNumber');
|
|||
|
|
$filepath = __DIR__ . config('wx.apiclientKey'); //私钥在本地的位置
|
|||
|
|
$file = file_get_contents($filepath);
|
|||
|
|
$mch_private_key = openssl_get_privatekey($file);
|
|||
|
|
|
|||
|
|
$appid = config('wx.AppID'); //小程序appid
|
|||
|
|
|
|||
|
|
$data = [
|
|||
|
|
"appid" => $appid,
|
|||
|
|
"mchid" => $merchantId,
|
|||
|
|
"description" => '即充即退',
|
|||
|
|
'out_trade_no' => $out_trade_no,
|
|||
|
|
'notify_url' => 'https://' . $_SERVER['HTTP_HOST'] . '/directlychargenotify', //回调地址
|
|||
|
|
"amount" => [
|
|||
|
|
"total" => $money * 100,
|
|||
|
|
"currency" => "CNY"
|
|||
|
|
],
|
|||
|
|
"payer" => [
|
|||
|
|
"openid" => $openid //用户openid
|
|||
|
|
]
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$timestamp = time();
|
|||
|
|
$nonce = date('YmdHis', time()) . rand(1000, 9999);
|
|||
|
|
$url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';
|
|||
|
|
$url_parts = parse_url($url);
|
|||
|
|
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
|
|||
|
|
|
|||
|
|
$data = json_encode($data);
|
|||
|
|
$message = 'POST' . "\n" .
|
|||
|
|
$canonical_url . "\n" .
|
|||
|
|
$timestamp . "\n" .
|
|||
|
|
$nonce . "\n" .
|
|||
|
|
$data . "\n";
|
|||
|
|
|
|||
|
|
openssl_sign($message, $signature, $mch_private_key, "sha256WithRSAEncryption");
|
|||
|
|
$sign = base64_encode($signature);
|
|||
|
|
$schema = 'WECHATPAY2-SHA256-RSA2048';
|
|||
|
|
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchantId, $nonce, $timestamp, $merchantSerialNumber, $sign);
|
|||
|
|
|
|||
|
|
$header = "Authorization: " . $schema . " " . $token;
|
|||
|
|
|
|||
|
|
$res = $this->http_post($url, $header, $data);
|
|||
|
|
$arr = json_decode($res, true);
|
|||
|
|
|
|||
|
|
$time = time();
|
|||
|
|
$str = time() . round('1000', '9999');
|
|||
|
|
$prepay = 'prepay_id=' . $arr['prepay_id'];
|
|||
|
|
|
|||
|
|
$message1 = $appid . "\n" .
|
|||
|
|
$time . "\n" .
|
|||
|
|
$str . "\n" .
|
|||
|
|
$prepay . "\n";
|
|||
|
|
$prepay_id = $arr['prepay_id'];
|
|||
|
|
|
|||
|
|
openssl_sign($message1, $signature, $mch_private_key, "sha256WithRSAEncryption");
|
|||
|
|
$sign1 = base64_encode($signature);
|
|||
|
|
|
|||
|
|
$data = array();
|
|||
|
|
$data['appId'] = $appid;
|
|||
|
|
$data['timeStamp'] = (string)$time;
|
|||
|
|
$data['nonceStr'] = $str;
|
|||
|
|
$data['package'] = 'prepay_id=' . $arr['prepay_id'];
|
|||
|
|
$data['signType'] = 'RSA';
|
|||
|
|
|
|||
|
|
$data['paySign'] = $sign1;
|
|||
|
|
$data['out_trade_no'] = $out_trade_no;
|
|||
|
|
$data['openid'] = $openid;
|
|||
|
|
return $data;
|
|||
|
|
return json($data);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function directlychargenotify()
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
//code...
|
|||
|
|
|
|||
|
|
$header = $this->getHeaders(); //读取http头信息 见下文
|
|||
|
|
$body = file_get_contents('php://input'); //读取微信传过来的信息,是一个json字符串
|
|||
|
|
|
|||
|
|
|
|||
|
|
if (empty($header) || empty($body)) {
|
|||
|
|
throw new \Exception('通知参数为空', 2001);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$timestamp = $header['WECHATPAY-TIMESTAMP'];
|
|||
|
|
$nonce = $header['WECHATPAY-NONCE'];
|
|||
|
|
$signature = $header['WECHATPAY-SIGNATURE'];
|
|||
|
|
$serialNo = $header['WECHATPAY-SERIAL'];
|
|||
|
|
|
|||
|
|
if (empty($timestamp) || empty($nonce) || empty($signature) || empty($serialNo)) {
|
|||
|
|
throw new \Exception('通知头参数为空', 2002);
|
|||
|
|
}
|
|||
|
|
$cert = $this->getzhengshuDb(1);
|
|||
|
|
|
|||
|
|
if ($cert != $serialNo) {
|
|||
|
|
throw new \Exception('验签失败', 2005);
|
|||
|
|
}
|
|||
|
|
$message = "$timestamp\n$nonce\n$body\n";
|
|||
|
|
|
|||
|
|
//校验签名
|
|||
|
|
if (!$this->verify($message, $signature, __DIR__ . config('wx.pingtai_public_key_path'))) {
|
|||
|
|
throw new \Exception('验签失败', 2005);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$decodeBody = json_decode($body, true);
|
|||
|
|
if (empty($decodeBody) || !isset($decodeBody['resource'])) {
|
|||
|
|
throw new \Exception('通知参数内容为空', 2003);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
$decodeBodyResource = $decodeBody['resource'];
|
|||
|
|
|
|||
|
|
|
|||
|
|
$decodeData_res = $this->decryptToString($decodeBodyResource['associated_data'], $decodeBodyResource['nonce'], $decodeBodyResource['ciphertext'], ''); //解密resource
|
|||
|
|
|
|||
|
|
|
|||
|
|
$decodeData = json_decode($decodeData_res, true);
|
|||
|
|
Log::error('即充即退充值返回数据: ' . $decodeData_res);
|
|||
|
|
Db::table('charge_logo')->save(['name' => '即充即退返回数据', 'mark' => $decodeData_res]);
|
|||
|
|
|
|||
|
|
//返回结果格式
|
|||
|
|
//array (
|
|||
|
|
// 'mchid' => 'xxx',
|
|||
|
|
// 'appid' => 'xxxxxxx',
|
|||
|
|
// 'out_trade_no' => '1217752501201407033233368026',
|
|||
|
|
// 'transaction_id' => '4200001336202201037507057791',
|
|||
|
|
// 'trade_type' => 'NATIVE',
|
|||
|
|
// 'trade_state' => 'SUCCESS',
|
|||
|
|
// 'trade_state_desc' => '支付成功',
|
|||
|
|
// 'bank_type' => 'OTHERS',
|
|||
|
|
// 'attach' => '',
|
|||
|
|
// 'success_time' => '2022-01-03T19:43:05+08:00',
|
|||
|
|
// 'payer' =>
|
|||
|
|
// array (
|
|||
|
|
// 'openid' => 'ovs326bgwfA4o8jlFQXMEma2JZek',
|
|||
|
|
// ),
|
|||
|
|
// 'amount' =>
|
|||
|
|
// array (
|
|||
|
|
// 'total' => 1,
|
|||
|
|
// 'payer_total' => 1,
|
|||
|
|
// 'currency' => 'CNY',
|
|||
|
|
// 'payer_currency' => 'CNY',
|
|||
|
|
// ),
|
|||
|
|
// )
|
|||
|
|
//执行自己的代码start
|
|||
|
|
|
|||
|
|
|
|||
|
|
if ($decodeData['trade_state'] == 'SUCCESS') {
|
|||
|
|
$account = $decodeData['amount']['total'] / 100;
|
|||
|
|
$time = time();
|
|||
|
|
Db::table('zxc_charge_order')->save([
|
|||
|
|
'type' => 1,
|
|||
|
|
'openid' => $decodeData['payer']['openid'],
|
|||
|
|
'directly_pay_no' => $decodeData['out_trade_no'],
|
|||
|
|
'directly_pay_status' => 1,
|
|||
|
|
'directly_pay_time' => $time,
|
|||
|
|
'directly_prepaid_amount' => $account,
|
|||
|
|
'WithholdingMoney' => $account,
|
|||
|
|
'start_time' => date('Y-m-d H:i:s', $time),
|
|||
|
|
]);
|
|||
|
|
Db::table('zxc_recharge')->save([
|
|||
|
|
'type' => 2,
|
|||
|
|
'openid' => $decodeData['payer']['openid'],
|
|||
|
|
'out_trade_no' => $decodeData['out_trade_no'],
|
|||
|
|
'transaction_id' => $decodeData['transaction_id'],
|
|||
|
|
'trade_state' => $decodeData['trade_state'],
|
|||
|
|
'total' => $account,
|
|||
|
|
'payer_total' => $account,
|
|||
|
|
'success_time' => date('Y-m-d H:i:s', $time),
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
\app\model\User::addMoneyLog($decodeData['payer']['openid'], $decodeData['amount']['total'] / 100, 2, '用户使用即充即退-充值');
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
echo 'OK';
|
|||
|
|
} catch (\Exception $e) {
|
|||
|
|
Log::error($e->getMessage());
|
|||
|
|
$arr = array("code" => "ERROR", "message" => $e->getMessage());
|
|||
|
|
echo json_encode($arr);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|