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
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Extensions\PaymentGateways\PayPal\PayPalExtension;
|
||||||
include_once(__DIR__ . '/index.php');
|
|
||||||
|
|
||||||
Route::middleware(['web', 'auth'])->group(function () {
|
Route::middleware(['web', 'auth'])->group(function () {
|
||||||
Route::get('payment/PayPalPay/{shopProduct}', function () {
|
Route::get('payment/PayPalPay/{shopProduct}', function () {
|
||||||
PaypalPay(request());
|
PayPalExtension::PaypalPay(request());
|
||||||
})->name('payment.PayPalPay');
|
})->name('payment.PayPalPay');
|
||||||
|
|
||||||
Route::get(
|
Route::get(
|
||||||
'payment/PayPalSuccess',
|
'payment/PayPalSuccess',
|
||||||
function () {
|
function () {
|
||||||
PaypalSuccess(request());
|
PayPalExtension::PaypalSuccess(request());
|
||||||
}
|
}
|
||||||
)->name('payment.PayPalSuccess');
|
)->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
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use App\Extensions\PaymentGateways\Stripe\StripeExtension;
|
||||||
|
|
||||||
include_once(__DIR__ . '/index.php');
|
|
||||||
Route::middleware(['web', 'auth'])->group(function () {
|
Route::middleware(['web', 'auth'])->group(function () {
|
||||||
Route::get('payment/StripePay/{shopProduct}', function () {
|
Route::get('payment/StripePay/{shopProduct}', function () {
|
||||||
StripePay(request());
|
StripeExtension::StripePay(request());
|
||||||
})->name('payment.StripePay');
|
})->name('payment.StripePay');
|
||||||
|
|
||||||
Route::get(
|
Route::get(
|
||||||
'payment/StripeSuccess',
|
'payment/StripeSuccess',
|
||||||
function () {
|
function () {
|
||||||
StripeSuccess(request());
|
StripeExtension::StripeSuccess(request());
|
||||||
}
|
}
|
||||||
)->name('payment.StripeSuccess');
|
)->name('payment.StripeSuccess');
|
||||||
});
|
});
|
||||||
|
@ -19,5 +19,5 @@ Route::middleware(['web', 'auth'])->group(function () {
|
||||||
|
|
||||||
// Stripe WebhookRoute -> validation in Route Handler
|
// Stripe WebhookRoute -> validation in Route Handler
|
||||||
Route::post('payment/StripeWebhooks', function () {
|
Route::post('payment/StripeWebhooks', function () {
|
||||||
StripeWebhooks(request());
|
StripeExtension::StripeWebhooks(request());
|
||||||
})->name('payment.StripeWebhooks');
|
})->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
|
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
|
* 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()
|
public static function getAllExtensions()
|
||||||
{
|
{
|
||||||
|
@ -72,24 +18,144 @@ class ExtensionHelper
|
||||||
foreach ($extensionNamespaces as $extensionNamespace) {
|
foreach ($extensionNamespaces as $extensionNamespace) {
|
||||||
$extensions = array_merge($extensions, glob($extensionNamespace . '/*', GLOB_ONLYDIR));
|
$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;
|
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)
|
public static function getAllExtensionsByNamespace(string $namespace)
|
||||||
{
|
{
|
||||||
$extensions = glob(app_path() . '/Extensions/' . $namespace . '/*', GLOB_ONLYDIR);
|
$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;
|
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
|
* Summary of getAllExtensionMigrations
|
||||||
* @return array of all migration paths look like: app/Extensions/ExtensionNamespace/ExtensionName/migrations/
|
* @return array of all migration paths look like: app/Extensions/ExtensionNamespace/ExtensionName/migrations/
|
||||||
*/
|
*/
|
||||||
public static function getAllExtensionMigrations()
|
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
|
// get all migration directories of the extensions and return them as array
|
||||||
$migrations = [];
|
$migrations = [];
|
||||||
|
@ -109,21 +175,15 @@ class ExtensionHelper
|
||||||
*/
|
*/
|
||||||
public static function getAllExtensionSettingsClasses()
|
public static function getAllExtensionSettingsClasses()
|
||||||
{
|
{
|
||||||
$extensions = ExtensionHelper::getAllExtensions();
|
$extensions = self::getAllExtensions();
|
||||||
|
|
||||||
$settings = [];
|
$settings = [];
|
||||||
foreach ($extensions as $extension) {
|
foreach ($extensions as $extension) {
|
||||||
|
|
||||||
$extensionName = basename($extension);
|
$extensionName = basename($extension);
|
||||||
$settingFile = $extension . '/' . $extensionName . 'Settings.php';
|
$settingsClass = $extension . '\\' . $extensionName . 'Settings';
|
||||||
if (file_exists($settingFile)) {
|
if (class_exists($settingsClass)) {
|
||||||
// remove the base path from the setting file path to get the namespace
|
$settings[] = $settingsClass;
|
||||||
|
|
||||||
$settingFile = str_replace(app_path() . '/', '', $settingFile);
|
|
||||||
$settingFile = str_replace('.php', '', $settingFile);
|
|
||||||
$settingFile = str_replace('/', '\\', $settingFile);
|
|
||||||
$settingFile = 'App\\' . $settingFile;
|
|
||||||
$settings[] = $settingFile;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,25 +192,21 @@ class ExtensionHelper
|
||||||
|
|
||||||
public static function getExtensionSettings(string $extensionName)
|
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
|
if (class_exists($settingClass)) {
|
||||||
foreach ($extensions as $extension) {
|
return new $settingClass();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
$settingsClass = new $settings_class();
|
||||||
|
|
||||||
foreach ($settingsClass->toArray() as $key => $value) {
|
foreach ($settingsClass->toArray() as $key => $value) {
|
||||||
switch (gettype($value)) {
|
switch (gettype($request->input($key))) {
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
$settingsClass->$key = $request->has($key);
|
$settingsClass->$key = $request->has($key);
|
||||||
break;
|
break;
|
||||||
|
@ -125,6 +125,9 @@ class SettingsController extends Controller
|
||||||
case 'double':
|
case 'double':
|
||||||
$settingsClass->$key = $request->input($key) ?? 0.0;
|
$settingsClass->$key = $request->input($key) ?? 0.0;
|
||||||
break;
|
break;
|
||||||
|
case 'NULL':
|
||||||
|
$settingsClass->$key = null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ class CreateInvoice
|
||||||
use Invoiceable;
|
use Invoiceable;
|
||||||
|
|
||||||
private $invoice_enabled;
|
private $invoice_enabled;
|
||||||
|
private $invoice_settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the event listener.
|
* Create the event listener.
|
||||||
|
@ -20,6 +21,7 @@ class CreateInvoice
|
||||||
public function __construct(InvoiceSettings $invoice_settings)
|
public function __construct(InvoiceSettings $invoice_settings)
|
||||||
{
|
{
|
||||||
$this->invoice_enabled = $invoice_settings->enabled;
|
$this->invoice_enabled = $invoice_settings->enabled;
|
||||||
|
$this->invoice_settings = $invoice_settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +34,7 @@ class CreateInvoice
|
||||||
{
|
{
|
||||||
if ($this->invoice_enabled) {
|
if ($this->invoice_enabled) {
|
||||||
// create invoice using the trait
|
// 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
|
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;
|
public int $per_page_limit;
|
||||||
|
|
||||||
|
@ -44,9 +44,9 @@ class PterodactylSettings extends Settings
|
||||||
public static function getValidations()
|
public static function getValidations()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'panel_url' => 'required|string|url',
|
'panel_url' => 'nullable|string|url',
|
||||||
'admin_token' => 'required|string',
|
'admin_token' => 'nullable|string',
|
||||||
'user_token' => 'required|string',
|
'user_token' => 'nullable|string',
|
||||||
'per_page_limit' => 'required|integer|min:1|max:10000',
|
'per_page_limit' => 'required|integer|min:1|max:10000',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,37 +11,38 @@
|
||||||
"php": "^8.1",
|
"php": "^8.1",
|
||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
"biscolab/laravel-recaptcha": "^5.4",
|
"biscolab/laravel-recaptcha": "^5.4",
|
||||||
"doctrine/dbal": "^3.1",
|
"doctrine/dbal": "^3.5.3",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.5",
|
||||||
"hidehalo/nanoid-php": "^1.1",
|
"hidehalo/nanoid-php": "^1.1.12",
|
||||||
"kkomelin/laravel-translatable-string-exporter": "^1.18",
|
"kkomelin/laravel-translatable-string-exporter": "^1.18",
|
||||||
"laravel/framework": "^9.46",
|
"laravel/framework": "^9.50.2",
|
||||||
"laravel/tinker": "^2.7",
|
"laravel/tinker": "^2.8",
|
||||||
"laravel/ui": "^3.3",
|
"laravel/ui": "^3.4.6",
|
||||||
"laraveldaily/laravel-invoices": "^3.0",
|
"laraveldaily/laravel-invoices": "^3.0.2",
|
||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
"league/flysystem-aws-s3-v3": "^3.12.2",
|
||||||
"paypal/paypal-checkout-sdk": "^1.0",
|
"paypal/paypal-checkout-sdk": "^1.0.2",
|
||||||
"paypal/rest-api-sdk-php": "^1.14",
|
"paypal/rest-api-sdk-php": "^1.14.0",
|
||||||
"qirolab/laravel-themer": "^2.0",
|
"predis/predis": "*",
|
||||||
"socialiteproviders/discord": "^4.1",
|
"qirolab/laravel-themer": "^2.0.2",
|
||||||
"spatie/laravel-activitylog": "^4.4",
|
"socialiteproviders/discord": "^4.1.2",
|
||||||
"spatie/laravel-query-builder": "^5.0",
|
"spatie/laravel-activitylog": "^4.7.3",
|
||||||
|
"spatie/laravel-query-builder": "^5.1.2",
|
||||||
"spatie/laravel-settings": "^2.7",
|
"spatie/laravel-settings": "^2.7",
|
||||||
"spatie/laravel-validation-rules": "^3.2",
|
"spatie/laravel-validation-rules": "^3.2.2",
|
||||||
"stripe/stripe-php": "^7.107",
|
"stripe/stripe-php": "^7.128",
|
||||||
"symfony/http-client": "^6.2",
|
"symfony/http-client": "^6.2.6",
|
||||||
"symfony/intl": "^6.0",
|
"symfony/intl": "^6.2.5",
|
||||||
"symfony/mailgun-mailer": "^6.2",
|
"symfony/mailgun-mailer": "^6.2.5",
|
||||||
"yajra/laravel-datatables-oracle": "^9.19"
|
"yajra/laravel-datatables-oracle": "^9.21.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"barryvdh/laravel-debugbar": "^3.6",
|
"barryvdh/laravel-debugbar": "^3.7",
|
||||||
"fakerphp/faker": "^1.9.1",
|
"fakerphp/faker": "^1.21",
|
||||||
"laravel/sail": "^1.15",
|
"laravel/sail": "^1.19",
|
||||||
"mockery/mockery": "^1.4.4",
|
"mockery/mockery": "^1.5.1",
|
||||||
"nunomaduro/collision": "^6.3",
|
"nunomaduro/collision": "^6.4",
|
||||||
"phpunit/phpunit": "^9.5.10",
|
"phpunit/phpunit": "^9.6",
|
||||||
"spatie/laravel-ignition": "^1.4"
|
"spatie/laravel-ignition": "^1.6"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"optimize-autoloader": true,
|
"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:
|
networks:
|
||||||
- laravel
|
- 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:
|
volumes:
|
||||||
mysql:
|
mysql:
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "controlpanel",
|
"name": "controllpanelgg",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"development": "vite",
|
"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": {
|
"devDependencies": {
|
||||||
"axios": "^0.25",
|
"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": {
|
"themes/default/sass/app.scss": {
|
||||||
"file": "assets/app-bac23d88.css",
|
"file": "assets/app-26e8174e.css",
|
||||||
"src": "themes/default/sass/app.scss",
|
"isEntry": true,
|
||||||
"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/stylesheet.css";
|
||||||
@import "../css/adminlte.min.css";
|
@import "../css/adminlte.min.css";
|
||||||
@import "../css/slim.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">
|
<section class="content">
|
||||||
<div class="container-fluid">
|
<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">
|
<div class="card">
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
</noscript>
|
</noscript>
|
||||||
<script src="{{ asset('js/app.js') }}"></script>
|
<script src="{{ asset('js/app.js') }}"></script>
|
||||||
<!-- tinymce -->
|
<!-- 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')
|
@vite('themes/default/sass/app.scss')
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -24,133 +24,155 @@
|
||||||
<!-- MAIN CONTENT -->
|
<!-- MAIN CONTENT -->
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
<form x-data="{ payment_method: '', clicked: false }" action="{{ route('payment.pay') }}" method="POST">
|
||||||
<div class="row">
|
@csrf
|
||||||
<div class="col-12">
|
@method('post')
|
||||||
|
<div class="row d-flex justify-content-center flex-wrap">
|
||||||
<form x-data="{ payment_method: '', clicked: false }" action="{{ route('payment.pay') }}" method="POST">
|
@if (!$productIsFree)
|
||||||
@csrf
|
<div class="col-xl-4">
|
||||||
@method('post')
|
<div class="card">
|
||||||
<!-- Main content -->
|
<div class="card-header">
|
||||||
<div class="invoice p-3 mb-3">
|
<h4 class="mb-0">
|
||||||
<!-- title row -->
|
<i class="fas fa-money-check-alt"></i>
|
||||||
<div class="row">
|
Payment Methods
|
||||||
<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>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.col -->
|
<div class="card-body">
|
||||||
</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>
|
|
||||||
<input type="hidden" name="product_id" value="{{ $product->id }}">
|
<input type="hidden" name="product_id" value="{{ $product->id }}">
|
||||||
</div>
|
<input type="hidden" name="payment_method" :value="payment_method"
|
||||||
<!-- /.col -->
|
x-model="payment_method">
|
||||||
</div>
|
<div class="row">
|
||||||
<!-- /.row -->
|
<div class="col-lg-12">
|
||||||
|
|
||||||
<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">
|
|
||||||
|
|
||||||
@foreach ($paymentGateways as $gateway)
|
@foreach ($paymentGateways as $gateway)
|
||||||
<div class="ml-2">
|
<div class="row checkout-gateways">
|
||||||
<label class="text-center" for="{{ $gateway->name }}">
|
<div class="col-12 d-flex justify-content-between">
|
||||||
<img class="mb-3" height="50"
|
<label class="form-check-label h4 checkout-gateway-label"
|
||||||
src="{{ $gateway->image }}"></br>
|
for="{{ $gateway->name }}">
|
||||||
<input x-on:click="console.log(payment_method)"
|
<span class="mr-3">{{ $gateway->name }}</span>
|
||||||
x-model="payment_method" type="radio"
|
</label>
|
||||||
id="{{ $gateway->name }}" value="{{ $gateway->name }}">
|
<button class="btn btn-primary rounded" type="button"
|
||||||
</input>
|
name="payment-method" id="{{ $gateway->name }}"
|
||||||
</label>
|
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>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</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>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
@endif
|
||||||
<!-- /.invoice -->
|
<div class="col-xl-3">
|
||||||
</div><!-- /.col -->
|
<div class="card">
|
||||||
</div><!-- /.row -->
|
<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>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<!-- END CONTENT -->
|
<!-- END CONTENT -->
|
||||||
@endsection
|
@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 laravel from "laravel-vite-plugin";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
laravel({
|
laravel({
|
||||||
input: [
|
input: ["themes/default/sass/app.scss", "themes/default/js/app.js"],
|
||||||
"themes/default/sass/app.scss",
|
|
||||||
"themes/default/js/app.js"
|
|
||||||
],
|
|
||||||
buildDirectory: "build",
|
buildDirectory: "build",
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "blade",
|
name: "blade",
|
||||||
handleHotUpdate({ file, server }) {
|
handleHotUpdate({ file, server }) {
|
||||||
|
@ -29,9 +22,8 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': '/themes/default/js',
|
"@": "/themes/default/js",
|
||||||
'~bootstrap': path.resolve('node_modules/bootstrap'),
|
"~bootstrap": path.resolve("node_modules/bootstrap"),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue