增加 支付宝支付网关

This commit is contained in:
iVampireSP.com 2022-09-01 17:48:29 +08:00
parent c8fcc8c9ed
commit 27e756156f
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
14 changed files with 1237 additions and 20 deletions

View File

@ -56,3 +56,5 @@ VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
ALIPAY_APP_ID=

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class PaymentException extends Exception
{
//
}

View File

@ -2,8 +2,13 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Exception;
use App\Models\User\Balance;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Alipay\EasySDK\Kernel\Util\ResponseChecker;
use Alipay\EasySDK\Kernel\Factory as AlipayFactory;
class BalanceController extends Controller
{
@ -24,22 +29,96 @@ public function store(Request $request)
'amount' => 'required|numeric',
]);
$balance = $request->user();
$user = $request->user();
// 启用事物
\DB::beginTransaction();
try {
$balance->increment('amount', $request->amount);
\DB::commit();
} catch (\Exception $e) {
\DB::rollBack();
return $this->error($e->getMessage());
}
$balance = new Balance();
$balance = $balance->create([
'user_id' => $user->id,
'amount' => $request->amount,
'payment' => 'alipay',
]);
// 生成 18 位订单号
$order_id = date('YmdHis') . $balance->id . rand(1000, 9999);
$balance->order_id = $order_id;
$balance->save();
$balance = $balance->toArray();
$balance['pay_url'] = route('balances.pay.show', ['balance' => $balance['order_id']]);
return $this->success($balance);
}
public function show(Request $request, Balance $balance)
{
// dd(AlipayFactory::payment()->common()->query('20220901070430102316'));
// dd(route(''));
if ($balance->paid_at !== null) {
return $this->error('订单已支付');
}
try {
$result = AlipayFactory::payment()->page()->pay("支付", $balance->order_id, $balance->amount, 'http://rcrmqishil.sharedwithexpose.com/api/pay/return');
$responseChecker = new ResponseChecker();
// dd($result);
if ($responseChecker->success($result)) {
$html = $result->body;
return view('pay', compact('html'));
}
} catch (Exception $e) {
dd($e->getMessage());
echo "调用失败," . $e->getMessage() . PHP_EOL;;
}
return $this->success($balance);
}
public function notify(Request $request)
{
// 检测订单是否存在
$balance = Balance::where('order_id', $request->out_trade_no)->with('user')->first();
if (!$balance) {
return $this->notFound('balance not found');
}
// 检测订单是否已支付
if ($balance->paid_at !== null) {
return $this->success('订单已支付');
}
$trade = AlipayFactory::payment()->common()->query($request->out_trade_no);
if ($trade->code == "10000" && $trade->tradeStatus == "TRADE_SUCCESS") {
$balance->paid_at = now();
$balance->save();
DB::beginTransaction();
try {
$balance->user->increment('balance', $trade->totalAmount);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
AlipayFactory::payment()->common()->refund($request->out_trade_no, $trade->totalAmount);
return $this->error($e->getMessage());
}
return $this->success('订单支付成功');
} else {
return $this->error('订单支付失败');
}
}
// // 转换为 drops
// public function transfer($amount = 1)
// {
@ -49,6 +128,4 @@ public function store(Request $request)
// }
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Models\User;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Balance extends Model
{
use HasFactory;
protected $fillable = [
'order_id',
'payment',
'amount',
'user_id',
'paid_at',
'trade_id'
];
// route key
public function getRouteKeyName()
{
return 'order_id';
}
public function user()
{
return $this->belongsTo(User::class);
}
}

View File

