Merge branch 'development' into servers-servercard

This commit is contained in:
IceToast 2021-12-22 15:14:21 +01:00
commit 2bfe26247a
30 changed files with 2344 additions and 1379 deletions

View file

@ -1,4 +1,4 @@
APP_NAME=Dashboard APP_NAME=Controlpanel.gg
APP_ENV=production APP_ENV=production
APP_KEY= APP_KEY=
APP_DEBUG=false APP_DEBUG=false
@ -25,6 +25,18 @@ PAYPAL_SECRET=
PAYPAL_CLIENT_ID= PAYPAL_CLIENT_ID=
PAYPAL_EMAIL= PAYPAL_EMAIL=
#stripe details, you only need "test" for testing! you can do this by setting the APP_ENV to local
STRIPE_TEST_SECRET=
STRIPE_SECRET=
#https://dashboard.stripe.com/webhooks -> webhook route: <your.controlpanel.gg>/payment/StripeWebhooks
STRIPE_ENDPOINT_TEST_SECRET=
STRIPE_ENDPOINT_SECRET=
#stripe payment methods, comma seperated list of methods you want to support:
#read into https://stripe.com/docs/payments/payment-methods/integration-options and/or https://stripe.com/docs/payments/payment-methods/overview
#Method needs to support the currency
STRIPE_METHODS=
#set-up for extra discord verification #set-up for extra discord verification
DISCORD_CLIENT_ID= DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET= DISCORD_CLIENT_SECRET=

View file

