diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php new file mode 100644 index 0000000..fbe3389 --- /dev/null +++ b/app/Http/Controllers/EmailController.php @@ -0,0 +1,20 @@ +post('to'); + $data = ['to' => $to, 'name' => '测试', 'date' => Lu::date()]; + Mail::to($to)->send(new Example($data)); + return Yo::echo(); + } +} diff --git a/app/Http/Controllers/WeChatController.php b/app/Http/Controllers/WeChatController.php index 02a3220..3f5de94 100644 --- a/app/Http/Controllers/WeChatController.php +++ b/app/Http/Controllers/WeChatController.php @@ -23,7 +23,7 @@ class WeChatController extends Controller { $we_chat = WeChat::where('app_id', $app_id)->first(); $login = false; - if (!$we_chat) return $login; + if (!$we_chat) return false; switch ($we_chat->type) { case 1: diff --git a/app/Http/Controllers/WeChatPayController.php b/app/Http/Controllers/WeChatPayController.php index 1b2830d..4efd1d5 100644 --- a/app/Http/Controllers/WeChatPayController.php +++ b/app/Http/Controllers/WeChatPayController.php @@ -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 + ]); + } } diff --git a/app/Mail/Example.php b/app/Mail/Example.php new file mode 100644 index 0000000..d991181 --- /dev/null +++ b/app/Mail/Example.php @@ -0,0 +1,41 @@ +data = $data; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + $email_log = new \App\Models\EmailLog(); + $email_log->to = $this->data['to']; + $email_log->view = 'emails.example'; + unset($this->data['to']); + $email_log->data = json_encode($this->data, JSON_UNESCAPED_UNICODE); + $email_log->save(); + return $this->view('emails.example')->subject('Example Email')->with('data', $this->data); + } +} diff --git a/app/Models/EmailLog.php b/app/Models/EmailLog.php new file mode 100644 index 0000000..0199f8b --- /dev/null +++ b/app/Models/EmailLog.php @@ -0,0 +1,11 @@ +=7.1.2" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.89 || ^1.0", + "phpunit/phpunit": "^7.5 || ^8.5.16 || ^9.3.5" + }, + "bin": [ + "bin/CertificateDownloader.php" + ], + "type": "library", + "autoload": { + "psr-4": { + "WeChatPay\\": "src/" + } + }, + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "James ZHANG", + "homepage": "https://github.com/TheNorthMemory" + }, + { + "name": "WeChatPay Community", + "homepage": "https://developers.weixin.qq.com/community/pay" + } + ], + "description": "[A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP", + "homepage": "https://pay.weixin.qq.com/", + "keywords": [ + "AES-GCM", + "aes-ecb", + "openapi-chainable", + "rsa-oaep", + "wechatpay", + "xml-builder", + "xml-parser" + ], + "time": "2023-01-05T03:50:12+00:00" + }, { "name": "workerman/gatewayclient", "version": "v3.0.14", diff --git a/config/mail.php b/config/mail.php index e652bd0..bd7a177 100644 --- a/config/mail.php +++ b/config/mail.php @@ -39,7 +39,7 @@ return [ 'url' => env('MAIL_URL'), 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 'port' => env('MAIL_PORT', 587), - 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'encryption' => env('MAIL_ENCRYPTION', 'ssl'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, diff --git a/database/migrations/2023_08_13_203344_create_we_chat_pays_table.php b/database/migrations/2023_08_13_203344_create_we_chat_pays_table.php index 157dac9..0bab7bf 100644 --- a/database/migrations/2023_08_13_203344_create_we_chat_pays_table.php +++ b/database/migrations/2023_08_13_203344_create_we_chat_pays_table.php @@ -18,6 +18,7 @@ return new class extends Migration { $table->string('v3', 100)->comment('API KEY V3'); $table->string('key', 200)->comment('证书 Key'); $table->string('crt', 200)->comment('证书 Crt'); + $table->string('notify_url', 200)->comment('回调地址'); $table->timestamps(); }); } diff --git a/database/migrations/2023_08_13_212644_create_we_chat_pay_logs_table.php b/database/migrations/2023_08_13_212644_create_we_chat_pay_logs_table.php new file mode 100644 index 0000000..b615e8a --- /dev/null +++ b/database/migrations/2023_08_13_212644_create_we_chat_pay_logs_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('out_trade_no', 100)->index(); + $table->text('post_data'); + $table->text('params'); + $table->longText('callback')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('we_chat_pay_logs'); + } +}; diff --git a/database/migrations/2023_08_13_221243_create_email_logs_table.php b/database/migrations/2023_08_13_221243_create_email_logs_table.php new file mode 100644 index 0000000..4e2f208 --- /dev/null +++ b/database/migrations/2023_08_13_221243_create_email_logs_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('to', 200)->index(); + $table->string('view', 50); + $table->string('data', 1000); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('email_logs'); + } +}; diff --git a/resources/views/emails/example.blade.php b/resources/views/emails/example.blade.php new file mode 100644 index 0000000..cc08eef --- /dev/null +++ b/resources/views/emails/example.blade.php @@ -0,0 +1,16 @@ + + + + + + + Example Email + + +
+