@ -2,7 +2,10 @@
namespace App\Providers;
use Alipay\EasySDK\Kernel\Config as AlipayConfig;
use Alipay\EasySDK\Kernel\Factory as AlipayFactory;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
@ -34,5 +37,33 @@ public function boot()
])->baseUrl($url);
});
AlipayFactory::setOptions($this->alipayOptions());
}
private function alipayOptions()
{
$options = new AlipayConfig();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipaydev.com';
$options->signType = 'RSA2';
$options->appId = config('alipay.app_id');
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
$options->merchantPrivateKey = trim(Storage::get('alipayAppPriv.key'));
$options->alipayCertPath = storage_path('app/alipayCertPublicKey_RSA2.crt');
$options->alipayRootCertPath = storage_path('app/alipayRootCert.crt');
$options->merchantCertPath = storage_path('app/appCertPublicKey.crt');
//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
// $options->alipayPublicKey = Storage::get('alipayCertPublicKey_RSA2.crt');
//可设置异步通知接收服务地址(可选)
$options->notifyUrl = "http://rcrmqishil.sharedwithexpose.com/api/pay/notify";
return $options;
}
}

View File

@ -6,6 +6,7 @@
"license": "MIT",
"require": {
"php": "^8.0.2",
"alipaysdk/easysdk": "^2.0",
"doctrine/dbal": "^3.3",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^9.19",
@ -13,7 +14,8 @@
"laravel/octane": "^1.2",
"laravel/sanctum": "^2.14.1",
"laravel/telescope": "^4.9",
"laravel/tinker": "^2.7"
"laravel/tinker": "^2.7",
"league/omnipay": "^3"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",

988
composer.lock generated

File diff suppressed because it is too large Load Diff

5
config/alipay.php Normal file
View File

@ -0,0 +1,5 @@
<?php
return [
'app_id' => env('ALIPAY_APP_ID'),
];

View File

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('balances', function (Blueprint $table) {
$table->id();
// order id
$table->string('order_id')->nullable()->index();
// trade id
$table->string('trade_id')->nullable()->index();
// payment
$table->string('payment')->nullable()->index();
// amount
$table->decimal('amount', 10, 2)->default(0);
// paid_at
$table->timestamp('paid_at')->nullable();
// user id
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->foreign('user_id')->references('id')->on('users');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('balances');
}
};

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>支付订单</title>
</head>
<body>
{!! $html !!}
{{--{{ $html }}--}}
</body>
</html>

View File

@ -1,10 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\User\DropController;
use App\Http\Controllers\User\TaskController;
use App\Http\Controllers\Remote\ModuleController;
use App\Http\Controllers\ServerController;
use App\Http\Controllers\User\BalanceController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\User\WorkOrder\ReplyController;
use App\Http\Controllers\User\WorkOrder\WorkOrderController;
@ -14,7 +14,8 @@
Route::get('servers', ServerController::class);
Route::apiResource('drops', DropController::class);
Route::apiResource('balances', BalanceController::class)->only(['index', 'store']);
// Route::apiResource('drops', DropController::class);
Route::get('tasks', TaskController::class);
@ -23,5 +24,8 @@
// 调用远程 API
Route::any('/modules/{module}', [ModuleController::class, 'call'])->name('module.call');
});
Route::get('/pay/return', [BalanceController::class, 'notify'])->name('balances.return');
Route::get('/pay/notify', [BalanceController::class, 'notify'])->name('balances.notify');

View File

@ -7,7 +7,7 @@
// Route::apiResource('users', Remote\UserController::class)->only(['show']);
Route::apiResource('modules', Remote\ModuleController::class)->only(['index']);
Route::apiResource('servers', Remote\ServerController::class);
Route::apiResource('servers', \App\Http\Controllers\ServerController::class);
Route::apiResource('hosts', Remote\Host\HostController::class);
// Route::patch('hosts/{host}', [Remote\Host\DropController::class, 'update']);
// Route::patch('tasks', Remote\Host\TaskController::class);

View File

@ -25,3 +25,7 @@
});
Route::get('/', [Controllers\AuthController::class, 'index'])->name('index');
Route::get('/balances/{balance}', [Controllers\User\BalanceController::class, 'show'])->name('balances.pay.show');

BIN
storage/.DS_Store vendored Normal file

Binary file not shown.