@ -1,25 +1,32 @@
### Features ### Features
- PayPal Integration - PayPal Integration
- Email Verification - Stripe Integration
- Audit Log - Email Verification
- Admin Dashboard - Audit Log
- User/Server Management - Admin Dashboard
- Store (credit system) - User/Server Management
- Vouchers - Store (credit system)
- and so much more! - Vouchers
- and so much more!
# ControlPanel-gg # ControlPanel-gg
![controlpanel](https://user-images.githubusercontent.com/45005889/123518824-06b05000-d6a8-11eb-91b9-d1ed36bd2317.png) ![controlpanel](https://user-images.githubusercontent.com/45005889/123518824-06b05000-d6a8-11eb-91b9-d1ed36bd2317.png)
![](https://img.shields.io/github/stars/ControlPanel-gg/dashboard) ![](https://img.shields.io/github/forks/ControlPanel-gg/dashboard) ![](https://img.shields.io/github/tag/ControlPanel-gg/dashboard) [![Crowdin](https://badges.crowdin.net/controlpanelgg/localized.svg)](https://crowdin.com/project/controlpanelgg) ![](https://img.shields.io/github/issues/ControlPanel-gg/dashboard) ![](https://img.shields.io/github/license/ControlPanel-gg/dashboard) ![](https://img.shields.io/discord/787829714483019826) ![](https://img.shields.io/github/stars/ControlPanel-gg/dashboard) ![](https://img.shields.io/github/forks/ControlPanel-gg/dashboard) ![](https://img.shields.io/github/tag/ControlPanel-gg/dashboard) [![Crowdin](https://badges.crowdin.net/controlpanelgg/localized.svg)](https://crowdin.com/project/controlpanelgg) ![](https://img.shields.io/github/issues/ControlPanel-gg/dashboard) ![](https://img.shields.io/github/license/ControlPanel-gg/dashboard) ![](https://img.shields.io/discord/787829714483019826)
## About ## About
ControlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that credits users hourly for each server they have and suspends them if they run out of credits. ControlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that credits users hourly for each server they have and suspends them if they run out of credits.
This dashboard offers an easy to use and free billing solution for all starting and experienced hosting providers. This dashboard has many customization options and added discord 0auth verification to offer a solid link between your discord server and your dashboard. This dashboard offers an easy to use and free billing solution for all starting and experienced hosting providers. This dashboard has many customization options and added discord 0auth verification to offer a solid link between your discord server and your dashboard.
### [Installation](https://controlpanel.gg/docs/intro "Installation") ### [Installation](https://controlpanel.gg/docs/intro "Installation")
### [Updating](https://controlpanel.gg/docs/Installation/updating "Updating") ### [Updating](https://controlpanel.gg/docs/Installation/updating "Updating")
### [Discord](https://discord.gg/4Y6HjD2uyU "discord") ### [Discord](https://discord.gg/4Y6HjD2uyU "discord")
### [Contributing](https://controlpanel.gg/docs/Contributing/contributing "Contributing") ### [Contributing](https://controlpanel.gg/docs/Contributing/contributing "Contributing")
### [Donating](https://controlpanel.gg/docs/Contributing/donating "Donating") ### [Donating](https://controlpanel.gg/docs/Contributing/donating "Donating")

View file

@ -2,7 +2,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Models\PaypalProduct; use App\Models\CreditProduct;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
@ -12,7 +12,7 @@ use Illuminate\Http\Response;
use Illuminate\Routing\Controller; use Illuminate\Routing\Controller;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
class PaypalProductController extends Controller class CreditProductController extends Controller
{ {
/** /**
* Display a listing of the resource. * Display a listing of the resource.
@ -21,11 +21,16 @@ class PaypalProductController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$isPaypalSetup = false; $isPaymentSetup = false;
if (env('PAYPAL_SECRET') && env('PAYPAL_CLIENT_ID')) $isPaypalSetup = true;
return view('admin.store.index' , [ if (
'isPaypalSetup' => $isPaypalSetup env('APP_ENV') == 'local' ||
env('PAYPAL_SECRET') && env('PAYPAL_CLIENT_ID') ||
env('STRIPE_SECRET') && env('STRIPE_ENDPOINT_SECRET') && env('STRIPE_METHODS')
) $isPaymentSetup = true;
return view('admin.store.index', [
'isPaymentSetup' => $isPaymentSetup
]); ]);
} }
@ -60,7 +65,7 @@ class PaypalProductController extends Controller
]); ]);
$disabled = !is_null($request->input('disabled')); $disabled = !is_null($request->input('disabled'));
PaypalProduct::create(array_merge($request->all(), ['disabled' => $disabled])); CreditProduct::create(array_merge($request->all(), ['disabled' => $disabled]));
return redirect()->route('admin.store.index')->with('success', __('Store item has been created!')); return redirect()->route('admin.store.index')->with('success', __('Store item has been created!'));
} }
@ -68,10 +73,10 @@ class PaypalProductController extends Controller
/** /**
* Display the specified resource. * Display the specified resource.
* *
* @param PaypalProduct $paypalProduct * @param CreditProduct $creditProduct
* @return Response * @return Response
*/ */
public function show(PaypalProduct $paypalProduct) public function show(CreditProduct $creditProduct)
{ {
// //
} }
@ -79,14 +84,14 @@ class PaypalProductController extends Controller
/** /**
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
* *
* @param PaypalProduct $paypalProduct * @param CreditProduct $creditProduct
* @return Application|Factory|View|Response * @return Application|Factory|View|Response
*/ */
public function edit(PaypalProduct $paypalProduct) public function edit(CreditProduct $creditProduct)
{ {
return view('admin.store.edit', [ return view('admin.store.edit', [
'currencyCodes' => config('currency_codes'), 'currencyCodes' => config('currency_codes'),
'paypalProduct' => $paypalProduct 'creditProduct' => $creditProduct
]); ]);
} }
@ -94,10 +99,10 @@ class PaypalProductController extends Controller
* Update the specified resource in storage. * Update the specified resource in storage.
* *
* @param Request $request * @param Request $request
* @param PaypalProduct $paypalProduct * @param CreditProduct $creditProduct
* @return RedirectResponse * @return RedirectResponse
*/ */
public function update(Request $request, PaypalProduct $paypalProduct) public function update(Request $request, CreditProduct $creditProduct)
{ {
$request->validate([ $request->validate([
"disabled" => "nullable", "disabled" => "nullable",
@ -110,19 +115,19 @@ class PaypalProductController extends Controller
]); ]);
$disabled = !is_null($request->input('disabled')); $disabled = !is_null($request->input('disabled'));
$paypalProduct->update(array_merge($request->all(), ['disabled' => $disabled])); $creditProduct->update(array_merge($request->all(), ['disabled' => $disabled]));
return redirect()->route('admin.store.index')->with('success', __('Store item has been updated!')); return redirect()->route('admin.store.index')->with('success', __('Store item has been updated!'));
} }
/** /**
* @param Request $request * @param Request $request
* @param PaypalProduct $paypalProduct * @param CreditProduct $creditProduct
* @return RedirectResponse * @return RedirectResponse
*/ */
public function disable(Request $request, PaypalProduct $paypalProduct) public function disable(Request $request, CreditProduct $creditProduct)
{ {
$paypalProduct->update(['disabled' => !$paypalProduct->disabled]); $creditProduct->update(['disabled' => !$creditProduct->disabled]);
return redirect()->route('admin.store.index')->with('success', __('Product has been updated!')); return redirect()->route('admin.store.index')->with('success', __('Product has been updated!'));
} }
@ -130,50 +135,50 @@ class PaypalProductController extends Controller
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
* *
* @param PaypalProduct $paypalProduct * @param CreditProduct $creditProduct
* @return RedirectResponse * @return RedirectResponse
*/ */
public function destroy(PaypalProduct $paypalProduct) public function destroy(CreditProduct $creditProduct)
{ {
$paypalProduct->delete(); $creditProduct->delete();
return redirect()->back()->with('success', __('Store item has been removed!')); return redirect()->back()->with('success', __('Store item has been removed!'));
} }
public function dataTable() public function dataTable()
{ {
$query = PaypalProduct::query(); $query = CreditProduct::query();
return datatables($query) return datatables($query)
->addColumn('actions', function (PaypalProduct $paypalProduct) { ->addColumn('actions', function (CreditProduct $creditProduct) {
return ' return '
<a data-content="'.__("Edit").'" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.store.edit', $paypalProduct->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> <a data-content="' . __("Edit") . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.store.edit', $creditProduct->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a>
<form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.store.destroy', $paypalProduct->id) . '"> <form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.store.destroy', $creditProduct->id) . '">
' . csrf_field() . ' ' . csrf_field() . '
' . method_field("DELETE") . ' ' . method_field("DELETE") . '
<button data-content="'.__("Delete").'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> <button data-content="' . __("Delete") . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button>
</form> </form>
'; ';
}) })
->addColumn('disabled', function (PaypalProduct $paypalProduct) { ->addColumn('disabled', function (CreditProduct $creditProduct) {
$checked = $paypalProduct->disabled == false ? "checked" : ""; $checked = $creditProduct->disabled == false ? "checked" : "";
return ' return '
<form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.store.disable', $paypalProduct->id) . '"> <form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.store.disable', $creditProduct->id) . '">
' . csrf_field() . ' ' . csrf_field() . '
' . method_field("PATCH") . ' ' . method_field("PATCH") . '
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input ' . $checked . ' name="disabled" onchange="this.form.submit()" type="checkbox" class="custom-control-input" id="switch' . $paypalProduct->id . '"> <input ' . $checked . ' name="disabled" onchange="this.form.submit()" type="checkbox" class="custom-control-input" id="switch' . $creditProduct->id . '">
<label class="custom-control-label" for="switch' . $paypalProduct->id . '"></label> <label class="custom-control-label" for="switch' . $creditProduct->id . '"></label>
</div> </div>
</form> </form>
'; ';
}) })
->editColumn('created_at', function (PaypalProduct $paypalProduct) { ->editColumn('created_at', function (CreditProduct $creditProduct) {
return $paypalProduct->created_at ? $paypalProduct->created_at->diffForHumans() : ''; return $creditProduct->created_at ? $creditProduct->created_at->diffForHumans() : '';
}) })
->editColumn('price', function (PaypalProduct $paypalProduct) { ->editColumn('price', function (CreditProduct $creditProduct) {
return $paypalProduct->formatToCurrency($paypalProduct->price); return $creditProduct->formatToCurrency($creditProduct->price);
}) })
->rawColumns(['actions', 'disabled']) ->rawColumns(['actions', 'disabled'])
->make(); ->make();

View file

@ -7,9 +7,10 @@ use App\Http\Controllers\Controller;
use App\Models\Configuration; use App\Models\Configuration;
use App\Models\InvoiceSettings; use App\Models\InvoiceSettings;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PaypalProduct; use App\Models\CreditProduct;
use App\Models\User; use App\Models\User;
use App\Notifications\InvoiceNotification; use App\Notifications\InvoiceNotification;
use App\Notifications\ConfirmPaymentNotification;
use Exception; use Exception;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
@ -29,6 +30,8 @@ use PayPalCheckoutSdk\Core\SandboxEnvironment;
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest; use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest; use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
use PayPalHttp\HttpException; use PayPalHttp\HttpException;
use Stripe\Stripe;
class PaymentController extends Controller class PaymentController extends Controller
{ {
@ -45,25 +48,25 @@ class PaymentController extends Controller
/** /**
* @param Request $request * @param Request $request
* @param PaypalProduct $paypalProduct * @param CreditProduct $creditProduct
* @return Application|Factory|View * @return Application|Factory|View
*/ */
public function checkOut(Request $request, PaypalProduct $paypalProduct) public function checkOut(Request $request, CreditProduct $creditProduct)
{ {
return view('store.checkout')->with([ return view('store.checkout')->with([
'product' => $paypalProduct, 'product' => $creditProduct,
'taxvalue' => $paypalProduct->getTaxValue(), 'taxvalue' => $creditProduct->getTaxValue(),
'taxpercent' => $paypalProduct->getTaxPercent(), 'taxpercent' => $creditProduct->getTaxPercent(),
'total' => $paypalProduct->getTotalPrice() 'total' => $creditProduct->getTotalPrice()
]); ]);
} }
/** /**
* @param Request $request * @param Request $request
* @param PaypalProduct $paypalProduct * @param CreditProduct $creditProduct
* @return RedirectResponse * @return RedirectResponse
*/ */
public function pay(Request $request, PaypalProduct $paypalProduct) public function PaypalPay(Request $request, CreditProduct $creditProduct)
{ {
$request = new OrdersCreateRequest(); $request = new OrdersCreateRequest();
$request->prefer('return=representation'); $request->prefer('return=representation');
@ -72,30 +75,30 @@ class PaymentController extends Controller
"purchase_units" => [ "purchase_units" => [
[ [
"reference_id" => uniqid(), "reference_id" => uniqid(),
"description" => $paypalProduct->description, "description" => $creditProduct->description,
"amount" => [ "amount" => [
"value" => $paypalProduct->getTotalPrice(), "value" => $creditProduct->getTotalPrice(),
'currency_code' => strtoupper($paypalProduct->currency_code), 'currency_code' => strtoupper($creditProduct->currency_code),
'breakdown' => [ 'breakdown' => [
'item_total' => 'item_total' =>
[ [
'currency_code' => strtoupper($paypalProduct->currency_code), 'currency_code' => strtoupper($creditProduct->currency_code),
'value' => $paypalProduct->price, 'value' => $creditProduct->price,
], ],
'tax_total' => 'tax_total' =>
[ [
'currency_code' => strtoupper($paypalProduct->currency_code), 'currency_code' => strtoupper($creditProduct->currency_code),
'value' => $paypalProduct->getTaxValue(), 'value' => $creditProduct->getTaxValue(),
] ]
] ]
] ]
] ]
], ],
"application_context" => [ "application_context" => [
"cancel_url" => route('payment.cancel'), "cancel_url" => route('payment.Cancel'),
"return_url" => route('payment.success', ['product' => $paypalProduct->id]), "return_url" => route('payment.PaypalSuccess', ['product' => $creditProduct->id]),
'brand_name' => config('app.name', 'Laravel'), 'brand_name' => config('app.name', 'Laravel'),
'shipping_preference' => 'NO_SHIPPING' 'shipping_preference' => 'NO_SHIPPING'
] ]
@ -112,7 +115,6 @@ class PaymentController extends Controller
echo $ex->statusCode; echo $ex->statusCode;
dd(json_decode($ex->getMessage())); dd(json_decode($ex->getMessage()));
} }
} }
/** /**
@ -121,8 +123,8 @@ class PaymentController extends Controller
protected function getPayPalClient() protected function getPayPalClient()
{ {
$environment = env('APP_ENV') == 'local' $environment = env('APP_ENV') == 'local'
? new SandboxEnvironment($this->getClientId(), $this->getClientSecret()) ? new SandboxEnvironment($this->getPaypalClientId(), $this->getPaypalClientSecret())
: new ProductionEnvironment($this->getClientId(), $this->getClientSecret()); : new ProductionEnvironment($this->getPaypalClientId(), $this->getPaypalClientSecret());
return new PayPalHttpClient($environment); return new PayPalHttpClient($environment);
} }
@ -130,7 +132,7 @@ class PaymentController extends Controller
/** /**
* @return string * @return string
*/ */
protected function getClientId() protected function getPaypalClientId()
{ {
return env('APP_ENV') == 'local' ? env('PAYPAL_SANDBOX_CLIENT_ID') : env('PAYPAL_CLIENT_ID'); return env('APP_ENV') == 'local' ? env('PAYPAL_SANDBOX_CLIENT_ID') : env('PAYPAL_CLIENT_ID');
} }
@ -138,7 +140,7 @@ class PaymentController extends Controller
/** /**
* @return string * @return string
*/ */
protected function getClientSecret() protected function getPaypalClientSecret()
{ {
return env('APP_ENV') == 'local' ? env('PAYPAL_SANDBOX_SECRET') : env('PAYPAL_SECRET'); return env('APP_ENV') == 'local' ? env('PAYPAL_SANDBOX_SECRET') : env('PAYPAL_SECRET');
} }
@ -146,10 +148,11 @@ class PaymentController extends Controller
/** /**
* @param Request $laravelRequest * @param Request $laravelRequest
*/ */
public function success(Request $laravelRequest) public function PaypalSuccess(Request $laravelRequest)
{ {
/** @var PaypalProduct $paypalProduct */ /** @var CreditProduct $creditProduct */
$paypalProduct = PaypalProduct::findOrFail($laravelRequest->input('product')); $creditProduct = CreditProduct::findOrFail($laravelRequest->input('product'));
/** @var User $user */ /** @var User $user */
$user = Auth::user(); $user = Auth::user();
@ -161,7 +164,7 @@ class PaymentController extends Controller
if ($response->statusCode == 201 || $response->statusCode == 200) { if ($response->statusCode == 201 || $response->statusCode == 200) {
//update credits //update credits
$user->increment('credits', $paypalProduct->quantity); $user->increment('credits', $creditProduct->quantity);
//update server limit //update server limit
if (Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) { if (Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
@ -179,82 +182,22 @@ class PaymentController extends Controller
$payment = Payment::create([ $payment = Payment::create([
'user_id' => $user->id, 'user_id' => $user->id,
'payment_id' => $response->result->id, 'payment_id' => $response->result->id,
'payer_id' => $laravelRequest->input('PayerID'), 'payment_method' => 'paypal',
'type' => 'Credits', 'type' => 'Credits',
'status' => $response->result->status, 'status' => 'paid',
'amount' => $paypalProduct->quantity, 'amount' => $creditProduct->quantity,
'price' => $paypalProduct->price, 'price' => $creditProduct->price,
'tax_value' => $paypalProduct->getTaxValue(), 'tax_value' => $creditProduct->getTaxValue(),
'tax_percent' => $paypalProduct->getTaxPercent(), 'tax_percent' => $creditProduct->getTaxPercent(),
'total_price' => $paypalProduct->getTotalPrice(), 'total_price' => $creditProduct->getTotalPrice(),
'currency_code' => $paypalProduct->currency_code, 'currency_code' => $creditProduct->currency_code,
'payer' => json_encode($response->result->payer), 'credit_product_id' => $creditProduct->id,
]); ]);
event(new UserUpdateCreditsEvent($user)); event(new UserUpdateCreditsEvent($user));
//create invoice $this->createInvoice($user, $payment, 'paid');
$lastInvoiceID = \App\Models\Invoice::where("invoice_name", "like", "%" . now()->format('mY') . "%")->count("id");
$newInvoiceID = $lastInvoiceID + 1;
$InvoiceSettings = InvoiceSettings::query()->first();
$logoPath = storage_path('app/public/logo.png');
$seller = new Party([
'name' => $InvoiceSettings->company_name,
'phone' => $InvoiceSettings->company_phone,
'address' => $InvoiceSettings->company_adress,
'vat' => $InvoiceSettings->company_vat,
'custom_fields' => [
'E-Mail' => $InvoiceSettings->company_mail,
"Web" => $InvoiceSettings->company_web
],
]);
$customer = new Buyer([
'name' => $user->name,
'custom_fields' => [
'E-Mail' => $user->email,
'Client ID' => $user->id,
],
]);
$item = (new InvoiceItem())
->title($paypalProduct->description)
->pricePerUnit($paypalProduct->price);
$invoice = Invoice::make()
->template('controlpanel')
->name(__("Invoice"))
->buyer($customer)
->seller($seller)
->discountByPercent(0)
->taxRate(floatval($paypalProduct->getTaxPercent()))
->shipping(0)
->addItem($item)
->status(__('Paid'))
->series(now()->format('mY'))
->delimiter("-")
->sequence($newInvoiceID)
->serialNumberFormat($InvoiceSettings->invoice_prefix . '{DELIMITER}{SERIES}{SEQUENCE}');
if (file_exists($logoPath)) {
$invoice->logo($logoPath);
}
//Save the invoice in "storage\app\invoice\USER_ID\YEAR"
$invoice->filename = $invoice->getSerialNumber() . '.pdf';
$invoice->render();
Storage::disk("local")->put("invoice/" . $user->id . "/" . now()->format('Y') . "/" . $invoice->filename, $invoice->output);
\App\Models\Invoice::create([
'invoice_user' => $user->id,
'invoice_name' => $invoice->getSerialNumber(),
'payment_id' => $payment->payment_id,
]);
//Send Invoice per Mail
$user->notify(new InvoiceNotification($invoice, $user, $payment));
//redirect back to home //redirect back to home
return redirect()->route('home')->with('success', __('Your credit balance has been increased!')); return redirect()->route('home')->with('success', __('Your credit balance has been increased!'));
@ -267,7 +210,6 @@ class PaymentController extends Controller
} else { } else {
abort(500); abort(500);
} }
} catch (HttpException $ex) { } catch (HttpException $ex) {
if (env('APP_ENV') == 'local') { if (env('APP_ENV') == 'local') {
echo $ex->statusCode; echo $ex->statusCode;
@ -275,20 +217,351 @@ class PaymentController extends Controller
} else { } else {
abort(422); abort(422);
} }
} }
} }
/** /**
* @param Request $request * @param Request $request
*/ */
public function cancel(Request $request) public function Cancel(Request $request)
{ {
return redirect()->route('store.index')->with('success', __('Payment was Canceled')); return redirect()->route('store.index')->with('success', 'Payment was Canceled');
} }
/**
* @param Request $request
* @param CreditProduct $creditProduct
* @return RedirectResponse
*/
public function StripePay(Request $request, CreditProduct $creditProduct)
{
$stripeClient = $this->getStripeClient();
$request = $stripeClient->checkout->sessions->create([
'line_items' => [
[
'price_data' => [
'currency' => $creditProduct->currency_code,
'product_data' => [
'name' => $creditProduct->display,
'description' => $creditProduct->description,
],
'unit_amount_decimal' => round($creditProduct->price * 100, 2),
],
'quantity' => 1,
],
[
'price_data' => [
'currency' => $creditProduct->currency_code,
'product_data' => [
'name' => 'Product Tax',
'description' => $creditProduct->getTaxPercent() . "%",
],
'unit_amount_decimal' => round($creditProduct->getTaxValue(), 2) * 100,
],
'quantity' => 1,
]
],
'mode' => 'payment',
"payment_method_types" => str_getcsv(env('STRIPE_METHODS')),
'success_url' => route('payment.StripeSuccess', ['product' => $creditProduct->id]) . '&session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('payment.Cancel'),
]);
return redirect($request->url, 303);
}
/**
* @param Request $request
*/
public function StripeSuccess(Request $request)
{
/** @var CreditProduct $creditProduct */
$creditProduct = CreditProduct::findOrFail($request->input('product'));
/** @var User $user */
$user = Auth::user();
$stripeClient = $this->getStripeClient();
try {
//get stripe data
$paymentSession = $stripeClient->checkout->sessions->retrieve($request->input('session_id'));
$paymentIntent = $stripeClient->paymentIntents->retrieve($paymentSession->payment_intent);
//get DB entry of this payment ID if existing
$paymentDbEntry = Payment::where('payment_id', $paymentSession->payment_intent)->count();
// check if payment is 100% completed and payment does not exist in db already
if ($paymentSession->status == "complete" && $paymentIntent->status == "succeeded" && $paymentDbEntry == 0) {
//update credits
$user->increment('credits', $creditProduct->quantity);
//update server limit
if (Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
if ($user->server_limit < Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
$user->update(['server_limit' => Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
}
}
//update role
if ($user->role == 'member') {
$user->update(['role' => 'client']);
}
//store paid payment
$payment = Payment::create([
'user_id' => $user->id,
'payment_id' => $paymentSession->payment_intent,
'payment_method' => 'stripe',
'type' => 'Credits',
'status' => 'paid',
'amount' => $creditProduct->quantity,
'price' => $creditProduct->price,
'tax_value' => $creditProduct->getTaxValue(),
'total_price' => $creditProduct->getTotalPrice(),
'tax_percent' => $creditProduct->getTaxPercent(),
'currency_code' => $creditProduct->currency_code,
'credit_product_id' => $creditProduct->id,
]);
//payment notification
$user->notify(new ConfirmPaymentNotification($payment));
event(new UserUpdateCreditsEvent($user));
$this->createInvoice($user, $payment, 'paid');
//redirect back to home
return redirect()->route('home')->with('success', __('Your credit balance has been increased!'));
} else {
if ($paymentIntent->status == "processing") {
//store processing payment
$payment = Payment::create([
'user_id' => $user->id,
'payment_id' => $paymentSession->payment_intent,
'payment_method' => 'stripe',
'type' => 'Credits',
'status' => 'processing',
'amount' => $creditProduct->quantity,
'price' => $creditProduct->price,
'tax_value' => $creditProduct->getTaxValue(),
'total_price' => $creditProduct->getTotalPrice(),
'tax_percent' => $creditProduct->getTaxPercent(),
'currency_code' => $creditProduct->currency_code,
'credit_product_id' => $creditProduct->id,
]);
$this->createInvoice($user, $payment, 'processing');
//redirect back to home
return redirect()->route('home')->with('success', __('Your payment is being processed!'));
}
if ($paymentDbEntry == 0 && $paymentIntent->status != "processing") {
$stripeClient->paymentIntents->cancel($paymentIntent->id);
//redirect back to home
return redirect()->route('home')->with('success', __('Your payment has been canceled!'));
} else {
abort(402);
}
}
} catch (HttpException $ex) {
if (env('APP_ENV') == 'local') {
echo $ex->statusCode;
dd($ex->getMessage());
} else {
abort(422);
}
}
}
/**
* @param Request $request
*/
protected function handleStripePaymentSuccessHook($paymentIntent)
{
try {
// Get payment db entry
$payment = Payment::where('payment_id', $paymentIntent->id)->first();
$user = User::where('id', $payment->user_id)->first();
if ($paymentIntent->status == 'succeeded' && $payment->status == 'processing') {
// Increment User Credits
$user->increment('credits', $payment->amount);
//update server limit
if (Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
if ($user->server_limit < Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
$user->update(['server_limit' => Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
}
}
//update role
if ($user->role == 'member') {
$user->update(['role' => 'client']);
}
//update payment db entry status
$payment->update(['status' => 'paid']);
//payment notification
$user->notify(new ConfirmPaymentNotification($payment));
event(new UserUpdateCreditsEvent($user));
$this->createInvoice($user, $payment, 'paid');
}
} catch (HttpException $ex) {
abort(422);
}
}
/**
* @param Request $request
*/
public function StripeWebhooks(Request $request)
{
\Stripe\Stripe::setApiKey($this->getStripeSecret());
try {
$payload = @file_get_contents('php://input');
$sig_header = $request->header('Stripe-Signature');
$event = null;
$event = \Stripe\Webhook::constructEvent(
$payload,
$sig_header,
$this->getStripeEndpointSecret()
);
} catch (\UnexpectedValueException $e) {
// Invalid payload
abort(400);
} catch (\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
abort(400);
}
// Handle the event
switch ($event->type) {
case 'payment_intent.succeeded':
$paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent
$this->handleStripePaymentSuccessHook($paymentIntent);
break;
default:
echo 'Received unknown event type ' . $event->type;
}
}
/**
* @return \Stripe\StripeClient
*/
protected function getStripeClient()
{
return new \Stripe\StripeClient($this->getStripeSecret());
}
/**
* @return string
*/
protected function getStripeSecret()
{
return env('APP_ENV') == 'local'
? env('STRIPE_TEST_SECRET')
: env('STRIPE_SECRET');
}
/**
* @return string
*/
protected function getStripeEndpointSecret()
{
return env('APP_ENV') == 'local'
? env('STRIPE_ENDPOINT_TEST_SECRET')
: env('STRIPE_ENDPOINT_SECRET');
}
protected function createInvoice($user, $payment, $paymentStatus)
{
$creditProduct = CreditProduct::where('id', $payment->credit_product_id)->first();
//create invoice
$lastInvoiceID = \App\Models\Invoice::where("invoice_name", "like", "%" . now()->format('mY') . "%")->count("id");
$newInvoiceID = $lastInvoiceID + 1;
$InvoiceSettings = InvoiceSettings::query()->first();
$logoPath = storage_path('app/public/logo.png');
$seller = new Party([
'name' => $InvoiceSettings->company_name,
'phone' => $InvoiceSettings->company_phone,
'address' => $InvoiceSettings->company_adress,
'vat' => $InvoiceSettings->company_vat,
'custom_fields' => [
'E-Mail' => $InvoiceSettings->company_mail,
"Web" => $InvoiceSettings->company_web
],
]);
$customer = new Buyer([
'name' => $user->name,
'custom_fields' => [
'E-Mail' => $user->email,
'Client ID' => $user->id,
],
]);
$item = (new InvoiceItem())
->title($creditProduct->description)
->pricePerUnit($creditProduct->price);
$notes = [
__("Payment method") .": ". $payment->payment_method,
];
$notes = implode("<br>", $notes);
$invoice = Invoice::make()
->template('controlpanel')
->name(__("Invoice"))
->buyer($customer)
->seller($seller)
->discountByPercent(0)
->taxRate(floatval($creditProduct->getTaxPercent()))
->shipping(0)
->addItem($item)
->status(__($paymentStatus))
->series(now()->format('mY'))
->delimiter("-")
->sequence($newInvoiceID)
->serialNumberFormat($InvoiceSettings->invoice_prefix . '{DELIMITER}{SERIES}{SEQUENCE}')
->notes($notes);
if (file_exists($logoPath)) {
$invoice->logo($logoPath);
}
//Save the invoice in "storage\app\invoice\USER_ID\YEAR"
$invoice->filename = $invoice->getSerialNumber() . '.pdf';
$invoice->render();
Storage::disk("local")->put("invoice/" . $user->id . "/" . now()->format('Y') . "/" . $invoice->filename, $invoice->output);
\App\Models\Invoice::create([
'invoice_user' => $user->id,
'invoice_name' => $invoice->getSerialNumber(),
'payment_id' => $payment->payment_id,
]);
//Send Invoice per Mail
$user->notify(new InvoiceNotification($invoice, $user, $payment));
}
/** /**
* @return JsonResponse|mixed * @return JsonResponse|mixed
@ -308,9 +581,13 @@ class PaymentController extends Controller
->editColumn('tax_value', function (Payment $payment) { ->editColumn('tax_value', function (Payment $payment) {
return $payment->formatToCurrency($payment->tax_value); return $payment->formatToCurrency($payment->tax_value);
}) })
->editColumn('tax_percent', function (Payment $payment) {
return $payment->tax_percent . ' %';
})
->editColumn('total_price', function (Payment $payment) { ->editColumn('total_price', function (Payment $payment) {
return $payment->formatToCurrency($payment->total_price); return $payment->formatToCurrency($payment->total_price);
}) })
->editColumn('created_at', function (Payment $payment) { ->editColumn('created_at', function (Payment $payment) {
return $payment->created_at ? $payment->created_at->diffForHumans() : ''; return $payment->created_at ? $payment->created_at->diffForHumans() : '';
}) })

View file

@ -3,7 +3,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Configuration; use App\Models\Configuration;
use App\Models\PaypalProduct; use App\Models\CreditProduct;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
class StoreController extends Controller class StoreController extends Controller
@ -11,10 +11,13 @@ class StoreController extends Controller
/** Display a listing of the resource. */ /** Display a listing of the resource. */
public function index() public function index()
{ {
$isPaypalSetup = false; $isPaymentSetup = false;
if (env('PAYPAL_SECRET') && env('PAYPAL_CLIENT_ID')) $isPaypalSetup = true;
if (env('APP_ENV', 'local') == 'local') $isPaypalSetup = true;
if (
env('APP_ENV') == 'local' ||
env('PAYPAL_SECRET') && env('PAYPAL_CLIENT_ID') ||
env('STRIPE_SECRET') && env('STRIPE_ENDPOINT_SECRET') && env('STRIPE_METHODS')
) $isPaymentSetup = true;
//Required Verification for creating an server //Required Verification for creating an server
if (Configuration::getValueByKey('FORCE_EMAIL_VERIFICATION', false) === 'true' && !Auth::user()->hasVerifiedEmail()) { if (Configuration::getValueByKey('FORCE_EMAIL_VERIFICATION', false) === 'true' && !Auth::user()->hasVerifiedEmail()) {
@ -27,8 +30,8 @@ class StoreController extends Controller
} }
return view('store.index')->with([ return view('store.index')->with([
'products' => PaypalProduct::where('disabled', '=', false)->orderBy('price', 'asc')->get(), 'products' => CreditProduct::where('disabled', '=', false)->orderBy('price', 'asc')->get(),
'isPaypalSetup' => $isPaypalSetup 'isPaymentSetup' => $isPaymentSetup,
]); ]);
} }
} }

View file

@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
* @var array * @var array
*/ */
protected $except = [ protected $except = [
// 'payment/StripeWebhooks'
]; ];
} }

View file

@ -8,7 +8,7 @@ use NumberFormatter;
use Spatie\Activitylog\Traits\LogsActivity; use Spatie\Activitylog\Traits\LogsActivity;
use App\Models\Configuration; use App\Models\Configuration;
class PaypalProduct extends Model class CreditProduct extends Model
{ {
use LogsActivity; use LogsActivity;
/** /**
@ -33,10 +33,10 @@ class PaypalProduct extends Model
{ {
parent::boot(); parent::boot();
static::creating(function (PaypalProduct $paypalProduct) { static::creating(function (CreditProduct $creditProduct) {
$client = new Client(); $client = new Client();
$paypalProduct->{$paypalProduct->getKeyName()} = $client->generateId($size = 21); $creditProduct->{$creditProduct->getKeyName()} = $client->generateId($size = 21);
}); });
} }

View file

@ -23,8 +23,7 @@ class Payment extends Model
'id', 'id',
'user_id', 'user_id',
'payment_id', 'payment_id',
'payer_id', 'payment_method',
'payer',
'status', 'status',
'type', 'type',
'amount', 'amount',
@ -33,6 +32,7 @@ class Payment extends Model
'total_price', 'total_price',
'tax_percent', 'tax_percent',
'currency_code', 'currency_code',
'credit_product_id',
]; ];
public static function boot() public static function boot()
@ -57,10 +57,10 @@ class Payment extends Model
/** /**
* @param mixed $value * @param mixed $value
* @param string $locale * @param string $locale
* *
* @return float * @return float
*/ */
public function formatToCurrency($value,$locale = 'en_US') public function formatToCurrency($value, $locale = 'en_US')
{ {
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
return $formatter->formatCurrency($value, $this->currency_code); return $formatter->formatCurrency($value, $this->currency_code);

View file

@ -1,7 +1,7 @@
{ {
"name": "laravel/laravel", "name": "cpgg/dashboard",
"type": "project", "type": "project",
"description": "The Laravel Framework.", "description": "A billing and control panel made for Pterodactyl.",
"keywords": [ "keywords": [
"framework", "framework",
"laravel" "laravel"
@ -27,6 +27,7 @@
"spatie/laravel-activitylog": "^3.16", "spatie/laravel-activitylog": "^3.16",
"spatie/laravel-query-builder": "^3.6", "spatie/laravel-query-builder": "^3.6",
"spatie/laravel-validation-rules": "^3.0", "spatie/laravel-validation-rules": "^3.0",
"stripe/stripe-php": "^7.107",
"yajra/laravel-datatables-oracle": "~9.0" "yajra/laravel-datatables-oracle": "~9.0"
}, },
"require-dev": { "require-dev": {

2337
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,72 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Routes group config
|--------------------------------------------------------------------------
|
| The default group settings for the elFinder routes.
|
*/
'route' => [
'prefix' => 'translations',
'middleware' => [
'web',
'auth',
],
],
/**
* Enable deletion of translations
*
* @type boolean
*/
'delete_enabled' => true,
/**
* Exclude specific groups from Laravel Translation Manager.
* This is useful if, for example, you want to avoid editing the official Laravel language files.
*
* @type array
*
* array(
* 'pagination',
* 'reminders',
* 'validation',
* )
*/
'exclude_groups' => [],
/**
* Exclude specific languages from Laravel Translation Manager.
*
* @type array
*
* array(
* 'fr',
* 'de',
* )
*/
'exclude_langs' => [],
/**
* Export translations with keys output alphabetically.
*/
'sort_keys' => false,
'trans_functions' => [
'trans',
'trans_choice',
'Lang::get',
'Lang::choice',
'Lang::trans',
'Lang::transChoice',
'@lang',
'@choice',
'__',
'$trans.get',
],
];

View file

@ -1,38 +0,0 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTranslationsTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ltm_translations', function(Blueprint $table)
{
$table->collation = 'utf8mb4_bin';
$table->bigIncrements('id');
$table->integer('status')->default(0);
$table->string('locale');
$table->string('group');
$table->text('key');
$table->text('value')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('ltm_translations');
}
}

View file

@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class CreatePaypalProductsTable extends Migration class CreateCreditProductsTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,7 +13,7 @@ class CreatePaypalProductsTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::create('paypal_products', function (Blueprint $table) { Schema::create('credit_products', function (Blueprint $table) {
$table->uuid('id')->primary(); $table->uuid('id')->primary();
$table->string('type'); $table->string('type');
$table->decimal('price')->default(0); $table->decimal('price')->default(0);
@ -32,6 +32,6 @@ class CreatePaypalProductsTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::dropIfExists('paypal_products'); Schema::dropIfExists('credit_products');
} }
} }

View file

@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
class AddDisplayToPaypalProductsTable extends Migration class AddDisplayToCreditProductsTable extends Migration
{ {
/** /**
* Run the migrations. * Run the migrations.
@ -13,7 +13,7 @@ class AddDisplayToPaypalProductsTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::table('paypal_products', function (Blueprint $table) { Schema::table('credit_products', function (Blueprint $table) {
$table->string('display'); $table->string('display');
}); });
} }
@ -25,7 +25,7 @@ class AddDisplayToPaypalProductsTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('paypal_products', function (Blueprint $table) { Schema::table('credit_products', function (Blueprint $table) {
$table->dropColumn('display'); $table->dropColumn('display');
}); });
} }

View file

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class UpdateToPaymentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('payments', function (Blueprint $table) {
$table->string('payment_method');
$table->dropColumn('payer');
$table->dropColumn('payer_id');
$table->string('credit_product_id');
});
DB::statement('UPDATE payments SET payment_method="paypal"');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('payments', function (Blueprint $table) {
$table->dropColumn('payment_method');
$table->string('payer_id')->nullable();
$table->text('payer')->nullable();
$table->dropColumn('credit_product_id');
});
}
}

View file

@ -3,7 +3,7 @@
namespace Database\Seeders; namespace Database\Seeders;
use Database\Seeders\Seeds\ProductSeeder; use Database\Seeders\Seeds\ProductSeeder;
use Database\Seeders\Seeds\PaypalProductSeeder; use Database\Seeders\Seeds\CreditProductSeeder;
use Database\Seeders\Seeds\ApplicationApiSeeder; use Database\Seeders\Seeds\ApplicationApiSeeder;
use Database\Seeders\Seeds\UsefulLinksSeeder; use Database\Seeders\Seeds\UsefulLinksSeeder;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
@ -19,7 +19,7 @@ class ExampleItemsSeeder extends Seeder
{ {
$this->call([ $this->call([
ProductSeeder::class, ProductSeeder::class,
PaypalProductSeeder::class, CreditProductSeeder::class,
ApplicationApiSeeder::class, ApplicationApiSeeder::class,
UsefulLinksSeeder::class UsefulLinksSeeder::class
]); ]);

View file

@ -2,10 +2,10 @@
namespace Database\Seeders\Seeds; namespace Database\Seeders\Seeds;
use App\Models\PaypalProduct; use App\Models\CreditProduct;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
class PaypalProductSeeder extends Seeder class CreditProductSeeder extends Seeder
{ {
/** /**
* Run the database seeds. * Run the database seeds.
@ -14,7 +14,7 @@ class PaypalProductSeeder extends Seeder
*/ */
public function run() public function run()
{ {
PaypalProduct::create([ CreditProduct::create([
'type' => 'Credits', 'type' => 'Credits',
'display' => '350', 'display' => '350',
'description' => 'Adds 350 credits to your account', 'description' => 'Adds 350 credits to your account',
@ -24,7 +24,7 @@ class PaypalProductSeeder extends Seeder
'disabled' => false, 'disabled' => false,
]); ]);
PaypalProduct::create([ CreditProduct::create([
'type' => 'Credits', 'type' => 'Credits',
'display' => '875 + 125', 'display' => '875 + 125',
'description' => 'Adds 1000 credits to your account', 'description' => 'Adds 1000 credits to your account',
@ -34,7 +34,7 @@ class PaypalProductSeeder extends Seeder
'disabled' => false, 'disabled' => false,
]); ]);
PaypalProduct::create([ CreditProduct::create([
'type' => 'Credits', 'type' => 'Credits',
'display' => '1750 + 250', 'display' => '1750 + 250',
'description' => 'Adds 2000 credits to your account', 'description' => 'Adds 2000 credits to your account',
@ -44,7 +44,7 @@ class PaypalProductSeeder extends Seeder
'disabled' => false, 'disabled' => false,
]); ]);
PaypalProduct::create([ CreditProduct::create([
'type' => 'Credits', 'type' => 'Credits',
'display' => '3500 + 500', 'display' => '3500 + 500',
'description' => 'Adds 4000 credits to your account', 'description' => 'Adds 4000 credits to your account',

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -95,8 +95,8 @@
"This is what the user sees at checkout": "Dies ist die Beschreibung auf der Rechnung und was der Kunde beim kauf sieht", "This is what the user sees at checkout": "Dies ist die Beschreibung auf der Rechnung und was der Kunde beim kauf sieht",
"Adds 1000 credits to your account": "Fügt deinem Account 1000 Credits hinzu", "Adds 1000 credits to your account": "Fügt deinem Account 1000 Credits hinzu",
"Active": "Aktiv", "Active": "Aktiv",
"Paypal is not configured.": "Paypal ist nicht konfiguriert!", "No payment method is configured.": "Keine Bezahlmethode konfiguriert!",
"To configure PayPal, head to the .env and add your PayPals client id and secret.": "Um Paypal zu konfigurieren, füge deine Paypal client ID und Secretkey in deine .env-Datei hinzu", "To configure the payment methods, head to the .env and add the required options for your prefered payment method.": "Um die Bezahlmethoden einzustellen, setze die passenden Variablen, für eine oder mehrere Bezahlmethoden, in der .env",
"Useful Links": "Nützliche Links", "Useful Links": "Nützliche Links",
"Icon class name": "Icon Klassen-Name", "Icon class name": "Icon Klassen-Name",
"You can find available free icons": "Hier gibt es kostenlose Icons", "You can find available free icons": "Hier gibt es kostenlose Icons",
@ -226,6 +226,7 @@
"Subtotal": "Zwischensumme", "Subtotal": "Zwischensumme",
"Submit Payment": "Zahlung bestätigen", "Submit Payment": "Zahlung bestätigen",
"Payment Methods": "Zahlungsmethoden", "Payment Methods": "Zahlungsmethoden",
"Payment method": "Zahlungsmethode",
"By purchasing this product you agree and accept our terms of service": "Mit dem kauf akzeptierst du unsere TOS", "By purchasing this product you agree and accept our terms of service": "Mit dem kauf akzeptierst du unsere TOS",
"There are no store products!": "Es gibt keine Produkte", "There are no store products!": "Es gibt keine Produkte",
"The store is not correctly configured!": "Der Laden wurde nicht richtig konfiguriert", "The store is not correctly configured!": "Der Laden wurde nicht richtig konfiguriert",

View file

@ -5,16 +5,17 @@
"Edit": "Edit", "Edit": "Edit",
"Delete": "Delete", "Delete": "Delete",
"configuration has been updated!": "configuration has been updated!", "configuration has been updated!": "configuration has been updated!",
"unknown": "unknown",
"Pterodactyl synced": "Pterodactyl synced",
"Invoice": "Invoice",
"Paid": "Paid",
"Your credit balance has been increased!": "Your credit balance has been increased!",
"Payment was Canceled": "Payment was Canceled",
"Store item has been created!": "Store item has been created!", "Store item has been created!": "Store item has been created!",
"Store item has been updated!": "Store item has been updated!", "Store item has been updated!": "Store item has been updated!",
"Product has been updated!": "Product has been updated!", "Product has been updated!": "Product has been updated!",
"Store item has been removed!": "Store item has been removed!", "Store item has been removed!": "Store item has been removed!",
"unknown": "unknown",
"Pterodactyl synced": "Pterodactyl synced",
"Your credit balance has been increased!": "Your credit balance has been increased!",
"Your payment is being processed!": "Your payment is being processed!",
"Your payment has been canceled!": "Your payment has been canceled!",
"Payment method": "Payment method",
"Invoice": "Invoice",
"Product has been created!": "Product has been created!", "Product has been created!": "Product has been created!",
"Product has been removed!": "Product has been removed!", "Product has been removed!": "Product has been removed!",
"Show": "Show", "Show": "Show",
@ -73,6 +74,7 @@
"Regards": "Regards", "Regards": "Regards",
"Getting started!": "Getting started!", "Getting started!": "Getting started!",
"Activity Logs": "Activity Logs", "Activity Logs": "Activity Logs",
"Dashboard": "Dashboard",
"No recent activity from cronjobs": "No recent activity from cronjobs", "No recent activity from cronjobs": "No recent activity from cronjobs",
"Are cronjobs running?": "Are cronjobs running?", "Are cronjobs running?": "Are cronjobs running?",
"Check the docs for it here": "Check the docs for it here", "Check the docs for it here": "Check the docs for it here",
@ -80,7 +82,6 @@
"Description": "Description", "Description": "Description",
"Created at": "Created at", "Created at": "Created at",
"Application API": "Application API", "Application API": "Application API",
"Dashboard": "Dashboard",
"Create": "Create", "Create": "Create",
"Memo": "Memo", "Memo": "Memo",
"Submit": "Submit", "Submit": "Submit",
@ -95,6 +96,14 @@
"Configurations": "Configurations", "Configurations": "Configurations",
"Key": "Key", "Key": "Key",
"Value": "Value", "Value": "Value",
"Nests": "Nests",
"Sync": "Sync",
"Active": "Active",
"ID": "ID",
"eggs": "eggs",
"Name": "Name",
"Nodes": "Nodes",
"Location": "Location",
"Admin Overview": "Admin Overview", "Admin Overview": "Admin Overview",
"Support server": "Support server", "Support server": "Support server",
"Documentation": "Documentation", "Documentation": "Documentation",
@ -104,26 +113,21 @@
"Total": "Total", "Total": "Total",
"Payments": "Payments", "Payments": "Payments",
"Pterodactyl": "Pterodactyl", "Pterodactyl": "Pterodactyl",
"Sync": "Sync",
"Resources": "Resources", "Resources": "Resources",
"Count": "Count", "Count": "Count",
"Locations": "Locations", "Locations": "Locations",
"Nodes": "Nodes",
"Nests": "Nests",
"Eggs": "Eggs", "Eggs": "Eggs",
"Last updated :date": "Last updated :date", "Last updated :date": "Last updated :date",
"ID": "ID",
"User": "User",
"Product Price": "Product Price", "Product Price": "Product Price",
"Tax": "Tax", "Tax Value": "Tax Value",
"Tax Percentage": "Tax Percentage",
"Total Price": "Total Price", "Total Price": "Total Price",
"Payment_ID": "Payment_ID", "Payment ID": "Payment ID",
"Payer_ID": "Payer_ID", "Payment Method": "Payment Method",
"Products": "Products", "Products": "Products",
"Product Details": "Product Details", "Product Details": "Product Details",
"Disabled": "Disabled", "Disabled": "Disabled",
"Will hide this option from being selected": "Will hide this option from being selected", "Will hide this option from being selected": "Will hide this option from being selected",
"Name": "Name",
"Price in": "Price in", "Price in": "Price in",
"Memory": "Memory", "Memory": "Memory",
"Cpu": "Cpu", "Cpu": "Cpu",
@ -140,10 +144,10 @@
"Link your products to nodes and eggs to create dynamic pricing for each option": "Link your products to nodes and eggs to create dynamic pricing for each option", "Link your products to nodes and eggs to create dynamic pricing for each option": "Link your products to nodes and eggs to create dynamic pricing for each option",
"This product will only be available for these nodes": "This product will only be available for these nodes", "This product will only be available for these nodes": "This product will only be available for these nodes",
"This product will only be available for these eggs": "This product will only be available for these eggs", "This product will only be available for these eggs": "This product will only be available for these eggs",
"Active": "Active",
"Product": "Product", "Product": "Product",
"CPU": "CPU", "CPU": "CPU",
"Updated at": "Updated at", "Updated at": "Updated at",
"User": "User",
"Config": "Config", "Config": "Config",
"Suspended at": "Suspended at", "Suspended at": "Suspended at",
"Settings": "Settings", "Settings": "Settings",
@ -170,8 +174,8 @@
"This is what the user sees at store and checkout": "This is what the user sees at store and checkout", "This is what the user sees at store and checkout": "This is what the user sees at store and checkout",
"Adds 1000 credits to your account": "Adds 1000 credits to your account", "Adds 1000 credits to your account": "Adds 1000 credits to your account",
"This is what the user sees at checkout": "This is what the user sees at checkout", "This is what the user sees at checkout": "This is what the user sees at checkout",
"Paypal is not configured.": "Paypal is not configured.", "No payment method is configured.": "No payment method is configured.",
"To configure PayPal, head to the .env and add your PayPals client id and secret.": "To configure PayPal, head to the .env and add your PayPals client id and secret.", "To configure the payment methods, head to the .env and add the required options for your prefered payment method.": "To configure the payment methods, head to the .env and add the required options for your prefered payment method.",
"Useful Links": "Useful Links", "Useful Links": "Useful Links",
"Icon class name": "Icon class name", "Icon class name": "Icon class name",
"You can find available free icons": "You can find available free icons", "You can find available free icons": "You can find available free icons",
@ -254,7 +258,6 @@
"Please contact support If you didnt receive your verification email.": "Please contact support If you didnt receive your verification email.", "Please contact support If you didnt receive your verification email.": "Please contact support If you didnt receive your verification email.",
"Thank you for your purchase!": "Thank you for your purchase!", "Thank you for your purchase!": "Thank you for your purchase!",
"Your payment has been confirmed; Your credit balance has been updated.": "Your payment has been confirmed; Your credit balance has been updated.", "Your payment has been confirmed; Your credit balance has been updated.": "Your payment has been confirmed; Your credit balance has been updated.",
"Payment ID": "Payment ID",
"Thanks": "Thanks", "Thanks": "Thanks",
"Redeem voucher code": "Redeem voucher code", "Redeem voucher code": "Redeem voucher code",
"Close": "Close", "Close": "Close",
@ -301,7 +304,6 @@
"Please select a configuration ...": "Please select a configuration ...", "Please select a configuration ...": "Please select a configuration ...",
"Not enough credits!": "Not enough credits!", "Not enough credits!": "Not enough credits!",
"Create Server": "Create Server", "Create Server": "Create Server",
"Location": "Location",
"Software": "Software", "Software": "Software",
"Specification": "Specification", "Specification": "Specification",
"Resource plan": "Resource plan", "Resource plan": "Resource plan",
@ -320,8 +322,8 @@
"Pending": "Pending", "Pending": "Pending",
"Subtotal": "Subtotal", "Subtotal": "Subtotal",
"Payment Methods": "Payment Methods", "Payment Methods": "Payment Methods",
"By purchasing this product you agree and accept our terms of service": "By purchasing this product you agree and accept our terms of service", "Amount Due": "Amount Due",
"Amount due": "Amount due", "Tax": "Tax",
"Submit Payment": "Submit Payment", "Submit Payment": "Submit Payment",
"Purchase": "Purchase", "Purchase": "Purchase",
"There are no store products!": "There are no store products!", "There are no store products!": "There are no store products!",

View file

@ -10,7 +10,7 @@
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li> <li class="breadcrumb-item"><a href="{{route('home')}}">{{ __('Dashboard')}}</a></li>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.activitylogs.index')}}">{{ __('Activity Logs')}}</a> <li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.activitylogs.index')}}">{{ __('Activity Logs')}}</a>
</li> </li>
</ol> </ol>

View file

@ -11,13 +11,13 @@ THIS FILE IS DEPRECATED
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1>Nests</h1> <h1>{{__('Nests')}}</h1>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li> <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li>
<li class="breadcrumb-item"><a class="text-muted" <li class="breadcrumb-item"><a class="text-muted"
href="{{route('admin.nests.index')}}">Nests</a></li> href="{{route('admin.nests.index')}}">{{__('Nests')}}</a></li>
</ol> </ol>
</div> </div>
</div> </div>
@ -33,9 +33,9 @@ THIS FILE IS DEPRECATED
<div class="card-header"> <div class="card-header">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<h5 class="card-title"><i class="fas fa-sitemap mr-2"></i>Nests</h5> <h5 class="card-title"><i class="fas fa-sitemap mr-2"></i>{{__('Nests')}}</h5>
<a href="{{route('admin.nests.sync')}}" class="btn btn-sm btn-info"><i <a href="{{route('admin.nests.sync')}}" class="btn btn-sm btn-info"><i
class="fas fa-sync mr-1"></i>Sync</a> class="fas fa-sync mr-1"></i>{{__('Sync')}}</a>
</div> </div>
</div> </div>
@ -44,12 +44,12 @@ THIS FILE IS DEPRECATED
<table id="datatable" class="table table-striped"> <table id="datatable" class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Active</th> <th>{{__('Active')}}</th>
<th>ID</th> <th>{{__('ID')}}</th>
<th>eggs</th> <th>{{__('eggs')}}</th>
<th>Name</th> <th>{{__('Name')}}</th>
<th>Description</th> <th>{{__('Description')}}</th>
<th>Created at</th> <th>{{__('Created at')}}</th>
</tr> </tr>
</thead> </thead>
@ -69,7 +69,7 @@ THIS FILE IS DEPRECATED
<script> <script>
function submitResult() { function submitResult() {
return confirm("Are you sure you wish to delete?") !== false; return confirm({{__("Are you sure you wish to delete?")}}) !== false;
} }
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {

View file

@ -11,13 +11,13 @@ THIS FILE IS DEPRECATED
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1>Nodes</h1> <h1>{{__('Nodes')}}</h1>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">Dashboard</a></li> <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li>
<li class="breadcrumb-item"><a class="text-muted" <li class="breadcrumb-item"><a class="text-muted"
href="{{route('admin.nodes.index')}}">Nodes</a></li> href="{{route('admin.nodes.index')}}">{{__('Nodes')}}</a></li>
</ol> </ol>
</div> </div>
</div> </div>
@ -33,9 +33,9 @@ THIS FILE IS DEPRECATED
<div class="card-header"> <div class="card-header">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<h5 class="card-title"><i class="fas fa-sitemap mr-2"></i>Nodes</h5> <h5 class="card-title"><i class="fas fa-sitemap mr-2"></i>{{__('Nodes')}}</h5>
<a href="{{route('admin.nodes.sync')}}" class="btn btn-sm btn-info"><i <a href="{{route('admin.nodes.sync')}}" class="btn btn-sm btn-info"><i
class="fas fa-sync mr-1"></i>Sync</a> class="fas fa-sync mr-1"></i>{{__('Sync')}}</a>
</div> </div>
</div> </div>
@ -44,12 +44,12 @@ THIS FILE IS DEPRECATED
<table id="datatable" class="table table-striped"> <table id="datatable" class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>Active</th> <th>{{__('Active')}}</th>
<th>ID</th> <th>{{__('ID')}}</th>
<th>Location</th> <th>{{__('Location')}}</th>
<th>Name</th> <th>{{__('Name')}}</th>
<th>Description</th> <th>{{__('Description')}}</th>
<th>Created at</th> <th>{{__('Created at')}}</th>
</tr> </tr>
</thead> </thead>
@ -69,7 +69,7 @@ THIS FILE IS DEPRECATED
<script> <script>
function submitResult() { function submitResult() {
return confirm("Are you sure you wish to delete?") !== false; return confirm({{__("Are you sure you wish to delete?")}}) !== false;
} }
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {

View file

@ -26,25 +26,24 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h5 class="card-title"><i class="fas fa-money-bill-wave mr-2"></i>{{__('Payments')}}</h5> <h5 class="card-title"><i class="fas fa-money-bill-wave mr-2"></i>{{ __('Payments') }}</h5>
</div> </div>
<div class="card-body table-responsive"> <div class="card-body table-responsive">
<table id="datatable" class="table table-striped"> <table id="datatable" class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>{{__('ID')}}</th> <th>{{ __('ID') }}</th>
<th>{{__('User')}}</th> <th>{{ __('Type') }}</th>
<th>{{__('Type')}}</th> <th>{{ __('Amount') }}</th>
<th>{{__('Amount')}}</th> <th>{{ __('Product Price') }}</th>
<th>{{__('Product Price')}}</th> <th>{{ __('Tax Value') }}</th>
<th>{{__('Tax')}}</th> <th>{{ __('Tax Percentage') }}</th>
<th>{{__('Tax')}}(%)</th> <th>{{ __('Total Price') }}</th>
<th>{{__('Total Price')}}</th> <th>{{ __('Payment ID') }}</th>
<th>{{__('Payment_ID')}}</th> <th>{{ __('Payment Method') }}</th>
<th>{{__('Payer_ID')}}</th> <th>{{ __('Created at') }}</th>
<th>{{__('Created at')}}</th> </tr>
</tr>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>
@ -59,7 +58,7 @@
<!-- END CONTENT --> <!-- END CONTENT -->
<script> <script>
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function() {
$('#datatable').DataTable({ $('#datatable').DataTable({
language: { language: {
url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("app.datatable_locale")}}.json' url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("app.datatable_locale")}}.json'
@ -67,10 +66,9 @@
processing: true, processing: true,
serverSide: true, serverSide: true,
stateSave: true, stateSave: true,
ajax: "{{route('admin.payments.datatable')}}", ajax: "{{ route('admin.payments.datatable') }}",
columns: [ columns: [
{data: 'id' , name : 'payments.id'}, {data: 'id',name: 'payments.id'},
{data: 'user', sortable: false},
{data: 'type'}, {data: 'type'},
{data: 'amount'}, {data: 'amount'},
{data: 'price'}, {data: 'price'},
@ -78,12 +76,12 @@
{data: 'tax_percent'}, {data: 'tax_percent'},
{data: 'total_price'}, {data: 'total_price'},
{data: 'payment_id'}, {data: 'payment_id'},
{data: 'payer_id'}, {data: 'payment_method'},
{data: 'created_at'}, {data: 'created_at'},
], ],
fnDrawCallback: function( oSettings ) { fnDrawCallback: function(oSettings) {
$('[data-toggle="popover"]').popover(); $('[data-toggle="popover"]').popover();
} },
}); });
}); });
</script> </script>

View file

@ -6,14 +6,14 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1>{{__('Store')}}</h1> <h1>{{ __('Store') }}</h1>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a href="{{route('admin.store.index')}}">{{__('Store')}}</a></li> <li class="breadcrumb-item"><a href="{{ route('admin.store.index') }}">{{ __('Store') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted" <li class="breadcrumb-item"><a class="text-muted"
href="{{route('admin.store.edit' , $paypalProduct->id)}}">{{__('Edit')}}</a> href="{{ route('admin.store.edit', $creditProduct->id) }}">{{ __('Edit') }}</a>
</li> </li>
</ol> </ol>
</div> </div>
@ -30,127 +30,115 @@
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<form action="{{route('admin.store.update' , $paypalProduct->id)}}" method="POST"> <form action="{{ route('admin.store.update', $creditProduct->id) }}" method="POST">
@csrf @csrf
@method('PATCH') @method('PATCH')
<div class="d-flex flex-row-reverse"> <div class="d-flex flex-row-reverse">
<div class="custom-control custom-switch"> <div class="custom-control custom-switch">
<input type="checkbox" @if($paypalProduct->disabled) checked <input type="checkbox" @if ($creditProduct->disabled) checked @endif name="disabled"
@endif name="disabled" class="custom-control-input custom-control-input-danger" id="switch1">
class="custom-control-input custom-control-input-danger" <label class="custom-control-label" for="switch1">{{ __('Disabled') }} <i
id="switch1"> data-toggle="popover" data-trigger="hover"
<label class="custom-control-label" for="switch1">{{__('Disabled')}} <i data-content="{{ __('Will hide this option from being selected') }}"
data-toggle="popover"
data-trigger="hover"
data-content="{{__('Will hide this option from being selected')}}"
class="fas fa-info-circle"></i></label> class="fas fa-info-circle"></i></label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="type">{{__('Type')}}</label> <label for="type">{{ __('Type') }}</label>
<select required name="type" id="type" <select required name="type" id="type"
class="custom-select @error('name') is-invalid @enderror"> class="custom-select @error('name') is-invalid @enderror">
<option selected value="Credits">{{CREDITS_DISPLAY_NAME}}</option> <option selected value="Credits">{{ CREDITS_DISPLAY_NAME }}</option>
</select> </select>
@error('name') @error('name')
<div class="text-danger"> <div class="text-danger">
{{$message}} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="currency_code">{{__('Currency code')}}</label> <label for="currency_code">{{ __('Currency code') }}</label>
<select required name="currency_code" id="currency_code" <select required name="currency_code" id="currency_code"
class="custom-select @error('name') is-invalid @enderror"> class="custom-select @error('name') is-invalid @enderror">
@foreach($currencyCodes as $code) @foreach ($currencyCodes as $code)
<option @if($paypalProduct->currency_code == $code) selected <option @if ($creditProduct->currency_code == $code) selected @endif value="{{ $code }}">
@endif value="{{$code}}">{{$code}}</option> {{ $code }}</option>
@endforeach @endforeach
</select> </select>
@error('currency_code') @error('currency_code')
<div class="text-danger"> <div class="text-danger">
{{$message}} {{ $message }}
</div> </div>
@enderror @enderror
<div class="text-muted"> <div class="text-muted">
{{__('Checkout the paypal docs to select the appropriate code')}} <a {{ __('Checkout the paypal docs to select the appropriate code') }} <a
target="_blank" target="_blank"
href="https://developer.paypal.com/docs/api/reference/currency-codes/">link</a> href="https://developer.paypal.com/docs/api/reference/currency-codes/">link</a>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="price">{{__('Price')}}</label> <label for="price">Price</label>
<input value="{{$paypalProduct->price}}" id="price" name="price" <input value="{{ $creditProduct->price }}" id="price" name="price" type="number"
type="number" placeholder="10.00" step="any"
placeholder="10.00" class="form-control @error('price') is-invalid @enderror" required="required">
step="any"
class="form-control @error('price') is-invalid @enderror"
required="required">
@error('price') @error('price')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{$message}} {{ $message }}
</div> </div>
@enderror @enderror
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="quantity">{{__('Quantity')}}</label> <label for="quantity">Quantity</label>
<input value="{{$paypalProduct->quantity}}" id="quantity" name="quantity" <input value="{{ $creditProduct->quantity }}" id="quantity" name="quantity"
type="number" type="number" placeholder="1000"
placeholder="1000" class="form-control @error('quantity') is-invalid @enderror" required="required">
class="form-control @error('quantity') is-invalid @enderror"
required="required">
@error('quantity') @error('quantity')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{$message}} {{ $message }}
</div> </div>
@enderror @enderror
<div class="text-muted"> <div class="text-muted">
{{__('Amount given to the user after purchasing')}} {{ __('Amount given to the user after purchasing') }}
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="display">{{__('Display')}}</label> <label for="display">Display</label>
<input value="{{$paypalProduct->display}}" id="display" name="display" <input value="{{ $creditProduct->display }}" id="display" name="display" type="text"
type="text" placeholder="750 + 250" class="form-control @error('display') is-invalid @enderror"
placeholder="750 + 250" required="required">
class="form-control @error('display') is-invalid @enderror"
required="required">
@error('display') @error('display')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{$message}} {{ $message }}
</div> </div>
@enderror @enderror
<div class="text-muted"> <div class="text-muted">
{{__('This is what the user sees at store and checkout')}} {{ __('This is what the user sees at store and checkout') }}
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">{{__('Description')}}</label> <label for="description">Description</label>
<input value="{{$paypalProduct->description}}" id="description" name="description" <input value="{{ $creditProduct->description }}" id="description" name="description"
type="text" type="text" placeholder="{{ __('Adds 1000 credits to your account') }}"
placeholder="{{__('Adds 1000 credits to your account')}}" class="form-control @error('description') is-invalid @enderror" required="required">
class="form-control @error('description') is-invalid @enderror"
required="required">
@error('description') @error('description')
<div class="invalid-feedback"> <div class="invalid-feedback">
{{$message}} {{ $message }}
</div> </div>
@enderror @enderror
<div class="text-muted"> <div class="text-muted">
{{__('This is what the user sees at checkout')}} {{ __('This is what the user sees at checkout') }}
</div> </div>
</div> </div>
<div class="form-group text-right"> <div class="form-group text-right">
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary">
{{__('Submit')}} {{ __('Submit') }}
</button> </button>
</div> </div>
</form> </form>

View file

@ -6,13 +6,13 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1>{{__('Store')}}</h1> <h1>{{ __('Store') }}</h1>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted" <li class="breadcrumb-item"><a class="text-muted"
href="{{route('admin.store.index')}}">{{__('Store')}}</a></li> href="{{ route('admin.store.index') }}">{{ __('Store') }}</a></li>
</ol> </ol>
</div> </div>
</div> </div>
@ -26,12 +26,14 @@
<div class="row"> <div class="row">
<div class="col-lg-4"> <div class="col-lg-4">
@if($isPaypalSetup == false) @if ($isPaymentSetup == false)
<div class="callout callout-danger"> <div class="callout callout-danger">
<h4>{{__('Paypal is not configured.')}}</h4> <h4>{{ __('No payment method is configured.') }}</h4>
<p>{{__('To configure PayPal, head to the .env and add your PayPals client id and secret.')}}</p> <p>{{ __('To configure the payment methods, head to the .env and add the required options for your prefered payment method.') }}
</p>
</div> </div>
@endif @endif
</div> </div>
</div> </div>
@ -39,9 +41,9 @@
<div class="card-header"> <div class="card-header">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<h5 class="card-title"><i class="fas fa-sliders-h mr-2"></i>{{__('Store')}}</h5> <h5 class="card-title"><i class="fas fa-sliders-h mr-2"></i>{{ __('Store') }}</h5>
<a href="{{route('admin.store.create')}}" class="btn btn-sm btn-primary"><i <a href="{{ route('admin.store.create') }}" class="btn btn-sm btn-primary"><i
class="fas fa-plus mr-1"></i>{{__('Create new')}}</a> class="fas fa-plus mr-1"></i>{{ __('Create new') }}</a>
</div> </div>
</div> </div>
@ -49,15 +51,15 @@
<table id="datatable" class="table table-striped"> <table id="datatable" class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>{{__('Active')}}</th> <th>{{ __('Active') }}</th>
<th>{{__('Type')}}</th> <th>{{ __('Type') }}</th>
<th>{{__('Price')}}</th> <th>{{ __('Price') }}</th>
<th>{{__('Display')}}</th> <th>{{ __('Display') }}</th>
<th>{{__('Description')}}</th> <th>{{ __('Description') }}</th>
<th>{{__('Created at')}}</th> <th>{{ __('Created at') }}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>
@ -78,26 +80,42 @@
return confirm("Are you sure you wish to delete?") !== false; return confirm("Are you sure you wish to delete?") !== false;
} }
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function() {
$('#datatable').DataTable({ $('#datatable').DataTable({
language: { language: {
url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("app.datatable_locale")}}.json' url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{ config('app.datatable_locale') }}.json'
}, },
processing: true, processing: true,
serverSide: true, serverSide: true,
stateSave: true, stateSave: true,
ajax: "{{route('admin.store.datatable')}}", ajax: "{{ route('admin.store.datatable') }}",
order: [[ 2, "desc" ]], order: [
columns: [ [2, "desc"]
{data: 'disabled'},
{data: 'type'},
{data: 'price'},
{data: 'display'},
{data: 'description'},
{data: 'created_at'},
{data: 'actions', sortable: false},
], ],
fnDrawCallback: function( oSettings ) { columns: [{
data: 'disabled'
},
{
data: 'type'
},
{
data: 'price'
},
{
data: 'display'
},
{
data: 'description'
},
{
data: 'created_at'
},
{
data: 'actions',
sortable: false
},
],
fnDrawCallback: function(oSettings) {
$('[data-toggle="popover"]').popover(); $('[data-toggle="popover"]').popover();
} }
}); });

View file

@ -6,12 +6,15 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1>{{__('Store')}}</h1> <h1>{{ __('Store') }}</h1>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a class="" href="{{route('home')}}">{{__('Dashboard')}}</a></li> <li class="breadcrumb-item"><a class=""
<li class="breadcrumb-item"><a class="text-muted" href="{{route('store.index')}}">{{__('Store')}}</a></li> href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('store.index') }}">{{ __('Store') }}</a>
</li>
</ol> </ol>
</div> </div>
</div> </div>
@ -20,7 +23,7 @@
<!-- END CONTENT HEADER --> <!-- END CONTENT HEADER -->
<!-- MAIN CONTENT --> <!-- MAIN CONTENT -->
<section class="content"> <section x-data="serverApp()" x-init="$watch('paymentMethod', value => setPaymentRoute(value))" class="content">
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
@ -34,7 +37,8 @@
<div class="col-12"> <div class="col-12">
<h4> <h4>
<i class="fas fa-globe"></i> {{ config('app.name', 'Laravel') }} <i class="fas fa-globe"></i> {{ config('app.name', 'Laravel') }}
<small class="float-right">{{__('Date')}}: {{Carbon\Carbon::now()->isoFormat('LL')}}</small> <small class="float-right">{{ __('Date') }}:
{{ Carbon\Carbon::now()->isoFormat('LL') }}</small>
</h4> </h4>
</div> </div>
<!-- /.col --> <!-- /.col -->
@ -42,25 +46,25 @@
<!-- info row --> <!-- info row -->
<div class="row invoice-info"> <div class="row invoice-info">
<div class="col-sm-4 invoice-col"> <div class="col-sm-4 invoice-col">
{{__('To')}} {{ __('To') }}
<address> <address>
<strong>{{config('app.name' , 'Laravel')}}</strong><br> <strong>{{ config('app.name', 'Controlpanel.GG') }}</strong><br>
{{__('Email')}}: {{env('PAYPAL_EMAIL' , env('MAIL_FROM_NAME'))}} {{ __('Email') }}: {{ env('PAYPAL_EMAIL', env('MAIL_FROM_NAME')) }}
</address> </address>
</div> </div>
<!-- /.col --> <!-- /.col -->
<div class="col-sm-4 invoice-col"> <div class="col-sm-4 invoice-col">
{{__('From')}} {{ __('From') }}
<address> <address>
<strong>{{Auth::user()->name}}</strong><br> <strong>{{ Auth::user()->name }}</strong><br>
{{__('Email')}}: {{Auth::user()->email}} {{ __('Email') }}: {{ Auth::user()->email }}
</address> </address>
</div> </div>
<!-- /.col --> <!-- /.col -->
<div class="col-sm-4 invoice-col"> <div class="col-sm-4 invoice-col">
<b>{{__('Status')}}</b><br> <b>{{ __('Status') }}</b><br>
<span class="badge badge-warning">{{__('Pending')}}</span><br> <span class="badge badge-warning">{{ __('Pending') }}</span><br>
{{-- <b>Order ID:</b> 4F3S8J<br>--}} {{-- <b>Order ID:</b> 4F3S8J<br> --}}
</div> </div>
<!-- /.col --> <!-- /.col -->
</div> </div>
@ -71,20 +75,22 @@
<div class="col-12 table-responsive"> <div class="col-12 table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th>{{__('Quantity')}}</th> <th>{{ __('Quantity') }}</th>
<th>{{__('Product')}}</th> <th>{{ __('Product') }}</th>
<th>{{__('Description')}}</th> <th>{{ __('Description') }}</th>
<th>{{__('Subtotal')}}</th> <th>{{ __('Subtotal') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>1</td> <td>1</td>
<td><i class="fa fa-coins mr-2"></i>{{$product->quantity}} {{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td> <td><i class="fa fa-coins mr-2"></i>{{ $product->quantity }}
<td>{{$product->description}}</td> {{ strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type }}
<td>{{$product->formatToCurrency($product->price)}}</td> </td>
</tr> <td>{{ $product->description }}</td>
<td>{{ $product->formatToCurrency($product->price) }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -95,35 +101,53 @@
<div class="row"> <div class="row">
<!-- accepted payments column --> <!-- accepted payments column -->
<div class="col-6"> <div class="col-6">
<p class="lead">{{__('Payment Methods')}}:</p> <p class="lead">{{ __('Payment Methods') }}:</p>
<img src="https://www.paypalobjects.com/digitalassets/c/website/logo/full-text/pp_fc_hl.svg" alt="Paypal"> <div>
@if (env('PAYPAL_SANDBOX_SECRET') || env('PAYPAL_SECRET'))
<label class="text-center " for="paypal">
<img class="mb-3" height="50"
src="{{ url('/images/paypal_logo.png') }}"></br>
<input x-model="paymentMethod" type="radio" id="paypal" value="paypal"
name="payment_method">
</input>
</label>
@endif
@if (env('STRIPE_TEST_SECRET') || env('STRIPE_SECRET'))
<label class="ml-5 text-center " for="stripe">
<img class="mb-3" height="50"
src="{{ url('/images/stripe_logo.png') }}" /></br>
<input x-model="paymentMethod" type="radio" id="stripe" value="stripe"
name="payment_method">
</input>
</label>
@endif
</div>
<p class="text-muted well well-sm shadow-none" style="margin-top: 10px;">
{{__('By purchasing this product you agree and accept our terms of service')}}</a>
</p>
</div> </div>
<!-- /.col --> <!-- /.col -->
<div class="col-6"> <div class="col-6">
<p class="lead">{{__('Amount due')}} {{Carbon\Carbon::now()->isoFormat('LL')}}</p> <p class="lead">{{ __('Amount Due') }}
{{ Carbon\Carbon::now()->isoFormat('LL') }}</p>
<div class="table-responsive"> <div class="table-responsive">
<table class="table"> <table class="table">
<tr> <tr>
<th style="width:50%">{{__('Subtotal')}}:</th> <th style="width:50%">{{ __('Subtotal') }}:</th>
<td>{{$product->formatToCurrency($product->price)}}</td> <td>{{ $product->formatToCurrency($product->price) }}</td>
</tr> </tr>
<tr> <tr>
<th>{{__('Tax')}} ({{$taxpercent}}%)</th> <th>{{ __('Tax') }} ({{ $taxpercent }}%)</th>
<td>{{$product->formatToCurrency($taxvalue)}}</td> <td>{{ $product->formatToCurrency($taxvalue) }}</td>
</tr> </tr>
<tr> <tr>
<th>{{__('Quantity')}}:</th> <th>{{ __('Quantity') }}:</th>
<td>1</td> <td>1</td>
</tr> </tr>
<tr> <tr>
<th>{{__('Total')}}:</th> <th>{{ __('Total') }}:</th>
<td>{{$product->formatToCurrency($total)}}</td> <td>{{ $product->formatToCurrency($total) }}</td>
</tr> </tr>
</table> </table>
</div> </div>
@ -135,7 +159,10 @@
<!-- this row will not appear when printing --> <!-- this row will not appear when printing -->
<div class="row no-print"> <div class="row no-print">
<div class="col-12"> <div class="col-12">
<a href="{{route('payment.pay' , $product->id)}}" type="button" class="btn btn-success float-right"><i class="far fa-credit-card mr-2"></i> {{__('Submit Payment')}} <a type="button" :href="paymentRoute" :disabled="!paymentRoute"
:class="!paymentRoute ? 'disabled' : ''" class="btn btn-success float-right"><i
class="far fa-credit-card mr-2"></i>
{{ __('Submit Payment') }}
</a> </a>
</div> </div>
</div> </div>
@ -143,10 +170,35 @@
<!-- /.invoice --> <!-- /.invoice -->
</div><!-- /.col --> </div><!-- /.col -->
</div><!-- /.row --> </div><!-- /.row -->
</div> </div>
</section> </section>
<!-- END CONTENT --> <!-- END CONTENT -->
<script>
function serverApp() {
return {
//loading
paymentMethod: '',
paymentRoute: '',
setPaymentRoute(provider) {
switch (provider) {
case 'paypal':
this.paymentRoute = '{{ route('payment.PaypalPay', $product->id) }}';
break;
case 'stripe':
this.paymentRoute = '{{ route('payment.StripePay', $product->id) }}';
break;
default:
this.paymentRoute = '{{ route('payment.PaypalPay', $product->id) }}';
}
},
}
}
</script>
@endsection @endsection

View file

@ -1,5 +1,5 @@
@extends('layouts.main') @extends('layouts.main')
<?php use App\Models\PaypalProduct; ?> <?php use App\Models\CreditProduct; ?>
@section('content') @section('content')
<!-- CONTENT HEADER --> <!-- CONTENT HEADER -->
@ -7,12 +7,14 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-sm-6"> <div class="col-sm-6">
<h1>{{__('Store')}}</h1> <h1>{{ __('Store') }}</h1>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<ol class="breadcrumb float-sm-right"> <ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a class="" href="{{route('home')}}">{{__('Dashboard')}}</a></li> <li class="breadcrumb-item"><a class=""
<li class="breadcrumb-item"><a class="text-muted" href="{{route('store.index')}}">{{__('Store')}}</a></li> href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('store.index') }}">{{ __('Store') }}</a></li>
</ol> </ol>
</div> </div>
</div> </div>
@ -26,37 +28,40 @@
<div class="text-right mb-3"> <div class="text-right mb-3">
<button type="button" data-toggle="modal" data-target="#redeemVoucherModal" class="btn btn-primary"> <button type="button" data-toggle="modal" data-target="#redeemVoucherModal" class="btn btn-primary">
<i class="fas fa-money-check-alt mr-2"></i>{{__('Redeem code')}} <i class="fas fa-money-check-alt mr-2"></i>{{ __('Redeem code') }}
</button> </button>
</div> </div>
@if($isPaypalSetup && $products->count() > 0) @if ($isPaymentSetup && $products->count() > 0)
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h5 class="card-title"><i class="fa fa-coins mr-2"></i>{{CREDITS_DISPLAY_NAME}}</h5> <h5 class="card-title"><i class="fa fa-coins mr-2"></i>{{ CREDITS_DISPLAY_NAME }}</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-striped table-responsive-sm"> <table class="table table-striped table-responsive-sm">
<thead> <thead>
<tr> <tr>
<th>{{__('Price')}}</th> <th>{{ __('Price') }}</th>
<th>{{__('Type')}}</th> <th>{{ __('Type') }}</th>
<th>{{__('Description')}}</th> <th>{{ __('Description') }}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php /** @var $product PaypalProduct */?> <?php /** @var $product CreditProduct */
@foreach($products as $product) ?>
<tr> @foreach ($products as $product)
<td>{{$product->formatToCurrency($product->price)}}</td> <tr>
<td>{{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td> <td>{{ $product->formatToCurrency($product->price) }}</td>
<td><i class="fa fa-coins mr-2"></i>{{$product->display}}</td> <td>{{ strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type }}
<td><a href="{{route('checkout' , $product->id)}}" class="btn btn-info">{{__('Purchase')}}</a> </td>
</td> <td><i class="fa fa-coins mr-2"></i>{{ $product->display }}</td>
</tr> <td><a href="{{ route('checkout', $product->id) }}"
@endforeach class="btn btn-info">{{ __('Purchase') }}</a>
</td>
</tr>
@endforeach
</tbody> </tbody>
</table> </table>
</div> </div>
@ -65,7 +70,7 @@
@else @else
<div class="alert alert-danger alert-dismissible"> <div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4><i class="icon fa fa-ban"></i> @if($products->count() == 0) {{__('There are no store products!')}} @else {{__('The store is not correctly configured!')}} @endif <h4><i class="icon fa fa-ban"></i> @if ($products->count() == 0) {{ __('There are no store products!') }} @else {{ __('The store is not correctly configured!') }} @endif
</h4> </h4>
</div> </div>

View file

@ -5,7 +5,7 @@ use App\Http\Controllers\Admin\ApplicationApiController;
use App\Http\Controllers\Admin\ConfigurationController; use App\Http\Controllers\Admin\ConfigurationController;
use App\Http\Controllers\Admin\OverViewController; use App\Http\Controllers\Admin\OverViewController;
use App\Http\Controllers\Admin\PaymentController; use App\Http\Controllers\Admin\PaymentController;
use App\Http\Controllers\Admin\PaypalProductController; use App\Http\Controllers\Admin\CreditProductController;
use App\Http\Controllers\Admin\ProductController; use App\Http\Controllers\Admin\ProductController;
use App\Http\Controllers\Admin\ServerController as AdminServerController; use App\Http\Controllers\Admin\ServerController as AdminServerController;
use App\Http\Controllers\Admin\SettingsController; use App\Http\Controllers\Admin\SettingsController;
@ -40,6 +40,9 @@ Route::middleware('guest')->get('/', function () {
Auth::routes(['verify' => true]); Auth::routes(['verify' => true]);
# Stripe WebhookRoute -> validation in Route Handler
Route::post('payment/StripeWebhooks', [PaymentController::class, 'StripeWebhooks'])->name('payment.StripeWebhooks');
Route::middleware(['auth', 'checkSuspended'])->group(function () { Route::middleware(['auth', 'checkSuspended'])->group(function () {
#resend verification email #resend verification email
Route::get('/email/verification-notification', function (Request $request) { Route::get('/email/verification-notification', function (Request $request) {
@ -61,10 +64,14 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
Route::get('/products/products/{egg?}/{node?}', [FrontProductController::class, 'getProductsBasedOnNode'])->name('products.products.node'); Route::get('/products/products/{egg?}/{node?}', [FrontProductController::class, 'getProductsBasedOnNode'])->name('products.products.node');
#payments #payments
Route::get('checkout/{paypalProduct}', [PaymentController::class, 'checkOut'])->name('checkout'); Route::get('checkout/{creditProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
Route::get('payment/success', [PaymentController::class, 'success'])->name('payment.success'); Route::get('payment/PaypalPay/{creditProduct}', [PaymentController::class, 'PaypalPay'])->name('payment.PaypalPay');
Route::get('payment/cancel', [PaymentController::class, 'cancel'])->name('payment.cancel'); Route::get('payment/PaypalSuccess', [PaymentController::class, 'PaypalSuccess'])->name('payment.PaypalSuccess');
Route::get('payment/pay/{paypalProduct}', [PaymentController::class, 'pay'])->name('payment.pay'); Route::get('payment/StripePay/{creditProduct}', [PaymentController::class, 'StripePay'])->name('payment.StripePay');
Route::get('payment/StripeSuccess', [PaymentController::class, 'StripeSuccess'])->name('payment.StripeSuccess');
Route::get('payment/Cancel', [PaymentController::class, 'Cancel'])->name('payment.Cancel');
Route::get('users/logbackin', [UserController::class, 'logBackIn'])->name('users.logbackin'); Route::get('users/logbackin', [UserController::class, 'logBackIn'])->name('users.logbackin');
@ -100,10 +107,10 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
Route::patch('products/disable/{product}', [ProductController::class, 'disable'])->name('products.disable'); Route::patch('products/disable/{product}', [ProductController::class, 'disable'])->name('products.disable');
Route::resource('products', ProductController::class); Route::resource('products', ProductController::class);
Route::get('store/datatable', [PaypalProductController::class, 'datatable'])->name('store.datatable'); Route::get('store/datatable', [CreditProductController::class, 'datatable'])->name('store.datatable');
Route::patch('store/disable/{paypalProduct}', [PaypalProductController::class, 'disable'])->name('store.disable'); Route::patch('store/disable/{creditProduct}', [CreditProductController::class, 'disable'])->name('store.disable');
Route::resource('store', PaypalProductController::class)->parameters([ Route::resource('store', CreditProductController::class)->parameters([
'store' => 'paypalProduct', 'store' => 'creditProduct',
]); ]);
Route::get('payments/datatable', [PaymentController::class, 'datatable'])->name('payments.datatable'); Route::get('payments/datatable', [PaymentController::class, 'datatable'])->name('payments.datatable');