You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
7.6 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\WeChatPay;
use App\Models\WeChatPayLog;
use Illuminate\Http\Request;
use WeChatPay\Builder;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;
use Yo;
use Lu;
class WeChatPayController extends Controller
{
public static $mp_instance = false;
public static $mp_config = false;
public function callback($input, $header, $apiv3Key, $pem)
{
$inWechatpaySignature = $header['wechatpay-signature'][0];
$inWechatpayTimestamp = $header['wechatpay-timestamp'][0];
$inWechatpaySerial = $header['wechatpay-serial'][0];
$inWechatpayNonce = $header['wechatpay-nonce'][0];
$inBody = $input;
$platformPublicKeyInstance = Rsa::from($pem, Rsa::KEY_TYPE_PUBLIC);
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
// $verifiedStatus = Rsa::verify(
// Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
// $inWechatpaySignature,
// $platformPublicKeyInstance
// );
// if ($timeOffsetStatus && $verifiedStatus) {
if ($timeOffsetStatus) {
$inBodyArray = (array)json_decode($inBody, true);
['resource' => [
'ciphertext' => $ciphertext,
'nonce' => $nonce,
'associated_data' => $aad
]] = $inBodyArray;
$inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
return (array)json_decode($inBodyResource, true);
} else {
return false;
}
}
public function builder($config)
{
self::$mp_config = $config;
$merchantPrivateKeyFilePath = 'file://' . self::$mp_config['pem_path'];
$platformCertificateFilePath = 'file://' . self::$mp_config['cer_path'];
$merchantId = self::$mp_config['mchid'];
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
self::$mp_config['pem_key'] = $merchantPrivateKeyInstance;
$merchantCertificateSerial = self::$mp_config['cer_num'];
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
$platformCertificateSerial = self::$mp_config['v3'];
self::$mp_instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
}
public function refund($config)
{
$res = false;
try {
$resp = self::$mp_instance
->v3->refund->domestic->refunds
->post([
'json' => [
'transaction_id' => $config['transaction_id'],
'out_refund_no' => $config['out_refund_no'],
'amount' => [
'refund' => $config['total'],
'total' => $config['total'],
'currency' => 'CNY',
],
],
]);
$res = json_decode($resp->getBody(), true);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$res = json_decode($r->getBody(), true);
}
}
return $res;
}
public function create($config)
{
$res = false;
try {
$post_data = [
'appid' => self::$mp_config['appid'],
'mchid' => self::$mp_config['mchid'],
'description' => $config['description'],
'out_trade_no' => $config['out_trade_no'],
'notify_url' => $config['notify_url'],
'amount' => [
'total' => $config['total'],
],
'payer' => [
'openid' => $config['openid']
]
];
$resp = self::$mp_instance
->v3->pay->transactions->jsapi
->post([
'json' => $post_data,
]);
$res = json_decode($resp->getBody(), true);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$res = json_decode($r->getBody(), true);
}
}
$params = [
'appId' => self::$mp_config['appid'],
'timeStamp' => (string)time(),
'nonceStr' => self::nonce(),
'package' => 'prepay_id=' . $res['prepay_id'],
];
$params += ['paySign' => Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
self::$mp_config['pem_key']
), 'signType' => 'RSA'];
$wc_chat_pay_log = new WeChatPayLog();
$wc_chat_pay_log->out_trade_no = $config['out_trade_no'];
$wc_chat_pay_log->post_data = json_encode($post_data, JSON_UNESCAPED_UNICODE);
$wc_chat_pay_log->params = json_encode($params, JSON_UNESCAPED_UNICODE);
$wc_chat_pay_log->save();
return [
'appid' => $params['appId'],
'timestamp' => $params['timeStamp'],
'nonce_str' => $params['nonceStr'],
'package' => $params['package'],
'pay_sign' => $params['paySign'],
'sign_type' => $params['signType'],
];
}
public function check($out_trade_no)
{
$res = false;
try {
$resp = self::$mp_instance
->v3->pay->transactions->outTradeNo->_out_trade_no_
->get([
'query' => ['mchid' => self::$mp_config['mchid']],
'out_trade_no' => (string)$out_trade_no,
]);
$res = json_decode($resp->getBody(), true);
} catch (\Exception $e) {
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
$res = json_decode($r->getBody(), true);
}
}
return $res;
}
public static function nonce($l = 16)
{
$charts = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz0123456789";
$max = strlen($charts) - 1;
$noncestr = "";
for ($i = 0; $i < $l; $i++) {
$noncestr .= $charts[rand(0, $max)];
}
return $noncestr;
}
public function pay($open_id, $app_id, $out_trade_no, $price, $description)
{
$we_chat_pay = WeChatPay::where('app_id', $app_id)->first();
if (!$we_chat_pay) return false;
self::builder([
'appid' => $we_chat_pay->app_id,
'pem_path' => base_path($we_chat_pay->key),
'cer_path' => base_path($we_chat_pay->crt),
'cer_num' => $we_chat_pay->number,
'mchid' => $we_chat_pay->pay_id,
'v3' => $we_chat_pay->v3,
]);
return self::create([
'description' => $description,
'out_trade_no' => $out_trade_no,
'notify_url' => $we_chat_pay->notify_url,
'total' => $price * 100,
'openid' => $open_id,
]);
}
public function pay_test(Request $request)
{
$app_id = $request->post('app_id');
$open_id = $request->post('open_id');
$info = self::pay(
$open_id,
$app_id,
time() . rand(1000, 9999),
0.01,
'测试支付'
);
return Yo::echo([
'info' => $info
]);
}
public function callback_test($app_id)
{
$input = file_get_contents('php://input');
$headers = request()->header();
$we_chat_pay = WeChatPay::where('app_id', $app_id)->first();
if (!$we_chat_pay) return false;
$res = $this->callback($input, $headers, $we_chat_pay->v3, 'file://' . base_path($we_chat_pay->crt));
if (!!$res) {
if ($res['trade_state'] == 'SUCCESS') {
$we_chat_pay_log = WeChatPayLog::where('out_trade_no', $res['out_trade_no'])->first();
if (!!$we_chat_pay_log) {
$we_chat_pay_log->callback = json_encode([
'type' => 'callback',
'input' => $input,
'headers' => $headers,
'res' => $res,
], JSON_UNESCAPED_UNICODE);
$we_chat_pay_log->save();
}
}
}
return Lu::exit([
'code' => 'SUCCESS',
'message' => '成功',
]);
}
}