|
|
|
|
@ -2,9 +2,217 @@
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|