checkout & create payment

This commit is contained in:
Bayu Lukman Yusuf 2026-01-29 08:26:00 +07:00
parent d80b42c297
commit fa00ee377a
8 changed files with 396 additions and 351 deletions

View File

@ -6,6 +6,7 @@ use App\Models\Address;
use App\Models\Location; use App\Models\Location;
use App\Repositories\Member\Cart\MemberCartRepository; use App\Repositories\Member\Cart\MemberCartRepository;
use App\Repositories\Member\ShippingRepository; use App\Repositories\Member\ShippingRepository;
use App\Repositories\Member\Transaction\TransactionRepository;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class CheckoutController extends Controller class CheckoutController extends Controller
@ -77,6 +78,12 @@ class CheckoutController extends Controller
$location_id = session('location_id', 22); $location_id = session('location_id', 22);
if ($delivery_method == null || $address_id == null || $location_id == null) {
return redirect()->route('checkout.delivery');
}
$subtotal = $memberCartRepository->getSubtotal($location_id); $subtotal = $memberCartRepository->getSubtotal($location_id);
$total = $subtotal; $total = $subtotal;
@ -97,11 +104,6 @@ class CheckoutController extends Controller
'cost' => $row['shipping_fee'], 'cost' => $row['shipping_fee'],
]; ];
}); });
return view('checkout.v1-delivery-1-shipping', [ return view('checkout.v1-delivery-1-shipping', [
'carts' => $request->user()->carts, 'carts' => $request->user()->carts,
@ -113,8 +115,6 @@ class CheckoutController extends Controller
'shipping_list' => $shipping_list, 'shipping_list' => $shipping_list,
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
dd($e);
return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data'); return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data');
} }
} }
@ -131,22 +131,73 @@ class CheckoutController extends Controller
session(['checkout_courier' => $courier]); session(['checkout_courier' => $courier]);
session(['checkout_service' => $service]); session(['checkout_service' => $service]);
session(['checkout_shipping_cost' => $cost]); // session(['checkout_shipping_cost' => $cost]);
return redirect()->route('checkout.payment'); return redirect()->route('checkout.payment');
} }
public function choosePayment(Request $request) public function choosePayment(Request $request, TransactionRepository $repository, MemberCartRepository $memberCartRepository)
{ {
try { try {
// proses checkout
$address_id = session('checkout_address_id');
$courier = session('checkout_courier');
$service = session('checkout_service');
$location_id = session('location_id', 22);
$items = [];
$request->merge(['location_id' => $location_id]);
$carts = $memberCartRepository->getList($request);
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
];
}
$data = [
"address_id" => $address_id,
"note" => "",
"courier_company" => $courier,
"courier_type" => $service,
"location_id" => $location_id,
"items" => $items,
"vouchers" => [],
"use_customer_points" => 0,
];
$item = $repository->create($data);
$notification = new \App\Notifications\Member\Transaction\OrderWaitPayment($item);
$user = auth()->user();
$user->notify($notification->delay(now()->addMinutes(1)));
// return new CheckoutResource($item);
// proses payment // 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);
} catch (\Exception $e) { } catch (\Exception $e) {
dd($e);
return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data'); return redirect()->route('checkout.delivery')->with('error', 'Invalid checkout data');
} }
} }

View File

@ -6,8 +6,9 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Awobaz\Compoships\Compoships; use Awobaz\Compoships\Compoships;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Cache;
class ItemReference extends Model class ItemReference extends Model
{ {
@ -126,9 +127,7 @@ class ItemReference extends Model
}); });
// Log::info([$location_id, $is_consignment]); return $this->hasOne(Discount::class, 'id', 'item_reference_id')
return $this->hasOne(Discount::class,DB::raw("discount_items.item_reference_id"))
->leftJoin("discount_items","discounts.id","=","discount_id") ->leftJoin("discount_items","discounts.id","=","discount_id")
->where("discounts.type","discount") ->where("discounts.type","discount")
->orderBy("discounts.created_at","desc") ->orderBy("discounts.created_at","desc")

View File

@ -0,0 +1,66 @@
<?php
namespace App\Repositories\Auth;
use App\Models\Permission;
use App\Models\Role;
use DB;
use Hash;
class RoleRepository
{
public function getList(array $params = [])
{
$limit = @$params["limit"] ? (int) @$params["limit"] : 10;
$sortColumn = @$params["sort"]["column"] ? $params["sort"]["column"] : "id";
$sortDir = @$params["sort"]["dir"] ? $params["sort"]["dir"] : "desc";
return Role::when(@$params['search'], function($query) use ($params) {
return $query->where('name', 'iLIKE', '%' . $params['search'] . '%');
})
->orderBy($sortColumn, $sortDir)
->paginate($limit);
}
public function getPermission(array $params = [])
{
return Permission::orderBy("code")->paginate(1000);
}
public function create(array $data)
{
$item = Role::create($data);
$item->permissions()->sync($data["permissions"]);
$item->save();
return $item;
}
public function update(Role $item, array $data)
{
$item->update($data);
$item->permissions()->sync($data["permissions"]);
$item->save();
return $item;
}
public function delete(Role $item)
{
$item->delete();
}
public function findBy($column, $value)
{
$item = Role::where($column, $value)->firstOrFail();
return $item;
}
public function findUserByPermission($code)
{
$roles = Role::whereHas("permissions", function($query) use ($code){
$query->where("code", $code);
})->get();
return $roles->flatMap(function($role){
return $role->users;
});
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace App\Http\Controllers\Member\Transaction;
use App\Http\Controllers\Controller;
use App\Http\Requests\Member\Transaction\TransactionRequest;
use App\Http\Resources\Member\Transaction\CheckoutResource;
use App\Repositories\Member\Transaction\TransactionRepository;
use App\Notifications\Member\Transaction\OrderWaitPayment;
use Illuminate\Support\Facades\Notification;
class CheckoutController extends Controller
{
public function index(TransactionRequest $request, TransactionRepository $repository)
{
$data = $request->validated();
$item = $repository->create($data);
$notification = new OrderWaitPayment($item);
$user = auth()->user();
$user->notify($notification->delay(now()->addMinutes(1)));
return new CheckoutResource($item);
}
}

View File

@ -0,0 +1,149 @@
<?php
namespace App\Repositories\Member\Voucher;
use App\Models\Voucher;
use App\Models\Customer;
use App\Models\User;
use App\Models\Cart;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Carbon\Carbon;
class VoucherRepository
{
public function getList($request)
{
$user_id = auth()->user()->id;
$customer = Customer::where("user_id", $user_id)->first();
$model = Voucher::where('customer_id', (int) @$customer->id)
->where('used_at', null)
->where('expired_at','>=',Carbon::now())
->orderBy('created_at','desc')->paginate($request->limit);
return $model;
}
public function getValidByItems($carts, $customer_id)
{
$item_reference_ids = $carts->pluck("item_reference_id")->toArray();
// collect voucher ids valids
if (count($item_reference_ids) == 0){
$item_reference_ids[] = 0;
}
$item_ids = implode(',', $item_reference_ids);
$ids = DB::select("SELECT distinct vouchers.id from vouchers
left join voucher_events on vouchers.voucher_event_id = voucher_events.id
left join voucher_items on voucher_items.voucher_event_id = voucher_events.id
where used_at is null and
(expired_at > now() or expired_at is null) and
customer_id = ? and (
redeem_point > 0 or
( vouchers.voucher_event_id is null and vouchers.item_reference_id is null) or
( vouchers.voucher_event_id is not null and voucher_items.item_reference_id is null) or
( vouchers.item_reference_id is not null and vouchers.item_reference_id in ($item_ids)) or
( vouchers.voucher_event_id is not null and voucher_items.item_reference_id in ($item_ids))
)",[$customer_id]);
$ids = collect($ids)->pluck("id")->toArray();
return $ids;
}
public function getSum($params)
{
$user_id = auth()->user()->id;
$customer = Customer::where("user_id", $user_id)->firstOrFail();
$voucher_ids = $params["voucher_ids"];
// collect item_reference_ids
$cart_ids = @$params["cart_ids"] ?? [];
$carts = Cart::where("user_id", $user_id)
->where(function($query) use ($cart_ids){
if (count($cart_ids)){
$query->whereIn("id", $cart_ids);
}
})
->get();
$ids = $this->getValidByItems($carts, $customer->id);
return Voucher::where('customer_id', (int) @$customer->id)
->where('used_at', null)
->whereIn('id', $ids)
->whereIn('id', $ids)
->orderBy('created_at','desc')
->get();
}
public function getValid($params)
{
$user_id = auth()->user()->id;
$customer = Customer::where("user_id", $user_id)->first();
$limit = $params["limit"] ?? 10;
// collect item_reference_ids
$cart_ids = @$params["cart_ids"] ?? [];
$carts = Cart::where("user_id", $user_id)
->where(function($query) use ($cart_ids){
if (count($cart_ids)){
$query->whereIn("id", $cart_ids);
}
})
->get();
$ids = $this->getValidByItems($carts, $customer->id);
return Voucher::where('customer_id', (int) @$customer->id)
->where('used_at', null)
->orderBy('created_at','desc')->paginate($limit)
->transform(function ($voucher, $key) use ($ids) {
$voucher->is_valid = in_array($voucher->id, $ids);
return $voucher;
});
}
public function detail($id)
{
$model = Voucher::findOrFail($id);
return $model;
}
public function send($id, $data)
{
$model = DB::transaction(function () use ($id, $data) {
$customer = Customer::where("user_id", $data['customer_id'])->firstOrFail();
$model = Voucher::findOrFail($id);
\Log::info("VOUCHER SEND FROM ".$model->customer_id." TO ".$customer->id);
$model->customer_id = $customer->id;
$model->save();
return $model;
});
return $model;
}
public function phoneCheck($data)
{
$model = DB::transaction(function () use ($data) {
$model = Customer::where('phone', $data['phone'])->first();
if ($model->user_id == null) {
throw ValidationException::withMessages([
"phone" => "User dari phone number belum terdaftar"
]);
}
$model = User::findOrfail($model->user_id);
return $model;
});
return $model;
}
}

24
app/ThirdParty/Xendit/Xendit.php vendored Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace App\ThirdParty\Xendit;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;
class Xendit
{
public function createPaymentLink($payload){
$url = "https://api.xendit.co/v2/invoices";
$key = env("XENDIT_PRIVATE_KEY");
$res = Http::withBasicAuth($key, "")
->withBody(json_encode($payload), 'application/json')
->post($url);
if ($res->status() == 200)
return $res->json();
return null;
}
}

View File

@ -37,9 +37,7 @@
</h1> </h1>
<form action="{{ route('checkout.shipping.process') }}" method="post"> <form action="{{ route('checkout.shipping.process') }}" method="post">
@csrf @csrf
<input type="hidden" name="delivery_method" value="{{ $delivery_method }}">
<input type="hidden" name="address_id" value="{{ $address_id }}">
@if ($delivery_method == 'shipping') @if ($delivery_method == 'shipping')
@foreach ($shipping_list as $shipping) @foreach ($shipping_list as $shipping)
<div class="form-check mb-3"> <div class="form-check mb-3">
@ -100,360 +98,137 @@
@section('scripts') @section('scripts')
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Delivery method selection // Shipping option selection handler
const deliveryOption = document.getElementById('deliveryOption'); const shippingRadios = document.querySelectorAll('input[name="shipping_option"]');
const pickupOption = document.getElementById('pickupOption'); console.log('Found shipping radios:', shippingRadios.length);
const deliveryOptions = document.getElementById('deliveryOptions');
const pickupOptions = document.getElementById('pickupOptions');
const shippingAddress = document.getElementById('shippingAddress');
const continueButton = document.getElementById('continueButton');
// Handle delivery method change shippingRadios.forEach((radio, index) => {
deliveryOption.addEventListener('change', function() { console.log(`Radio ${index}:`, radio.value, radio.checked);
if (this.checked) {
deliveryOptions.style.display = 'block';
pickupOptions.style.display = 'none';
resetPickupSelection();
// Update button text for delivery
document.getElementById('continueButtonText').textContent =
'{{ __('checkout.continue_to_shipping') }}';
// Update hidden input values
document.getElementById('deliveryMethodInput').value = 'delivery';
// Update address ID from selected address
const addressSelect = document.getElementById('addressSelect');
if (addressSelect && addressSelect.value) {
document.getElementById('addressIdInput').value = addressSelect.value;
}
// Show shipping address step and shipping row
const shippingAddressStep = document.getElementById('shippingAddressStep');
const shippingRow = document.getElementById('shipping-row');
if (shippingAddressStep) {
console.log("Showing shipping address step");
shippingAddressStep.style.visibility = 'visible';
shippingAddressStep.style.height = 'auto';
shippingAddressStep.style.overflow = 'visible';
shippingAddressStep.style.margin = '';
shippingAddressStep.style.padding = '';
}
if (shippingRow) {
console.log("Showing shipping row");
shippingRow.style.visibility = 'visible';
shippingRow.style.height = 'auto';
shippingRow.style.overflow = 'visible';
shippingRow.style.margin = '';
shippingRow.style.padding = '';
} else {
console.log("Shipping row not found");
}
}
});
pickupOption.addEventListener('change', function() {
if (this.checked) {
deliveryOptions.style.display = 'none';
pickupOptions.style.display = 'block';
resetDeliverySelection();
// Update button text for pickup
document.getElementById('continueButtonText').textContent =
'{{ __('checkout.continue_to_payment') }}';
// Update hidden input values
document.getElementById('deliveryMethodInput').value = 'pickup';
document.getElementById('addressIdInput').value = '';
// Hide shipping address step and shipping row
const shippingAddressStep = document.getElementById('shippingAddressStep');
const shippingRow = document.getElementById('shipping-row');
console.log("Elements found:", shippingAddressStep, shippingRow);
if (shippingAddressStep) {
console.log("Hiding shipping address step");
shippingAddressStep.style.visibility = 'hidden';
shippingAddressStep.style.height = '0';
shippingAddressStep.style.overflow = 'hidden';
shippingAddressStep.style.margin = '0';
shippingAddressStep.style.padding = '0';
}
if (shippingRow) {
console.log("Hiding shipping row");
shippingRow.style.visibility = 'hidden';
shippingRow.style.height = '0';
shippingRow.style.overflow = 'hidden';
shippingRow.style.margin = '0';
shippingRow.style.padding = '0';
} else {
console.log("Shipping row not found");
}
}
});
// Address selection handler
const addressSelect = document.getElementById('addressSelect');
if (addressSelect) {
// Auto-populate form with first selected address on page load
function populateAddressForm() {
const selectedOption = addressSelect.options[addressSelect.selectedIndex];
if (selectedOption && selectedOption.value) {
// Populate all shipping address form fields
document.getElementById('firstName').value = selectedOption.dataset.firstName || '';
document.getElementById('lastName').value = selectedOption.dataset.lastName || '';
document.getElementById('address').value = selectedOption.dataset.address || '';
document.getElementById('city').value = selectedOption.dataset.city || '';
document.getElementById('state').value = selectedOption.dataset.state || '';
document.getElementById('zip').value = selectedOption.dataset.postcode || '';
document.getElementById('phone').value = selectedOption.dataset.phone || '';
document.getElementById('postcode').value = selectedOption.dataset.postcode || '';
// Update order summary with postcode
if (selectedOption.dataset.postcode) {
updateOrderSummaryWithShipping();
}
}
}
// Populate on page load
populateAddressForm();
// Set initial address ID
if (addressSelect.value) {
document.getElementById('addressIdInput').value = addressSelect.value;
}
addressSelect.addEventListener('change', function() {
const selectedOption = this.options[this.selectedIndex];
if (this.value === 'new' || this.value === '') {
// Clear form fields for new address
document.getElementById('firstName').value = '';
document.getElementById('lastName').value = '';
document.getElementById('address').value = '';
document.getElementById('city').value = '';
document.getElementById('state').value = '';
document.getElementById('zip').value = '';
document.getElementById('phone').value = '';
document.getElementById('postcode').value = '';
// Clear address ID input
document.getElementById('addressIdInput').value = '';
} else {
// Populate form fields with selected address
document.getElementById('firstName').value = selectedOption.dataset.firstName || '';
document.getElementById('lastName').value = selectedOption.dataset.lastName || '';
document.getElementById('address').value = selectedOption.dataset.address || '';
document.getElementById('city').value = selectedOption.dataset.city || '';
document.getElementById('state').value = selectedOption.dataset.state || '';
document.getElementById('zip').value = selectedOption.dataset.postcode || '';
document.getElementById('phone').value = selectedOption.dataset.phone || '';
document.getElementById('postcode').value = selectedOption.dataset.postcode || '';
// Update address ID input
document.getElementById('addressIdInput').value = this.value;
// Update order summary with postcode
if (selectedOption.dataset.postcode) {
updateOrderSummaryWithShipping();
}
}
});
}
// Auto-update order summary when postcode changes
document.getElementById('postcode').addEventListener('input', function() {
if (this.value) {
updateOrderSummaryWithShipping();
}
});
// Store selection for pickup
const storeRadios = document.querySelectorAll('input[name="store"]');
storeRadios.forEach(radio => {
radio.addEventListener('change', function() { radio.addEventListener('change', function() {
console.log('Shipping option changed:', this.value, this.checked);
if (this.checked) { if (this.checked) {
updateOrderSummaryForPickup(); updateOrderSummaryWithShippingCost(this.value);
} }
}); });
}); });
// Form validation before continue function updateOrderSummaryWithShippingCost(shippingValue) {
continueButton.addEventListener('click', function(e) { console.log('updateOrderSummaryWithShippingCost called with:', shippingValue);
const selectedMethod = document.querySelector('input[name="deliveryMethod"]:checked').value;
if (selectedMethod === 'delivery') { // Parse the shipping value: courier|service|cost
if (!validateDeliveryForm()) { const parts = shippingValue.split('|');
e.preventDefault(); console.log('Parsed parts:', parts);
return; if (parts.length !== 3) {
} console.log('Invalid shipping value format');
} else if (selectedMethod === 'pickup') { return;
if (!validatePickupForm()) {
e.preventDefault();
return;
}
}
});
function resetDeliverySelection() {
resetShippingCalculation();
}
function resetPickupSelection() {
document.querySelectorAll('input[name="store"]').forEach(radio => {
radio.checked = false;
});
resetShippingCalculation();
}
function resetShippingCalculation() {
// Reset order summary to original state
const shippingElement = document.querySelector('[data-shipping-cost]');
if (shippingElement) {
shippingElement.style.display = 'none';
} }
const totalElement = document.getElementById('cart-estimated-total'); const courier = parts[0];
if (totalElement && window.originalTotal) { const service = parts[1];
totalElement.textContent = `Rp ${window.originalTotal}`; const cost = parseInt(parts[2]);
} console.log('Extracted cost:', cost);
}
function updateOrderSummaryWithShipping() {
// Simulate shipping cost calculation based on postcode
const shippingCost = calculateShippingCost(document.getElementById('postcode').value);
// Update order summary // Update order summary
const subtotalElement = document.getElementById('cart-subtotal'); const subtotalElement = document.getElementById('cart-subtotal');
console.log('Subtotal element:', subtotalElement);
if (!subtotalElement) {
console.log('Subtotal element not found');
return;
}
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, '')); const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
const newTotal = currentSubtotal + shippingCost; console.log('Current subtotal:', currentSubtotal);
const newTotal = currentSubtotal + cost;
console.log('New total:', newTotal);
// Store original total // Store original total
if (!window.originalTotal) { if (!window.originalTotal) {
window.originalTotal = subtotalElement.textContent; window.originalTotal = subtotalElement.textContent;
console.log('Stored original total:', window.originalTotal);
} }
// Update total // Update total
const totalElement = document.getElementById('cart-estimated-total'); const totalElement = document.getElementById('cart-estimated-total');
totalElement.textContent = `Rp ${number_format(newTotal, 0, ',', '.')}`; console.log('Total element:', totalElement);
if (totalElement) {
totalElement.textContent = `Rp ${number_format(newTotal, 0, ',', '.')}`;
console.log('Updated total to:', totalElement.textContent);
}
// Show shipping cost in summary // Show shipping cost in summary
showShippingCost(shippingCost); showShippingCost(cost);
}
function updateOrderSummaryForPickup() {
// Pickup is usually free
const subtotalElement = document.getElementById('cart-subtotal');
const currentSubtotal = parseFloat(subtotalElement.textContent.replace(/[^\d]/g, ''));
// Store original total
if (!window.originalTotal) {
window.originalTotal = subtotalElement.textContent;
}
// Update total (same as subtotal for pickup)
const totalElement = document.getElementById('cart-estimated-total');
totalElement.textContent = subtotalElement.textContent;
// Show free shipping
showShippingCost(0, true);
}
function calculateShippingCost(postcode) {
// Simple shipping cost calculation based on postcode
// In real implementation, this would call an API
const jakartaPostcodes = ['10000', '10110', '10220', '10310', '10410'];
const bandungPostcodes = ['40111', '40112', '40113', '40114', '40115'];
if (jakartaPostcodes.includes(postcode)) {
return 15000; // Jakarta: Rp 15,000
} else if (bandungPostcodes.includes(postcode)) {
return 25000; // Bandung: Rp 25,000
} else {
return 35000; // Other areas: Rp 35,000
}
} }
function showShippingCost(cost, isFree = false) { function showShippingCost(cost, isFree = false) {
// Find or create shipping cost element in order summary console.log('showShippingCost called with:', cost, isFree);
let shippingElement = document.querySelector('[data-shipping-cost]');
if (!shippingElement) { // Use existing shipping row in order summary
const orderSummary = document.querySelector('.list-unstyled'); const shippingRow = document.getElementById('shipping-row');
const shippingLi = document.createElement('li'); console.log('Shipping row element:', shippingRow);
shippingLi.className = 'd-flex justify-content-between'; if (!shippingRow) {
shippingLi.setAttribute('data-shipping-cost', ''); console.log('Shipping row not found');
shippingLi.innerHTML = ` return;
<span>{{ __('checkout.shipping') }}:</span> }
<span class="text-dark-emphasis fw-medium" id="shipping-cost">Rp 0</span>
`; const costElement = shippingRow.querySelector('span:last-child');
orderSummary.appendChild(shippingLi); console.log('Cost element:', costElement);
shippingElement = shippingLi; if (!costElement) {
console.log('Cost element not found');
return;
} }
const costElement = document.getElementById('shipping-cost');
if (isFree) { if (isFree) {
costElement.textContent = '{{ __('checkout.free') }}'; costElement.textContent = '{{ __('checkout.free') }}';
costElement.className = 'text-success fw-medium'; costElement.className = 'text-success fw-medium';
console.log('Set to free shipping');
} else { } else {
costElement.textContent = `Rp ${number_format(cost, 0, ',', '.')}`; costElement.textContent = `Rp ${number_format(cost, 0, ',', '.')}`;
costElement.className = 'text-dark-emphasis fw-medium'; costElement.className = 'text-dark-emphasis fw-medium';
console.log('Set shipping cost to:', costElement.textContent);
} }
shippingElement.style.display = 'flex'; // Ensure the shipping row is visible
shippingRow.style.display = 'flex';
console.log('Made shipping row visible');
} }
function validateDeliveryForm() { // Initialize shipping cost on page load for checked option
const postcode = document.getElementById('postcode').value; const checkedShippingRadio = document.querySelector('input[name="shipping_option"]:checked');
const firstName = document.getElementById('firstName').value; console.log('Checked shipping radio on load:', checkedShippingRadio);
const address = document.getElementById('address').value; if (checkedShippingRadio) {
const city = document.getElementById('city').value; console.log('Initializing with value:', checkedShippingRadio.value);
const phone = document.getElementById('phone').value; updateOrderSummaryWithShippingCost(checkedShippingRadio.value);
if (!postcode) {
alert('{{ __('checkout.enter_postcode') }}');
return false;
}
if (!firstName || !address || !city || !phone) {
alert('{{ __('checkout.fill_required_fields') }}');
return false;
}
return true;
} }
function validatePickupForm() { // Number formatting helper
const selectedStore = document.querySelector('input[name="store"]:checked'); function number_format(number, decimals, dec_point, thousands_sep) {
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
var dec = (typeof dec_point === 'undefined') ? '.' : dec_point;
if (!selectedStore) { var s = '';
alert('{{ __('checkout.select_store') }}'); var toFixedFix = function(n, prec) {
return false; var k = Math.pow(10, prec);
return '' + Math.round(n * k) / k;
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (thousands_sep) {
var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])) {
s[0] = s[0].replace(re, '$1' + sep + '$2');
} }
return true;
} }
// Number formatting helper if ((s[1] || '').length < prec) {
function number_format(number, decimals, dec_point, thousands_sep) { s[1] = s[1] || '';
number = (number + '').replace(/[^0-9+\-Ee.]/g, ''); s[1] += new Array(prec - s[1].length + 1).join('0');
var n = !isFinite(+number) ? 0 : +number;
var prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
var sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep;
var dec = (typeof dec_point === 'undefined') ? '.' : dec_point;
var s = '';
var toFixedFix = function(n, prec) {
var k = Math.pow(10, prec);
return '' + Math.round(n * k) / k;
};
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (thousands_sep) {
var re = /(-?\d+)(\d{3})/;
while (re.test(s[0])) {
s[0] = s[0].replace(re, '$1' + sep + '$2');
}
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
} }
return s.join(dec);
}
}); });
</script> </script>
@endsection @endsection

View File

@ -8,6 +8,13 @@
<div class="container py-5"> <div class="container py-5">
<div class="row pt-1 pt-sm-3 pt-lg-4 pb-2 pb-md-3 pb-lg-4 pb-xl-5"> <div class="row pt-1 pt-sm-3 pt-lg-4 pb-2 pb-md-3 pb-lg-4 pb-xl-5">
{{-- show error message --}}
@if(session('error'))
<div class="alert alert-danger">
{{ session('error') }}
</div>
@endif
<!-- Delivery info (Step 1) --> <!-- Delivery info (Step 1) -->
<div class="col-lg-8 col-xl-7 mb-5 mb-lg-0"> <div class="col-lg-8 col-xl-7 mb-5 mb-lg-0">
<div class="d-flex flex-column gap-5 pe-lg-4 pe-xl-0"> <div class="d-flex flex-column gap-5 pe-lg-4 pe-xl-0">