Merge pull request #328 from IceToast/stripe_integration

feature: Stripe integration
This commit is contained in:
Dennis 2021-12-22 13:06:46 +01:00 committed by GitHub
commit c614e03526
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 2292 additions and 1229 deletions

View file

@ -1,4 +1,4 @@
APP_NAME=Dashboard
APP_NAME=Controlpanel.gg
APP_ENV=production
APP_KEY=
APP_DEBUG=false
@ -25,6 +25,18 @@ PAYPAL_SECRET=
PAYPAL_CLIENT_ID=
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
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=

View file

@ -1,25 +1,32 @@
### Features
- PayPal Integration
- Email Verification
- Audit Log
- Admin Dashboard
- User/Server Management
- Store (credit system)
- Vouchers
- and so much more!
- PayPal Integration
- Stripe Integration
- Email Verification
- Audit Log
- Admin Dashboard
- User/Server Management
- Store (credit system)
- Vouchers
- and so much more!
# ControlPanel-gg
![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)
## 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.
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")
### [Updating](https://controlpanel.gg/docs/Installation/updating "Updating")
### [Discord](https://discord.gg/4Y6HjD2uyU "discord")
### [Contributing](https://controlpanel.gg/docs/Contributing/contributing "Contributing")
### [Donating](https://controlpanel.gg/docs/Contributing/donating "Donating")

View file

@ -2,7 +2,7 @@
namespace App\Http\Controllers\Admin;
use App\Models\PaypalProduct;
use App\Models\CreditProduct;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
@ -12,7 +12,7 @@ use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Illuminate\Validation\Rule;
class PaypalProductController extends Controller
class CreditProductController extends Controller
{
/**
* Display a listing of the resource.
@ -21,11 +21,16 @@ class PaypalProductController extends Controller
*/
public function index(Request $request)
{
$isPaypalSetup = false;
if (env('PAYPAL_SECRET') && env('PAYPAL_CLIENT_ID')) $isPaypalSetup = true;
$isPaymentSetup = false;
return view('admin.store.index' , [
'isPaypalSetup' => $isPaypalSetup
if (
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'));
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!'));
}
@ -68,10 +73,10 @@ class PaypalProductController extends Controller
/**
* Display the specified resource.
*
* @param PaypalProduct $paypalProduct
* @param CreditProduct $creditProduct
* @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.
*
* @param PaypalProduct $paypalProduct
* @param CreditProduct $creditProduct
* @return Application|Factory|View|Response
*/
public function edit(PaypalProduct $paypalProduct)
public function edit(CreditProduct $creditProduct)
{
return view('admin.store.edit', [
'currencyCodes' => config('currency_codes'),
'paypalProduct' => $paypalProduct
'creditProduct' => $creditProduct
]);
}
@ -94,10 +99,10 @@ class PaypalProductController extends Controller
* Update the specified resource in storage.
*
* @param Request $request
* @param PaypalProduct $paypalProduct
* @param CreditProduct $creditProduct
* @return RedirectResponse
*/
public function update(Request $request, PaypalProduct $paypalProduct)
public function update(Request $request, CreditProduct $creditProduct)
{
$request->validate([
"disabled" => "nullable",
@ -110,19 +115,19 @@ class PaypalProductController extends Controller
]);
$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!'));
}
/**
* @param Request $request
* @param PaypalProduct $paypalProduct
* @param CreditProduct $creditProduct
* @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!'));
}
@ -130,50 +135,50 @@ class PaypalProductController extends Controller
/**
* Remove the specified resource from storage.
*
* @param PaypalProduct $paypalProduct
* @param CreditProduct $creditProduct
* @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!'));
}
public function dataTable()
{
$query = PaypalProduct::query();
$query = CreditProduct::query();
return datatables($query)
->addColumn('actions', function (PaypalProduct $paypalProduct) {
->addColumn('actions', function (CreditProduct $creditProduct) {
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() . '
' . 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>
';
})
->addColumn('disabled', function (PaypalProduct $paypalProduct) {
$checked = $paypalProduct->disabled == false ? "checked" : "";
->addColumn('disabled', function (CreditProduct $creditProduct) {
$checked = $creditProduct->disabled == false ? "checked" : "";
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() . '
' . method_field("PATCH") . '
<div class="custom-control custom-switch">
<input ' . $checked . ' name="disabled" onchange="this.form.submit()" type="checkbox" class="custom-control-input" id="switch' . $paypalProduct->id . '">
<label class="custom-control-label" for="switch' . $paypalProduct->id . '"></label>
<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' . $creditProduct->id . '"></label>
</div>
</form>
';
})
->editColumn('created_at', function (PaypalProduct $paypalProduct) {
return $paypalProduct->created_at ? $paypalProduct->created_at->diffForHumans() : '';
->editColumn('created_at', function (CreditProduct $creditProduct) {
return $creditProduct->created_at ? $creditProduct->created_at->diffForHumans() : '';
})
->editColumn('price', function (PaypalProduct $paypalProduct) {
return $paypalProduct->formatToCurrency($paypalProduct->price);
->editColumn('price', function (CreditProduct $creditProduct) {
return $creditProduct->formatToCurrency($creditProduct->price);
})
->rawColumns(['actions', 'disabled'])
->make();

View file

@ -7,9 +7,10 @@ use App\Http\Controllers\Controller;
use App\Models\Configuration;
use App\Models\InvoiceSettings;
use App\Models\Payment;
use App\Models\PaypalProduct;
use App\Models\CreditProduct;
use App\Models\User;
use App\Notifications\InvoiceNotification;
use App\Notifications\ConfirmPaymentNotification;
use Exception;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
@ -29,6 +30,8 @@ use PayPalCheckoutSdk\Core\SandboxEnvironment;
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
use PayPalHttp\HttpException;
use Stripe\Stripe;
class PaymentController extends Controller
{
@ -45,25 +48,25 @@ class PaymentController extends Controller
/**
* @param Request $request
* @param PaypalProduct $paypalProduct
* @param CreditProduct $creditProduct
* @return Application|Factory|View
*/
public function checkOut(Request $request, PaypalProduct $paypalProduct)
public function checkOut(Request $request, CreditProduct $creditProduct)
{
return view('store.checkout')->with([
'product' => $paypalProduct,
'taxvalue' => $paypalProduct->getTaxValue(),
'taxpercent' => $paypalProduct->getTaxPercent(),
'total' => $paypalProduct->getTotalPrice()
'product' => $creditProduct,
'taxvalue' => $creditProduct->getTaxValue(),
'taxpercent' => $creditProduct->getTaxPercent(),
'total' => $creditProduct->getTotalPrice()
]);
}
/**
* @param Request $request
* @param PaypalProduct $paypalProduct
* @param CreditProduct $creditProduct
* @return RedirectResponse
*/
public function pay(Request $request, PaypalProduct $paypalProduct)
public function PaypalPay(Request $request, CreditProduct $creditProduct)
{
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
@ -72,30 +75,30 @@ class PaymentController extends Controller
"purchase_units" => [
[
"reference_id" => uniqid(),
"description" => $paypalProduct->description,
"amount" => [
"value" => $paypalProduct->getTotalPrice(),
'currency_code' => strtoupper($paypalProduct->currency_code),
"description" => $creditProduct->description,
"amount" => [
"value" => $creditProduct->getTotalPrice(),
'currency_code' => strtoupper($creditProduct->currency_code),
'breakdown' => [
'item_total' =>
[
'currency_code' => strtoupper($paypalProduct->currency_code),
'value' => $paypalProduct->price,
],
[
'currency_code' => strtoupper($creditProduct->currency_code),
'value' => $creditProduct->price,
],
'tax_total' =>
[
'currency_code' => strtoupper($paypalProduct->currency_code),
'value' => $paypalProduct->getTaxValue(),
]
[
'currency_code' => strtoupper($creditProduct->currency_code),
'value' => $creditProduct->getTaxValue(),
]
]
]
]
],
"application_context" => [
"cancel_url" => route('payment.cancel'),
"return_url" => route('payment.success', ['product' => $paypalProduct->id]),
'brand_name' => config('app.name', 'Laravel'),
'shipping_preference' => 'NO_SHIPPING'
"cancel_url" => route('payment.Cancel'),
"return_url" => route('payment.PaypalSuccess', ['product' => $creditProduct->id]),
'brand_name' => config('app.name', 'Laravel'),
'shipping_preference' => 'NO_SHIPPING'
]
@ -112,7 +115,6 @@ class PaymentController extends Controller
echo $ex->statusCode;
dd(json_decode($ex->getMessage()));
}
}
/**
@ -121,8 +123,8 @@ class PaymentController extends Controller
protected function getPayPalClient()
{
$environment = env('APP_ENV') == 'local'
? new SandboxEnvironment($this->getClientId(), $this->getClientSecret())
: new ProductionEnvironment($this->getClientId(), $this->getClientSecret());
? new SandboxEnvironment($this->getPaypalClientId(), $this->getPaypalClientSecret())
: new ProductionEnvironment($this->getPaypalClientId(), $this->getPaypalClientSecret());
return new PayPalHttpClient($environment);
}
@ -130,7 +132,7 @@ class PaymentController extends Controller
/**
* @return string
*/
protected function getClientId()
protected function getPaypalClientId()
{
return env('APP_ENV') == 'local' ? env('PAYPAL_SANDBOX_CLIENT_ID') : env('PAYPAL_CLIENT_ID');
}
@ -138,7 +140,7 @@ class PaymentController extends Controller
/**
* @return string
*/
protected function getClientSecret()
protected function getPaypalClientSecret()
{
return env('APP_ENV') == 'local' ? env('PAYPAL_SANDBOX_SECRET') : env('PAYPAL_SECRET');
}
@ -146,10 +148,11 @@ class PaymentController extends Controller
/**
* @param Request $laravelRequest
*/
public function success(Request $laravelRequest)
public function PaypalSuccess(Request $laravelRequest)
{
/** @var PaypalProduct $paypalProduct */
$paypalProduct = PaypalProduct::findOrFail($laravelRequest->input('product'));
/** @var CreditProduct $creditProduct */
$creditProduct = CreditProduct::findOrFail($laravelRequest->input('product'));
/** @var User $user */
$user = Auth::user();
@ -161,7 +164,7 @@ class PaymentController extends Controller
if ($response->statusCode == 201 || $response->statusCode == 200) {
//update credits
$user->increment('credits', $paypalProduct->quantity);
$user->increment('credits', $creditProduct->quantity);
//update server limit
if (Configuration::getValueByKey('SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
@ -179,82 +182,22 @@ class PaymentController extends Controller
$payment = Payment::create([
'user_id' => $user->id,
'payment_id' => $response->result->id,
'payer_id' => $laravelRequest->input('PayerID'),
'payment_method' => 'paypal',
'type' => 'Credits',
'status' => $response->result->status,
'amount' => $paypalProduct->quantity,
'price' => $paypalProduct->price,
'tax_value' => $paypalProduct->getTaxValue(),
'tax_percent' => $paypalProduct->getTaxPercent(),
'total_price' => $paypalProduct->getTotalPrice(),
'currency_code' => $paypalProduct->currency_code,
'payer' => json_encode($response->result->payer),
'status' => 'paid',
'amount' => $creditProduct->quantity,
'price' => $creditProduct->price,
'tax_value' => $creditProduct->getTaxValue(),
'tax_percent' => $creditProduct->getTaxPercent(),
'total_price' => $creditProduct->getTotalPrice(),
'currency_code' => $creditProduct->currency_code,
'credit_product_id' => $creditProduct->id,
]);
event(new UserUpdateCreditsEvent($user));
//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($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));
$this->createInvoice($user, $payment, 'paid');
//redirect back to home
return redirect()->route('home')->with('success', __('Your credit balance has been increased!'));
@ -267,7 +210,6 @@ class PaymentController extends Controller
} else {
abort(500);
}
} catch (HttpException $ex) {
if (env('APP_ENV') == 'local') {
echo $ex->statusCode;
@ -275,20 +217,344 @@ class PaymentController extends Controller
} else {
abort(422);
}
}
}
/**
* @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);
$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}');
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
@ -308,9 +574,13 @@ class PaymentController extends Controller
->editColumn('tax_value', function (Payment $payment) {
return $payment->formatToCurrency($payment->tax_value);
})
->editColumn('tax_percent', function (Payment $payment) {
return $payment->tax_percent . ' %';
})
->editColumn('total_price', function (Payment $payment) {
return $payment->formatToCurrency($payment->total_price);
})
->editColumn('created_at', function (Payment $payment) {
return $payment->created_at ? $payment->created_at->diffForHumans() : '';
})

View file

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

View file

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

View file

@ -8,7 +8,7 @@ use NumberFormatter;
use Spatie\Activitylog\Traits\LogsActivity;
use App\Models\Configuration;
class PaypalProduct extends Model
class CreditProduct extends Model
{
use LogsActivity;
/**
@ -33,10 +33,10 @@ class PaypalProduct extends Model
{
parent::boot();
static::creating(function (PaypalProduct $paypalProduct) {
static::creating(function (CreditProduct $creditProduct) {
$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',
'user_id',
'payment_id',
'payer_id',
'payer',
'payment_method',
'status',
'type',
'amount',
@ -33,6 +32,7 @@ class Payment extends Model
'total_price',
'tax_percent',
'currency_code',
'credit_product_id',
];
public static function boot()
@ -57,10 +57,10 @@ class Payment extends Model
/**
* @param mixed $value
* @param string $locale
*
*
* @return float
*/
public function formatToCurrency($value,$locale = 'en_US')
public function formatToCurrency($value, $locale = 'en_US')
{
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
return $formatter->formatCurrency($value, $this->currency_code);

View file

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

2339
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePaypalProductsTable extends Migration
class CreateCreditProductsTable extends Migration
{
/**
* Run the migrations.
@ -13,7 +13,7 @@ class CreatePaypalProductsTable extends Migration
*/
public function up()
{
Schema::create('paypal_products', function (Blueprint $table) {
Schema::create('credit_products', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->string('type');
$table->decimal('price')->default(0);
@ -32,6 +32,6 @@ class CreatePaypalProductsTable extends Migration
*/
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\Support\Facades\Schema;
class AddDisplayToPaypalProductsTable extends Migration
class AddDisplayToCreditProductsTable extends Migration
{
/**
* Run the migrations.
@ -13,7 +13,7 @@ class AddDisplayToPaypalProductsTable extends Migration
*/
public function up()
{
Schema::table('paypal_products', function (Blueprint $table) {
Schema::table('credit_products', function (Blueprint $table) {
$table->string('display');
});
}
@ -25,7 +25,7 @@ class AddDisplayToPaypalProductsTable extends Migration
*/
public function down()
{
Schema::table('paypal_products', function (Blueprint $table) {
Schema::table('credit_products', function (Blueprint $table) {
$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;
use Database\Seeders\Seeds\ProductSeeder;
use Database\Seeders\Seeds\PaypalProductSeeder;
use Database\Seeders\Seeds\CreditProductSeeder;
use Database\Seeders\Seeds\ApplicationApiSeeder;
use Database\Seeders\Seeds\UsefulLinksSeeder;
use Illuminate\Database\Seeder;
@ -19,7 +19,7 @@ class ExampleItemsSeeder extends Seeder
{
$this->call([
ProductSeeder::class,
PaypalProductSeeder::class,
CreditProductSeeder::class,
ApplicationApiSeeder::class,
UsefulLinksSeeder::class
]);

View file

@ -2,10 +2,10 @@
namespace Database\Seeders\Seeds;
use App\Models\PaypalProduct;
use App\Models\CreditProduct;
use Illuminate\Database\Seeder;
class PaypalProductSeeder extends Seeder
class CreditProductSeeder extends Seeder
{
/**
* Run the database seeds.
@ -14,7 +14,7 @@ class PaypalProductSeeder extends Seeder
*/
public function run()
{
PaypalProduct::create([
CreditProduct::create([
'type' => 'Credits',
'display' => '350',
'description' => 'Adds 350 credits to your account',
@ -24,7 +24,7 @@ class PaypalProductSeeder extends Seeder
'disabled' => false,
]);
PaypalProduct::create([
CreditProduct::create([
'type' => 'Credits',
'display' => '875 + 125',
'description' => 'Adds 1000 credits to your account',
@ -34,7 +34,7 @@ class PaypalProductSeeder extends Seeder
'disabled' => false,
]);
PaypalProduct::create([
CreditProduct::create([
'type' => 'Credits',
'display' => '1750 + 250',
'description' => 'Adds 2000 credits to your account',
@ -44,7 +44,7 @@ class PaypalProductSeeder extends Seeder
'disabled' => false,
]);
PaypalProduct::create([
CreditProduct::create([
'type' => 'Credits',
'display' => '3500 + 500',
'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",
"Adds 1000 credits to your account": "Fügt deinem Account 1000 Credits hinzu",
"Active": "Aktiv",
"Paypal is not configured.": "Paypal ist nicht 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",
"No payment method is configured.": "Keine Bezahlmethode konfiguriert!",
"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",
"Icon class name": "Icon Klassen-Name",
"You can find available free icons": "Hier gibt es kostenlose Icons",

View file

@ -106,8 +106,8 @@
"Adds 1000 credits to your account": "Adds 1000 credits to your account",
"Active": "Active",
"Paypal is not configured.": "Paypal is not 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.",
"No payment method is configured.": "No payment method is configured.",
"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",
"Icon class name": "Icon class name",
@ -164,7 +164,7 @@
"You forgot your password? Here you can easily retrieve a new password.": "You forgot your password? Here you can easily retrieve a new password.",
"Request new password": "Request new password",
"Login": "Login",
"You are only one step a way from your new password, recover your password now.":"You are only one step a way from your new password, recover your password now.",
"You are only one step a way from your new password, recover your password now.": "You are only one step a way from your new password, recover your password now.",
"Retype password": "Retype password",
"Change password": "Change password",
"I already have a membership": "I already have a membership",
@ -364,8 +364,6 @@
"Amount due": "Amount due",
"Your Payment was successful!": "Your Payment was successful!",
"Hello":"Hello",
"Your payment was processed successfully!":"Your payment was processed successfully!"
"Hello": "Hello",
"Your payment was processed successfully!": "Your payment was processed successfully!"
}

View file

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

View file

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

View file

@ -6,13 +6,13 @@
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>{{__('Store')}}</h1>
<h1>{{ __('Store') }}</h1>
</div>
<div class="col-sm-6">
<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.store.index')}}">{{__('Store')}}</a></li>
href="{{ route('admin.store.index') }}">{{ __('Store') }}</a></li>
</ol>
</div>
</div>
@ -26,12 +26,14 @@
<div class="row">
<div class="col-lg-4">
@if($isPaypalSetup == false)
@if ($isPaymentSetup == false)
<div class="callout callout-danger">
<h4>{{__('Paypal is not configured.')}}</h4>
<p>{{__('To configure PayPal, head to the .env and add your PayPals client id and secret.')}}</p>
<h4>{{ __('No payment method is configured.') }}</h4>
<p>{{ __('To configure the payment methods, head to the .env and add the required options for your prefered payment method.') }}
</p>
</div>
@endif
</div>
</div>
@ -39,9 +41,9 @@
<div class="card-header">
<div class="d-flex justify-content-between">
<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
class="fas fa-plus mr-1"></i>{{__('Create new')}}</a>
<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
class="fas fa-plus mr-1"></i>{{ __('Create new') }}</a>
</div>
</div>
@ -49,15 +51,15 @@
<table id="datatable" class="table table-striped">
<thead>
<tr>
<th>{{__('Active')}}</th>
<th>{{__('Type')}}</th>
<th>{{__('Price')}}</th>
<th>{{__('Display')}}</th>
<th>{{__('Description')}}</th>
<th>{{__('Created at')}}</th>
<th></th>
</tr>
<tr>
<th>{{ __('Active') }}</th>
<th>{{ __('Type') }}</th>
<th>{{ __('Price') }}</th>
<th>{{ __('Display') }}</th>
<th>{{ __('Description') }}</th>
<th>{{ __('Created at') }}</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
@ -78,26 +80,42 @@
return confirm("Are you sure you wish to delete?") !== false;
}
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("DOMContentLoaded", function() {
$('#datatable').DataTable({
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,
serverSide: true,
stateSave: true,
ajax: "{{route('admin.store.datatable')}}",
order: [[ 2, "desc" ]],
columns: [
{data: 'disabled'},
{data: 'type'},
{data: 'price'},
{data: 'display'},
{data: 'description'},
{data: 'created_at'},
{data: 'actions', sortable: false},
ajax: "{{ route('admin.store.datatable') }}",
order: [
[2, "desc"]
],
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();
}
});

View file

@ -6,12 +6,15 @@
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>{{__('Store')}}</h1>
<h1>{{ __('Store') }}</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a class="" href="{{route('home')}}">{{__('Dashboard')}}</a></li>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('store.index')}}">{{__('Store')}}</a></li>
<li class="breadcrumb-item"><a class=""
href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('store.index') }}">{{ __('Store') }}</a>
</li>
</ol>
</div>
</div>
@ -20,7 +23,7 @@
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section class="content">
<section x-data="serverApp()" x-init="$watch('paymentMethod', value => setPaymentRoute(value))" class="content">
<div class="container-fluid">
<div class="row">
@ -34,7 +37,8 @@
<div class="col-12">
<h4>
<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>
</div>
<!-- /.col -->
@ -42,25 +46,25 @@
<!-- info row -->
<div class="row invoice-info">
<div class="col-sm-4 invoice-col">
{{__('To')}}
{{ __('To') }}
<address>
<strong>{{config('app.name' , 'Laravel')}}</strong><br>
{{__('Email')}}: {{env('PAYPAL_EMAIL' , env('MAIL_FROM_NAME'))}}
<strong>{{ config('app.name', 'Controlpanel.GG') }}</strong><br>
{{ __('Email') }}: {{ env('PAYPAL_EMAIL', env('MAIL_FROM_NAME')) }}
</address>
</div>
<!-- /.col -->
<div class="col-sm-4 invoice-col">
{{__('From')}}
{{ __('From') }}
<address>
<strong>{{Auth::user()->name}}</strong><br>
{{__('Email')}}: {{Auth::user()->email}}
<strong>{{ Auth::user()->name }}</strong><br>
{{ __('Email') }}: {{ Auth::user()->email }}
</address>
</div>
<!-- /.col -->
<div class="col-sm-4 invoice-col">
<b>{{__('Status')}}</b><br>
<span class="badge badge-warning">{{__('Pending')}}</span><br>
{{-- <b>Order ID:</b> 4F3S8J<br>--}}
<b>{{ __('Status') }}</b><br>
<span class="badge badge-warning">{{ __('Pending') }}</span><br>
{{-- <b>Order ID:</b> 4F3S8J<br> --}}
</div>
<!-- /.col -->
</div>
@ -71,20 +75,22 @@
<div class="col-12 table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{{__('Quantity')}}</th>
<th>{{__('Product')}}</th>
<th>{{__('Description')}}</th>
<th>{{__('Subtotal')}}</th>
</tr>
<tr>
<th>{{ __('Quantity') }}</th>
<th>{{ __('Product') }}</th>
<th>{{ __('Description') }}</th>
<th>{{ __('Subtotal') }}</th>
</tr>
</thead>
<tbody>
<tr>
<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>{{$product->description}}</td>
<td>{{$product->formatToCurrency($product->price)}}</td>
</tr>
<tr>
<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>{{ $product->description }}</td>
<td>{{ $product->formatToCurrency($product->price) }}</td>
</tr>
</tbody>
</table>
</div>
@ -95,35 +101,53 @@
<div class="row">
<!-- accepted payments column -->
<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>
<!-- /.col -->
<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">
<table class="table">
<tr>
<th style="width:50%">{{__('Subtotal')}}:</th>
<td>{{$product->formatToCurrency($product->price)}}</td>
<th style="width:50%">{{ __('Subtotal') }}:</th>
<td>{{ $product->formatToCurrency($product->price) }}</td>
</tr>
<tr>
<th>{{__('Tax')}} ({{$taxpercent}}%)</th>
<td>{{$product->formatToCurrency($taxvalue)}}</td>
<th>{{ __('Tax') }} ({{ $taxpercent }}%)</th>
<td>{{ $product->formatToCurrency($taxvalue) }}</td>
</tr>
<tr>
<th>{{__('Quantity')}}:</th>
<th>{{ __('Quantity') }}:</th>
<td>1</td>
</tr>
<tr>
<th>{{__('Total')}}:</th>
<td>{{$product->formatToCurrency($total)}}</td>
<th>{{ __('Total') }}:</th>
<td>{{ $product->formatToCurrency($total) }}</td>
</tr>
</table>
</div>
@ -135,7 +159,10 @@
<!-- this row will not appear when printing -->
<div class="row no-print">
<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>
</div>
</div>
@ -143,10 +170,35 @@
<!-- /.invoice -->
</div><!-- /.col -->
</div><!-- /.row -->
</div>
</section>
<!-- 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

View file

@ -1,5 +1,5 @@
@extends('layouts.main')
<?php use App\Models\PaypalProduct; ?>
<?php use App\Models\CreditProduct; ?>
@section('content')
<!-- CONTENT HEADER -->
@ -7,12 +7,14 @@
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>{{__('Store')}}</h1>
<h1>{{ __('Store') }}</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a class="" href="{{route('home')}}">{{__('Dashboard')}}</a></li>
<li class="breadcrumb-item"><a class="text-muted" href="{{route('store.index')}}">{{__('Store')}}</a></li>
<li class="breadcrumb-item"><a class=""
href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('store.index') }}">{{ __('Store') }}</a></li>
</ol>
</div>
</div>
@ -26,37 +28,40 @@
<div class="text-right mb-3">
<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>
</div>
@if($isPaypalSetup && $products->count() > 0)
@if ($isPaymentSetup && $products->count() > 0)
<div class="card">
<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 class="card-body">
<table class="table table-striped table-responsive-sm">
<thead>
<tr>
<th>{{__('Price')}}</th>
<th>{{__('Type')}}</th>
<th>{{__('Description')}}</th>
<th></th>
</tr>
<tr>
<th>{{ __('Price') }}</th>
<th>{{ __('Type') }}</th>
<th>{{ __('Description') }}</th>
<th></th>
</tr>
</thead>
<tbody>
<?php /** @var $product PaypalProduct */?>
@foreach($products as $product)
<tr>
<td>{{$product->formatToCurrency($product->price)}}</td>
<td>{{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td>
<td><i class="fa fa-coins mr-2"></i>{{$product->display}}</td>
<td><a href="{{route('checkout' , $product->id)}}" class="btn btn-info">{{__('Purchase')}}</a>
</td>
</tr>
@endforeach
<?php /** @var $product CreditProduct */
?>
@foreach ($products as $product)
<tr>
<td>{{ $product->formatToCurrency($product->price) }}</td>
<td>{{ strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type }}
</td>
<td><i class="fa fa-coins mr-2"></i>{{ $product->display }}</td>
<td><a href="{{ route('checkout', $product->id) }}"
class="btn btn-info">{{ __('Purchase') }}</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@ -65,7 +70,7 @@
@else
<div class="alert alert-danger alert-dismissible">
<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>
</div>

View file

@ -5,7 +5,7 @@ use App\Http\Controllers\Admin\ApplicationApiController;
use App\Http\Controllers\Admin\ConfigurationController;
use App\Http\Controllers\Admin\OverViewController;
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\ServerController as AdminServerController;
use App\Http\Controllers\Admin\SettingsController;
@ -40,6 +40,9 @@ Route::middleware('guest')->get('/', function () {
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 () {
#resend verification email
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');
#payments
Route::get('checkout/{paypalProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
Route::get('payment/success', [PaymentController::class, 'success'])->name('payment.success');
Route::get('payment/cancel', [PaymentController::class, 'cancel'])->name('payment.cancel');
Route::get('payment/pay/{paypalProduct}', [PaymentController::class, 'pay'])->name('payment.pay');
Route::get('checkout/{creditProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
Route::get('payment/PaypalPay/{creditProduct}', [PaymentController::class, 'PaypalPay'])->name('payment.PaypalPay');
Route::get('payment/PaypalSuccess', [PaymentController::class, 'PaypalSuccess'])->name('payment.PaypalSuccess');
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');
@ -100,10 +107,10 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
Route::patch('products/disable/{product}', [ProductController::class, 'disable'])->name('products.disable');
Route::resource('products', ProductController::class);
Route::get('store/datatable', [PaypalProductController::class, 'datatable'])->name('store.datatable');
Route::patch('store/disable/{paypalProduct}', [PaypalProductController::class, 'disable'])->name('store.disable');
Route::resource('store', PaypalProductController::class)->parameters([
'store' => 'paypalProduct',
Route::get('store/datatable', [CreditProductController::class, 'datatable'])->name('store.datatable');
Route::patch('store/disable/{creditProduct}', [CreditProductController::class, 'disable'])->name('store.disable');
Route::resource('store', CreditProductController::class)->parameters([
'store' => 'creditProduct',
]);
Route::get('payments/datatable', [PaymentController::class, 'datatable'])->name('payments.datatable');