Merge branch 'ControlPanel-gg:development' into development
This commit is contained in:
commit
bb0243df47
146
app/Extensions/PaymentGateways/Mollie/MollieExtension.php
Normal file
146
app/Extensions/PaymentGateways/Mollie/MollieExtension.php
Normal file
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace App\Extensions\PaymentGateways\Mollie;
|
||||
|
||||
use App\Helpers\AbstractExtension;
|
||||
use App\Events\PaymentEvent;
|
||||
use App\Events\UserUpdateCreditsEvent;
|
||||
use App\Models\PartnerDiscount;
|
||||
use App\Models\Payment;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
/**
|
||||
* Summary of PayPalExtension
|
||||
*/
|
||||
class MollieExtension extends AbstractExtension
|
||||
{
|
||||
public static function getConfig(): array
|
||||
{
|
||||
return [
|
||||
"name" => "Mollie",
|
||||
"RoutesIgnoreCsrf" => [
|
||||
"payment/MollieWebhook"
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
static function pay(Request $request): void
|
||||
{
|
||||
$url = 'https://api.mollie.com/v2/payments';
|
||||
$settings = new MollieSettings();
|
||||
|
||||
$user = Auth::user();
|
||||
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
|
||||
$discount = PartnerDiscount::getDiscount();
|
||||
|
||||
// create a new payment
|
||||
$payment = Payment::create([
|
||||
'user_id' => $user->id,
|
||||
'payment_id' => null,
|
||||
'payment_method' => 'mollie',
|
||||
'type' => $shopProduct->type,
|
||||
'status' => 'open',
|
||||
'amount' => $shopProduct->quantity,
|
||||
'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
|
||||
'tax_value' => $shopProduct->getTaxValue(),
|
||||
'tax_percent' => $shopProduct->getTaxPercent(),
|
||||
'total_price' => $shopProduct->getTotalPrice(),
|
||||
'currency_code' => $shopProduct->currency_code,
|
||||
'shop_item_product_id' => $shopProduct->id,
|
||||
]);
|
||||
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $settings->api_key,
|
||||
])->post($url, [
|
||||
'amount' => [
|
||||
'currency' => $shopProduct->currency_code,
|
||||
'value' => number_format($shopProduct->getTotalPrice(), 2, '.', ''),
|
||||
],
|
||||
'description' => "Order #{$payment->id} - " . $shopProduct->name,
|
||||
'redirectUrl' => route('payment.MollieSuccess'),
|
||||
'cancelUrl' => route('payment.Cancel'),
|
||||
'webhookUrl' => url('/extensions/payment/MollieWebhook'),
|
||||
'metadata' => [
|
||||
'payment_id' => $payment->id,
|
||||
],
|
||||
]);
|
||||
|
||||
if ($response->status() != 201) {
|
||||
Log::error('Mollie Payment: ' . $response->body());
|
||||
$payment->delete();
|
||||
|
||||
Redirect::route('store.index')->with('error', __('Payment failed'))->send();
|
||||
return;
|
||||
}
|
||||
|
||||
$payment->update([
|
||||
'payment_id' => $response->json()['id'],
|
||||
]);
|
||||
|
||||
Redirect::away($response->json()['_links']['checkout']['href'])->send();
|
||||
return;
|
||||
} catch (Exception $ex) {
|
||||
Log::error('Mollie Payment: ' . $ex->getMessage());
|
||||
$payment->delete();
|
||||
|
||||
Redirect::route('store.index')->with('error', __('Payment failed'))->send();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static function success(Request $request): void
|
||||
{
|
||||
$payment = Payment::findOrFail($request->input('payment'));
|
||||
$payment->status = 'pending';
|
||||
|
||||
Redirect::route('home')->with('success', 'Your payment is being processed')->send();
|
||||
return;
|
||||
}
|
||||
|
||||
static function webhook(Request $request): JsonResponse
|
||||
{
|
||||
$url = 'https://api.mollie.com/v2/payments/' . $request->id;
|
||||
$settings = new MollieSettings();
|
||||
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $settings->api_key,
|
||||
])->get($url);
|
||||
if ($response->status() != 200) {
|
||||
Log::error('Mollie Payment Webhook: ' . $response->json()['title']);
|
||||
return response()->json(['success' => false]);
|
||||
}
|
||||
|
||||
$payment = Payment::findOrFail($response->json()['metadata']['payment_id']);
|
||||
$payment->status->update([
|
||||
'status' => $response->json()['status'],
|
||||
]);
|
||||
|
||||
$shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
|
||||
event(new PaymentEvent($payment, $payment, $shopProduct));
|
||||
|
||||
if ($response->json()['status'] == 'paid') {
|
||||
$user = User::findOrFail($payment->user_id);
|
||||
event(new UserUpdateCreditsEvent($user));
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
Log::error('Mollie Payment Webhook: ' . $ex->getMessage());
|
||||
return response()->json(['success' => false]);
|
||||
}
|
||||
|
||||
// return a 200 status code
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
41
app/Extensions/PaymentGateways/Mollie/MollieSettings.php
Normal file
41
app/Extensions/PaymentGateways/Mollie/MollieSettings.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Extensions\PaymentGateways\Mollie;
|
||||
|
||||
use Spatie\LaravelSettings\Settings;
|
||||
|
||||
class MollieSettings extends Settings
|
||||
{
|
||||
|
||||
public bool $enabled = false;
|
||||
public ?string $api_key;
|
||||
|
||||
public static function group(): string
|
||||
{
|
||||
return 'mollie';
|
||||
}
|
||||
|
||||
public static function encrypted(): array
|
||||
{
|
||||
return [
|
||||
'api_key',
|
||||
];
|
||||
}
|
||||
|
||||
public static function getOptionInputData()
|
||||
{
|
||||
return [
|
||||
'category_icon' => 'fas fa-dollar-sign',
|
||||
'api_key' => [
|
||||
'type' => 'string',
|
||||
'label' => 'API Key',
|
||||
'description' => 'The API Key of your Mollie App',
|
||||
],
|
||||
'enabled' => [
|
||||
'type' => 'boolean',
|
||||
'label' => 'Enabled',
|
||||
'description' => 'Enable or disable this payment gateway',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
class CreateMollieSettings extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->migrator->addEncrypted('mollie.api_key', null);
|
||||
$this->migrator->add('mollie.enabled', false);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->migrator->delete('mollie.api_key');
|
||||
$this->migrator->delete('mollie.enabled');
|
||||
}
|
||||
}
|
22
app/Extensions/PaymentGateways/Mollie/web_routes.php
Normal file
22
app/Extensions/PaymentGateways/Mollie/web_routes.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Extensions\PaymentGateways\Mollie\MollieExtension;
|
||||
|
||||
Route::middleware(['web', 'auth'])->group(function () {
|
||||
Route::get('payment/MolliePay/{shopProduct}', function () {
|
||||
MollieExtension::pay(request());
|
||||
})->name('payment.MolliePay');
|
||||
|
||||
Route::get(
|
||||
'payment/MollieSuccess',
|
||||
function () {
|
||||
MollieExtension::success(request());
|
||||
}
|
||||
)->name('payment.MollieSuccess');
|
||||
});
|
||||
|
||||
|
||||
Route::post('payment/MollieWebhook', function () {
|
||||
MollieExtension::webhook(request());
|
||||
})->name('payment.MollieWebhook');
|
197
app/Extensions/PaymentGateways/PayPal/PayPalExtension.php
Normal file
197
app/Extensions/PaymentGateways/PayPal/PayPalExtension.php
Normal file
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
namespace App\Extensions\PaymentGateways\PayPal;
|
||||
|
||||
use App\Helpers\AbstractExtension;
|
||||
use App\Events\PaymentEvent;
|
||||
use App\Events\UserUpdateCreditsEvent;
|
||||
use App\Extensions\PaymentGateways\PayPal\PayPalSettings;
|
||||
use App\Models\PartnerDiscount;
|
||||
use App\Models\Payment;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PayPalCheckoutSdk\Core\PayPalHttpClient;
|
||||
use PayPalCheckoutSdk\Core\ProductionEnvironment;
|
||||
use PayPalCheckoutSdk\Core\SandboxEnvironment;
|
||||
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
|
||||
use PayPalCheckoutSdk\Orders\OrdersCreateRequest;
|
||||
use PayPalHttp\HttpException;
|
||||
|
||||
|
||||
/**
|
||||
* Summary of PayPalExtension
|
||||
*/
|
||||
class PayPalExtension extends AbstractExtension
|
||||
{
|
||||
public static function getConfig(): array
|
||||
{
|
||||
return [
|
||||
"name" => "PayPal",
|
||||
"RoutesIgnoreCsrf" => [],
|
||||
];
|
||||
}
|
||||
|
||||
static function PaypalPay(Request $request): void
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = Auth::user();
|
||||
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
|
||||
$discount = PartnerDiscount::getDiscount();
|
||||
|
||||
// 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 * $discount / 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 . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""),
|
||||
"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', 'Controlpanel.GG'),
|
||||
'shipping_preference' => 'NO_SHIPPING'
|
||||
]
|
||||
|
||||
|
||||
];
|
||||
|
||||
try {
|
||||
// Call API with your client and get a response for your call
|
||||
$response = self::getPayPalClient()->execute($request);
|
||||
|
||||
// check for any errors in the response
|
||||
if ($response->statusCode != 201) {
|
||||
throw new \Exception($response->statusCode);
|
||||
}
|
||||
|
||||
// make sure the link is not empty
|
||||
if (empty($response->result->links[1]->href)) {
|
||||
throw new \Exception('No redirect link found');
|
||||
}
|
||||
|
||||
Redirect::away($response->result->links[1]->href)->send();
|
||||
return;
|
||||
} catch (HttpException $ex) {
|
||||
Log::error('PayPal Payment: ' . $ex->getMessage());
|
||||
$payment->delete();
|
||||
|
||||
Redirect::route('store.index')->with('error', __('Payment failed'))->send();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static function PaypalSuccess(Request $laravelRequest): void
|
||||
{
|
||||
$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 = self::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static function getPayPalClient(): PayPalHttpClient
|
||||
{
|
||||
$environment = env('APP_ENV') == 'local'
|
||||
? new SandboxEnvironment(self::getPaypalClientId(), self::getPaypalClientSecret())
|
||||
: new ProductionEnvironment(self::getPaypalClientId(), self::getPaypalClientSecret());
|
||||
return new PayPalHttpClient($environment);
|
||||
}
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
static function getPaypalClientId(): string
|
||||
{
|
||||
$settings = new PayPalSettings();
|
||||
return env('APP_ENV') == 'local' ? $settings->sandbox_client_id : $settings->client_id;
|
||||
}
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
static function getPaypalClientSecret(): string
|
||||
{
|
||||
$settings = new PayPalSettings();
|
||||
return env('APP_ENV') == 'local' ? $settings->sandbox_client_secret : $settings->client_secret;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Extensions\PaymentGateways\PayPal;
|
||||
|
||||
function getConfig()
|
||||
{
|
||||
return [
|
||||
"name" => "PayPal",
|
||||
"RoutesIgnoreCsrf" => [],
|
||||
];
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Events\PaymentEvent;
|
||||
use App\Events\UserUpdateCreditsEvent;
|
||||
use App\Extensions\PaymentGateways\PayPal\PayPalSettings;
|
||||
use App\Models\PartnerDiscount;
|
||||
use App\Models\Payment;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
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)
|
||||
{
|
||||
$settings = new PayPalSettings();
|
||||
|
||||
/** @var User $user */
|
||||
$user = Auth::user();
|
||||
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
|
||||
$discount = PartnerDiscount::getDiscount();
|
||||
|
||||
// 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 * $discount / 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 . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""),
|
||||
"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', 'Controlpanel.GG'),
|
||||
'shipping_preference' => 'NO_SHIPPING'
|
||||
]
|
||||
|
||||
|
||||
];
|
||||
|
||||
try {
|
||||
// Call API with your client and get a response for your call
|
||||
$response = getPayPalClient()->execute($request);
|
||||
|
||||
// check for any errors in the response
|
||||
if ($response->statusCode != 201) {
|
||||
throw new \Exception($response->statusCode);
|
||||
}
|
||||
|
||||
// make sure the link is not empty
|
||||
if (empty($response->result->links[1]->href)) {
|
||||
throw new \Exception('No redirect link found');
|
||||
}
|
||||
|
||||
Redirect::away($response->result->links[1]->href)->send();
|
||||
return;
|
||||
} catch (HttpException $ex) {
|
||||
Log::error('PayPal Payment: ' . $ex->getMessage());
|
||||
$payment->delete();
|
||||
|
||||
Redirect::route('store.index')->with('error', __('Payment failed'))->send();
|
||||
return;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param Request $laravelRequest
|
||||
*/
|
||||
function PaypalSuccess(Request $laravelRequest)
|
||||
{
|
||||
$settings = new PayPalSettings();
|
||||
|
||||
$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()
|
||||
{
|
||||
$settings = new PayPalSettings();
|
||||
|
||||
$environment = env('APP_ENV') == 'local'
|
||||
? new SandboxEnvironment(getPaypalClientId(), getPaypalClientSecret())
|
||||
: new ProductionEnvironment(getPaypalClientId(), getPaypalClientSecret());
|
||||
return new PayPalHttpClient($environment);
|
||||
}
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function getPaypalClientId()
|
||||
{
|
||||
$settings = new PayPalSettings();
|
||||
return env('APP_ENV') == 'local' ? $settings->sandbox_client_id : $settings->client_id;
|
||||
}
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function getPaypalClientSecret()
|
||||
{
|
||||
$settings = new PayPalSettings();
|
||||
return env('APP_ENV') == 'local' ? $settings->sandbox_client_secret : $settings->client_secret;
|
||||
}
|
|
@ -1,18 +1,17 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
include_once(__DIR__ . '/index.php');
|
||||
use App\Extensions\PaymentGateways\PayPal\PayPalExtension;
|
||||
|
||||
Route::middleware(['web', 'auth'])->group(function () {
|
||||
Route::get('payment/PayPalPay/{shopProduct}', function () {
|
||||
PaypalPay(request());
|
||||
PayPalExtension::PaypalPay(request());
|
||||
})->name('payment.PayPalPay');
|
||||
|
||||
Route::get(
|
||||
'payment/PayPalSuccess',
|
||||
function () {
|
||||
PaypalSuccess(request());
|
||||
PayPalExtension::PaypalSuccess(request());
|
||||
}
|
||||
)->name('payment.PayPalSuccess');
|
||||
});
|
||||
|
|
390
app/Extensions/PaymentGateways/Stripe/StripeExtension.php
Normal file
390
app/Extensions/PaymentGateways/Stripe/StripeExtension.php
Normal file
|
@ -0,0 +1,390 @@
|
|||
<?php
|
||||
|
||||
namespace App\Extensions\PaymentGateways\Stripe;
|
||||
|
||||
use App\Helpers\AbstractExtension;
|
||||
use App\Events\PaymentEvent;
|
||||
use App\Events\UserUpdateCreditsEvent;
|
||||
use App\Extensions\PaymentGateways\Stripe\StripeSettings;
|
||||
use App\Models\PartnerDiscount;
|
||||
use App\Models\Payment;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Models\User;
|
||||
use App\Notifications\ConfirmPaymentNotification;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Stripe\Exception\SignatureVerificationException;
|
||||
use Stripe\Stripe;
|
||||
use Stripe\StripeClient;
|
||||
|
||||
class StripeExtension extends AbstractExtension
|
||||
{
|
||||
public static function getConfig(): array
|
||||
{
|
||||
return [
|
||||
"name" => "Stripe",
|
||||
"RoutesIgnoreCsrf" => [
|
||||
"payment/StripeWebhooks",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param ShopProduct $shopProduct
|
||||
*/
|
||||
public static function StripePay(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
|
||||
|
||||
// check if the price is valid for stripe
|
||||
if (!self::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;
|
||||
}
|
||||
|
||||
$discount = PartnerDiscount::getDiscount();
|
||||
|
||||
|
||||
// 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 * $discount / 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 = self::getStripeClient();
|
||||
$request = $stripeClient->checkout->sessions->create([
|
||||
'line_items' => [
|
||||
[
|
||||
'price_data' => [
|
||||
'currency' => $shopProduct->currency_code,
|
||||
'product_data' => [
|
||||
'name' => $shopProduct->display . ($discount ? (' (' . __('Discount') . ' ' . $discount . '%)') : ''),
|
||||
'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',
|
||||
'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id]) . '&session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => route('payment.Cancel'),
|
||||
'payment_intent_data' => [
|
||||
'metadata' => [
|
||||
'payment_id' => $payment->id,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Redirect::to($request->url)->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*/
|
||||
public static 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);
|
||||
|
||||
|
||||
Redirect::route('home')->with('success', 'Please wait for success')->send();
|
||||
|
||||
$stripeClient = self::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
|
||||
*/
|
||||
public static function handleStripePaymentSuccessHook($paymentIntent)
|
||||
{
|
||||
try {
|
||||
$payment = Payment::where('id', $paymentIntent->metadata->payment_id)->with('user')->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([
|
||||
'payment_id' => $payment->payment_id ?? $paymentIntent->id,
|
||||
'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
|
||||
*/
|
||||
public static function StripeWebhooks(Request $request)
|
||||
{
|
||||
Stripe::setApiKey(self::getStripeSecret());
|
||||
|
||||
try {
|
||||
$payload = @file_get_contents('php://input');
|
||||
$sig_header = $request->header('Stripe-Signature');
|
||||
$event = null;
|
||||
$event = \Stripe\Webhook::constructEvent(
|
||||
$payload,
|
||||
$sig_header,
|
||||
self::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
|
||||
self::handleStripePaymentSuccessHook($paymentIntent);
|
||||
break;
|
||||
default:
|
||||
echo 'Received unknown event type ' . $event->type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Stripe\StripeClient
|
||||
*/
|
||||
public static function getStripeClient()
|
||||
{
|
||||
return new StripeClient(self::getStripeSecret());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getStripeSecret()
|
||||
{
|
||||
$settings = new StripeSettings();
|
||||
|
||||
return env('APP_ENV') == 'local'
|
||||
? $settings->test_secret_key
|
||||
: $settings->secret_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getStripeEndpointSecret()
|
||||
{
|
||||
$settings = new StripeSettings();
|
||||
return env('APP_ENV') == 'local'
|
||||
? $settings->test_endpoint_secret
|
||||
: $settings->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
|
||||
*/
|
||||
public static 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];
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Extensions\PaymentGateways\Stripe;
|
||||
|
||||
function getConfig()
|
||||
{
|
||||
return [
|
||||
"name" => "Stripe",
|
||||
"RoutesIgnoreCsrf" => [
|
||||
"payment/StripeWebhooks",
|
||||
],
|
||||
];
|
||||
}
|
|
@ -1,376 +0,0 @@
|
|||
<?php
|
||||
|
||||
use App\Events\PaymentEvent;
|
||||
use App\Events\UserUpdateCreditsEvent;
|
||||
use App\Extensions\PaymentGateways\Stripe\StripeSettings;
|
||||
use App\Models\PartnerDiscount;
|
||||
use App\Models\Payment;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Models\User;
|
||||
use App\Notifications\ConfirmPaymentNotification;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
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;
|
||||
}
|
||||
|
||||
$discount = PartnerDiscount::getDiscount();
|
||||
|
||||
|
||||
// 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 * $discount / 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 . ($discount ? (' (' . __('Discount') . ' ' . $discount . '%)') : ''),
|
||||
'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',
|
||||
'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id]) . '&session_id={CHECKOUT_SESSION_ID}',
|
||||
'cancel_url' => route('payment.Cancel'),
|
||||
'payment_intent_data' => [
|
||||
'metadata' => [
|
||||
'payment_id' => $payment->id,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
Redirect::route('home')->with('success', 'Please wait for success')->send();
|
||||
|
||||
$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 {
|
||||
$payment = Payment::where('id', $paymentIntent->metadata->payment_id)->with('user')->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([
|
||||
'payment_id' => $payment->payment_id ?? $paymentIntent->id,
|
||||
'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()
|
||||
{
|
||||
$settings = new StripeSettings();
|
||||
|
||||
return env('APP_ENV') == 'local'
|
||||
? $settings->test_secret_key
|
||||
: $settings->secret_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
function getStripeEndpointSecret()
|
||||
{
|
||||
$settings = new StripeSettings();
|
||||
return env('APP_ENV') == 'local'
|
||||
? $settings->test_endpoint_secret
|
||||
: $settings->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];
|
||||
}
|
|
@ -1,17 +1,17 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Extensions\PaymentGateways\Stripe\StripeExtension;
|
||||
|
||||
include_once(__DIR__ . '/index.php');
|
||||
Route::middleware(['web', 'auth'])->group(function () {
|
||||
Route::get('payment/StripePay/{shopProduct}', function () {
|
||||
StripePay(request());
|
||||
StripeExtension::StripePay(request());
|
||||
})->name('payment.StripePay');
|
||||
|
||||
Route::get(
|
||||
'payment/StripeSuccess',
|
||||
function () {
|
||||
StripeSuccess(request());
|
||||
StripeExtension::StripeSuccess(request());
|
||||
}
|
||||
)->name('payment.StripeSuccess');
|
||||
});
|
||||
|
@ -19,5 +19,5 @@ Route::middleware(['web', 'auth'])->group(function () {
|
|||
|
||||
// Stripe WebhookRoute -> validation in Route Handler
|
||||
Route::post('payment/StripeWebhooks', function () {
|
||||
StripeWebhooks(request());
|
||||
StripeExtension::StripeWebhooks(request());
|
||||
})->name('payment.StripeWebhooks');
|
||||
|
|
9
app/Helpers/AbstractExtension.php
Normal file
9
app/Helpers/AbstractExtension.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
// create a abstract class for the extension that will contain all the methods that will be used in the extension
|
||||
abstract class AbstractExtension
|
||||
{
|
||||
abstract public static function getConfig(): array;
|
||||
}
|
|
@ -7,63 +7,9 @@ namespace App\Helpers;
|
|||
*/
|
||||
class ExtensionHelper
|
||||
{
|
||||
/**
|
||||
* Get a config of an extension by its name
|
||||
* @param string $extensionName
|
||||
* @param string $configname
|
||||
*/
|
||||
public static function getExtensionConfig(string $extensionName, string $configname)
|
||||
{
|
||||
$extensions = ExtensionHelper::getAllExtensions();
|
||||
|
||||
// call the getConfig function of the config file of the extension like that
|
||||
// call_user_func("App\\Extensions\\PaymentGateways\\Stripe" . "\\getConfig");
|
||||
foreach ($extensions as $extension) {
|
||||
if (!(basename($extension) == $extensionName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$configFile = $extension . '/config.php';
|
||||
if (file_exists($configFile)) {
|
||||
include_once $configFile;
|
||||
$config = call_user_func('App\\Extensions\\' . basename(dirname($extension)) . '\\' . basename($extension) . "\\getConfig");
|
||||
}
|
||||
|
||||
|
||||
if (isset($config[$configname])) {
|
||||
return $config[$configname];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getAllCsrfIgnoredRoutes()
|
||||
{
|
||||
$extensions = ExtensionHelper::getAllExtensions();
|
||||
|
||||
$routes = [];
|
||||
foreach ($extensions as $extension) {
|
||||
$configFile = $extension . '/config.php';
|
||||
if (file_exists($configFile)) {
|
||||
include_once $configFile;
|
||||
$config = call_user_func('App\\Extensions\\' . basename(dirname($extension)) . '\\' . basename($extension) . "\\getConfig");
|
||||
}
|
||||
|
||||
if (isset($config['RoutesIgnoreCsrf'])) {
|
||||
$routes = array_merge($routes, $config['RoutesIgnoreCsrf']);
|
||||
}
|
||||
|
||||
// map over the routes and add the extension name as prefix
|
||||
$result = array_map(fn ($item) => "extensions/{$item}", $routes);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all extensions
|
||||
* @return array of all extension paths look like: app/Extensions/ExtensionNamespace/ExtensionName
|
||||
* @return array array of all extensions e.g. ["App\Extensions\PayPal", "App\Extensions\Stripe"]
|
||||
*/
|
||||
public static function getAllExtensions()
|
||||
{
|
||||
|
@ -72,24 +18,144 @@ class ExtensionHelper
|
|||
foreach ($extensionNamespaces as $extensionNamespace) {
|
||||
$extensions = array_merge($extensions, glob($extensionNamespace . '/*', GLOB_ONLYDIR));
|
||||
}
|
||||
// remove base path from every extension but keep app/Extensions/...
|
||||
$extensions = array_map(fn ($item) => str_replace('/', '\\', str_replace(app_path() . '/', 'App/', $item)), $extensions);
|
||||
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all extensions by namespace
|
||||
* @param string $namespace case sensitive namespace of the extension e.g. PaymentGateways
|
||||
* @return array array of all extensions e.g. ["App\Extensions\PayPal", "App\Extensions\Stripe"]
|
||||
*/
|
||||
public static function getAllExtensionsByNamespace(string $namespace)
|
||||
{
|
||||
$extensions = glob(app_path() . '/Extensions/' . $namespace . '/*', GLOB_ONLYDIR);
|
||||
// remove base path from every extension but keep app/Extensions/...
|
||||
$extensions = array_map(fn ($item) => str_replace('/', '\\', str_replace(app_path() . '/', 'App/', $item)), $extensions);
|
||||
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an extension by its name
|
||||
* @param string $extensionName case sensitive name of the extension e.g. PayPal
|
||||
* @return string|null the path of the extension e.g. App\Extensions\PayPal
|
||||
*/
|
||||
public static function getExtension(string $extensionName)
|
||||
{
|
||||
$extensions = self::getAllExtensions();
|
||||
// filter the extensions by the extension name
|
||||
$extensions = array_filter($extensions, fn ($item) => basename($item) == $extensionName);
|
||||
|
||||
// return the only extension
|
||||
return array_shift($extensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all extension classes
|
||||
* @return array array of all extension classes e.g. ["App\Extensions\PayPal\PayPalExtension", "App\Extensions\Stripe\StripeExtension"]
|
||||
*/
|
||||
public static function getAllExtensionClasses()
|
||||
{
|
||||
$extensions = self::getAllExtensions();
|
||||
// add the ExtensionClass to the end of the namespace
|
||||
$extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions);
|
||||
// filter out non existing extension classes
|
||||
$extensions = array_filter($extensions, fn ($item) => class_exists($item));
|
||||
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all extension classes by namespace
|
||||
* @param string $namespace case sensitive namespace of the extension e.g. PaymentGateways
|
||||
* @return array array of all extension classes e.g. ["App\Extensions\PayPal\PayPalExtension", "App\Extensions\Stripe\StripeExtension"]
|
||||
*/
|
||||
public static function getAllExtensionClassesByNamespace(string $namespace)
|
||||
{
|
||||
$extensions = self::getAllExtensionsByNamespace($namespace);
|
||||
// add the ExtensionClass to the end of the namespace
|
||||
$extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions);
|
||||
// filter out non existing extension classes
|
||||
$extensions = array_filter($extensions, fn ($item) => class_exists($item));
|
||||
|
||||
return $extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class of an extension by its name
|
||||
* @param string $extensionName case sensitive name of the extension e.g. PayPal
|
||||
* @return string|null the class name of the extension e.g. App\Extensions\PayPal\PayPalExtension
|
||||
*/
|
||||
public static function getExtensionClass(string $extensionName)
|
||||
{
|
||||
$extensions = self::getAllExtensions();
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
if (!(basename($extension) == $extensionName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$extensionClass = $extension . '\\' . $extensionName . 'Extension';
|
||||
return $extensionClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a config of an extension by its name
|
||||
* @param string $extensionName
|
||||
* @param string $configname
|
||||
*/
|
||||
public static function getExtensionConfig(string $extensionName, string $configname)
|
||||
{
|
||||
|
||||
$extension = self::getExtensionClass($extensionName);
|
||||
|
||||
$config = $extension::getConfig();
|
||||
|
||||
|
||||
|
||||
if (isset($config[$configname])) {
|
||||
return $config[$configname];
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getAllCsrfIgnoredRoutes()
|
||||
{
|
||||
$extensions = self::getAllExtensionClasses();
|
||||
|
||||
$routes = [];
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
$config = $extension::getConfig();
|
||||
|
||||
if (isset($config['RoutesIgnoreCsrf'])) {
|
||||
$routes = array_merge($routes, $config['RoutesIgnoreCsrf']);
|
||||
}
|
||||
}
|
||||
// map over the routes and add the extension name as prefix
|
||||
$result = array_map(fn ($item) => "extensions/{$item}", $routes);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary of getAllExtensionMigrations
|
||||
* @return array of all migration paths look like: app/Extensions/ExtensionNamespace/ExtensionName/migrations/
|
||||
*/
|
||||
public static function getAllExtensionMigrations()
|
||||
{
|
||||
$extensions = ExtensionHelper::getAllExtensions();
|
||||
$extensions = self::getAllExtensions();
|
||||
// Transform the extensions to a path
|
||||
$extensions = array_map(fn ($item) => self::extensionNameToPath($item), $extensions);
|
||||
|
||||
// get all migration directories of the extensions and return them as array
|
||||
$migrations = [];
|
||||
|
@ -109,21 +175,15 @@ class ExtensionHelper
|
|||
*/
|
||||
public static function getAllExtensionSettingsClasses()
|
||||
{
|
||||
$extensions = ExtensionHelper::getAllExtensions();
|
||||
$extensions = self::getAllExtensions();
|
||||
|
||||
$settings = [];
|
||||
foreach ($extensions as $extension) {
|
||||
|
||||
$extensionName = basename($extension);
|
||||
$settingFile = $extension . '/' . $extensionName . 'Settings.php';
|
||||
if (file_exists($settingFile)) {
|
||||
// remove the base path from the setting file path to get the namespace
|
||||
|
||||
$settingFile = str_replace(app_path() . '/', '', $settingFile);
|
||||
$settingFile = str_replace('.php', '', $settingFile);
|
||||
$settingFile = str_replace('/', '\\', $settingFile);
|
||||
$settingFile = 'App\\' . $settingFile;
|
||||
$settings[] = $settingFile;
|
||||
$settingsClass = $extension . '\\' . $extensionName . 'Settings';
|
||||
if (class_exists($settingsClass)) {
|
||||
$settings[] = $settingsClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,25 +192,21 @@ class ExtensionHelper
|
|||
|
||||
public static function getExtensionSettings(string $extensionName)
|
||||
{
|
||||
$extensions = ExtensionHelper::getAllExtensions();
|
||||
$extension = self::getExtension($extensionName);
|
||||
$settingClass = $extension . '\\' . $extensionName . 'Settings';
|
||||
|
||||
// find the setting file of the extension and return an instance of it
|
||||
foreach ($extensions as $extension) {
|
||||
if (!(basename($extension) == $extensionName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$extensionName = basename($extension);
|
||||
$settingFile = $extension . '/' . $extensionName . 'Settings.php';
|
||||
if (file_exists($settingFile)) {
|
||||
// remove the base path from the setting file path to get the namespace
|
||||
|
||||
$settingFile = str_replace(app_path() . '/', '', $settingFile);
|
||||
$settingFile = str_replace('.php', '', $settingFile);
|
||||
$settingFile = str_replace('/', '\\', $settingFile);
|
||||
$settingFile = 'App\\' . $settingFile;
|
||||
return new $settingFile();
|
||||
}
|
||||
if (class_exists($settingClass)) {
|
||||
return new $settingClass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a extension name to a path
|
||||
* @param string $extensionName e.g. App\Extensions\PaymentGateways\PayPal
|
||||
* @return string e.g. C:\xampp\htdocs\laravel\app/Extensions/PaymentGateways/PayPal
|
||||
*/
|
||||
private static function extensionNameToPath(string $extensionName)
|
||||
{
|
||||
return app_path() . '/' . str_replace('\\', '/', str_replace('App\\', '', $extensionName));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ class SettingsController extends Controller
|
|||
$settingsClass = new $settings_class();
|
||||
|
||||
foreach ($settingsClass->toArray() as $key => $value) {
|
||||
switch (gettype($value)) {
|
||||
switch (gettype($request->input($key))) {
|
||||
case 'boolean':
|
||||
$settingsClass->$key = $request->has($key);
|
||||
break;
|
||||
|
@ -125,6 +125,9 @@ class SettingsController extends Controller
|
|||
case 'double':
|
||||
$settingsClass->$key = $request->input($key) ?? 0.0;
|
||||
break;
|
||||
case 'NULL':
|
||||
$settingsClass->$key = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ class CreateInvoice
|
|||
use Invoiceable;
|
||||
|
||||
private $invoice_enabled;
|
||||
private $invoice_settings;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
|
@ -20,6 +21,7 @@ class CreateInvoice
|
|||
public function __construct(InvoiceSettings $invoice_settings)
|
||||
{
|
||||
$this->invoice_enabled = $invoice_settings->enabled;
|
||||
$this->invoice_settings = $invoice_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,7 +34,7 @@ class CreateInvoice
|
|||
{
|
||||
if ($this->invoice_enabled) {
|
||||
// create invoice using the trait
|
||||
$this->createInvoice($event->payment, $event->shopProduct);
|
||||
$this->createInvoice($event->payment, $event->shopProduct, $this->invoice_settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ use Spatie\LaravelSettings\Settings;
|
|||
|
||||
class PterodactylSettings extends Settings
|
||||
{
|
||||
public string $admin_token;
|
||||
public ?string $admin_token;
|
||||
|
||||
public string $user_token;
|
||||
public ?string $user_token;
|
||||
|
||||
public string $panel_url;
|
||||
public ?string $panel_url;
|
||||
|
||||
public int $per_page_limit;
|
||||
|
||||
|
@ -44,9 +44,9 @@ class PterodactylSettings extends Settings
|
|||
public static function getValidations()
|
||||
{
|
||||
return [
|
||||
'panel_url' => 'required|string|url',
|
||||
'admin_token' => 'required|string',
|
||||
'user_token' => 'required|string',
|
||||
'panel_url' => 'nullable|string|url',
|
||||
'admin_token' => 'nullable|string',
|
||||
'user_token' => 'nullable|string',
|
||||
'per_page_limit' => 'required|integer|min:1|max:10000',
|
||||
];
|
||||
}
|
||||
|
|
|
@ -11,37 +11,38 @@
|
|||
"php": "^8.1",
|
||||
"ext-intl": "*",
|
||||
"biscolab/laravel-recaptcha": "^5.4",
|
||||
"doctrine/dbal": "^3.1",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"hidehalo/nanoid-php": "^1.1",
|
||||
"doctrine/dbal": "^3.5.3",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"hidehalo/nanoid-php": "^1.1.12",
|
||||
"kkomelin/laravel-translatable-string-exporter": "^1.18",
|
||||
"laravel/framework": "^9.46",
|
||||
"laravel/tinker": "^2.7",
|
||||
"laravel/ui": "^3.3",
|
||||
"laraveldaily/laravel-invoices": "^3.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"paypal/paypal-checkout-sdk": "^1.0",
|
||||
"paypal/rest-api-sdk-php": "^1.14",
|
||||
"qirolab/laravel-themer": "^2.0",
|
||||
"socialiteproviders/discord": "^4.1",
|
||||
"spatie/laravel-activitylog": "^4.4",
|
||||
"spatie/laravel-query-builder": "^5.0",
|
||||
"laravel/framework": "^9.50.2",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/ui": "^3.4.6",
|
||||
"laraveldaily/laravel-invoices": "^3.0.2",
|
||||
"league/flysystem-aws-s3-v3": "^3.12.2",
|
||||
"paypal/paypal-checkout-sdk": "^1.0.2",
|
||||
"paypal/rest-api-sdk-php": "^1.14.0",
|
||||
"predis/predis": "*",
|
||||
"qirolab/laravel-themer": "^2.0.2",
|
||||
"socialiteproviders/discord": "^4.1.2",
|
||||
"spatie/laravel-activitylog": "^4.7.3",
|
||||
"spatie/laravel-query-builder": "^5.1.2",
|
||||
"spatie/laravel-settings": "^2.7",
|
||||
"spatie/laravel-validation-rules": "^3.2",
|
||||
"stripe/stripe-php": "^7.107",
|
||||
"symfony/http-client": "^6.2",
|
||||
"symfony/intl": "^6.0",
|
||||
"symfony/mailgun-mailer": "^6.2",
|
||||
"yajra/laravel-datatables-oracle": "^9.19"
|
||||
"spatie/laravel-validation-rules": "^3.2.2",
|
||||
"stripe/stripe-php": "^7.128",
|
||||
"symfony/http-client": "^6.2.6",
|
||||
"symfony/intl": "^6.2.5",
|
||||
"symfony/mailgun-mailer": "^6.2.5",
|
||||
"yajra/laravel-datatables-oracle": "^9.21.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.6",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/sail": "^1.15",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^6.3",
|
||||
"phpunit/phpunit": "^9.5.10",
|
||||
"spatie/laravel-ignition": "^1.4"
|
||||
"barryvdh/laravel-debugbar": "^3.7",
|
||||
"fakerphp/faker": "^1.21",
|
||||
"laravel/sail": "^1.19",
|
||||
"mockery/mockery": "^1.5.1",
|
||||
"nunomaduro/collision": "^6.4",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"spatie/laravel-ignition": "^1.6"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
|
|
1028
composer.lock
generated
1028
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -61,5 +61,19 @@ services:
|
|||
networks:
|
||||
- laravel
|
||||
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
command: redis-server --requirepass sOmE_sEcUrE_pAsS
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- $PWD/redis-data:/var/lib/redis
|
||||
- $PWD/redis.conf:/usr/local/etc/redis/redis.conf
|
||||
environment:
|
||||
- REDIS_REPLICATION_MODE=master
|
||||
networks:
|
||||
- laravel
|
||||
|
||||
|
||||
volumes:
|
||||
mysql:
|
||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "controlpanel",
|
||||
"name": "controllpanelgg",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"development": "vite",
|
||||
"production": "vite build"
|
||||
"production": "vite build",
|
||||
"dev:default": "vite --config themes/default/vite.config.js",
|
||||
"build:default": "vite build --config themes/default/vite.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^0.25",
|
||||
|
|
1
public/build/assets/app-0fd5dfcd.js
vendored
Normal file
1
public/build/assets/app-0fd5dfcd.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
require("./adminlte");require("./slim.kickstart.min");require("./bootstrap");
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,12 @@
|
|||
{
|
||||
"themes/default/js/app.js": {
|
||||
"file": "assets/app-0fd5dfcd.js",
|
||||
"isEntry": true,
|
||||
"src": "themes/default/js/app.js"
|
||||
},
|
||||
"themes/default/sass/app.scss": {
|
||||
"file": "assets/app-bac23d88.css",
|
||||
"src": "themes/default/sass/app.scss",
|
||||
"isEntry": true
|
||||
"file": "assets/app-26e8174e.css",
|
||||
"isEntry": true,
|
||||
"src": "themes/default/sass/app.scss"
|
||||
}
|
||||
}
|
BIN
public/images/Extensions/PaymentGateways/mollie_logo.png
Normal file
BIN
public/images/Extensions/PaymentGateways/mollie_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 36 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 52 KiB |
2
storage/framework/cache/data/.gitignore
vendored
2
storage/framework/cache/data/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
19
themes/default/sass/app.scss
vendored
19
themes/default/sass/app.scss
vendored
|
@ -5,3 +5,22 @@
|
|||
@import "../css/stylesheet.css";
|
||||
@import "../css/adminlte.min.css";
|
||||
@import "../css/slim.min.css";
|
||||
|
||||
.checkout-gateways {
|
||||
// make the radio button clickable
|
||||
cursor: pointer;
|
||||
|
||||
// add some space between all gateway divs bit the last one
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.checkout-gateway-label {
|
||||
// make the label clickable
|
||||
cursor: pointer;
|
||||
// center the label
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
@ -24,18 +24,7 @@
|
|||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
@if ($isPaymentSetup == false)
|
||||
<div class="callout callout-danger">
|
||||
<h4>{{ __('No payment method is configured.') }}</h4>
|
||||
<p>{{ __('To configure the payment methods, head to the settings-page and add the required options for your prefered payment method.') }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</noscript>
|
||||
<script src="{{ asset('js/app.js') }}"></script>
|
||||
<!-- tinymce -->
|
||||
<script src={{ asset('plugins/tinymce/js/tinymce/tinymce.min.js') }}></script>
|
||||
<script src="{{ asset('plugins/tinymce/js/tinymce/tinymce.min.js') }}"></script>
|
||||
@vite('themes/default/sass/app.scss')
|
||||
</head>
|
||||
|
||||
|
|
|
@ -24,133 +24,155 @@
|
|||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
|
||||
<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>
|
||||
<form x-data="{ payment_method: '', clicked: false }" action="{{ route('payment.pay') }}" method="POST">
|
||||
@csrf
|
||||
@method('post')
|
||||
<div class="row d-flex justify-content-center flex-wrap">
|
||||
@if (!$productIsFree)
|
||||
<div class="col-xl-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">
|
||||
<i class="fas fa-money-check-alt"></i>
|
||||
Payment Methods
|
||||
</h4>
|
||||
</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>
|
||||
<div class="card-body">
|
||||
<input type="hidden" name="product_id" value="{{ $product->id }}">
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
<div class="row">
|
||||
<!-- accepted payments column -->
|
||||
<div class="col-6">
|
||||
@if (!$productIsFree)
|
||||
<p class="lead">{{ __('Payment Methods') }}:</p>
|
||||
|
||||
<div class="d-flex flex-wrap flex-direction-row">
|
||||
|
||||
<input type="hidden" name="payment_method" :value="payment_method"
|
||||
x-model="payment_method">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
@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 class="row checkout-gateways">
|
||||
<div class="col-12 d-flex justify-content-between">
|
||||
<label class="form-check-label h4 checkout-gateway-label"
|
||||
for="{{ $gateway->name }}">
|
||||
<span class="mr-3">{{ $gateway->name }}</span>
|
||||
</label>
|
||||
<button class="btn btn-primary rounded" type="button"
|
||||
name="payment-method" id="{{ $gateway->name }}"
|
||||
value="{{ $gateway->name }}"
|
||||
:class="payment_method === '{{ $gateway->name }}' ?
|
||||
'active' : ''"
|
||||
@click="payment_method = '{{ $gateway->name }}'; clicked = true;"
|
||||
x-text="payment_method == '{{ $gateway->name }}' ? 'Selected' : 'Select'">Select</button>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@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)
|
||||
<tr>
|
||||
<th>{{ __('Discount') }} ({{ $discountpercent }}%):</th>
|
||||
<td>{{ $product->formatToCurrency($discountvalue) }}</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>
|
||||
</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) && {{ !$productIsFree }}"
|
||||
:class="(!payment_method || clicked) && {{ !$productIsFree }} ? 'disabled' : ''"
|
||||
class="btn btn-success float-right"><i class="far fa-credit-card mr-2"
|
||||
@click="clicked = true"></i>
|
||||
@if ($productIsFree)
|
||||
{{ __('Get for free') }}
|
||||
@else
|
||||
{{ __('Submit Payment') }}
|
||||
@endif
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- /.invoice -->
|
||||
</div><!-- /.col -->
|
||||
</div><!-- /.row -->
|
||||
@endif
|
||||
<div class="col-xl-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0 text-center">
|
||||
<i class="fas fa-shopping-cart"></i>
|
||||
Checkout details
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item">
|
||||
<div>
|
||||
<h5 class="my-0">{{ __('Product details') }}</h5>
|
||||
</div>
|
||||
<ul class="pl-0">
|
||||
<li class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Type') }}</span>
|
||||
<span
|
||||
class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $credits_display_name : $product->type }}</span>
|
||||
</li>
|
||||
<li class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Amount') }}</span>
|
||||
<span class="text-muted d-inline-block">{{ $product->quantity }}</span>
|
||||
</li>
|
||||
<li class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Total Amount') }}</span>
|
||||
<span class="text-muted d-inline-block">{{ $product->quantity }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between lh-condensed">
|
||||
<div>
|
||||
<h6 class="my-0">{{ __('Description') }}</h6>
|
||||
<span class="text-muted">
|
||||
{{ $product->description }}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div>
|
||||
<h5 class="my-0">{{ __('Pricing') }}</h5>
|
||||
</div>
|
||||
|
||||
<ul class="pl-0">
|
||||
<li class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Subtotal') }}</span>
|
||||
<span class="text-muted d-inline-block">
|
||||
{{ $product->formatToCurrency($discountedprice) }}</span>
|
||||
</li>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Tax') }}
|
||||
@if ($taxpercent > 0)
|
||||
({{ $taxpercent }}%):
|
||||
@endif
|
||||
</span>
|
||||
<span class="text-muted d-inline-block">
|
||||
+ {{ $product->formatToCurrency($taxvalue) }}</span>
|
||||
</div>
|
||||
@if ($discountpercent && $discountvalue)
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Discount') }}
|
||||
({{ $discountpercent }}%):</span>
|
||||
<span
|
||||
class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
<hr class="text-white border-secondary">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Total') }}</span>
|
||||
<span
|
||||
class="text-muted d-inline-block">{{ $product->formatToCurrency($total) }}</span>
|
||||
</div>
|
||||
<template x-if="payment_method">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Pay with') }}</span>
|
||||
<span class="text-muted d-inline-block" x-text="payment_method"></span>
|
||||
</div>
|
||||
</template>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button :disabled="(!payment_method || !clicked) && {{ !$productIsFree }}"
|
||||
:class="(!payment_method || !clicked) && {{ !$productIsFree }} ? 'disabled' : ''"
|
||||
class="btn btn-success float-right w-100">
|
||||
<i class="far fa-credit-card mr-2" @click="clicked == true"></i>
|
||||
@if ($productIsFree)
|
||||
{{ __('Get for free') }}
|
||||
@else
|
||||
{{ __('Submit Payment') }}
|
||||
@endif
|
||||
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
@endsection
|
||||
|
|
16
themes/default/vite.config.js
vendored
16
themes/default/vite.config.js
vendored
|
@ -2,19 +2,12 @@ import { defineConfig } from "vite";
|
|||
import laravel from "laravel-vite-plugin";
|
||||
import path from "path";
|
||||
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
laravel({
|
||||
input: [
|
||||
"themes/default/sass/app.scss",
|
||||
"themes/default/js/app.js"
|
||||
],
|
||||
input: ["themes/default/sass/app.scss", "themes/default/js/app.js"],
|
||||
buildDirectory: "build",
|
||||
}),
|
||||
|
||||
|
||||
{
|
||||
name: "blade",
|
||||
handleHotUpdate({ file, server }) {
|
||||
|
@ -29,9 +22,8 @@ export default defineConfig({
|
|||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/themes/default/js',
|
||||
'~bootstrap': path.resolve('node_modules/bootstrap'),
|
||||
}
|
||||
"@": "/themes/default/js",
|
||||
"~bootstrap": path.resolve("node_modules/bootstrap"),
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue