Merge branch 'product_linking' into patch-3

This commit is contained in:
AVMG 2021-11-12 18:45:00 +01:00 committed by GitHub
commit e9bea605c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 209 additions and 55 deletions

View file

@ -27,6 +27,7 @@ use PayPalHttp\HttpException;
class PaymentController extends Controller class PaymentController extends Controller
{ {
/** /**
* @return Application|Factory|View * @return Application|Factory|View
*/ */
@ -45,7 +46,10 @@ class PaymentController extends Controller
public function checkOut(Request $request, PaypalProduct $paypalProduct) public function checkOut(Request $request, PaypalProduct $paypalProduct)
{ {
return view('store.checkout')->with([ return view('store.checkout')->with([
'product' => $paypalProduct 'product' => $paypalProduct,
'taxvalue' => $paypalProduct->getTaxValue(),
'taxpercent' => $paypalProduct->getTaxPercent(),
'total' => $paypalProduct->getTotalPrice()
]); ]);
} }
@ -65,8 +69,20 @@ class PaymentController extends Controller
"reference_id" => uniqid(), "reference_id" => uniqid(),
"description" => $paypalProduct->description, "description" => $paypalProduct->description,
"amount" => [ "amount" => [
"value" => $paypalProduct->price, "value" => $paypalProduct->getTotalPrice(),
"currency_code" => strtoupper($paypalProduct->currency_code) 'currency_code' => strtoupper($paypalProduct->currency_code),
'breakdown' =>[
'item_total' =>
[
'currency_code' => strtoupper($paypalProduct->currency_code),
'value' => $paypalProduct->price,
],
'tax_total' =>
[
'currency_code' => strtoupper($paypalProduct->currency_code),
'value' => $paypalProduct->getTaxValue(),
]
]
] ]
] ]
], ],
@ -76,6 +92,8 @@ class PaymentController extends Controller
'brand_name' => config('app.name', 'Laravel'), 'brand_name' => config('app.name', 'Laravel'),
'shipping_preference' => 'NO_SHIPPING' 'shipping_preference' => 'NO_SHIPPING'
] ]
]; ];
@ -161,6 +179,9 @@ class PaymentController extends Controller
'status' => $response->result->status, 'status' => $response->result->status,
'amount' => $paypalProduct->quantity, 'amount' => $paypalProduct->quantity,
'price' => $paypalProduct->price, 'price' => $paypalProduct->price,
'tax_value' => $paypalProduct->getTaxValue(),
'tax_percent' => $paypalProduct->getTaxPercent(),
'total_price' => $paypalProduct->getTotalPrice(),
'currency_code' => $paypalProduct->currency_code, 'currency_code' => $paypalProduct->currency_code,
'payer' => json_encode($response->result->payer), 'payer' => json_encode($response->result->payer),
]); ]);
@ -199,7 +220,7 @@ class PaymentController extends Controller
*/ */
public function cancel(Request $request) public function cancel(Request $request)
{ {
return redirect()->route('store.index')->with('success', 'Payment was Cannceled'); return redirect()->route('store.index')->with('success', 'Payment was Canceled');
} }
@ -216,7 +237,13 @@ class PaymentController extends Controller
return $payment->user->name; return $payment->user->name;
}) })
->editColumn('price', function (Payment $payment) { ->editColumn('price', function (Payment $payment) {
return $payment->formatCurrency(); return $payment->formatToCurrency($payment->price);
})
->editColumn('tax_value', function (Payment $payment) {
return $payment->formatToCurrency($payment->tax_value);
})
->editColumn('total_price', function (Payment $payment) {
return $payment->formatToCurrency($payment->total_price);
}) })
->editColumn('created_at', function (Payment $payment) { ->editColumn('created_at', function (Payment $payment) {
return $payment->created_at ? $payment->created_at->diffForHumans() : ''; return $payment->created_at ? $payment->created_at->diffForHumans() : '';

View file

@ -173,7 +173,7 @@ class PaypalProductController extends Controller
return $paypalProduct->created_at ? $paypalProduct->created_at->diffForHumans() : ''; return $paypalProduct->created_at ? $paypalProduct->created_at->diffForHumans() : '';
}) })
->editColumn('price', function (PaypalProduct $paypalProduct) { ->editColumn('price', function (PaypalProduct $paypalProduct) {
return $paypalProduct->formatCurrency(); return $paypalProduct->formatToCurrency($paypalProduct->price);
}) })
->rawColumns(['actions', 'disabled']) ->rawColumns(['actions', 'disabled'])
->make(); ->make();

View file

@ -58,13 +58,13 @@ class ProductController extends Controller
/** @var Location $location */ /** @var Location $location */
$location = $node->location; $location = $node->location;
if (!$locations->contains('id' , $location->id)){ if (!$locations->contains('id', $location->id)) {
$nodeIds = $nodes->map(function ($node){ $nodeIds = $nodes->map(function ($node) {
return $node->id; return $node->id;
}); });
$location->nodes = $location->nodes() $location->nodes = $location->nodes()
->whereIn('id' , $nodeIds) ->whereIn('id', $nodeIds)
->get(); ->get();
$locations->add($location); $locations->add($location);
@ -76,14 +76,20 @@ class ProductController extends Controller
/** /**
* @param Node $node * @param Node $node
* @param Egg $egg
* @return Collection|JsonResponse * @return Collection|JsonResponse
*/ */
public function getProductsBasedOnNode(Node $node) public function getProductsBasedOnNode(Egg $egg, Node $node)
{ {
if (is_null($node->id)) return response()->json('Node ID is required', '400'); if (is_null($egg->id) || is_null($node->id)) return response()->json('node and egg id is required', '400');
return Product::query()->whereHas('nodes', function (Builder $builder) use ($node) { return Product::query()
$builder->where('id', '=', $node->id); ->whereHas('nodes', function (Builder $builder) use ($node) {
})->get(); $builder->where('id', '=', $node->id);
})
->whereHas('eggs', function (Builder $builder) use ($egg) {
$builder->where('id', '=', $egg->id);
})
->get();
} }
} }

View file

@ -29,6 +29,9 @@ class Payment extends Model
'type', 'type',
'amount', 'amount',
'price', 'price',
'tax_value',
'total_price',
'tax_percent',
'currency_code', 'currency_code',
]; ];
@ -51,9 +54,15 @@ class Payment extends Model
return $this->belongsTo(User::class); return $this->belongsTo(User::class);
} }
public function formatCurrency($locale = 'en_US') /**
* @param mixed $value
* @param string $locale
*
* @return float
*/
public function formatToCurrency($value,$locale = 'en_US')
{ {
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
return $formatter->formatCurrency($this->price, $this->currency_code); return $formatter->formatCurrency($value, $this->currency_code);
} }
} }

View file

@ -6,6 +6,7 @@ use Hidehalo\Nanoid\Client;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use NumberFormatter; use NumberFormatter;
use Spatie\Activitylog\Traits\LogsActivity; use Spatie\Activitylog\Traits\LogsActivity;
use App\Models\Configuration;
class PaypalProduct extends Model class PaypalProduct extends Model
{ {
@ -40,12 +41,45 @@ class PaypalProduct extends Model
} }
/** /**
* @param mixed $value
* @param string $locale * @param string $locale
* @return string *
* @return float
*/ */
public function formatCurrency($locale = 'en_US') public function formatToCurrency($value,$locale = 'en_US')
{ {
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
return $formatter->formatCurrency($this->price, $this->currency_code); return $formatter->formatCurrency($value, $this->currency_code);
}
/**
* @description Returns the tax in % taken from the Configuration
*
* @return int
*/
public function getTaxPercent()
{
$tax = Configuration::getValueByKey("SALES_TAX");
return $tax < 0 ? 0 : $tax;
}
/**
* @description Returns the tax as Number
*
* @return float
*/
public function getTaxValue()
{
return $this->price*$this->getTaxPercent()/100;
}
/**
* @description Returns the full price of a Product including tax
*
* @return float
*/
public function getTotalPrice()
{
return $this->price+($this->getTaxValue());
} }
} }

View file

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddTaxToPaymentlogs extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('payments', function (Blueprint $table) {
$table->decimal('tax_value',8,2)->after('price')->nullable();
$table->integer('tax_percent')->after('tax_value')->nullable();
$table->decimal('total_price',8,2)->after('tax_percent')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('payments', function (Blueprint $table) {
$table->dropColumn('tax_value');
$table->dropColumn('tax_percent');
$table->dropColumn('total_price');
});
}
}

View file

@ -136,6 +136,14 @@ class ConfigurationSeeder extends Seeder
'type' => 'boolean', 'type' => 'boolean',
'description' => 'Charges the first hour worth of credits upon creating a server.' 'description' => 'Charges the first hour worth of credits upon creating a server.'
]); ]);
//sales tax
Configuration::firstOrCreate([
'key' => 'SALES_TAX',
], [
'value' => '0',
'type' => 'integer',
'description' => 'The %-value of tax that will be added to the product price on checkout'
]);
} }
} }

View file

@ -37,7 +37,10 @@
<th>User</th> <th>User</th>
<th>Type</th> <th>Type</th>
<th>Amount</th> <th>Amount</th>
<th>Price</th> <th>Product Price</th>
<th>Tax</th>
<th>Tax(%)</th>
<th>Total Price</th>
<th>Payment_ID</th> <th>Payment_ID</th>
<th>Payer_ID</th> <th>Payer_ID</th>
<th>Created at</th> <th>Created at</th>
@ -68,6 +71,9 @@
{data: 'type'}, {data: 'type'},
{data: 'amount'}, {data: 'amount'},
{data: 'price'}, {data: 'price'},
{data: 'tax_value'},
{data: 'tax_percent'},
{data: 'total_price'},
{data: 'payment_id'}, {data: 'payment_id'},
{data: 'payer_id'}, {data: 'payer_id'},
{data: 'created_at'}, {data: 'created_at'},

View file

@ -6,7 +6,7 @@ Your payment has been confirmed; Your credit balance has been updated.<br>
___ ___
### Payment ID: **{{$payment->id}}**<br> ### Payment ID: **{{$payment->id}}**<br>
### Status: **{{$payment->status}}**<br> ### Status: **{{$payment->status}}**<br>
### Price: **{{$payment->formatCurrency()}}**<br> ### Price: **{{$payment->formatToCurrency($payment->total_price)}}**<br>
### Type: **{{$payment->type}}**<br> ### Type: **{{$payment->type}}**<br>
### Amount: **{{$payment->amount}}**<br> ### Amount: **{{$payment->amount}}**<br>
### Balance: **{{$payment->user->credits}}**<br> ### Balance: **{{$payment->user->credits}}**<br>

View file

@ -101,7 +101,7 @@
name="nest" name="nest"
id="nest" id="nest"
x-model="selectedNest" x-model="selectedNest"
@change="setNests();"> @change="setEggs();">
<option selected disabled hidden <option selected disabled hidden
value="null">{{count($nests) > 0 ? __('Please select software ...') : __('---')}}</option> value="null">{{count($nests) > 0 ? __('Please select software ...') : __('---')}}</option>
@foreach ($nests as $nest) @foreach ($nests as $nest)
@ -229,33 +229,39 @@
<small x-text="selectedProductObject?.name ?? '{{__('No selection')}}'" <small x-text="selectedProductObject?.name ?? '{{__('No selection')}}'"
class="text-muted"></small> class="text-muted"></small>
</div> </div>
<template x-if="selectedProductObject?.name"> <template x-if="selectedProductObject?.name">
<ul class="pl-0"> <ul class="pl-0">
<li class="d-flex justify-content-between"> <li class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('CPU')}}</small> <small class="text-muted d-inline-block">{{__('CPU')}}</small>
<small class="text-muted d-inline-block" x-text="selectedProductObject.cpu + ' %'"></small> <small class="text-muted d-inline-block"
</li> x-text="selectedProductObject.cpu + ' %'"></small>
<div class="d-flex justify-content-between"> </li>
<small class="text-muted d-inline-block">{{__('Memory')}}</small> <div class="d-flex justify-content-between">
<small class="text-muted d-inline-block" x-text="selectedProductObject.memory + ' {{__('MB')}}'"></small> <small class="text-muted d-inline-block">{{__('Memory')}}</small>
</div> <small class="text-muted d-inline-block"
<div class="d-flex justify-content-between"> x-text="selectedProductObject.memory + ' {{__('MB')}}'"></small>
<small class="text-muted d-inline-block">{{__('Disk')}}</small> </div>
<small class="text-muted d-inline-block" x-text="selectedProductObject.disk + ' {{__('MB')}}'"></small> <div class="d-flex justify-content-between">
</div> <small class="text-muted d-inline-block">{{__('Disk')}}</small>
<div class="d-flex justify-content-between"> <small class="text-muted d-inline-block"
<small class="text-muted d-inline-block">{{__('Databases')}}</small> x-text="selectedProductObject.disk + ' {{__('MB')}}'"></small>
<small class="text-muted d-inline-block" x-text="selectedProductObject.databases + ' {{__('MySQL')}}'"></small> </div>
</div> <div class="d-flex justify-content-between">
<div class="d-flex justify-content-between"> <small class="text-muted d-inline-block">{{__('Databases')}}</small>
<small class="text-muted d-inline-block">{{__('Backups')}}</small> <small class="text-muted d-inline-block"
<small class="text-muted d-inline-block" x-text="selectedProductObject.backups"></small> x-text="selectedProductObject.databases + ' {{__('MySQL')}}'"></small>
</div> </div>
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Allocations (ports)')}}</small> <small class="text-muted d-inline-block">{{__('Backups')}}</small>
<small class="text-muted d-inline-block"
x-text="selectedProductObject.backups"></small>
</div>
<div class="d-flex justify-content-between">
<small class="text-muted d-inline-block">{{__('Allocations')}} ({{__('ports')}})</small>
<small class="text-muted d-inline-block" x-text="selectedProductObject.allocations"></small> <small class="text-muted d-inline-block" x-text="selectedProductObject.allocations"></small>
</div> </div>
</ul> </ul>
</template> </template>
</li> </li>
@ -319,7 +325,7 @@
* @note called whenever a nest is selected * @note called whenever a nest is selected
* @see selectedNest * @see selectedNest
*/ */
setNests() { async setEggs() {
this.fetchedLocations = false; this.fetchedLocations = false;
this.fetchedProducts = false; this.fetchedProducts = false;
this.locations = []; this.locations = [];
@ -329,6 +335,14 @@
this.selectedProduct = 'null'; this.selectedProduct = 'null';
this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest) this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest)
//automatically select the first entry if there is only 1
if (this.eggs.length === 1) {
this.selectedEgg = this.eggs[0].id;
await this.fetchLocations();
return;
}
this.updateSelectedObjects() this.updateSelectedObjects()
}, },
@ -351,6 +365,14 @@
this.fetchedLocations = true; this.fetchedLocations = true;
this.locations = response.data this.locations = response.data
//automatically select the first entry if there is only 1
if (this.locations.length === 1 && this.locations[0]?.nodes?.length === 1) {
this.selectedNode = this.locations[0].id;
await this.fetchProducts();
return;
}
this.loading = false; this.loading = false;
this.updateSelectedObjects() this.updateSelectedObjects()
}, },
@ -366,11 +388,17 @@
this.products = []; this.products = [];
this.selectedProduct = 'null'; this.selectedProduct = 'null';
let response = await axios.get(`{{route('products.products.node')}}/${this.selectedNode}`) let response = await axios.get(`{{route('products.products.node')}}/${this.selectedEgg}/${this.selectedNode}`)
.catch(console.error) .catch(console.error)
this.fetchedProducts = true; this.fetchedProducts = true;
this.products = response.data this.products = response.data
//automatically select the first entry if there is only 1
if (this.products.length === 1) {
this.selectedProduct = this.products[0].id;
}
this.loading = false; this.loading = false;
this.updateSelectedObjects() this.updateSelectedObjects()
}, },

View file

@ -83,7 +83,7 @@
<td>1</td> <td>1</td>
<td><i class="fa fa-coins mr-2"></i>{{$product->quantity}} {{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td> <td><i class="fa fa-coins mr-2"></i>{{$product->quantity}} {{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td>
<td>{{$product->description}}</td> <td>{{$product->description}}</td>
<td>{{$product->formatCurrency()}}</td> <td>{{$product->formatToCurrency($product->price)}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -111,11 +111,11 @@
<table class="table"> <table class="table">
<tr> <tr>
<th style="width:50%">Subtotal:</th> <th style="width:50%">Subtotal:</th>
<td>{{$product->formatCurrency()}}</td> <td>{{$product->formatToCurrency($product->price)}}</td>
</tr> </tr>
<tr> <tr>
<th>Tax (0%)</th> <th>Tax ({{$taxpercent}}%)</th>
<td>0.00</td> <td>{{$product->formatToCurrency($taxvalue)}}</td>
</tr> </tr>
<tr> <tr>
<th>Quantity:</th> <th>Quantity:</th>
@ -123,7 +123,7 @@
</tr> </tr>
<tr> <tr>
<th>Total:</th> <th>Total:</th>
<td>{{$product->formatCurrency()}}</td> <td>{{$product->formatToCurrency($total)}}</td>
</tr> </tr>
</table> </table>
</div> </div>

View file

@ -50,7 +50,7 @@
<?php /** @var $product PaypalProduct */?> <?php /** @var $product PaypalProduct */?>
@foreach($products as $product) @foreach($products as $product)
<tr> <tr>
<td>{{$product->formatCurrency()}}</td> <td>{{$product->formatToCurrency($product->price)}}</td>
<td>{{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td> <td>{{strtolower($product->type) == 'credits' ? CREDITS_DISPLAY_NAME : $product->type}}</td>
<td><i class="fa fa-coins mr-2"></i>{{$product->display}}</td> <td><i class="fa fa-coins mr-2"></i>{{$product->display}}</td>
<td><a href="{{route('checkout' , $product->id)}}" class="btn btn-info">Purchase</a> <td><a href="{{route('checkout' , $product->id)}}" class="btn btn-info">Purchase</a>

View file

@ -58,7 +58,7 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
#routes made for server create page to fetch product info #routes made for server create page to fetch product info
Route::get('/products/nodes/egg/{egg?}', [FrontProductController::class, 'getNodesBasedOnEgg'])->name('products.nodes.egg'); Route::get('/products/nodes/egg/{egg?}', [FrontProductController::class, 'getNodesBasedOnEgg'])->name('products.nodes.egg');
Route::get('/products/locations/egg/{egg?}', [FrontProductController::class, 'getLocationsBasedOnEgg'])->name('products.locations.egg'); Route::get('/products/locations/egg/{egg?}', [FrontProductController::class, 'getLocationsBasedOnEgg'])->name('products.locations.egg');
Route::get('/products/products/node/{node?}', [FrontProductController::class, 'getProductsBasedOnNode'])->name('products.products.node'); Route::get('/products/products/{egg?}/{node?}', [FrontProductController::class, 'getProductsBasedOnNode'])->name('products.products.node');
#payments #payments
Route::get('checkout/{paypalProduct}', [PaymentController::class, 'checkOut'])->name('checkout'); Route::get('checkout/{paypalProduct}', [PaymentController::class, 'checkOut'])->name('checkout');