1. 支付功能 ( 回调等上线之后才能开始写。

2. 调整了测试接口的地址
3. 邮件发送功能
DLC
鹿和sa0ChunLuyu 2 years ago
parent ab52efd1d6
commit 23757d15c6

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use App\Mail\Example;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Yo;
use Lu;
class EmailController extends Controller
{
public function email_test(Request $request)
{
$to = $request->post('to');
$data = ['to' => $to, 'name' => '测试', 'date' => Lu::date()];
Mail::to($to)->send(new Example($data));
return Yo::echo();
}
}

@ -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:

@ -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
]);
}
}

@ -0,0 +1,41 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class Example extends Mailable
{
use Queueable, SerializesModels;
public $data;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($data)
{
$this->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);
}
}

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class EmailLog extends Model
{
use HasFactory;
}

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class WeChatPayLog extends Model
{
use HasFactory;
}

@ -13,6 +13,7 @@
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8",
"wechatpay/wechatpay": "^1.4",
"workerman/gatewayclient": "^3.0"
},
"require-dev": {

59
composer.lock generated

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bf594492c6a4c951789cc15ff31c1a5a",
"content-hash": "7979397b35b3bb544044c9e99ed03024",
"packages": [
{
"name": "brick/math",
@ -4147,6 +4147,63 @@
],
"time": "2022-06-03T18:03:27+00:00"
},
{
"name": "wechatpay/wechatpay",
"version": "1.4.8",
"dist": {
"type": "zip",
"url": "https://mirrors.cloud.tencent.com/repository/composer/wechatpay/wechatpay/1.4.8/wechatpay-wechatpay-1.4.8.zip",
"reference": "fbe8d7c2b3b367e42bf98caa0cce582b3205d427",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-libxml": "*",
"ext-openssl": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^6.5 || ^7.0",
"guzzlehttp/uri-template": "^0.2 || ^1.0",
"php": ">=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",

@ -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,

@ -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();
});
}

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('we_chat_pay_logs', function (Blueprint $table) {
$table->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');
}
};

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('email_logs', function (Blueprint $table) {
$table->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');
}
};

@ -0,0 +1,16 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Example Email</title>
</head>
<body>
<div>
<h1>欢迎 {{ $data['name'] }}</h1>
<p>{{ $data['date'] }}</p>
</div>
</body>
</html>

@ -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']);

@ -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({

@ -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)
})
}
</script>
<template>
<uni-drawer :ref="drawerRef" mode="right">
<view class="navbar_wrapper"></view>
<textarea :maxlength="-1" class="textarea_wrapper" v-model="pat_test" />
<button size="mini" @click="payTestDo()"></button>
</uni-drawer>
<uni-section title="支付测试" type="line">
<view class="uni-ma-5 uni-pb-5 example_item_wrapper">
<button size="mini" @click="payTest()"></button>
</view>
</uni-section>
<uni-drawer :ref="drawerRef" mode="right">
<view class="navbar_wrapper"></view>
<textarea :maxlength="-1" class="textarea_wrapper" v-model="pay_test" />
<view class="button_line_wrapper">
<button size="mini" @click="WeChatPayPayTest()"></button>
<button size="mini" @click="payTestDo()"></button>
</view>
</uni-drawer>
<uni-section title="支付测试" type="line">
<view class="uni-ma-5 uni-pb-5 example_item_wrapper">
<button size="mini" @click="payTest()"></button>
</view>
</uni-section>
</template>
<style scoped>
.textarea_wrapper {
margin-top: 100rpx;
height: 400rpx;
}
</style>
.button_line_wrapper {
display: flex;
justify-content: space-around;
}
.textarea_wrapper {
margin-top: 100rpx;
height: 400rpx;
}
</style>

@ -37,7 +37,6 @@
data: user_code.value
})
}
const login_info = ref('')
const WeChatLoginTest = async () => {
const user_code_data = JSON.parse(user_code.value)
const response = await WeChatLoginTestAction({
@ -45,7 +44,7 @@
app_id: uni.$lu.config.app_id
})
$response(response, () => {
login_info.value = JSON.stringify(response.data.info, null, 4)
user_code.value = JSON.stringify(response.data.info, null, 4)
})
}
</script>
@ -57,7 +56,6 @@
<button size="mini" @click="copyContent()"></button>
<button size="mini" @click="WeChatLoginTest()"></button>
</view>
<textarea :maxlength="-1" class="textarea_wrapper" v-model="login_info" />
</uni-drawer>
<uni-section title="用户Code" type="line">

Loading…
Cancel
Save