донат шоп, управление магазином, яндекс метрика
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\AdjustBalanceRequest;
|
||||
use App\Services\AzerothCoreAccountService;
|
||||
use App\Services\BalanceService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class BalanceController extends Controller
|
||||
{
|
||||
public function index(Request $request, BalanceService $balances): View
|
||||
{
|
||||
$search = trim((string) $request->query('search', ''));
|
||||
|
||||
return view('admin.balances.index', [
|
||||
'transactions' => $balances->adminTransactions($search !== '' ? $search : null),
|
||||
'search' => $search,
|
||||
]);
|
||||
}
|
||||
|
||||
public function adjust(
|
||||
AdjustBalanceRequest $request,
|
||||
AzerothCoreAccountService $accounts,
|
||||
BalanceService $balances,
|
||||
): RedirectResponse {
|
||||
$accountInput = trim($request->string('account')->toString());
|
||||
$account = ctype_digit($accountInput)
|
||||
? $accounts->findUserById((int) $accountInput)
|
||||
: $accounts->findUserByUsername($accountInput);
|
||||
|
||||
if (! $account) {
|
||||
return back()->withInput()->withErrors(['account' => 'Игровой аккаунт не найден.']);
|
||||
}
|
||||
|
||||
try {
|
||||
$transaction = $balances->adjustByAdmin(
|
||||
$account->id,
|
||||
$request->string('amount')->toString(),
|
||||
$request->string('direction')->toString(),
|
||||
$request->user()->id,
|
||||
$request->user()->username,
|
||||
$request->string('reason')->toString(),
|
||||
);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
return back()->withInput()->withErrors(['amount' => $exception->getMessage()]);
|
||||
}
|
||||
|
||||
return back()->with('status', sprintf(
|
||||
'Счёт %s изменён. Теперь на нём %s Слёз Элуны.',
|
||||
$account->username,
|
||||
number_format((float) $transaction->balance_after, 2, ',', ' '),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\SaveShopCategoryRequest;
|
||||
use App\Http\Requests\Admin\SaveShopProductRequest;
|
||||
use App\Models\ShopCategory;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Services\ShopCatalogService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ShopController extends Controller
|
||||
{
|
||||
public function index(ShopCatalogService $catalog): View
|
||||
{
|
||||
return view('admin.shop.index', [
|
||||
'categories' => $catalog->categories(),
|
||||
'products' => $catalog->products(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeCategory(SaveShopCategoryRequest $request, ShopCatalogService $catalog): RedirectResponse
|
||||
{
|
||||
$catalog->createCategory($request->validated());
|
||||
|
||||
return back()->with('status', 'Категория создана.');
|
||||
}
|
||||
|
||||
public function updateCategory(ShopCategory $category, SaveShopCategoryRequest $request, ShopCatalogService $catalog): RedirectResponse
|
||||
{
|
||||
$catalog->updateCategory($category, $request->validated());
|
||||
|
||||
return back()->with('status', 'Категория обновлена.');
|
||||
}
|
||||
|
||||
public function destroyCategory(ShopCategory $category, ShopCatalogService $catalog): RedirectResponse
|
||||
{
|
||||
$catalog->deleteCategory($category);
|
||||
|
||||
return back()->with('status', 'Категория удалена.');
|
||||
}
|
||||
|
||||
public function storeProduct(SaveShopProductRequest $request, ShopCatalogService $catalog): RedirectResponse
|
||||
{
|
||||
$catalog->createProduct($request->validated());
|
||||
|
||||
return back()->with('status', 'Товар создан.');
|
||||
}
|
||||
|
||||
public function updateProduct(ShopProduct $product, SaveShopProductRequest $request, ShopCatalogService $catalog): RedirectResponse
|
||||
{
|
||||
$catalog->updateProduct($product, $request->validated());
|
||||
|
||||
return back()->with('status', 'Товар обновлён.');
|
||||
}
|
||||
|
||||
public function destroyProduct(ShopProduct $product, ShopCatalogService $catalog): RedirectResponse
|
||||
{
|
||||
$catalog->deleteProduct($product);
|
||||
|
||||
return back()->with('status', 'Товар удалён.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\BalanceService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class GameBalanceController extends Controller
|
||||
{
|
||||
public function show(int $accountId, BalanceService $balances): JsonResponse
|
||||
{
|
||||
return response()->json(['account_id' => $accountId, 'balance' => $balances->balance($accountId)]);
|
||||
}
|
||||
|
||||
public function spend(Request $request, int $accountId, BalanceService $balances): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'amount' => ['required', 'numeric', 'decimal:0,2', 'gt:0'],
|
||||
'idempotency_key' => ['required', 'string', 'max:160'],
|
||||
'description' => ['nullable', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$transaction = $balances->spend(
|
||||
$accountId,
|
||||
(string) $data['amount'],
|
||||
'game:'.$data['idempotency_key'],
|
||||
$data['description'] ?? null,
|
||||
);
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
return response()->json(['message' => $exception->getMessage()], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'transaction_id' => $transaction->id,
|
||||
'account_id' => $transaction->account_id,
|
||||
'amount' => $transaction->amount,
|
||||
'balance' => $transaction->balance_after,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\RobokassaService;
|
||||
use Illuminate\Http\Request;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Throwable;
|
||||
|
||||
class RobokassaController extends Controller
|
||||
{
|
||||
public function result(Request $request, RobokassaService $robokassa): Response
|
||||
{
|
||||
try {
|
||||
$invoice = $robokassa->processResult($request->all());
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
report($exception);
|
||||
|
||||
return response('bad signature', 400)->header('Content-Type', 'text/plain');
|
||||
} catch (Throwable $exception) {
|
||||
report($exception);
|
||||
|
||||
return response('temporary error', 503)->header('Content-Type', 'text/plain');
|
||||
}
|
||||
|
||||
return response('OK'.$invoice->id)->header('Content-Type', 'text/plain');
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\ChangePasswordRequest;
|
||||
use App\Services\AzerothCoreAccountService;
|
||||
use App\Services\BalanceService;
|
||||
use App\Services\GameClientDownloadService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -12,7 +13,7 @@ use Throwable;
|
||||
|
||||
class CabinetController extends Controller
|
||||
{
|
||||
public function index(Request $request, AzerothCoreAccountService $accounts): View
|
||||
public function index(Request $request, AzerothCoreAccountService $accounts, BalanceService $balances): View
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
@@ -20,6 +21,11 @@ class CabinetController extends Controller
|
||||
'user' => $user,
|
||||
'characters' => $accounts->charactersForAccount($user->id),
|
||||
'clientFilename' => config('moonwell.client.object_key'),
|
||||
'balance' => $balances->balance($user->id),
|
||||
'balanceTransactions' => $balances->recentTransactions($user->id),
|
||||
'minimumDeposit' => config('services.robokassa.minimum_amount'),
|
||||
'maximumDeposit' => config('services.robokassa.maximum_amount'),
|
||||
'tearsPerRuble' => config('services.robokassa.tears_per_ruble'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ class LandingController extends Controller
|
||||
'moon' => true,
|
||||
],
|
||||
'registerEndpoint' => route('game-account.store'),
|
||||
'inviteRequired' => (bool) config('moonwell.registration.require_invite_code'),
|
||||
'csrfToken' => csrf_token(),
|
||||
'auth' => [
|
||||
'authenticated' => $user !== null,
|
||||
@@ -110,15 +111,19 @@ class LandingController extends Controller
|
||||
): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$result = $inviteCodes->redeemForRegistration(
|
||||
$request->string('invite_code')->toString(),
|
||||
$request->string('username')->toString(),
|
||||
fn (): array => $registrar->register(
|
||||
$register = fn (): array => $registrar->register(
|
||||
$request->string('username')->toString(),
|
||||
$request->string('email')->toString(),
|
||||
$request->string('password')->toString(),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
$result = config('moonwell.registration.require_invite_code')
|
||||
? $inviteCodes->redeemForRegistration(
|
||||
$request->string('invite_code')->toString(),
|
||||
$request->string('username')->toString(),
|
||||
$register,
|
||||
)
|
||||
: $register();
|
||||
} catch (DuplicateGameAccountException $exception) {
|
||||
return back()
|
||||
->withErrors([$exception->field => $exception->getMessage()])
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\RobokassaService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PaymentController extends Controller
|
||||
{
|
||||
public function create(Request $request, RobokassaService $robokassa): View
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'amount' => [
|
||||
'required',
|
||||
'numeric',
|
||||
'decimal:0,2',
|
||||
Rule::numeric()->min((float) config('services.robokassa.minimum_amount'))->max((float) config('services.robokassa.maximum_amount')),
|
||||
],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
$invoice = $robokassa->createInvoice($user->id, (string) $validated['amount']);
|
||||
|
||||
return view('payments.redirect', [
|
||||
'paymentUrl' => config('services.robokassa.payment_url'),
|
||||
'parameters' => $robokassa->paymentParameters($invoice, $user->email),
|
||||
]);
|
||||
}
|
||||
|
||||
public function success(): RedirectResponse
|
||||
{
|
||||
return redirect()->route('cabinet.index')->with('status', 'Пополнение принято. Слёзы Элуны скоро появятся на счёте.');
|
||||
}
|
||||
|
||||
public function fail(): RedirectResponse
|
||||
{
|
||||
return redirect()->route('cabinet.index')->with('error', 'Оплата отменена или не была завершена.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureGameServer
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$expected = (string) config('services.game_server.balance_api_token');
|
||||
$provided = (string) $request->bearerToken();
|
||||
|
||||
if ($expected === '' || $provided === '' || ! hash_equals($expected, $provided)) {
|
||||
abort(401, 'Неверный токен игрового сервера.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AdjustBalanceRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'account' => ['required', 'string', 'max:255'],
|
||||
'direction' => ['required', Rule::in(['credit', 'debit'])],
|
||||
'amount' => ['required', 'numeric', 'decimal:0,2', 'gt:0', 'max:1000000'],
|
||||
'reason' => ['required', 'string', 'min:3', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SaveShopCategoryRequest extends FormRequest
|
||||
{
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge(['enabled' => $this->boolean('enabled')]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:120'],
|
||||
'icon' => ['nullable', 'string', 'max:255'],
|
||||
'requiredRank' => ['required', 'integer', 'min:0', 'max:255'],
|
||||
'flags' => ['required', 'integer', 'min:0'],
|
||||
'enabled' => ['boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SaveShopProductRequest extends FormRequest
|
||||
{
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge(['new' => $this->boolean('new'), 'enabled' => $this->boolean('enabled')]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'category_ids' => ['required', 'array', 'min:1'],
|
||||
'category_ids.*' => ['integer', Rule::exists('store.store_categories', 'id')],
|
||||
'type' => ['required', 'integer', Rule::in([1, 3, 4, 5, 7, 8, 9])],
|
||||
'name' => ['required', 'string', 'max:765'],
|
||||
'tooltipName' => ['nullable', 'string', 'max:765'],
|
||||
'tooltipType' => ['nullable', 'string', 'max:765'],
|
||||
'tooltipText' => ['nullable', 'string', 'max:10000'],
|
||||
'icon' => ['nullable', 'string', 'max:765'],
|
||||
'price' => ['required', 'integer', 'min:1', 'max:100000000'],
|
||||
'hyperlinkId' => ['nullable', 'integer', 'min:0'],
|
||||
'creatureEntry' => ['nullable', 'integer', 'min:0'],
|
||||
'discountAmount' => ['nullable', 'integer', 'min:0', 'max:100'],
|
||||
'flags' => ['nullable', 'integer', 'min:0'],
|
||||
'new' => ['boolean'],
|
||||
'enabled' => ['boolean'],
|
||||
'reward_1' => ['nullable', 'integer', 'min:0'],
|
||||
'reward_2' => ['nullable', 'integer', 'min:0'],
|
||||
'reward_3' => ['nullable', 'integer', 'min:0'],
|
||||
'reward_4' => ['nullable', 'integer', 'min:0'],
|
||||
'reward_5' => ['nullable', 'integer', 'min:0'],
|
||||
'reward_6' => ['nullable', 'integer', 'min:0'],
|
||||
'reward_7' => ['nullable', 'integer', 'min:0'],
|
||||
'reward_8' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_1' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_2' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_3' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_4' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_5' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_6' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_7' => ['nullable', 'integer', 'min:0'],
|
||||
'rewardcount_8' => ['nullable', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class RegisterGameAccountRequest extends FormRequest
|
||||
return [
|
||||
'username' => ['required', 'string', 'min:3', 'max:32', 'regex:/^[A-Za-z0-9]+$/'],
|
||||
'email' => ['required', 'string', 'email:rfc', 'max:255'],
|
||||
'invite_code' => ['required', 'string', 'min:6', 'max:32'],
|
||||
'invite_code' => [config('moonwell.registration.require_invite_code') ? 'required' : 'nullable', 'string', 'min:6', 'max:32'],
|
||||
'password' => ['required', 'string', 'min:8', 'max:32', 'confirmed', 'regex:/^(?=.*[A-Za-z])(?=.*\d).+$/'],
|
||||
'terms' => ['accepted'],
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user