discount - use point
This commit is contained in:
parent
a60ca44ca1
commit
8ef3a2783a
|
|
@ -35,7 +35,7 @@ class CartController extends Controller
|
|||
|
||||
public function add(MemberCartRequest $request, MemberCartRepository $repository)
|
||||
{
|
||||
Log::info($request->all());
|
||||
// Log::info($request->all());
|
||||
$data = $request->validated();
|
||||
$item = $repository->create($data);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use App\Repositories\Member\ShippingRepository;
|
|||
use App\Repositories\Member\Transaction\TransactionRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CheckoutController extends Controller
|
||||
|
|
@ -22,22 +21,18 @@ class CheckoutController extends Controller
|
|||
|
||||
$subtotal = $memberCartRepository->getSubtotal($request->input('location_id'));
|
||||
|
||||
$address_list = Address::where('user_id', auth()->user()->id)->orderBy('is_primary','desc')->get();
|
||||
|
||||
$address_list = Address::where('user_id', auth()->user()->id)->orderBy('is_primary', 'desc')->get();
|
||||
|
||||
$total = $subtotal;
|
||||
|
||||
|
||||
|
||||
$carts = $memberCartRepository->getList($request);
|
||||
|
||||
|
||||
return view('checkout.v1-delivery-1', [
|
||||
'carts' => $carts,
|
||||
'subtotal' => $subtotal,
|
||||
'total' => $total,
|
||||
'store' => $store,
|
||||
'address_list' => $address_list,
|
||||
'address_list' => $address_list,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -46,33 +41,33 @@ class CheckoutController extends Controller
|
|||
$delivery_method = $request->input('delivery_method') ?? 'shipping';
|
||||
$address_id = $request->input('address_id');
|
||||
|
||||
|
||||
if ($address_id == null) {
|
||||
$address_list = Address::where('user_id', $request->user()->id)->orderBy('is_primary','desc')->get();
|
||||
$address_list = Address::where('user_id', $request->user()->id)->orderBy('is_primary', 'desc')->get();
|
||||
$address_id = $address_list->first()->id;
|
||||
}
|
||||
|
||||
if ($delivery_method == null || $address_id == null) {
|
||||
|
||||
|
||||
return redirect()->back()->with('error', 'Delivery method or address is required');
|
||||
}
|
||||
|
||||
if ($delivery_method == 'shipping') {
|
||||
session(['checkout_delivery_method' => $delivery_method]);
|
||||
session(['checkout_address_id' => $address_id]);
|
||||
|
||||
return redirect()->route('checkout.shipping');
|
||||
}
|
||||
|
||||
if ($delivery_method == 'pickup') {
|
||||
session(['checkout_delivery_method' => $delivery_method]);
|
||||
session(['checkout_address_id' => null]);
|
||||
|
||||
return redirect()->route('checkout.payment');
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Delivery method is not valid');
|
||||
}
|
||||
|
||||
|
||||
public function chooseShipping(Request $request, MemberCartRepository $memberCartRepository, ShippingRepository $shippingRepository)
|
||||
{
|
||||
try {
|
||||
|
|
@ -81,7 +76,6 @@ class CheckoutController extends Controller
|
|||
|
||||
$location_id = session('location_id', 22);
|
||||
|
||||
|
||||
if ($delivery_method == null || $address_id == null || $location_id == null) {
|
||||
|
||||
return redirect()->route('checkout.delivery');
|
||||
|
|
@ -90,37 +84,43 @@ class CheckoutController extends Controller
|
|||
$subtotal = $memberCartRepository->getSubtotal($location_id);
|
||||
$total = $subtotal;
|
||||
|
||||
|
||||
$request->merge(['location_id' => $location_id]);
|
||||
$carts = $memberCartRepository->getList($request);
|
||||
|
||||
|
||||
try{
|
||||
try {
|
||||
$shipping_list = collect($shippingRepository->getList([
|
||||
"location_id" => $location_id,
|
||||
"address_id" => $address_id,
|
||||
"items" => $carts,
|
||||
])['pricing'] ?? [])->map(function($row){
|
||||
'location_id' => $location_id,
|
||||
'address_id' => $address_id,
|
||||
'items' => $carts,
|
||||
])['pricing'] ?? [])->map(function ($row) {
|
||||
return [
|
||||
'courier' => $row['courier_code'],
|
||||
'service' => $row['courier_service_code'],
|
||||
'title' => $row['courier_name']." - ".$row['courier_service_name'],
|
||||
'courier' => $row['courier_code'],
|
||||
'service' => $row['courier_service_code'],
|
||||
'title' => $row['courier_name'].' - '.$row['courier_service_name'],
|
||||
'description' => $row['duration'],
|
||||
'cost' => $row['shipping_fee'],
|
||||
'cost' => $row['shipping_fee'],
|
||||
];
|
||||
});
|
||||
|
||||
if (count($shipping_list) == 0) {
|
||||
throw ValidationException::withMessages([
|
||||
"message" => "Tidak dapat menghitung ongkir"
|
||||
'message' => 'Tidak dapat menghitung ongkir',
|
||||
]);
|
||||
}
|
||||
}catch(\Exception $e){
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
// if contain Failed due to invalid or missing postal code
|
||||
if (str_contains($message, 'Failed due to invalid or missing postal code')) {
|
||||
$message = 'Kode pos tidak valid atau tidak ditemukan';
|
||||
} else if (str_contains($message, 'support@biteship.com')) {
|
||||
$message = 'Layanan pengiriman tidak tersedia untuk alamat ini';
|
||||
}
|
||||
throw ValidationException::withMessages([
|
||||
"message" => $e->getMessage()
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
return view('checkout.v1-delivery-1-shipping', [
|
||||
'carts' => $request->user()->carts,
|
||||
'subtotal' => $subtotal,
|
||||
|
|
@ -132,6 +132,7 @@ class CheckoutController extends Controller
|
|||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::info($e);
|
||||
|
||||
return redirect()->route('checkout.delivery')->with('error', $e->getMessage() ?? 'Invalid checkout data');
|
||||
}
|
||||
}
|
||||
|
|
@ -164,6 +165,8 @@ class CheckoutController extends Controller
|
|||
|
||||
$location_id = session('location_id', 22);
|
||||
|
||||
$use_point = session('use_point') ?? 0;
|
||||
|
||||
$items = [];
|
||||
|
||||
$request->merge(['location_id' => $location_id]);
|
||||
|
|
@ -172,25 +175,27 @@ class CheckoutController extends Controller
|
|||
if (count($carts) == 0) {
|
||||
return redirect()->route('checkout.delivery')->with('error', 'No items in cart');
|
||||
}
|
||||
|
||||
|
||||
foreach ($carts as $cart) {
|
||||
$items[] = [
|
||||
"item_reference_id" => $cart->item_reference_id,
|
||||
"qty" => $cart->qty
|
||||
'item_reference_id' => $cart->item_reference_id,
|
||||
'qty' => $cart->qty,
|
||||
];
|
||||
}
|
||||
|
||||
$data = [
|
||||
"address_id" => $address_id,
|
||||
"note" => "",
|
||||
"courier_company" => $courier,
|
||||
"courier_type" => $service,
|
||||
"location_id" => $location_id,
|
||||
"items" => $items,
|
||||
"vouchers" => [],
|
||||
"use_customer_points" => 0,
|
||||
'address_id' => $address_id,
|
||||
'note' => '',
|
||||
'courier_company' => $courier,
|
||||
'courier_type' => $service,
|
||||
'location_id' => $location_id,
|
||||
'items' => $items,
|
||||
'vouchers' => [],
|
||||
'use_customer_points' => $use_point ?? 0,
|
||||
];
|
||||
|
||||
|
||||
dd($data);
|
||||
|
||||
$item = $repository->create($data);
|
||||
|
||||
$notification = new \App\Notifications\Member\Transaction\OrderWaitPayment($item);
|
||||
|
|
@ -201,19 +206,20 @@ class CheckoutController extends Controller
|
|||
|
||||
// proses payment
|
||||
|
||||
|
||||
$payment = $item->payments()->where("method_type",'App\Models\XenditLink')
|
||||
->where("status",'PENDING')
|
||||
->first();
|
||||
$invoice_url = $payment ? @$payment->method->invoice_url: "";
|
||||
|
||||
return redirect()->to($invoice_url)->withHeaders([
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0'
|
||||
]);
|
||||
$payment = $item->payments()->where('method_type', 'App\Models\XenditLink')
|
||||
->where('status', 'PENDING')
|
||||
->first();
|
||||
$invoice_url = $payment ? @$payment->method->invoice_url : '';
|
||||
|
||||
|
||||
// reset state session
|
||||
session()->forget(['checkout_delivery_method', 'checkout_address_id', 'checkout_courier', 'checkout_service', 'use_point']);
|
||||
|
||||
return redirect()->to($invoice_url)->withHeaders([
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0',
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
|
|
@ -222,4 +228,92 @@ class CheckoutController extends Controller
|
|||
return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply points to session
|
||||
*/
|
||||
public function applyPoint(Request $request)
|
||||
{
|
||||
try {
|
||||
$usePoint = $request->input('use_point');
|
||||
|
||||
// Validate input
|
||||
if (!$usePoint || !is_numeric($usePoint) || $usePoint < 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Invalid points value'
|
||||
]);
|
||||
}
|
||||
|
||||
$usePoint = (int) $usePoint;
|
||||
|
||||
// Get user's available points
|
||||
$userPoints = auth()->user()->customer->point ?? 0;
|
||||
|
||||
// Check if user has enough points
|
||||
if ($usePoint > $userPoints) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'You cannot use more points than available'
|
||||
]);
|
||||
}
|
||||
|
||||
// Set points in session
|
||||
session(['use_point' => $usePoint]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Points applied successfully',
|
||||
'points_applied' => $usePoint,
|
||||
'remaining_points' => $userPoints - $usePoint
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error applying points: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'An error occurred while applying points'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove points from session
|
||||
*/
|
||||
public function removePoint(Request $request)
|
||||
{
|
||||
try {
|
||||
// Check if points are currently applied
|
||||
$currentPoints = session('use_point', 0);
|
||||
|
||||
if ($currentPoints <= 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'No points are currently applied'
|
||||
]);
|
||||
}
|
||||
|
||||
// Remove points from session
|
||||
session()->forget('use_point');
|
||||
|
||||
// Get user's available points
|
||||
$userPoints = auth()->user()->customer->point ?? 0;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Points removed successfully',
|
||||
'points_removed' => $currentPoints,
|
||||
'available_points' => $userPoints
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error removing points: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'An error occurred while removing points'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class VoucherEventController extends Controller
|
|||
// Call the repository's redeem method
|
||||
$result = $this->voucherEventRepository->redeem($voucherEvent, $user);
|
||||
|
||||
if ($result['success']) {
|
||||
if ($result) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $result['message'] ?? 'Voucher redeemed successfully!',
|
||||
|
|
|
|||
|
|
@ -56,4 +56,16 @@ return [
|
|||
'terms_and_conditions' => 'Terms and Conditions',
|
||||
'close' => 'Close',
|
||||
'redeem' => 'Redeem',
|
||||
'use_point' => 'Use Points',
|
||||
'your_points' => 'Your Points',
|
||||
'available_points' => 'Available Points',
|
||||
'input_point' => 'Enter points to use',
|
||||
'apply_point' => 'Apply Points',
|
||||
'remove_point' => 'Remove Points',
|
||||
'points_applied' => 'Points Applied',
|
||||
'points_used' => 'points used',
|
||||
'saving' => 'Discount:',
|
||||
'shipping' => 'Shipping:',
|
||||
'estimated_total' => 'Estimated total:',
|
||||
'proceed_to_checkout' => 'Proceed to checkout',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -50,10 +50,22 @@ return [
|
|||
'enter_valid_promo_code' => 'Masukkan kode promo yang valid!',
|
||||
'apply' => 'Gunakan',
|
||||
'available_vouchers' => 'Voucher Tersedia',
|
||||
'available_voucher_events' => 'Event Voucher Tersedia',
|
||||
'available_voucher_events' => 'Tukar Poin',
|
||||
'points_required' => 'Poin Diperlukan',
|
||||
'valid_period' => 'Periode Berlaku',
|
||||
'terms_and_conditions' => 'Syarat dan Ketentuan',
|
||||
'close' => 'Tutup',
|
||||
'redeem' => 'Tukar',
|
||||
'use_point' => 'Gunakan Poin',
|
||||
'your_points' => 'Poin Anda',
|
||||
'available_points' => 'Poin Tersedia',
|
||||
'input_point' => 'Masukkan poin yang akan digunakan',
|
||||
'apply_point' => 'Gunakan Poin',
|
||||
'remove_point' => 'Hapus Poin',
|
||||
'points_applied' => 'Poin Diterapkan',
|
||||
'points_used' => 'poin digunakan',
|
||||
'saving' => 'Diskon:',
|
||||
'shipping' => 'Pengiriman:',
|
||||
'estimated_total' => 'Total estimasi:',
|
||||
'proceed_to_checkout' => 'Lanjut ke checkout',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -186,26 +186,26 @@
|
|||
<span class="text-dark-emphasis fw-medium" id="cart-subtotal">Rp 0</span>
|
||||
</li>
|
||||
<li class="d-flex justify-content-between">
|
||||
Saving:
|
||||
<span class="text-danger fw-medium">Rp 0</span>
|
||||
{{ __('checkout.saving') }}
|
||||
<span class="text-danger fw-medium">Rp {{ number_format(session('use_point') ?? 0,0,",",".") }}</span>
|
||||
</li>
|
||||
{{-- <li class="d-flex justify-content-between" id="tax-row" style="display: none;">
|
||||
Tax collected:
|
||||
<span class="text-dark-emphasis fw-medium">Rp 0</span>
|
||||
</li> --}}
|
||||
<li class="d-flex justify-content-between">
|
||||
Shipping:
|
||||
{{ __('checkout.shipping') }}
|
||||
<span class="text-dark-emphasis fw-medium">Calculated at checkout</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="border-top pt-4 mt-4">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="fs-sm">Estimated total:</span>
|
||||
<span class="fs-sm">{{ __('checkout.estimated_total') }}</span>
|
||||
<span class="h5 mb-0" id="cart-estimated-total">$0.00</span>
|
||||
</div>
|
||||
<a class="btn btn-lg btn-primary w-100"
|
||||
href="{{ route('checkout.delivery') }}">
|
||||
Proceed to checkout
|
||||
{{ __('checkout.proceed_to_checkout') }}
|
||||
<i class="ci-chevron-right fs-lg ms-1 me-n1"></i>
|
||||
</a>
|
||||
{{-- <div class="nav justify-content-center fs-sm mt-3">
|
||||
|
|
@ -217,7 +217,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<x-checkout.promo-code />
|
||||
{{-- <x-checkout.promo-code /> --}}
|
||||
<x-checkout.use-point />
|
||||
</div>
|
||||
</aside>
|
||||
@endif
|
||||
|
|
@ -1039,7 +1040,7 @@
|
|||
}
|
||||
|
||||
// Calculate estimated total (subtotal - savings + tax)
|
||||
const savings = 0; // Fixed savings amount
|
||||
const savings = {{ session('use_point') ?? 0 }}; // Fixed savings amount
|
||||
const tax = 0; // Fixed tax amount
|
||||
const estimatedTotal = subtotal - savings + tax;
|
||||
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
<!-- Order summary (sticky sidebar) -->
|
||||
<aside class="col-lg-4 offset-xl-1" style="margin-top: -100px">
|
||||
<div class="position-sticky top-0" style="padding-top: 100px">
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total" :savings="0" :tax="0"
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total"
|
||||
:showEdit="true" :editUrl="route('cart.index')" />
|
||||
</div>
|
||||
</aside>
|
||||
|
|
@ -113,6 +113,7 @@
|
|||
});
|
||||
|
||||
function updateOrderSummaryWithShippingCost(shippingValue) {
|
||||
const saving = {{ session('use_point') ?? 0 }};
|
||||
console.log('updateOrderSummaryWithShippingCost called with:', shippingValue);
|
||||
|
||||
// Parse the shipping value: courier|service|cost
|
||||
|
|
@ -138,7 +139,7 @@
|
|||
|
||||
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||
console.log('Current subtotal:', currentSubtotal);
|
||||
const newTotal = currentSubtotal + cost;
|
||||
const newTotal = currentSubtotal + cost - saving;
|
||||
console.log('New total:', newTotal);
|
||||
|
||||
// Store original total
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@
|
|||
<!-- Order summary (sticky sidebar) -->
|
||||
<aside class="col-lg-4 offset-xl-1" style="margin-top: -100px">
|
||||
<div class="position-sticky top-0" style="padding-top: 100px">
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total" :savings="0" :tax="0"
|
||||
<x-checkout.order-summary :subtotal="$subtotal" :total="$total"
|
||||
:showEdit="true" :editUrl="route('cart.index')" />
|
||||
</div>
|
||||
</aside>
|
||||
|
|
@ -428,11 +428,12 @@
|
|||
function updateOrderSummaryWithShipping() {
|
||||
// Simulate shipping cost calculation based on postcode
|
||||
const shippingCost = calculateShippingCost(document.getElementById('zip').value);
|
||||
const saving = {{ session('use_point') ?? 0 }};
|
||||
|
||||
// Update order summary
|
||||
const subtotalElement = document.getElementById('cart-subtotal');
|
||||
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
|
||||
const newTotal = currentSubtotal + shippingCost;
|
||||
const newTotal = currentSubtotal + 0 - saving;
|
||||
|
||||
// Store original total
|
||||
if (!window.originalTotal) {
|
||||
|
|
@ -481,40 +482,15 @@
|
|||
}
|
||||
|
||||
function showShippingCost(cost, isFree = false) {
|
||||
// Find or create shipping cost element in order summary
|
||||
let shippingElement = document.querySelector('[data-shipping-cost]');
|
||||
|
||||
if (!shippingElement) {
|
||||
const orderSummary = document.querySelector('.list-unstyled');
|
||||
const shippingLi = document.createElement('li');
|
||||
shippingLi.className = 'd-flex justify-content-between';
|
||||
shippingLi.setAttribute('data-shipping-cost', '');
|
||||
shippingLi.innerHTML = `
|
||||
<span>{{ __('checkout.shipping') }}:</span>
|
||||
<span class="text-dark-emphasis fw-medium" id="shipping-cost">Rp 0</span>
|
||||
`;
|
||||
orderSummary.appendChild(shippingLi);
|
||||
shippingElement = shippingLi;
|
||||
}
|
||||
|
||||
const costElement = document.getElementById('shipping-cost');
|
||||
if (isFree) {
|
||||
costElement.textContent = '{{ __('checkout.free') }}';
|
||||
costElement.className = 'text-success fw-medium';
|
||||
} else {
|
||||
costElement.textContent = `Rp ${number_format(cost, 0, ',', '.')}`;
|
||||
costElement.className = 'text-dark-emphasis fw-medium';
|
||||
}
|
||||
|
||||
shippingElement.style.display = 'flex';
|
||||
|
||||
}
|
||||
|
||||
function validateDeliveryForm() {
|
||||
const postcode = document.getElementById('zip').value;
|
||||
const firstName = document.getElementById('firstName').value;
|
||||
const address = document.getElementById('address').value;
|
||||
const city = document.getElementById('city').value;
|
||||
const phone = document.getElementById('phone').value;
|
||||
const postcode = document.getElementById('zip').textContent;
|
||||
const firstName = document.getElementById('firstName').textContent;
|
||||
const address = document.getElementById('address').textContent;
|
||||
const city = document.getElementById('city').textContent;
|
||||
const phone = document.getElementById('phone').textContent;
|
||||
|
||||
if (!postcode) {
|
||||
alert('{{ __('checkout.enter_postcode') }}');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
@props([
|
||||
'subtotal' => 0,
|
||||
'total' => 0,
|
||||
'savings' => 0,
|
||||
'savings' => session('use_point'),
|
||||
'tax' => 0,
|
||||
'showEdit' => false,
|
||||
'editUrl' => null,
|
||||
|
|
@ -36,9 +36,7 @@
|
|||
</div>
|
||||
<ul class="list-unstyled fs-sm gap-3 mb-0">
|
||||
<li class="d-flex justify-content-between">
|
||||
<div>{{ __('cart_summary.subtotal') }} (
|
||||
{{ __('cart_summary.items_count', ['count' => auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0]) }}):
|
||||
</div>
|
||||
<div>{{ __('cart_summary.subtotal') }} ({{ __('cart_summary.items_count', ['count' => auth()->check() ? \App\Repositories\Member\Cart\MemberCartRepository::getCount() : 0]) }})</div>
|
||||
<span class="text-dark-emphasis fw-medium" id="cart-subtotal">Rp
|
||||
{{ number_format($subtotal, 0, ',', '.') }}</span>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -48,8 +48,13 @@
|
|||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@foreach($vouchers as $voucher)
|
||||
<span class="badge bg-light text-dark px-3 py-2">
|
||||
{{ $voucher->code }}
|
||||
<span class="badge bg-light text-dark px-3 py-2"
|
||||
style="cursor: pointer; transition: all 0.2s ease;"
|
||||
onclick="applyVoucher('{{ $voucher->code }}')"
|
||||
onmouseover="this.style.backgroundColor='#e9ecef'; this.style.transform='scale(1.05)';"
|
||||
onmouseout="this.style.backgroundColor='#f8f9fa'; this.style.transform='scale(1)';"
|
||||
title="Click to apply {{ $voucher->code }}">
|
||||
{{ $voucher->description }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
|
|
@ -187,6 +192,84 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
|
||||
});
|
||||
|
||||
// Handle promo code form submission
|
||||
document.addEventListener('submit', function(e) {
|
||||
const form = e.target;
|
||||
|
||||
// Check if this is the promo code form
|
||||
if (!form || !form.classList.contains('needs-validation')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const promoInput = form.querySelector('input[placeholder="{{ __('checkout.enter_promo_code') }}"]');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.textContent;
|
||||
|
||||
if (!promoInput || !promoInput.value.trim()) {
|
||||
alert('Please enter a promo code');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Applying...';
|
||||
|
||||
// Get CSRF token
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
// Submit voucher code to server
|
||||
fetch('/apply-voucher', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
'promo_code': promoInput.value.trim()
|
||||
}).toString()
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Voucher applied successfully!');
|
||||
|
||||
// Clear input
|
||||
promoInput.value = '';
|
||||
|
||||
// Add visual feedback
|
||||
promoInput.style.backgroundColor = '#d4edda';
|
||||
promoInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
promoInput.style.backgroundColor = '';
|
||||
promoInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to apply voucher');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error applying voucher:', error);
|
||||
alert('An error occurred while applying the voucher');
|
||||
})
|
||||
.finally(() => {
|
||||
// Restore button state
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle redeem button click
|
||||
document.getElementById('redeemVoucherBtn').addEventListener('click', function() {
|
||||
const voucherEventId = this.getAttribute('data-voucher-event-id');
|
||||
|
|
@ -215,14 +298,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
// Show success message
|
||||
alert('Voucher redeemed successfully!');
|
||||
|
||||
// Close modal
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('voucherEventModal'));
|
||||
modal.hide();
|
||||
|
||||
// Reload page to update cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to redeem voucher');
|
||||
|
|
@ -259,6 +335,34 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
});
|
||||
});
|
||||
|
||||
// Function to apply voucher code when badge is clicked
|
||||
function applyVoucher(voucherCode) {
|
||||
// Find the promo code input
|
||||
const promoInput = document.querySelector('input[placeholder="{{ __('checkout.enter_promo_code') }}"]');
|
||||
|
||||
if (promoInput) {
|
||||
// Set the voucher code
|
||||
promoInput.value = voucherCode;
|
||||
|
||||
// Add visual feedback
|
||||
promoInput.style.backgroundColor = '#d4edda';
|
||||
promoInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
promoInput.style.backgroundColor = '';
|
||||
promoInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Focus on the input
|
||||
promoInput.focus();
|
||||
|
||||
// Optionally submit the form automatically
|
||||
// Uncomment the next line if you want auto-submission
|
||||
// promoInput.closest('form').requestSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
<div class="accordion bg-body-tertiary rounded-5 p-4">
|
||||
<div class="accordion-item border-0">
|
||||
<h3 class="accordion-header" id="promoCodeHeading">
|
||||
<button type="button"
|
||||
class="accordion-button animate-underline collapsed py-0 ps-sm-2 ps-lg-0 ps-xl-2"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#promoCode"
|
||||
aria-expanded="false"
|
||||
aria-controls="promoCode">
|
||||
<i class="ci-percent fs-xl me-2"></i>
|
||||
<span class="animate-target me-2">
|
||||
{{ __('checkout.use_point') }}
|
||||
</span>
|
||||
</button>
|
||||
</h3>
|
||||
|
||||
<div class="accordion-collapse collapse"
|
||||
id="promoCode"
|
||||
aria-labelledby="promoCodeHeading">
|
||||
|
||||
<div class="accordion-body pt-3 pb-2 ps-sm-2 px-lg-0 px-xl-2">
|
||||
|
||||
{{-- Your Points --}}
|
||||
<div class="alert bg-white dark:bg-gray-800 text-white mb-3">
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div>
|
||||
<h4 class="mb-0 fw-bold text-dark dark:text-white" id="userPoints">{{ number_format(auth()->user()->customer->point, 0, ",",".") }}</h4>
|
||||
<small class="text-black dark:text-white">{{ __('checkout.available_points') }}</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Points Form --}}
|
||||
@if(session('use_point'))
|
||||
{{-- Points Applied Display --}}
|
||||
<div class="alert alert-success d-flex align-items-center gap-3">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-1">{{ __('checkout.points_applied') }}</h6>
|
||||
<p class="mb-0">
|
||||
<strong>{{ number_format(session('use_point'), 0, ",", ".") }}</strong> {{ __('checkout.points_used') }}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm" id="remove-points-btn">
|
||||
{{ __('checkout.remove_point') }}
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
{{-- Apply Points Form --}}
|
||||
<form class="needs-validation d-flex gap-2" novalidate>
|
||||
<div class="position-relative w-100">
|
||||
<input type="number"
|
||||
class="form-control"
|
||||
id="input-point"
|
||||
min="1"
|
||||
max="{{ auth()->user()->customer->point }}"
|
||||
placeholder="{{ __('checkout.input_point') }}"
|
||||
required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-dark">
|
||||
{{ __('checkout.apply_point') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{{-- SINGLE REUSABLE MODAL --}}
|
||||
<div class="modal fade" id="voucherEventModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content rounded-4">
|
||||
<div class="modal-header border-0">
|
||||
<h5 class="modal-title fw-semibold">
|
||||
Voucher Event
|
||||
</h5>
|
||||
<button type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body pt-0">
|
||||
<div id="voucherEventContent"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal">
|
||||
{{ __('checkout.close') }}
|
||||
</button>
|
||||
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
id="redeemVoucherBtn"
|
||||
data-voucher-event-id="">
|
||||
{{ __('checkout.redeem') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
const modal = document.getElementById('voucherEventModal');
|
||||
|
||||
modal.addEventListener('show.bs.modal', function (event) {
|
||||
|
||||
const button = event.relatedTarget;
|
||||
|
||||
const name = button.getAttribute('data-name');
|
||||
const description = button.getAttribute('data-description');
|
||||
const point = button.getAttribute('data-point');
|
||||
const start = button.getAttribute('data-start');
|
||||
const end = button.getAttribute('data-end');
|
||||
const terms = button.getAttribute('data-terms');
|
||||
|
||||
let content = '';
|
||||
|
||||
if (description) {
|
||||
content += `<p>${description}</p>`;
|
||||
}
|
||||
|
||||
if (point) {
|
||||
content += `
|
||||
<div class="mb-2">
|
||||
<strong>{{ __('checkout.points_required') }}:</strong>
|
||||
${point}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (start && end) {
|
||||
content += `
|
||||
<div class="mb-2">
|
||||
<strong>{{ __('checkout.valid_period') }}:</strong><br>
|
||||
${start} - ${end}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (terms) {
|
||||
content += `
|
||||
<div class="mt-3 small text-muted">
|
||||
<strong>{{ __('checkout.terms_and_conditions') }}:</strong>
|
||||
<p>${terms}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
modal.querySelector('.modal-title').textContent = name;
|
||||
document.getElementById('voucherEventContent').innerHTML = content;
|
||||
|
||||
// Set voucher event ID for redeem button
|
||||
const redeemBtn = document.getElementById('redeemVoucherBtn');
|
||||
redeemBtn.setAttribute('data-voucher-event-id', button.getAttribute('data-id'));
|
||||
|
||||
});
|
||||
|
||||
// Handle points form submission
|
||||
document.addEventListener('submit', function(e) {
|
||||
const form = e.target;
|
||||
|
||||
// Check if this is the points form
|
||||
if (!form || !form.classList.contains('needs-validation')) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const pointInput = form.querySelector('input[placeholder="{{ __('checkout.input_point') }}"]');
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
const originalText = submitBtn.textContent;
|
||||
const availablePoints = parseInt(document.getElementById('userPoints').textContent.replace(/\./g, ''));
|
||||
|
||||
if (!pointInput || !pointInput.value.trim()) {
|
||||
alert('Please enter points to use');
|
||||
return;
|
||||
}
|
||||
|
||||
const pointsToUse = parseInt(pointInput.value.trim());
|
||||
|
||||
if (isNaN(pointsToUse) || pointsToUse <= 0) {
|
||||
alert('Please enter a valid number of points');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointsToUse > availablePoints) {
|
||||
alert('You cannot use more points than available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Applying...';
|
||||
|
||||
// Get CSRF token
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
// Submit points to server to set session
|
||||
fetch('/apply-points', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
'use_point': pointsToUse
|
||||
}).toString()
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Points applied successfully!');
|
||||
|
||||
// Clear input
|
||||
pointInput.value = '';
|
||||
|
||||
// Add visual feedback
|
||||
pointInput.style.backgroundColor = '#d4edda';
|
||||
pointInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
pointInput.style.backgroundColor = '';
|
||||
pointInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to apply points');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error applying points:', error);
|
||||
alert('An error occurred while applying the points');
|
||||
})
|
||||
.finally(() => {
|
||||
// Restore button state
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = originalText;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle remove points button click
|
||||
document.getElementById('remove-points-btn')?.addEventListener('click', function() {
|
||||
const removeBtn = this;
|
||||
const originalText = removeBtn.textContent;
|
||||
|
||||
// Show loading state
|
||||
removeBtn.disabled = true;
|
||||
removeBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Removing...';
|
||||
|
||||
// Get CSRF token
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
// Remove points via AJAX
|
||||
fetch('/remove-points', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Points removed successfully!');
|
||||
|
||||
// Clear input field
|
||||
const pointInput = document.querySelector('input#input-point');
|
||||
if (pointInput) {
|
||||
pointInput.value = '';
|
||||
pointInput.style.backgroundColor = '#f8d7da';
|
||||
pointInput.style.borderColor = '#dc3545';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
pointInput.style.backgroundColor = '';
|
||||
pointInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to remove points');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error removing points:', error);
|
||||
alert('An error occurred while removing the points');
|
||||
})
|
||||
.finally(() => {
|
||||
// Restore button state
|
||||
removeBtn.disabled = false;
|
||||
removeBtn.textContent = originalText;
|
||||
});
|
||||
});
|
||||
|
||||
// Handle redeem button click
|
||||
document.getElementById('redeemVoucherBtn').addEventListener('click', function() {
|
||||
const voucherEventId = this.getAttribute('data-voucher-event-id');
|
||||
|
||||
if (!voucherEventId) {
|
||||
console.error('No voucher event ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button during processing
|
||||
this.disabled = true;
|
||||
this.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Processing...';
|
||||
|
||||
// Make AJAX call to redeem voucher
|
||||
fetch(`/voucher-events/${voucherEventId}/redeem`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Show success message
|
||||
alert('Voucher redeemed successfully!');
|
||||
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to redeem voucher');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while redeeming the voucher');
|
||||
})
|
||||
.finally(() => {
|
||||
// Re-enable button
|
||||
this.disabled = false;
|
||||
this.innerHTML = '{{ __('checkout.redeem') }}';
|
||||
});
|
||||
});
|
||||
|
||||
// Custom modal handler to prevent backdrop issues
|
||||
const voucherBadges = document.querySelectorAll('[data-bs-toggle="modal"][data-bs-target="#voucherEventModal"]');
|
||||
voucherBadges.forEach(function(badge) {
|
||||
badge.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove any existing backdrop
|
||||
const existingBackdrop = document.querySelector('.modal-backdrop');
|
||||
if (existingBackdrop) {
|
||||
existingBackdrop.remove();
|
||||
}
|
||||
|
||||
// Show modal without backdrop
|
||||
const modalInstance = new bootstrap.Modal(modal, {
|
||||
backdrop: false
|
||||
});
|
||||
modalInstance.show();
|
||||
});
|
||||
});
|
||||
|
||||
// Function to apply voucher code when badge is clicked
|
||||
function applyVoucher(voucherCode) {
|
||||
// Find the point input
|
||||
const pointInput = document.querySelector('input#input-point');
|
||||
|
||||
if (pointInput) {
|
||||
|
||||
// set point session via AJAX
|
||||
const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
|
||||
fetch('/apply-points', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-TOKEN': token,
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
'use_point': voucherCode
|
||||
}).toString()
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Set the voucher code in input
|
||||
pointInput.value = voucherCode;
|
||||
|
||||
// Add visual feedback
|
||||
pointInput.style.backgroundColor = '#d4edda';
|
||||
pointInput.style.borderColor = '#28a745';
|
||||
|
||||
// Remove visual feedback after 2 seconds
|
||||
setTimeout(() => {
|
||||
pointInput.style.backgroundColor = '';
|
||||
pointInput.style.borderColor = '';
|
||||
}, 2000);
|
||||
|
||||
// Show success message
|
||||
alert('Points applied successfully!');
|
||||
|
||||
// Reload page to show updated cart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Show error message
|
||||
alert(data.message || 'Failed to apply points');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error applying points:', error);
|
||||
alert('An error occurred while applying the points');
|
||||
});
|
||||
|
||||
// Focus on the input
|
||||
pointInput.focus();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
@ -133,6 +133,7 @@
|
|||
showLoginDialog();
|
||||
} else {
|
||||
// You could show an error message here
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,10 @@ Route::middleware(['auth'])->prefix('/checkout')->group(function () {
|
|||
|
||||
});
|
||||
|
||||
// Apply points route (outside checkout prefix for easier access)
|
||||
Route::post('/apply-points', [CheckoutController::class, 'applyPoint'])->name('checkout.apply.points');
|
||||
Route::post('/remove-points', [CheckoutController::class, 'removePoint'])->name('checkout.remove.points');
|
||||
|
||||
Route::middleware(['auth'])->prefix('/voucher-events')->group(function () {
|
||||
Route::post('/{voucherEvent}/redeem', [VoucherEventController::class, 'redeem'])->name('voucher-events.redeem');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue