[FEATURE] Extension Support & Payment Gateways as Extensions 🔥

[FEATURE] Extension Support & Payment Gateways as Extensions 🔥
This commit is contained in:
Dennis 2023-01-19 11:29:27 +01:00 committed by GitHub
commit 7a91013bd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1140 additions and 854 deletions

View file

@ -0,0 +1,31 @@
<?php
namespace App\Events;
use App\Models\Payment;
use App\Models\ShopProduct;
use App\Models\User;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class PaymentEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public User $user;
public Payment $payment;
public ShopProduct $shopProduct;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(User $user, Payment $payment, ShopProduct $shopProduct)
{
$this->user = $user;
$this->payment = $payment;
$this->shopProduct = $shopProduct;
}
}

View file

@ -0,0 +1,216 @@
<?php
use App\Events\PaymentEvent;
use App\Events\UserUpdateCreditsEvent;
use App\Models\PartnerDiscount;
use App\Models\Payment;
use App\Models\Product;
use App\Models\ShopProduct;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redirect;
use PayPalCheckoutSdk\Core\PayPalHttpClient;
use PayPalCheckoutSdk\Core\ProductionEnvironment;
use PayPalCheckoutSdk\Core\SandboxEnvironment;
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
use PayPalHttp\HttpException;
/**
* @param Request $request
* @param ShopProduct $shopProduct
*/
function PaypalPay(Request $request)
{
/** @var User $user */
$user = Auth::user();
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
// create a new payment
$payment = Payment::create([
'user_id' => $user->id,
'payment_id' => null,
'payment_method' => 'paypal',
'type' => $shopProduct->type,
'status' => 'open',
'amount' => $shopProduct->quantity,
'price' => $shopProduct->price - ($shopProduct->price * PartnerDiscount::getDiscount() / 100),
'tax_value' => $shopProduct->getTaxValue(),
'tax_percent' => $shopProduct->getTaxPercent(),
'total_price' => $shopProduct->getTotalPrice(),
'currency_code' => $shopProduct->currency_code,
'shop_item_product_id' => $shopProduct->id,
]);
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = [
"intent" => "CAPTURE",
"purchase_units" => [
[
"reference_id" => uniqid(),
"description" => $shopProduct->display . (PartnerDiscount::getDiscount() ? (" (" . __('Discount') . " " . PartnerDiscount::getDiscount() . '%)') : ""),
"amount" => [
"value" => $shopProduct->getTotalPrice(),
'currency_code' => strtoupper($shopProduct->currency_code),
'breakdown' => [
'item_total' =>
[
'currency_code' => strtoupper($shopProduct->currency_code),
'value' => $shopProduct->getPriceAfterDiscount(),
],
'tax_total' =>
[
'currency_code' => strtoupper($shopProduct->currency_code),
'value' => $shopProduct->getTaxValue(),
]
]
]
]
],
"application_context" => [
"cancel_url" => route('payment.Cancel'),
"return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id]),
'brand_name' => config('app.name', 'Laravel'),
'shipping_preference' => 'NO_SHIPPING'
]
];
try {
// Call API with your client and get a response for your call
$response = getPayPalClient()->execute($request);
Redirect::away($response->result->links[1]->href)->send();
return;
} catch (HttpException $ex) {
error_log($ex->statusCode);
error_log($ex->getMessage());
$payment->delete();
Redirect::route('payment.Cancel');
return;
}
}
/**
* @param Request $laravelRequest
*/
function PaypalSuccess(Request $laravelRequest)
{
$user = Auth::user();
$user = User::findOrFail($user->id);
$payment = Payment::findOrFail($laravelRequest->payment);
$shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
$request = new OrdersCaptureRequest($laravelRequest->input('token'));
$request->prefer('return=representation');
try {
// Call API with your client and get a response for your call
$response = getPayPalClient()->execute($request);
if ($response->statusCode == 201 || $response->statusCode == 200) {
//update payment
$payment->update([
'status' => 'paid',
'payment_id' => $response->result->id,
]);
event(new UserUpdateCreditsEvent($user));
event(new PaymentEvent($user, $payment, $shopProduct));
// redirect to the payment success page with success message
Redirect::route('home')->with('success', 'Payment successful')->send();
} elseif (env('APP_ENV') == 'local') {
// If call returns body in response, you can get the deserialized version from the result attribute of the response
$payment->delete();
dd($response);
} else {
$payment->update([
'status' => 'cancelled',
'payment_id' => $response->result->id,
]);
abort(500);
}
} catch (HttpException $ex) {
if (env('APP_ENV') == 'local') {
echo $ex->statusCode;
$payment->delete();
dd($ex->getMessage());
} else {
$payment->update([
'status' => 'cancelled',
'payment_id' => $response->result->id,
]);
abort(422);
}
}
}
/**
* @return PayPalHttpClient
*/
function getPayPalClient()
{
$environment = env('APP_ENV') == 'local'
? new SandboxEnvironment(getPaypalClientId(), getPaypalClientSecret())
: new ProductionEnvironment(getPaypalClientId(), getPaypalClientSecret());
return new PayPalHttpClient($environment);
}
/**
* @return string
*/
function getPaypalClientId()
{
return env('APP_ENV') == 'local' ? config("SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID") : config("SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID");
}
/**
* @return string
*/
function getPaypalClientSecret()
{
return env('APP_ENV') == 'local' ? config("SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET") : config("SETTINGS::PAYMENTS:PAYPAL:SECRET");
}
function getPayPalConfig()
{
return [
"name" => "PayPal",
"description" => "PayPal payment gateway",
"settings" => [
"mode" => [
"type" => "select",
"label" => "Mode",
"value" => config("APP_ENV") == 'local' ? "sandbox" : "live",
"options" => [
"sandbox" => "Sandbox",
"live" => "Live",
],
],
"CLIENT_ID" => [
"type" => "text",
"label" => "PayPal Client ID",
"value" => config("SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID"),
],
"SECRET" => [
"type" => "text",
"label" => "PayPal Secret",
"value" => config("SETTINGS::PAYMENTS:PAYPAL:SECRET"),
],
"SANDBOX_CLIENT_ID" => [
"type" => "text",
"label" => "PayPal Sandbox Client ID",
"value" => config("SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID"),
],
"SANDBOX_SECRET" => [
"type" => "text",
"label" => "PayPal Sandbox Secret",
"value" => config("SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET"),
],
],
];
}

View file

@ -0,0 +1,18 @@
<?php
use Illuminate\Support\Facades\Route;
include_once(__DIR__ . '/index.php');
Route::middleware(['web', 'auth'])->group(function () {
Route::get('payment/PayPalPay/{shopProduct}', function () {
PaypalPay(request());
})->name('payment.PayPalPay');
Route::get(
'payment/PayPalSuccess',
function () {
PaypalSuccess(request());
}
)->name('payment.PayPalSuccess');
});

View file

@ -0,0 +1,402 @@
<?php
use App\Events\PaymentEvent;
use App\Events\UserUpdateCreditsEvent;
use App\Models\PartnerDiscount;
use App\Models\Payment;
use App\Models\Product;
use App\Models\ShopProduct;
use App\Models\User;
use App\Notifications\ConfirmPaymentNotification;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redirect;
use Stripe\Exception\SignatureVerificationException;
use Stripe\Stripe;
use Stripe\StripeClient;
/**
* @param Request $request
* @param ShopProduct $shopProduct
*/
function StripePay(Request $request)
{
$user = Auth::user();
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
// check if the price is valid for stripe
if (!checkPriceAmount($shopProduct->getTotalPrice(), strtoupper($shopProduct->currency_code), 'stripe')) {
Redirect::route('home')->with('error', __('The product you chose can\'t be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.'))->send();
return;
}
// create payment
$payment = Payment::create([
'user_id' => $user->id,
'payment_id' => null,
'payment_method' => 'stripe',
'type' => $shopProduct->type,
'status' => 'open',
'amount' => $shopProduct->quantity,
'price' => $shopProduct->price - ($shopProduct->price * PartnerDiscount::getDiscount() / 100),
'tax_value' => $shopProduct->getTaxValue(),
'total_price' => $shopProduct->getTotalPrice(),
'tax_percent' => $shopProduct->getTaxPercent(),
'currency_code' => $shopProduct->currency_code,
'shop_item_product_id' => $shopProduct->id,
]);
$stripeClient = getStripeClient();
$request = $stripeClient->checkout->sessions->create([
'line_items' => [
[
'price_data' => [
'currency' => $shopProduct->currency_code,
'product_data' => [
'name' => $shopProduct->display . (PartnerDiscount::getDiscount() ? (' (' . __('Discount') . ' ' . PartnerDiscount::getDiscount() . '%)') : ''),
'description' => $shopProduct->description,
],
'unit_amount_decimal' => round($shopProduct->getPriceAfterDiscount() * 100, 2),
],
'quantity' => 1,
],
[
'price_data' => [
'currency' => $shopProduct->currency_code,
'product_data' => [
'name' => __('Tax'),
'description' => $shopProduct->getTaxPercent() . '%',
],
'unit_amount_decimal' => round($shopProduct->getTaxValue(), 2) * 100,
],
'quantity' => 1,
],
],
'mode' => 'payment',
'payment_method_types' => str_getcsv(config('SETTINGS::PAYMENTS:STRIPE:METHODS')),
'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id]) . '&session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => route('payment.Cancel'),
]);
Redirect::to($request->url)->send();
}
/**
* @param Request $request
*/
function StripeSuccess(Request $request)
{
$user = Auth::user();
$user = User::findOrFail($user->id);
$payment = Payment::findOrFail($request->input('payment'));
$shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
$stripeClient = 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 payment
$payment->update([
'payment_id' => $paymentSession->payment_intent,
'status' => 'paid',
]);
//payment notification
$user->notify(new ConfirmPaymentNotification($payment));
event(new UserUpdateCreditsEvent($user));
event(new PaymentEvent($user, $payment, $shopProduct));
//redirect back to home
Redirect::route('home')->with('success', 'Payment successful')->send();
} else {
if ($paymentIntent->status == 'processing') {
//update payment
$payment->update([
'payment_id' => $paymentSession->payment_intent,
'status' => 'processing',
]);
event(new PaymentEvent($user, $payment, $shopProduct));
Redirect::route('home')->with('success', 'Your payment is being processed')->send();
}
if ($paymentDbEntry == 0 && $paymentIntent->status != 'processing') {
$stripeClient->paymentIntents->cancel($paymentIntent->id);
//redirect back to home
Redirect::route('home')->with('info', __('Your payment has been canceled!'))->send();
} else {
abort(402);
}
}
} catch (Exception $e) {
if (env('APP_ENV') == 'local') {
dd($e->getMessage());
} else {
abort(422);
}
}
}
/**
* @param Request $request
*/
function handleStripePaymentSuccessHook($paymentIntent)
{
try {
// Get payment db entry
$payment = Payment::where('payment_id', $paymentIntent->id)->first();
$user = User::where('id', $payment->user_id)->first();
$shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
if ($paymentIntent->status == 'succeeded' && $payment->status == 'processing') {
//update payment db entry status
$payment->update(['status' => 'paid']);
//payment notification
$user->notify(new ConfirmPaymentNotification($payment));
event(new UserUpdateCreditsEvent($user));
event(new PaymentEvent($user, $payment, $shopProduct));
}
// return 200
return response()->json(['success' => true], 200);
} catch (Exception $ex) {
abort(422);
}
}
/**
* @param Request $request
*/
function StripeWebhooks(Request $request)
{
Stripe::setApiKey(getStripeSecret());
try {
$payload = @file_get_contents('php://input');
$sig_header = $request->header('Stripe-Signature');
$event = null;
$event = \Stripe\Webhook::constructEvent(
$payload,
$sig_header,
getStripeEndpointSecret()
);
} catch (\UnexpectedValueException $e) {
// Invalid payload
abort(400);
} catch (SignatureVerificationException $e) {
// Invalid signature
abort(400);
}
// Handle the event
switch ($event->type) {
case 'payment_intent.succeeded':
$paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent
handleStripePaymentSuccessHook($paymentIntent);
break;
default:
echo 'Received unknown event type ' . $event->type;
}
}
/**
* @return \Stripe\StripeClient
*/
function getStripeClient()
{
return new StripeClient(getStripeSecret());
}
/**
* @return string
*/
function getStripeSecret()
{
return env('APP_ENV') == 'local'
? config('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET')
: config('SETTINGS::PAYMENTS:STRIPE:SECRET');
}
/**
* @return string
*/
function getStripeEndpointSecret()
{
return env('APP_ENV') == 'local'
? config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET')
: config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET');
}
/**
* @param $amount
* @param $currencyCode
* @param $payment_method
* @return bool
* @description check if the amount is higher than the minimum amount for the stripe gateway
*/
function checkPriceAmount($amount, $currencyCode, $payment_method)
{
$minimums = [
"USD" => [
"paypal" => 0,
"stripe" => 0.5
],
"AED" => [
"paypal" => 0,
"stripe" => 2
],
"AUD" => [
"paypal" => 0,
"stripe" => 0.5
],
"BGN" => [
"paypal" => 0,
"stripe" => 1
],
"BRL" => [
"paypal" => 0,
"stripe" => 0.5
],
"CAD" => [
"paypal" => 0,
"stripe" => 0.5
],
"CHF" => [
"paypal" => 0,
"stripe" => 0.5
],
"CZK" => [
"paypal" => 0,
"stripe" => 15
],
"DKK" => [
"paypal" => 0,
"stripe" => 2.5
],
"EUR" => [
"paypal" => 0,
"stripe" => 0.5
],
"GBP" => [
"paypal" => 0,
"stripe" => 0.3
],
"HKD" => [
"paypal" => 0,
"stripe" => 4
],
"HRK" => [
"paypal" => 0,
"stripe" => 0.5
],
"HUF" => [
"paypal" => 0,
"stripe" => 175
],
"INR" => [
"paypal" => 0,
"stripe" => 0.5
],
"JPY" => [
"paypal" => 0,
"stripe" => 0.5
],
"MXN" => [
"paypal" => 0,
"stripe" => 10
],
"MYR" => [
"paypal" => 0,
"stripe" => 2
],
"NOK" => [
"paypal" => 0,
"stripe" => 3
],
"NZD" => [
"paypal" => 0,
"stripe" => 0.5
],
"PLN" => [
"paypal" => 0,
"stripe" => 2
],
"RON" => [
"paypal" => 0,
"stripe" => 2
],
"SEK" => [
"paypal" => 0,
"stripe" => 3
],
"SGD" => [
"paypal" => 0,
"stripe" => 0.5
],
"THB" => [
"paypal" => 0,
"stripe" => 10
]
];
return $amount >= $minimums[$currencyCode][$payment_method];
}
function getStripeConfig()
{
return [
"name" => "Stripe",
"description" => "Stripe payment gateway",
"mode" => [
"type" => "select",
"label" => "Mode",
"value" => config("APP_ENV") == 'local' ? "sandbox" : "live",
"options" => [
"sandbox" => "Sandbox",
"live" => "Live",
],
],
"TEST_SECRET" => [
"type" => "text",
"label" => "Test Secret Key",
"value" => config("SETTINGS::PAYMENTS:STRIPE:TEST_SECRET"),
],
"SECRET" => [
"type" => "text",
"label" => "Live Secret Key",
"value" => config("SETTINGS::PAYMENTS:STRIPE:SECRET"),
],
"ENDPOINT_TEST_SECRET" => [
"type" => "text",
"label" => "Test Endpoint Secret",
"value" => config("SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET"),
],
"ENDPOINT_SECRET" => [
"type" => "text",
"label" => "Live Endpoint Secret",
"value" => config("SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET"),
],
];
}

View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Support\Facades\Route;
include_once(__DIR__ . '/index.php');
Route::middleware(['web', 'auth'])->group(function () {
Route::get('payment/StripePay/{shopProduct}', function () {
StripePay(request());
})->name('payment.StripePay');
Route::get(
'payment/StripeSuccess',
function () {
StripeSuccess(request());
}
)->name('payment.StripeSuccess');
});
// Stripe WebhookRoute -> validation in Route Handler
Route::post('payment/StripeWebhooks', function () {
StripeWebhooks(request());
})->name('payment.StripeWebhooks');

View file

@ -0,0 +1,47 @@
<?php
namespace App\Helpers;
class ExtensionHelper
{
public static function getExtensionConfig($extensionName, $nameSpace)
{
$extension = app_path() . '/Extensions/' . $nameSpace . "/" . $extensionName . "/index.php";
// Check if extension exists
if (!file_exists($extension)) {
return null;
}
// call the getConfig function from the index.php file of the extension
$config = include_once $extension;
// Check if the getConfig function exists
if (!function_exists('get' . $extensionName . 'Config')) {
return null;
}
$config = call_user_func('get' . $extensionName . 'Config');
// Check if the getConfig function returned an array
if (!is_array($config)) {
return null;
}
return $config;
}
public static function getPayMethod($extensionName, $nameSpace)
{
// return the payment method of the extension to be used elsewhere
// for example in the payment controller
// the function starts with the name of the extension and ends with Pay
$config = self::getExtensionConfig($extensionName, $nameSpace);
if ($config == null) {
return null;
}
return $config['payMethod'];
}
}

View file

@ -6,10 +6,8 @@ use App\Events\UserUpdateCreditsEvent;
use App\Http\Controllers\Controller;
use App\Models\PartnerDiscount;
use App\Models\Payment;
use App\Models\ShopProduct;
use App\Models\User;
use App\Notifications\ConfirmPaymentNotification;
use App\Notifications\InvoiceNotification;
use App\Models\ShopProduct;
use Exception;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
@ -18,19 +16,8 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use LaravelDaily\Invoices\Classes\Buyer;
use LaravelDaily\Invoices\Classes\InvoiceItem;
use LaravelDaily\Invoices\Classes\Party;
use LaravelDaily\Invoices\Invoice;
use PayPalCheckoutSdk\Core\PayPalHttpClient;
use PayPalCheckoutSdk\Core\ProductionEnvironment;
use PayPalCheckoutSdk\Core\SandboxEnvironment;
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
use PayPalHttp\HttpException;
use Symfony\Component\Intl\Currencies;
use App\Helpers\ExtensionHelper;
class PaymentController extends Controller
{
@ -49,8 +36,24 @@ class PaymentController extends Controller
* @param ShopProduct $shopProduct
* @return Application|Factory|View
*/
public function checkOut(Request $request, ShopProduct $shopProduct)
public function checkOut(ShopProduct $shopProduct)
{
// get all payment gateway extensions
$extensions = glob(app_path() . '/Extensions/PaymentGateways/*', GLOB_ONLYDIR);
// build a paymentgateways array that contains the routes for the payment gateways and the image path for the payment gateway which lays in public/images/Extensions/PaymentGateways with the extensionname in lowercase
$paymentGateways = [];
foreach ($extensions as $extension) {
$extensionName = basename($extension);
$config = ExtensionHelper::getExtensionConfig($extensionName, 'PaymentGateways');
if ($config) {
$payment = new \stdClass();
$payment->name = $config['name'];
$payment->image = asset('images/Extensions/PaymentGateways/' . strtolower($extensionName) . '_logo.png');
$paymentGateways[] = $payment;
}
}
return view('store.checkout')->with([
'product' => $shopProduct,
'discountpercent' => PartnerDiscount::getDiscount(),
@ -59,6 +62,7 @@ class PaymentController extends Controller
'taxvalue' => $shopProduct->getTaxValue(),
'taxpercent' => $shopProduct->getTaxPercent(),
'total' => $shopProduct->getTotalPrice(),
'paymentGateways' => $paymentGateways,
]);
}
@ -67,11 +71,10 @@ class PaymentController extends Controller
* @param ShopProduct $shopProduct
* @return RedirectResponse
*/
public function FreePay(Request $request, ShopProduct $shopProduct)
public function FreePay(ShopProduct $shopProduct)
{
//dd($shopProduct);
//check if the product is really free or the discount is 100%
if($shopProduct->getTotalPrice()>0) return redirect()->route('home')->with('error', __('An error ocured. Please try again.'));
if ($shopProduct->getTotalPrice() > 0) return redirect()->route('home')->with('error', __('An error ocured. Please try again.'));
//give product
/** @var User $user */
@ -80,9 +83,9 @@ class PaymentController extends Controller
//not updating server limit
//update User with bought item
if ($shopProduct->type=="Credits") {
if ($shopProduct->type == "Credits") {
$user->increment('credits', $shopProduct->quantity);
}elseif ($shopProduct->type=="Server slots"){
} elseif ($shopProduct->type == "Server slots") {
$user->increment('server_limit', $shopProduct->quantity);
}
@ -98,7 +101,7 @@ class PaymentController extends Controller
'type' => $shopProduct->type,
'status' => 'paid',
'amount' => $shopProduct->quantity,
'price' => $shopProduct->price - ($shopProduct->price*PartnerDiscount::getDiscount()/100),
'price' => $shopProduct->price - ($shopProduct->price * PartnerDiscount::getDiscount() / 100),
'tax_value' => $shopProduct->getTaxValue(),
'tax_percent' => $shopProduct->getTaxPercent(),
'total_price' => $shopProduct->getTotalPrice(),
@ -114,196 +117,12 @@ class PaymentController extends Controller
return redirect()->route('home')->with('success', __('Your credit balance has been increased!'));
}
/**
* @param Request $request
* @param ShopProduct $shopProduct
* @return RedirectResponse
*/
public function PaypalPay(Request $request, ShopProduct $shopProduct)
public function pay(Request $request)
{
if(!$this->checkAmount($shopProduct->getTotalPrice(), strtoupper($shopProduct->currency_code), "paypal")) return redirect()->route('home')->with('error', __('The product you chose can´t be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.'));
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = [
'intent' => 'CAPTURE',
'purchase_units' => [
[
'reference_id' => uniqid(),
'description' => $shopProduct->display.(PartnerDiscount::getDiscount() ? (' ('.__('Discount').' '.PartnerDiscount::getDiscount().'%)') : ''),
'amount' => [
'value' => $shopProduct->getTotalPrice(),
'currency_code' => strtoupper($shopProduct->currency_code),
'breakdown' => [
'item_total' => [
'currency_code' => strtoupper($shopProduct->currency_code),
'value' => $shopProduct->getPriceAfterDiscount(),
],
'tax_total' => [
'currency_code' => strtoupper($shopProduct->currency_code),
'value' => $shopProduct->getTaxValue(),
],
],
],
],
],
'application_context' => [
'cancel_url' => route('payment.Cancel'),
'return_url' => route('payment.PaypalSuccess', ['product' => $shopProduct->id]),
'brand_name' => config('app.name', 'Laravel'),
'shipping_preference' => 'NO_SHIPPING',
],
$product = ShopProduct::find($request->product_id);
$paymentGateway = $request->payment_method;
];
try {
// Call API with your client and get a response for your call
$response = $this->getPayPalClient()->execute($request);
return redirect()->away($response->result->links[1]->href);
// If call returns body in response, you can get the deserialized version from the result attribute of the response
} catch (HttpException $ex) {
echo $ex->statusCode;
dd(json_decode($ex->getMessage()));
}
}
/**
* @return PayPalHttpClient
*/
protected function getPayPalClient()
{
$environment = env('APP_ENV') == 'local'
? new SandboxEnvironment($this->getPaypalClientId(), $this->getPaypalClientSecret())
: new ProductionEnvironment($this->getPaypalClientId(), $this->getPaypalClientSecret());
return new PayPalHttpClient($environment);
}
/**
* @return string
*/
protected function getPaypalClientId()
{
return env('APP_ENV') == 'local' ? config('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID') : config('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID');
}
/**
* @return string
*/
protected function getPaypalClientSecret()
{
return env('APP_ENV') == 'local' ? config('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET') : config('SETTINGS::PAYMENTS:PAYPAL:SECRET');
}
/**
* @param Request $laravelRequest
*/
public function PaypalSuccess(Request $laravelRequest)
{
/** @var ShopProduct $shopProduct */
$shopProduct = ShopProduct::findOrFail($laravelRequest->input('product'));
/** @var User $user */
$user = Auth::user();
$request = new OrdersCaptureRequest($laravelRequest->input('token'));
$request->prefer('return=representation');
try {
// Call API with your client and get a response for your call
$response = $this->getPayPalClient()->execute($request);
if ($response->statusCode == 201 || $response->statusCode == 200) {
//update server limit
if (config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
if ($user->server_limit < config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
$user->update(['server_limit' => config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
}
}
//update User with bought item
if ($shopProduct->type == 'Credits') {
$user->increment('credits', $shopProduct->quantity);
} elseif ($shopProduct->type == 'Server slots') {
$user->increment('server_limit', $shopProduct->quantity);
}
//give referral commission always
if ((config('SETTINGS::REFERRAL:MODE') == 'commission' || config('SETTINGS::REFERRAL:MODE') == 'both') && $shopProduct->type == 'Credits' && config('SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION') == 'true') {
if ($ref_user = DB::table('user_referrals')->where('registered_user_id', '=', $user->id)->first()) {
$ref_user = User::findOrFail($ref_user->referral_id);
$increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id)) / 100, 0, '', '');
$ref_user->increment('credits', $increment);
//LOGS REFERRALS IN THE ACTIVITY LOG
activity()
->performedOn($user)
->causedBy($ref_user)
->log('gained '.$increment.' '.config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME').' for commission-referral of '.$user->name.' (ID:'.$user->id.')');
}
}
//update role give Referral-reward
if ($user->role == 'member') {
$user->update(['role' => 'client']);
//give referral commission only on first purchase
if ((config('SETTINGS::REFERRAL:MODE') == 'commission' || config('SETTINGS::REFERRAL:MODE') == 'both') && $shopProduct->type == 'Credits' && config('SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION') == 'false') {
if ($ref_user = DB::table('user_referrals')->where('registered_user_id', '=', $user->id)->first()) {
$ref_user = User::findOrFail($ref_user->referral_id);
$increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id)) / 100, 0, '', '');
$ref_user->increment('credits', $increment);
//LOGS REFERRALS IN THE ACTIVITY LOG
activity()
->performedOn($user)
->causedBy($ref_user)
->log('gained '.$increment.' '.config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME').' for commission-referral of '.$user->name.' (ID:'.$user->id.')');
}
}
}
//store payment
$payment = Payment::create([
'user_id' => $user->id,
'payment_id' => $response->result->id,
'payment_method' => 'paypal',
'type' => $shopProduct->type,
'status' => 'paid',
'amount' => $shopProduct->quantity,
'price' => $shopProduct->price - ($shopProduct->price * PartnerDiscount::getDiscount() / 100),
'tax_value' => $shopProduct->getTaxValue(),
'tax_percent' => $shopProduct->getTaxPercent(),
'total_price' => $shopProduct->getTotalPrice(),
'currency_code' => $shopProduct->currency_code,
'shop_item_product_id' => $shopProduct->id,
]);
event(new UserUpdateCreditsEvent($user));
//only create invoice if SETTINGS::INVOICE:ENABLED is true
if (config('SETTINGS::INVOICE:ENABLED') == 'true') {
$this->createInvoice($user, $payment, 'paid', $shopProduct->currency_code);
}
//redirect back to home
return redirect()->route('home')->with('success', __('Your credit balance has been increased!'));
}
// If call returns body in response, you can get the deserialized version from the result attribute of the response
if (env('APP_ENV') == 'local') {
dd($response);
} else {
abort(500);
}
} catch (HttpException $ex) {
if (env('APP_ENV') == 'local') {
echo $ex->statusCode;
dd($ex->getMessage());
} else {
abort(422);
}
}
return redirect()->route('payment.' . $paymentGateway . 'Pay', ['shopProduct' => $product->id]);
}
/**
@ -311,491 +130,9 @@ class PaymentController extends Controller
*/
public function Cancel(Request $request)
{
return redirect()->route('store.index')->with('success', 'Payment was Canceled');
return redirect()->route('store.index')->with('info', 'Payment was Canceled');
}
/**
* @param Request $request
* @param ShopProduct $shopProduct
* @return RedirectResponse
*/
public function StripePay(Request $request, ShopProduct $shopProduct)
{
if(!$this->checkAmount($shopProduct->getTotalPrice(), strtoupper($shopProduct->currency_code), "stripe")) return redirect()->route('home')->with('error', __('The product you chose can´t be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.'));
$stripeClient = $this->getStripeClient();
$request = $stripeClient->checkout->sessions->create([
'line_items' => [
[
'price_data' => [
'currency' => $shopProduct->currency_code,
'product_data' => [
'name' => $shopProduct->display.(PartnerDiscount::getDiscount() ? (' ('.__('Discount').' '.PartnerDiscount::getDiscount().'%)') : ''),
'description' => $shopProduct->description,
],
'unit_amount_decimal' => round($shopProduct->getPriceAfterDiscount() * 100, 2),
],
'quantity' => 1,
],
[
'price_data' => [
'currency' => $shopProduct->currency_code,
'product_data' => [
'name' => __('Tax'),
'description' => $shopProduct->getTaxPercent().'%',
],
'unit_amount_decimal' => round($shopProduct->getTaxValue(), 2) * 100,
],
'quantity' => 1,
],
],
'mode' => 'payment',
'payment_method_types' => str_getcsv(config('SETTINGS::PAYMENTS:STRIPE:METHODS')),
'success_url' => route('payment.StripeSuccess', ['product' => $shopProduct->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 ShopProduct $shopProduct */
$shopProduct = ShopProduct::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 server limit
if (config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
if ($user->server_limit < config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
$user->update(['server_limit' => config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
}
}
//update User with bought item
if ($shopProduct->type == 'Credits') {
$user->increment('credits', $shopProduct->quantity);
} elseif ($shopProduct->type == 'Server slots') {
$user->increment('server_limit', $shopProduct->quantity);
}
//update role give Referral-reward
if ($user->role == 'member') {
$user->update(['role' => 'client']);
if ((config('SETTINGS::REFERRAL:MODE') == 'commission' || config('SETTINGS::REFERRAL:MODE') == 'both') && $shopProduct->type == 'Credits') {
if ($ref_user = DB::table('user_referrals')->where('registered_user_id', '=', $user->id)->first()) {
$ref_user = User::findOrFail($ref_user->referral_id);
$increment = number_format($shopProduct->quantity / 100 * config('SETTINGS::REFERRAL:PERCENTAGE'), 0, '', '');
$ref_user->increment('credits', $increment);
//LOGS REFERRALS IN THE ACTIVITY LOG
activity()
->performedOn($user)
->causedBy($ref_user)
->log('gained '.$increment.' '.config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME').' for commission-referral of '.$user->name.' (ID:'.$user->id.')');
}
}
}
//store paid payment
$payment = Payment::create([
'user_id' => $user->id,
'payment_id' => $paymentSession->payment_intent,
'payment_method' => 'stripe',
'type' => $shopProduct->type,
'status' => 'paid',
'amount' => $shopProduct->quantity,
'price' => $shopProduct->price - ($shopProduct->price * PartnerDiscount::getDiscount() / 100),
'tax_value' => $shopProduct->getTaxValue(),
'total_price' => $shopProduct->getTotalPrice(),
'tax_percent' => $shopProduct->getTaxPercent(),
'currency_code' => $shopProduct->currency_code,
'shop_item_product_id' => $shopProduct->id,
]);
//payment notification
$user->notify(new ConfirmPaymentNotification($payment));
event(new UserUpdateCreditsEvent($user));
//only create invoice if SETTINGS::INVOICE:ENABLED is true
if (config('SETTINGS::INVOICE:ENABLED') == 'true') {
$this->createInvoice($user, $payment, 'paid', $shopProduct->currency_code);
}
//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' => $shopProduct->type,
'status' => 'processing',
'amount' => $shopProduct->quantity,
'price' => $shopProduct->price,
'tax_value' => $shopProduct->getTaxValue(),
'total_price' => $shopProduct->getTotalPrice(),
'tax_percent' => $shopProduct->getTaxPercent(),
'currency_code' => $shopProduct->currency_code,
'shop_item_product_id' => $shopProduct->id,
]);
//only create invoice if SETTINGS::INVOICE:ENABLED is true
if (config('SETTINGS::INVOICE:ENABLED') == 'true') {
$this->createInvoice($user, $payment, 'paid', $shopProduct->currency_code);
}
//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') {
//update server limit
if (config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0) {
if ($user->server_limit < config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
$user->update(['server_limit' => config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
}
}
//update User with bought item
if ($shopProduct->type == 'Credits') {
$user->increment('credits', $shopProduct->quantity);
} elseif ($shopProduct->type == 'Server slots') {
$user->increment('server_limit', $shopProduct->quantity);
}
//update role give Referral-reward
if ($user->role == 'member') {
$user->update(['role' => 'client']);
if ((config('SETTINGS::REFERRAL:MODE') == 'commission' || config('SETTINGS::REFERRAL:MODE') == 'both') && $shopProduct->type == 'Credits') {
if ($ref_user = DB::table('user_referrals')->where('registered_user_id', '=', $user->id)->first()) {
$ref_user = User::findOrFail($ref_user->referral_id);
$increment = number_format($shopProduct->quantity / 100 * config('SETTINGS::REFERRAL:PERCENTAGE'), 0, '', '');
$ref_user->increment('credits', $increment);
//LOGS REFERRALS IN THE ACTIVITY LOG
activity()
->performedOn($user)
->causedBy($ref_user)
->log('gained '.$increment.' '.config('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME').' for commission-referral of '.$user->name.' (ID:'.$user->id.')');
}
}
}
//update payment db entry status
$payment->update(['status' => 'paid']);
//payment notification
$user->notify(new ConfirmPaymentNotification($payment));
event(new UserUpdateCreditsEvent($user));
//only create invoice if SETTINGS::INVOICE:ENABLED is true
if (config('SETTINGS::INVOICE:ENABLED') == 'true') {
$this->createInvoice($user, $payment, 'paid', strtoupper($paymentIntent->currency));
}
}
} 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'
? config('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET')
: config('SETTINGS::PAYMENTS:STRIPE:SECRET');
}
/**
* @return string
*/
protected function getStripeEndpointSecret()
{
return env('APP_ENV') == 'local'
? config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET')
: config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET');
}
protected function createInvoice($user, $payment, $paymentStatus, $currencyCode)
{
$shopProduct = ShopProduct::where('id', $payment->shop_item_product_id)->first();
//create invoice
$lastInvoiceID = \App\Models\Invoice::where('invoice_name', 'like', '%'.now()->format('mY').'%')->count('id');
$newInvoiceID = $lastInvoiceID + 1;
$logoPath = storage_path('app/public/logo.png');
$seller = new Party([
'name' => config('SETTINGS::INVOICE:COMPANY_NAME'),
'phone' => config('SETTINGS::INVOICE:COMPANY_PHONE'),
'address' => config('SETTINGS::INVOICE:COMPANY_ADDRESS'),
'vat' => config('SETTINGS::INVOICE:COMPANY_VAT'),
'custom_fields' => [
'E-Mail' => config('SETTINGS::INVOICE:COMPANY_MAIL'),
'Web' => config('SETTINGS::INVOICE:COMPANY_WEBSITE'),
],
]);
$customer = new Buyer([
'name' => $user->name,
'custom_fields' => [
'E-Mail' => $user->email,
'Client ID' => $user->id,
],
]);
$item = (new InvoiceItem())
->title($shopProduct->description)
->pricePerUnit($shopProduct->price);
$notes = [
__('Payment method').': '.$payment->payment_method,
];
$notes = implode('<br>', $notes);
$invoice = Invoice::make()
->template('controlpanel')
->name(__('Invoice'))
->buyer($customer)
->seller($seller)
->discountByPercent(PartnerDiscount::getDiscount())
->taxRate(floatval($shopProduct->getTaxPercent()))
->shipping(0)
->addItem($item)
->status(__($paymentStatus))
->series(now()->format('mY'))
->delimiter('-')
->sequence($newInvoiceID)
->serialNumberFormat(config('SETTINGS::INVOICE:PREFIX').'{DELIMITER}{SERIES}{SEQUENCE}')
->currencyCode($currencyCode)
->currencySymbol(Currencies::getSymbol($currencyCode))
->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));
}
public function checkAmount($amount, $currencyCode, $payment_method)
{
$minimums = [
"USD" => [
"paypal" => 0,
"stripe" => 0.5
],
"AED" => [
"paypal" => 0,
"stripe" => 2
],
"AUD" => [
"paypal" => 0,
"stripe" => 0.5
],
"BGN" => [
"paypal" => 0,
"stripe" => 1
],
"BRL" => [
"paypal" => 0,
"stripe" => 0.5
],
"CAD" => [
"paypal" => 0,
"stripe" => 0.5
],
"CHF" => [
"paypal" => 0,
"stripe" => 0.5
],
"CZK" => [
"paypal" => 0,
"stripe" => 15
],
"DKK" => [
"paypal" => 0,
"stripe" => 2.5
],
"EUR" => [
"paypal" => 0,
"stripe" => 0.5
],
"GBP" => [
"paypal" => 0,
"stripe" => 0.3
],
"HKD" => [
"paypal" => 0,
"stripe" => 4
],
"HRK" => [
"paypal" => 0,
"stripe" => 0.5
],
"HUF" => [
"paypal" => 0,
"stripe" => 175
],
"INR" => [
"paypal" => 0,
"stripe" => 0.5
],
"JPY" => [
"paypal" => 0,
"stripe" => 0.5
],
"MXN" => [
"paypal" => 0,
"stripe" => 10
],
"MYR" => [
"paypal" => 0,
"stripe" => 2
],
"NOK" => [
"paypal" => 0,
"stripe" => 3
],
"NZD" => [
"paypal" => 0,
"stripe" => 0.5
],
"PLN" => [
"paypal" => 0,
"stripe" => 2
],
"RON" => [
"paypal" => 0,
"stripe" => 2
],
"SEK" => [
"paypal" => 0,
"stripe" => 3
],
"SGD" => [
"paypal" => 0,
"stripe" => 0.5
],
"THB" => [
"paypal" => 0,
"stripe" => 10
]
];
return $amount >= $minimums[$currencyCode][$payment_method];
}
/**
* @return JsonResponse|mixed
*
@ -808,9 +145,7 @@ class PaymentController extends Controller
return datatables($query)
->addColumn('user', function (Payment $payment) {
return
($payment->user)?'<a href="'.route('admin.users.show', $payment->user->id).'">'.$payment->user->name.'</a>':__('Unknown user');
return ($payment->user) ? '<a href="' . route('admin.users.show', $payment->user->id) . '">' . $payment->user->name . '</a>' : __('Unknown user');
})
->editColumn('price', function (Payment $payment) {
return $payment->formatToCurrency($payment->price);
@ -819,18 +154,20 @@ class PaymentController extends Controller
return $payment->formatToCurrency($payment->tax_value);
})
->editColumn('tax_percent', function (Payment $payment) {
return $payment->tax_percent.' %';
return $payment->tax_percent . ' %';
})
->editColumn('total_price', function (Payment $payment) {
return $payment->formatToCurrency($payment->total_price);
})
->editColumn('created_at', function (Payment $payment) {
return ['display' => $payment->created_at ? $payment->created_at->diffForHumans() : '',
'raw' => $payment->created_at ? strtotime($payment->created_at) : ''];
return [
'display' => $payment->created_at ? $payment->created_at->diffForHumans() : '',
'raw' => $payment->created_at ? strtotime($payment->created_at) : ''
];
})
->addColumn('actions', function (Payment $payment) {
return '<a data-content="'.__('Download').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.invoices.downloadSingleInvoice', 'id='.$payment->payment_id).'" class="btn btn-sm text-white btn-info mr-1"><i class="fas fa-file-download"></i></a>';
return '<a data-content="' . __('Download') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.invoices.downloadSingleInvoice', 'id=' . $payment->payment_id) . '" class="btn btn-sm text-white btn-info mr-1"><i class="fas fa-file-download"></i></a>';
})
->rawColumns(['actions', 'user'])
->make(true);

View file

@ -0,0 +1,31 @@
<?php
namespace App\Listeners;
use App\Events\PaymentEvent;
use App\Traits\Invoiceable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class CreateInvoice implements ShouldQueue
{
use Invoiceable;
/**
* Handle the event.
*
* @param \App\Events\PaymentEvent $event
* @return void
*/
public function handle(PaymentEvent $event)
{
if (config('SETTINGS::INVOICE:ENABLED') == 'true') {
// get user from payment which does hold the user_id
$user = $event->payment->user;
// create invoice using the trait
$this->createInvoice($user, $event->payment);
}
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace App\Listeners;
use App\Events\PaymentEvent;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use App\Models\PartnerDiscount;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class UserPayment
{
/**
* Handle the event.
*
* @param \App\Events\PaymentEvent $event
* @return void
*/
public function handle(PaymentEvent $event)
{
$user = $event->user;
$shopProduct = $event->shopProduct;
// only update user if payment is paid
if ($event->payment->status != "paid") {
return;
}
//update server limit
if (config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE') !== 0 && $user->server_limit < config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')) {
$user->update(['server_limit' => config('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE')]);
}
//update User with bought item
if ($shopProduct->type == "Credits") {
$user->increment('credits', $shopProduct->quantity);
} elseif ($shopProduct->type == "Server slots") {
$user->increment('server_limit', $shopProduct->quantity);
}
//give referral commission always
if ((config("SETTINGS::REFERRAL:MODE") == "commission" || config("SETTINGS::REFERRAL:MODE") == "both") && $shopProduct->type == "Credits" && config("SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION") == "true") {
if ($ref_user = DB::table("user_referrals")->where('registered_user_id', '=', $user->id)->first()) {
$ref_user = User::findOrFail($ref_user->referral_id);
$increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id)) / 100, 0, "", "");
$ref_user->increment('credits', $increment);
//LOGS REFERRALS IN THE ACTIVITY LOG
activity()
->performedOn($user)
->causedBy($ref_user)
->log('gained ' . $increment . ' ' . config("SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME") . ' for commission-referral of ' . $user->name . ' (ID:' . $user->id . ')');
}
}
//update role give Referral-reward
if ($user->role == 'member') {
$user->update(['role' => 'client']);
//give referral commission only on first purchase
if ((config("SETTINGS::REFERRAL:MODE") == "commission" || config("SETTINGS::REFERRAL:MODE") == "both") && $shopProduct->type == "Credits" && config("SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION") == "false") {
if ($ref_user = DB::table("user_referrals")->where('registered_user_id', '=', $user->id)->first()) {
$ref_user = User::findOrFail($ref_user->referral_id);
$increment = number_format($shopProduct->quantity * (PartnerDiscount::getCommission($ref_user->id)) / 100, 0, "", "");
$ref_user->increment('credits', $increment);
//LOGS REFERRALS IN THE ACTIVITY LOG
activity()
->performedOn($user)
->causedBy($ref_user)
->log('gained ' . $increment . ' ' . config("SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME") . ' for commission-referral of ' . $user->name . ' (ID:' . $user->id . ')');
}
}
}
// LOGS PAYMENT IN THE ACTIVITY LOG
activity()
->performedOn($user)
->causedBy($user)
->log('bought ' . $shopProduct->quantity . ' ' . $shopProduct->type . ' for ' . $shopProduct->price . $shopProduct->currency_code);
}
}

View file

@ -7,20 +7,11 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use NumberFormatter;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
class Payment extends Model
{
use HasFactory;
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
-> logOnlyDirty()
-> logOnly(['*'])
-> dontSubmitEmptyLogs();
}
public $incrementing = false;
/**

View file

@ -2,8 +2,11 @@
namespace App\Providers;
use App\Events\PaymentEvent;
use App\Events\UserUpdateCreditsEvent;
use App\Listeners\CreateInvoice;
use App\Listeners\UnsuspendServers;
use App\Listeners\UserPayment;
use App\Listeners\Verified;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
@ -24,6 +27,10 @@ class EventServiceProvider extends ServiceProvider
UserUpdateCreditsEvent::class => [
UnsuspendServers::class,
],
PaymentEvent::class => [
CreateInvoice::class,
UserPayment::class,
],
SocialiteWasCalled::class => [
// ... other providers
'SocialiteProviders\\Discord\\DiscordExtendSocialite@handle',

View file

@ -0,0 +1,91 @@
<?php
namespace App\Traits;
use App\Models\PartnerDiscount;
use App\Models\ShopProduct;
use App\Notifications\InvoiceNotification;
use Illuminate\Support\Facades\Storage;
use LaravelDaily\Invoices\Classes\Buyer;
use LaravelDaily\Invoices\Classes\InvoiceItem;
use LaravelDaily\Invoices\Classes\Party;
use LaravelDaily\Invoices\Invoice;
use Symfony\Component\Intl\Currencies;
trait Invoiceable
{
public function createInvoice($user, $payment)
{
$shopProduct = ShopProduct::where('id', $payment->shop_item_product_id)->first();
//create invoice
$lastInvoiceID = \App\Models\Invoice::where("invoice_name", "like", "%" . now()->format('mY') . "%")->count("id");
$newInvoiceID = $lastInvoiceID + 1;
$logoPath = storage_path('app/public/logo.png');
$seller = new Party([
'name' => config("SETTINGS::INVOICE:COMPANY_NAME"),
'phone' => config("SETTINGS::INVOICE:COMPANY_PHONE"),
'address' => config("SETTINGS::INVOICE:COMPANY_ADDRESS"),
'vat' => config("SETTINGS::INVOICE:COMPANY_VAT"),
'custom_fields' => [
'E-Mail' => config("SETTINGS::INVOICE:COMPANY_MAIL"),
"Web" => config("SETTINGS::INVOICE:COMPANY_WEBSITE")
],
]);
$customer = new Buyer([
'name' => $user->name,
'custom_fields' => [
'E-Mail' => $user->email,
'Client ID' => $user->id,
],
]);
$item = (new InvoiceItem())
->title($shopProduct->description)
->pricePerUnit($shopProduct->price);
$notes = [
__("Payment method") . ": " . $payment->payment_method,
];
$notes = implode("<br>", $notes);
$invoice = Invoice::make()
->template('controlpanel')
->name(__("Invoice"))
->buyer($customer)
->seller($seller)
->discountByPercent(PartnerDiscount::getDiscount())
->taxRate(floatval($shopProduct->getTaxPercent()))
->shipping(0)
->addItem($item)
->status(__($payment->status))
->series(now()->format('mY'))
->delimiter("-")
->sequence($newInvoiceID)
->serialNumberFormat(config("SETTINGS::INVOICE:PREFIX") . '{DELIMITER}{SERIES}{SEQUENCE}')
->currencyCode(strtoupper($payment->currency_code))
->currencySymbol(Currencies::getSymbol(strtoupper($payment->currency_code)))
->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));
}
}

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -34,6 +34,8 @@ Route::middleware('api.token')->group(function () {
Route::get('/notifications/{user}', [NotificationController::class, 'index']);
Route::get('/notifications/{user}/{notification}', [NotificationController::class, 'view']);
Route::post('/notifications', [NotificationController::class, 'send']);
Route::delete('/notifications/{user}', [NotificationController::class, 'delete']);
Route::delete('/notifications/{user}/{notification}', [NotificationController::class, 'deleteOne']);
Route::delete('/notifications/{user}', [NotificationController::class, 'delete']);
});
require __DIR__ . '/extensions_api.php';

21
routes/extensions_api.php Normal file
View file

@ -0,0 +1,21 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'extensions', 'middleware' => 'api.token'], function () {
// get all extensions that are inside App/Extensions
// It is important that the extensions are inside a folder with the name of the extension
// while those folders are inside Folders with the name of the type of extension like PaymentGateways, Themes, etc.
$extensionNamespaces = glob(app_path() . '/Extensions/*', GLOB_ONLYDIR);
$extensions = [];
foreach ($extensionNamespaces as $extensionNamespace) {
$extensions = array_merge($extensions, glob($extensionNamespace . '/*', GLOB_ONLYDIR));
}
foreach ($extensions as $extension) {
$routesFile = $extension . '/api_routes.php';
if (file_exists($routesFile)) {
include_once $routesFile;
}
}
});

22
routes/extensions_web.php Normal file
View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'extensions'], function () {
// get all extensions that are inside App/Extensions
// It is important that the extensions are inside a folder with the name of the extension
// while those folders are inside Folders with the name of the type of extension like PaymentGateways, Themes, etc.
$extensionNamespaces = glob(app_path() . '/Extensions/*', GLOB_ONLYDIR);
$extensions = [];
foreach ($extensionNamespaces as $extensionNamespace) {
$extensions = array_merge($extensions, glob($extensionNamespace . '/*', GLOB_ONLYDIR));
}
foreach ($extensions as $extension) {
$routesFile = $extension . '/web_routes.php';
if (file_exists($routesFile)) {
include_once $routesFile;
}
}
});

View file

@ -49,17 +49,14 @@ 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::get('/privacy', function () {
return view('information.privacy');
return view('information.privacy');
})->name('privacy');
Route::get('/imprint', function () {
return view('information.imprint');
return view('information.imprint');
})->name('imprint');
Route::get('/tos', function () {
return view('information.tos');
return view('information.tos');
})->name('tos');
Route::middleware(['auth', 'checkSuspended'])->group(function () {
@ -90,10 +87,7 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
//payments
Route::get('checkout/{shopProduct}', [PaymentController::class, 'checkOut'])->name('checkout');
Route::get('payment/PaypalPay/{shopProduct}', [PaymentController::class, 'PaypalPay'])->name('payment.PaypalPay');
Route::get('payment/PaypalSuccess', [PaymentController::class, 'PaypalSuccess'])->name('payment.PaypalSuccess');
Route::get('payment/StripePay/{shopProduct}', [PaymentController::class, 'StripePay'])->name('payment.StripePay');
Route::get('payment/StripeSuccess', [PaymentController::class, 'StripeSuccess'])->name('payment.StripeSuccess');
Route::post('payment/pay', [PaymentController::class, 'pay'])->name('payment.pay');
Route::get('payment/FreePay/{shopProduct}', [PaymentController::class, 'FreePay'])->name('payment.FreePay');
Route::get('payment/Cancel', [PaymentController::class, 'Cancel'])->name('payment.Cancel');
@ -221,3 +215,5 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
Route::get('/home', [HomeController::class, 'index'])->name('home');
});
require __DIR__ . '/extensions_web.php';

View file

@ -10,8 +10,7 @@
</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="" href="{{ route('home') }}">{{ __('Dashboard') }}</a></li>
<li class="breadcrumb-item"><a class="text-muted"
href="{{ route('store.index') }}">{{ __('Store') }}</a>
</li>
@ -23,157 +22,127 @@
<!-- END CONTENT HEADER -->
<!-- MAIN CONTENT -->
<section x-data="serverApp()" x-init="$watch('paymentMethod', value => setPaymentRoute(value))" class="content">
<section class="content">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<!-- Main content -->
<div class="invoice p-3 mb-3">
<!-- title row -->
<div class="row">
<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>
</h4>
<form x-data="{ payment_method: '', clicked: false }" action="{{ route('payment.pay') }}" method="POST">
@csrf
@method('post')
<!-- Main content -->
<div class="invoice p-3 mb-3">
<!-- title row -->
<div class="row">
<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>
</h4>
</div>
<!-- /.col -->
</div>
<!-- /.col -->
</div>
<!-- Table row -->
<div class="row">
<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>
</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>
</tbody>
</table>
<!-- Table row -->
<div class="row">
<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>
</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>
</tbody>
</table>
<input type="hidden" name="product_id" value="{{ $product->id }}">
</div>
<!-- /.col -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
<!-- /.row -->
<div class="row">
<!-- accepted payments column -->
<div class="col-6">
@if($total!=0)
<div class="row">
<!-- accepted payments column -->
<div class="col-6">
<p class="lead">{{ __('Payment Methods') }}:</p>
<div>
@if (config('SETTINGS::PAYMENTS:PAYPAL:SECRET') || config('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_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 (config('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET') || config('SETTINGS::PAYMENTS: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 class="d-flex flex-wrap flex-direction-row">
@foreach ($paymentGateways as $gateway)
<div class="ml-2">
<label class="text-center" for="{{ $gateway->name }}">
<img class="mb-3" height="50" src="{{ $gateway->image }}"></br>
<input x-on:click="console.log(payment_method)" x-model="payment_method"
type="radio" id="{{ $gateway->name }}"
value="{{ $gateway->name }}">
</input>
</label>
</div>
@endforeach
</div>
@else
<p class="lead" style="text-align: center">{{ __('This product is free for you') }}.</p>
@endif
</div>
<!-- /.col -->
<div class="col-6">
<p class="lead">{{ __('Amount Due') }}
{{ Carbon\Carbon::now()->isoFormat('LL') }}</p>
<div class="table-responsive">
<table class="table">
@if($discountpercent&&$discountvalue)
</div>
<!-- /.col -->
<div class="col-6">
<p class="lead">{{ __('Amount Due') }}
{{ Carbon\Carbon::now()->isoFormat('LL') }}</p>
<div class="table-responsive">
<table class="table">
@if ($discountpercent && $discountvalue)
<tr>
<th>{{ __('Discount') }} ({{ $discountpercent }}%):</th>
<td>{{ $product->formatToCurrency($discountvalue) }}</td>
</tr>
@endif
<tr>
<th>{{ __('Discount') }} ({{ $discountpercent }}%):</th>
<td>{{$product->formatToCurrency($discountvalue)}}</td>
<th style="width:50%">{{ __('Subtotal') }}:</th>
<td>{{ $product->formatToCurrency($discountedprice) }}</td>
</tr>
@endif
<tr>
<th style="width:50%">{{ __('Subtotal') }}:</th>
<td>{{ $product->formatToCurrency($discountedprice) }}</td>
</tr>
<tr>
<th>{{ __('Tax') }} ({{ $taxpercent }}%):</th>
<td>{{ $product->formatToCurrency($taxvalue) }}</td>
</tr>
<tr>
<th>{{ __('Total') }}:</th>
<td>{{ $product->formatToCurrency($total) }}</td>
</tr>
</table>
<tr>
<th>{{ __('Tax') }} ({{ $taxpercent }}%):</th>
<td>{{ $product->formatToCurrency($taxvalue) }}</td>
</tr>
<tr>
<th>{{ __('Total') }}:</th>
<td>{{ $product->formatToCurrency($total) }}</td>
</tr>
</table>
</div>
</div>
<!-- /.col -->
</div>
<!-- /.row -->
<!-- this row will not appear when printing -->
<div class="row no-print">
<div class="col-12">
<button :disabled="!payment_method || clicked"
:class="!payment_method || clicked ? 'disabled' : ''"
class="btn btn-success float-right"><i class="far fa-credit-card mr-2"
@click="clicked = true"></i>
{{ __('Submit Payment') }}
</button>
</div>
</div>
<!-- /.col -->
</div>
<!-- /.row -->
<!-- this row will not appear when printing -->
<div class="row no-print">
<div class="col-12">
<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>
</div>
</form>
<!-- /.invoice -->
</div><!-- /.col -->
</div><!-- /.row -->
</div>
</section>
<!-- END CONTENT -->
<script>
function serverApp() {
return {
//loading
paymentMethod: '',
paymentRoute: ({{ $total }} == 0)?('{{ route('payment.FreePay', $product->id) }}'):'',
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