欢迎 {{ $data['name'] }}!

+

{{ $data['date'] }}

+
+ + diff --git a/routes/web.php b/routes/web.php index 6aa59b0..b7c5386 100644 --- a/routes/web.php +++ b/routes/web.php @@ -15,7 +15,9 @@ use Illuminate\Support\Facades\Route; $admin_path = 'Admin'; $zero_path = 'Zero'; $app_path = 'App'; -Route::post("api/$app_path/WeChat/login_test", [\App\Http\Controllers\WeChatController::class, 'login_test']); +Route::post("api/Test/Email/email_test", [\App\Http\Controllers\EmailController::class, 'email_test']); +Route::post("api/Test/WeChatPay/pay_test", [\App\Http\Controllers\WeChatPayController::class, 'pay_test']); +Route::post("api/Test/WeChat/login_test", [\App\Http\Controllers\WeChatController::class, 'login_test']); Route::post("api/Gateway/close", [\App\Http\Controllers\GatewayController::class, 'close']); Route::post("api/$admin_path/Config/create", [\App\Http\Controllers\ConfigController::class, 'create']); diff --git a/uniapp/api/index.js b/uniapp/api/index.js index 03c79ce..0795ff3 100644 --- a/uniapp/api/index.js +++ b/uniapp/api/index.js @@ -3,8 +3,12 @@ import { } from '@/lu/axios.js' import $config from '@/config.js' const app_path = 'App' +export const WeChatPayPayTestAction = async (data) => await $post({ + url: `${$config.url}/api/Test/WeChatPay/pay_test`, + data +}) export const WeChatLoginTestAction = async (data) => await $post({ - url: `${$config.url}/api/${app_path}/WeChat/login_test`, + url: `${$config.url}/api/Test/WeChat/login_test`, data }) export const yo = async (data) => await $post({ diff --git a/uniapp/pages/package/example/PayTest/PayTest.vue b/uniapp/pages/package/example/PayTest/PayTest.vue index 48d312c..7996289 100644 --- a/uniapp/pages/package/example/PayTest/PayTest.vue +++ b/uniapp/pages/package/example/PayTest/PayTest.vue @@ -7,18 +7,22 @@ import { ref } from 'vue' - const pat_test = ref('') + import { + WeChatPayPayTestAction, + $response + } from '@/api' + const pay_test = ref('') const payTest = (e) => { drawer_ref.value.open() } const payTestDo = () => { - let pay_info = JSON.parse(pat_test.value); + let pay_info = JSON.parse(pay_test.value); wx.requestPayment({ - 'timeStamp': pay_info.timeStamp, - 'nonceStr': pay_info.nonceStr, + 'timeStamp': pay_info.timestamp, + 'nonceStr': pay_info.nonce_str, 'package': pay_info.package, 'signType': 'RSA', - 'paySign': pay_info.paySign, + 'paySign': pay_info.pay_sign, 'success': function(res) { console.log(res) }, @@ -28,28 +32,46 @@ 'complete': function(res) {} }); } - - const drawer_ref = ref(null) - const drawerRef = (e) => { - drawer_ref.value = e - } + + const drawer_ref = ref(null) + const drawerRef = (e) => { + drawer_ref.value = e + } + + const WeChatPayPayTest = async () => { + const response = await WeChatPayPayTestAction({ + app_id: uni.$lu.config.app_id, + open_id: JSON.parse(pay_test.value)['openid'] + }) + $response(response, () => { + pay_test.value = JSON.stringify(response.data.info) + }) + }