parent
aa29baab24
commit
a7c178d541
|
|
@ -18,7 +18,7 @@ class AdminForgotPasswordController extends Controller
|
||||||
// STEP 1 — Tampilkan form input email
|
// STEP 1 — Tampilkan form input email
|
||||||
// ─────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
protected FonnteService $fonnte;
|
protected $fonnte; // ✅
|
||||||
|
|
||||||
public function __construct(FonnteService $fonnte)
|
public function __construct(FonnteService $fonnte)
|
||||||
{
|
{
|
||||||
|
|
@ -84,24 +84,27 @@ private function sendOtpViaEmail(Request $request)
|
||||||
private function sendOtpViaWhatsapp(Request $request)
|
private function sendOtpViaWhatsapp(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'phone' => ['required', 'digits_between:10,15'],
|
'phone' => ['required', 'min:10', 'max:15'],
|
||||||
], [
|
], [
|
||||||
'phone.required' => 'Nomor WhatsApp wajib diisi.',
|
'phone.required' => 'Nomor WhatsApp wajib diisi.',
|
||||||
'phone.digits_between' => 'Nomor WhatsApp tidak valid (10-15 digit).',
|
'phone.min' => 'Nomor WhatsApp minimal 10 digit.',
|
||||||
]);
|
'phone.max' => 'Nomor WhatsApp maksimal 15 digit.',
|
||||||
|
]);
|
||||||
|
|
||||||
$phoneFormatted = FonnteService::formatPhone($request->phone);
|
$phone = preg_replace('/\D/', '', $request->phone);
|
||||||
|
|
||||||
// Cari user dengan nomor WA ini — WAJIB is_admin = true
|
$admin = User::where('is_admin', true)
|
||||||
$admin = User::where('is_admin', true)
|
->where(function ($q) use ($phone, $request) {
|
||||||
->where(function ($q) use ($phoneFormatted, $request) {
|
$q->where('phone', $phone)
|
||||||
$q->where('phone', $phoneFormatted)
|
->orWhere('phone', '+' . $phone)
|
||||||
->orWhere('phone', '+' . $phoneFormatted)
|
|
||||||
->orWhere('phone', $request->phone)
|
->orWhere('phone', $request->phone)
|
||||||
->orWhere('phone', '0' . ltrim($request->phone, '0'));
|
->orWhere('phone', '0' . ltrim($phone, '0'))
|
||||||
|
->orWhere('phone', '+62' . ltrim($phone, '0'));
|
||||||
})
|
})
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
$phoneFormatted = FonnteService::formatPhone($phone);
|
||||||
|
|
||||||
if (!$admin) {
|
if (!$admin) {
|
||||||
return back()->withErrors([
|
return back()->withErrors([
|
||||||
'phone' => 'Nomor ini tidak terdaftar sebagai admin.',
|
'phone' => 'Nomor ini tidak terdaftar sebagai admin.',
|
||||||
|
|
@ -117,11 +120,21 @@ private function sendOtpViaWhatsapp(Request $request)
|
||||||
. "Berlaku selama *3 menit*.\n"
|
. "Berlaku selama *3 menit*.\n"
|
||||||
. "Jangan berikan kode ini kepada siapapun.";
|
. "Jangan berikan kode ini kepada siapapun.";
|
||||||
|
|
||||||
$sent = $this->fonnte->sendMessage($phoneFormatted, $pesan);
|
\Illuminate\Support\Facades\Log::info('Fonnte Admin Debug', [
|
||||||
|
'phone_input' => $request->phone,
|
||||||
|
'phone_formatted' => $phoneFormatted,
|
||||||
|
'admin_found' => $admin->name,
|
||||||
|
'admin_phone_db' => $admin->phone,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!$sent) {
|
$sent = $this->fonnte->sendMessage($phoneFormatted, $pesan);
|
||||||
|
|
||||||
|
if (!$sent) {
|
||||||
|
\Illuminate\Support\Facades\Log::error('Fonnte Admin GAGAL', [
|
||||||
|
'phone_formatted' => $phoneFormatted,
|
||||||
|
]);
|
||||||
return back()->with('error', 'Gagal mengirim pesan WhatsApp. Coba lagi.');
|
return back()->with('error', 'Gagal mengirim pesan WhatsApp. Coba lagi.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->session()->put('admin_reset_email', $admin->email);
|
$request->session()->put('admin_reset_email', $admin->email);
|
||||||
$request->session()->put('admin_reset_method', 'whatsapp');
|
$request->session()->put('admin_reset_method', 'whatsapp');
|
||||||
|
|
|
||||||
|
|
@ -77,20 +77,24 @@ private function sendOtpViaEmail(Request $request)
|
||||||
private function sendOtpViaWhatsapp(Request $request)
|
private function sendOtpViaWhatsapp(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'phone' => ['required', 'digits_between:10,15'],
|
'phone' => ['required', 'min:10', 'max:15'],
|
||||||
], [
|
], [
|
||||||
'phone.required' => 'Nomor WhatsApp wajib diisi.',
|
'phone.required' => 'Nomor WhatsApp wajib diisi.',
|
||||||
'phone.digits_between' => 'Nomor WhatsApp tidak valid (10-15 digit).',
|
'phone.min' => 'Nomor WhatsApp minimal 10 digit.',
|
||||||
]);
|
'phone.max' => 'Nomor WhatsApp maksimal 15 digit.',
|
||||||
|
]);
|
||||||
|
|
||||||
$phoneFormatted = FonnteService::formatPhone($request->phone);
|
$phone = preg_replace('/\D/', '', $request->phone);
|
||||||
|
|
||||||
$user = User::where('phone', $phoneFormatted)
|
$user = User::where('phone', $phone)
|
||||||
->orWhere('phone', '+' . $phoneFormatted)
|
->orWhere('phone', '+' . $phone)
|
||||||
->orWhere('phone', $request->phone)
|
->orWhere('phone', $request->phone)
|
||||||
->orWhere('phone', '0' . ltrim($request->phone, '0'))
|
->orWhere('phone', '0' . ltrim($phone, '0'))
|
||||||
|
->orWhere('phone', '+62' . ltrim($phone, '0'))
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
$phoneFormatted = FonnteService::formatPhone($phone);
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return back()->withErrors([
|
return back()->withErrors([
|
||||||
'phone' => 'Nomor WhatsApp tidak ditemukan di sistem kami.',
|
'phone' => 'Nomor WhatsApp tidak ditemukan di sistem kami.',
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ public function register(Request $request): RedirectResponse
|
||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
'name' => 'required|string|min:8|max:255',
|
'name' => 'required|string|min:8|max:255',
|
||||||
'email' => 'required|string|email|max:255|unique:users|regex:/@gmail\.com$/|regex:/^\S+$/',
|
'email' => 'required|string|email|max:255|unique:users|regex:/@gmail\.com$/|regex:/^\S+$/',
|
||||||
'phone' => 'required|string|regex:/^\+62[0-9]{9,15}$/|max:20',
|
'phone' => 'required|string|min:10|max:20',
|
||||||
'address' => 'required|string|min:12|max:500',
|
'address' => 'required|string|min:20|max:500',
|
||||||
'password' => 'required|string|min:8|confirmed|regex:/^\S+$/',
|
'password' => 'required|string|min:8|confirmed',
|
||||||
'agree' => 'required|accepted',
|
'agree' => 'required|accepted',
|
||||||
], [
|
], [
|
||||||
'name.required' => 'Nama wajib diisi.',
|
'name.required' => 'Nama wajib diisi.',
|
||||||
'name.min' => 'Nama minimal harus 8 karakter.',
|
'name.min' => 'Nama minimal harus 8 karakter.',
|
||||||
'email.required' => 'Email wajib diisi.',
|
'email.required' => 'Email wajib diisi.',
|
||||||
|
|
@ -36,38 +36,35 @@ public function register(Request $request): RedirectResponse
|
||||||
'email.unique' => 'Email sudah digunakan.',
|
'email.unique' => 'Email sudah digunakan.',
|
||||||
'email.regex' => 'Email harus menggunakan domain @gmail.com dan tidak boleh mengandung spasi.',
|
'email.regex' => 'Email harus menggunakan domain @gmail.com dan tidak boleh mengandung spasi.',
|
||||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
'phone.regex' => 'Nomor telepon harus diawali dengan +62 dan hanya boleh berisi angka.',
|
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||||
'phone.unique' => 'Nomor telepon sudah terdaftar di sistem kami.',
|
|
||||||
// 'phone.min' => 'Nomor telepon minimal harus 12 karakter.',
|
|
||||||
'address.required' => 'Alamat wajib diisi.',
|
'address.required' => 'Alamat wajib diisi.',
|
||||||
'address.min' => 'Alamat minimal harus 12 karakter.',
|
'address.min' => 'Alamat terlalu pendek, minimal 20 karakter. Harap isi lengkap.',
|
||||||
'password.required' => 'Password wajib diisi.',
|
'password.required' => 'Password wajib diisi.',
|
||||||
'password.min' => 'Password minimal harus 8 karakter.',
|
'password.min' => 'Password minimal harus 8 karakter.',
|
||||||
'password.confirmed' => 'Konfirmasi password tidak cocok.',
|
'password.confirmed'=> 'Konfirmasi password tidak cocok.',
|
||||||
'password.regex' => 'Password tidak boleh mengandung spasi.',
|
|
||||||
'agree.required' => 'Anda harus menyetujui syarat dan ketentuan.',
|
'agree.required' => 'Anda harus menyetujui syarat dan ketentuan.',
|
||||||
'agree.accepted' => 'Anda harus menyetujui syarat dan ketentuan.',
|
'agree.accepted' => 'Anda harus menyetujui syarat dan ketentuan.',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Validasi password familiar
|
// Validasi password familiar
|
||||||
// Validasi password familiar & pernah dipakai user lain
|
// Validasi password familiar & pernah dipakai user lain
|
||||||
$validator->after(function ($validator) use ($request) {
|
// $validator->after(function ($validator) use ($request) {
|
||||||
if ($request->has('password')) {
|
// if ($request->has('password')) {
|
||||||
if ($this->isCommonPassword($request->password)) {
|
// if ($this->isCommonPassword($request->password)) {
|
||||||
$validator->errors()->add('password', 'Password yang Anda gunakan terlalu umum. Silakan pilih password yang lebih unik.');
|
// $validator->errors()->add('password', 'Password yang Anda gunakan terlalu umum. Silakan pilih password yang lebih unik.');
|
||||||
}
|
// }
|
||||||
|
|
||||||
if ($this->isPasswordAlreadyUsed($request->password)) {
|
// if ($this->isPasswordAlreadyUsed($request->password)) {
|
||||||
$validator->errors()->add('password', 'Password ini sudah pernah digunakan oleh akun lain. Silakan gunakan password yang berbeda.');
|
// $validator->errors()->add('password', 'Password ini sudah pernah digunakan oleh akun lain. Silakan gunakan password yang berbeda.');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if ($request->has('phone') && $request->phone) {
|
// if ($request->has('phone') && $request->phone) {
|
||||||
if ($this->isPhoneAlreadyUsed($request->phone)) {
|
// if ($this->isPhoneAlreadyUsed($request->phone)) {
|
||||||
$validator->errors()->add('phone', 'Nomor telepon sudah terdaftar di sistem kami.');
|
// $validator->errors()->add('phone', 'Nomor telepon sudah terdaftar di sistem kami.');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return redirect()->back()->withErrors($validator)->withInput();
|
return redirect()->back()->withErrors($validator)->withInput();
|
||||||
|
|
@ -91,84 +88,84 @@ public function register(Request $request): RedirectResponse
|
||||||
/**
|
/**
|
||||||
* Cek apakah password termasuk password yang umum/familiar
|
* Cek apakah password termasuk password yang umum/familiar
|
||||||
*/
|
*/
|
||||||
private function isCommonPassword(string $password): bool
|
// private function isCommonPassword(string $password): bool
|
||||||
{
|
// {
|
||||||
$commonPasswords = [
|
// $commonPasswords = [
|
||||||
// Password numerik umum
|
// // Password numerik umum
|
||||||
'12345678', '123456789', '1234567890',
|
// '12345678', '123456789', '1234567890',
|
||||||
'87654321', '11111111', '22222222', '33333333', '44444444', '55555555',
|
// '87654321', '11111111', '22222222', '33333333', '44444444', '55555555',
|
||||||
'66666666', '77777777', '88888888', '99999999', '00000000',
|
// '66666666', '77777777', '88888888', '99999999', '00000000',
|
||||||
|
|
||||||
// Password alfabetik umum
|
// // Password alfabetik umum
|
||||||
'password', 'Password', 'PASSWORD', 'password123', 'Password123',
|
// 'password', 'Password', 'PASSWORD', 'password123', 'Password123',
|
||||||
'qwerty123', 'qwertyui', 'asdfghjk', 'zxcvbnm123',
|
// 'qwerty123', 'qwertyui', 'asdfghjk', 'zxcvbnm123',
|
||||||
'abcd1234', 'admin123', 'user1234', 'welcome123',
|
// 'abcd1234', 'admin123', 'user1234', 'welcome123',
|
||||||
|
|
||||||
// Password Indonesia umum
|
// // Password Indonesia umum
|
||||||
'indonesia', 'Indonesia', 'garuda123', 'nusantara',
|
// 'indonesia', 'Indonesia', 'garuda123', 'nusantara',
|
||||||
'jakarta123', 'bali2023', 'surabaya', 'bandung123',
|
// 'jakarta123', 'bali2023', 'surabaya', 'bandung123',
|
||||||
'password1', 'rahasia123', 'bismillah',
|
// 'password1', 'rahasia123', 'bismillah',
|
||||||
|
|
||||||
// Kombinasi tanggal umum
|
// // Kombinasi tanggal umum
|
||||||
'01011990', '01011995', '01012000', '17081945',
|
// '01011990', '01011995', '01012000', '17081945',
|
||||||
'12345abc', 'abc12345', '123qwe123', 'qwe12345',
|
// '12345abc', 'abc12345', '123qwe123', 'qwe12345',
|
||||||
|
|
||||||
// Pattern keyboard umum
|
// // Pattern keyboard umum
|
||||||
'qwerty12', 'asdf1234', 'zxcv1234', '1qaz2wsx',
|
// 'qwerty12', 'asdf1234', 'zxcv1234', '1qaz2wsx',
|
||||||
'1q2w3e4r', 'qazwsxed', 'plokijuh',
|
// '1q2w3e4r', 'qazwsxed', 'plokijuh',
|
||||||
|
|
||||||
// Password dengan nama umum
|
// // Password dengan nama umum
|
||||||
'admin1234', 'guest1234', 'test1234', 'demo1234',
|
// 'admin1234', 'guest1234', 'test1234', 'demo1234',
|
||||||
'sample123', 'example123', 'default123',
|
// 'sample123', 'example123', 'default123',
|
||||||
|
|
||||||
// Password sosial media umum
|
// // Password sosial media umum
|
||||||
'facebook', 'instagram', 'whatsapp', 'twitter123',
|
// 'facebook', 'instagram', 'whatsapp', 'twitter123',
|
||||||
'gmail123', 'yahoo123', 'google123',
|
// 'gmail123', 'yahoo123', 'google123',
|
||||||
|
|
||||||
// Password gaming umum
|
// // Password gaming umum
|
||||||
'minecraft', 'roblox123', 'fortnite', 'pubg1234',
|
// 'minecraft', 'roblox123', 'fortnite', 'pubg1234',
|
||||||
'mobilelegend', 'freefire', 'valorant',
|
// 'mobilelegend', 'freefire', 'valorant',
|
||||||
|
|
||||||
// Password device umum
|
// // Password device umum
|
||||||
'android123', 'iphone123', 'samsung123', 'nokia1234',
|
// 'android123', 'iphone123', 'samsung123', 'nokia1234',
|
||||||
|
|
||||||
// Tahun umum
|
// // Tahun umum
|
||||||
'20232023', '20242024', '19901990', '20002000',
|
// '20232023', '20242024', '19901990', '20002000',
|
||||||
];
|
// ];
|
||||||
|
|
||||||
return in_array(strtolower($password), array_map('strtolower', $commonPasswords));
|
// return in_array(strtolower($password), array_map('strtolower', $commonPasswords));
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cek apakah password sudah pernah dipakai oleh user lain di database
|
* Cek apakah password sudah pernah dipakai oleh user lain di database
|
||||||
*/
|
*/
|
||||||
private function isPasswordAlreadyUsed(string $password): bool
|
// private function isPasswordAlreadyUsed(string $password): bool
|
||||||
{
|
// {
|
||||||
$users = User::whereNotNull('password')->get(['password']);
|
// $users = User::whereNotNull('password')->get(['password']);
|
||||||
|
|
||||||
foreach ($users as $user) {
|
// foreach ($users as $user) {
|
||||||
if (Hash::check($password, $user->password)) {
|
// if (Hash::check($password, $user->password)) {
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return false;
|
// return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cek nomor telepon sudah terdaftar dalam berbagai format
|
* Cek nomor telepon sudah terdaftar dalam berbagai format
|
||||||
*/
|
*/
|
||||||
private function isPhoneAlreadyUsed(string $phone): bool
|
// private function isPhoneAlreadyUsed(string $phone): bool
|
||||||
{
|
// {
|
||||||
$digitsOnly = preg_replace('/\D/', '', $phone);
|
// $digitsOnly = preg_replace('/\D/', '', $phone);
|
||||||
|
|
||||||
$formats = [
|
// $formats = [
|
||||||
$phone, // +6281234567890
|
// $phone, // +6281234567890
|
||||||
$digitsOnly, // 6281234567890
|
// $digitsOnly, // 6281234567890
|
||||||
'+' . $digitsOnly, // +6281234567890
|
// '+' . $digitsOnly, // +6281234567890
|
||||||
'0' . substr($digitsOnly, 2), // 081234567890
|
// '0' . substr($digitsOnly, 2), // 081234567890
|
||||||
];
|
// ];
|
||||||
|
|
||||||
|
// return User::whereIn('phone', $formats)->exists();
|
||||||
|
// }
|
||||||
|
|
||||||
return User::whereIn('phone', $formats)->exists();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use App\Mail\AdminOrderNotification;
|
use App\Mail\AdminOrderNotification;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class CheckoutController extends Controller
|
class CheckoutController extends Controller
|
||||||
{
|
{
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$productId = $request->get('product_id');
|
$productId = $request->get('product_id');
|
||||||
$quantity = $request->get('quantity', 1);
|
$quantity = $request->get('quantity', 1);
|
||||||
|
|
@ -38,14 +40,26 @@ public function index(Request $request)
|
||||||
$ongkir = 15000;
|
$ongkir = 15000;
|
||||||
$total = $subtotal + $ongkir;
|
$total = $subtotal + $ongkir;
|
||||||
|
|
||||||
// Panggil view checkout.index (form checkout)
|
$prefill = null;
|
||||||
return view('checkout.index', compact('product', 'quantity', 'subtotal', 'ongkir', 'total'));
|
if ($request->get('use_saved') && Auth::check()) {
|
||||||
|
$user = Auth::user();
|
||||||
|
$prefill = [
|
||||||
|
'name' => $user->name,
|
||||||
|
'phone' => $user->phone ?? '',
|
||||||
|
'email' => $user->email,
|
||||||
|
'address' => $user->address ?? '',
|
||||||
|
'city' => $user->city ?? '',
|
||||||
|
'postal_code' => $user->postal_code ?? '68200',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('checkout.index', compact('product', 'quantity', 'subtotal', 'ongkir', 'total', 'prefill'));
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('Checkout Index Error: ' . $e->getMessage());
|
Log::error('Checkout Index Error: ' . $e->getMessage());
|
||||||
return redirect()->route('home')->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
return redirect()->route('home')->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function process(Request $request)
|
public function process(Request $request)
|
||||||
{
|
{
|
||||||
|
|
@ -53,7 +67,7 @@ public function process(Request $request)
|
||||||
'product_id' => 'required|exists:products,id',
|
'product_id' => 'required|exists:products,id',
|
||||||
'quantity' => 'required|integer|min:1',
|
'quantity' => 'required|integer|min:1',
|
||||||
'name' => 'required|string|min:8|max:255',
|
'name' => 'required|string|min:8|max:255',
|
||||||
'phone' => ['required', 'string', 'min:11', 'regex:/^\+62[0-9]{8,}$/'],
|
'phone' => ['required', 'string', 'min:10', 'max:15'],
|
||||||
'email' => 'required|email|min:8|max:255',
|
'email' => 'required|email|min:8|max:255',
|
||||||
'address' => ['required', 'string', 'min:20',
|
'address' => ['required', 'string', 'min:20',
|
||||||
'regex:/(?=.*[Jj]l\.?|.*[Jj]alan)(?=.*[Nn]o\.?)(?=.*[Rr][Tt])(?=.*[Rr][Ww])/'],
|
'regex:/(?=.*[Jj]l\.?|.*[Jj]alan)(?=.*[Nn]o\.?)(?=.*[Rr][Tt])(?=.*[Rr][Ww])/'],
|
||||||
|
|
@ -66,8 +80,8 @@ public function process(Request $request)
|
||||||
'name.required' => 'Nama lengkap wajib diisi.',
|
'name.required' => 'Nama lengkap wajib diisi.',
|
||||||
'name.min' => 'Nama lengkap minimal 8 karakter.',
|
'name.min' => 'Nama lengkap minimal 8 karakter.',
|
||||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
'phone.min' => 'Nomor telepon minimal 11 karakter.',
|
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||||
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya berisi angka. Contoh: +6281234567890',
|
'phone.max' => 'Nomor telepon maksimal 15 karakter.',
|
||||||
'email.required' => 'Email wajib diisi.',
|
'email.required' => 'Email wajib diisi.',
|
||||||
'email.email' => 'Format email tidak valid.',
|
'email.email' => 'Format email tidak valid.',
|
||||||
'email.min' => 'Email minimal 8 karakter.',
|
'email.min' => 'Email minimal 8 karakter.',
|
||||||
|
|
@ -230,4 +244,68 @@ public function getStatus($id)
|
||||||
'order_status' => $transaksi->order_status,
|
'order_status' => $transaksi->order_status,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
public function getLastAddress()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
return response()->json([
|
||||||
|
'name' => $user->name,
|
||||||
|
'phone' => $user->phone ?? '',
|
||||||
|
'email' => $user->email,
|
||||||
|
'address' => $user->address ?? '',
|
||||||
|
'city' => $user->city ?? '',
|
||||||
|
'postal_code' => $user->postal_code ?? '',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan method ini di CheckoutController
|
||||||
|
|
||||||
|
public function konfirmasiPembayaran(Request $request, $id)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'payment_proof' => 'required|image|mimes:jpg,jpeg,png|max:2048',
|
||||||
|
], [
|
||||||
|
'payment_proof.required' => 'Bukti pembayaran wajib diupload.',
|
||||||
|
'payment_proof.image' => 'File harus berupa gambar.',
|
||||||
|
'payment_proof.mimes' => 'Format gambar harus JPG atau PNG.',
|
||||||
|
'payment_proof.max' => 'Ukuran gambar maksimal 2MB.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$transaksi = Transaksi::findOrFail($id);
|
||||||
|
|
||||||
|
// Simpan bukti bayar
|
||||||
|
$path = $request->file('payment_proof')->store('payment_proofs', 'public');
|
||||||
|
$transaksi->update([
|
||||||
|
'payment_proof' => $path,
|
||||||
|
'payment_status' => 'pending',// menunggu konfirmasi admin
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Kirim notifikasi WA ke admin via Fonnte
|
||||||
|
$this->kirimNotifWaAdmin($transaksi);
|
||||||
|
|
||||||
|
return redirect()->route('home')
|
||||||
|
->with('success', 'Bukti pembayaran berhasil dikirim! Admin akan segera mengkonfirmasi.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function kirimNotifWaAdmin(Transaksi $transaksi)
|
||||||
|
{
|
||||||
|
$adminPhone = config('app.admin_phone'); // simpan di .env: ADMIN_PHONE=628131830561
|
||||||
|
$fonnteToken = config('app.fonnte_token'); // simpan di .env: FONNTE_TOKEN=xxxxx
|
||||||
|
|
||||||
|
$pesan = "🔔 *Konfirmasi Pembayaran Masuk!*\n\n"
|
||||||
|
. "📋 Invoice: *{$transaksi->invoice_number}*\n"
|
||||||
|
. "👤 Pembeli: {$transaksi->customer_name}\n"
|
||||||
|
. "📞 Telepon: {$transaksi->customer_phone}\n"
|
||||||
|
. "💳 Metode: " . strtoupper($transaksi->payment_method) . "\n"
|
||||||
|
. "💰 Total: Rp " . number_format($transaksi->total_amount, 0, ',', '.') . "\n\n"
|
||||||
|
. "Silakan cek dan approve di panel admin:\n"
|
||||||
|
. url("/admin/transaksi/{$transaksi->id}");
|
||||||
|
|
||||||
|
Http::withHeaders([
|
||||||
|
'Authorization' => $fonnteToken,
|
||||||
|
])->post('https://api.fonnte.com/send', [
|
||||||
|
'target' => $adminPhone,
|
||||||
|
'message' => $pesan,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,7 @@ public function store(Request $request)
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'category' => 'required|string|max:100',
|
'category' => 'required|string|max:100',
|
||||||
'price' => 'required|numeric|min:0',
|
'price' => 'required|numeric|min:1',
|
||||||
'stock' => 'required|integer|min:0',
|
'stock' => 'required|integer|min:0',
|
||||||
'status' => 'required|in:aktif,nonaktif',
|
'status' => 'required|in:aktif,nonaktif',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
|
|
@ -225,21 +225,24 @@ public function debugImage($id)
|
||||||
private function syncLabelAfterSave(Product $product)
|
private function syncLabelAfterSave(Product $product)
|
||||||
{
|
{
|
||||||
if ($product->stock <= 0) {
|
if ($product->stock <= 0) {
|
||||||
return; // Sudah di-handle updateLabelBasedOnStock
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$bestSellingIds = \Illuminate\Support\Facades\DB::table('transaksi_details')
|
$bestSellingIds = \Illuminate\Support\Facades\DB::table('transaksi_details')
|
||||||
->select('product_id', \Illuminate\Support\Facades\DB::raw('SUM(quantity) as total_sold'))
|
->select('product_id', \Illuminate\Support\Facades\DB::raw('SUM(quantity) as total_sold'))// ← ini rumus (1)
|
||||||
->groupBy('product_id')
|
->groupBy('product_id')
|
||||||
->orderByDesc('total_sold')
|
->having('total_sold', '>=', 100)
|
||||||
->take(10)
|
->orderByDesc('total_sold') // ← ini rumus (2): si > sj
|
||||||
|
->take(4)
|
||||||
->pluck('product_id')
|
->pluck('product_id')
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
if (in_array($product->id, $bestSellingIds)) {
|
if (in_array($product->id, $bestSellingIds)) {
|
||||||
$product->label = 'terlaris';
|
$product->label = 'terlaris';
|
||||||
$product->save();
|
$product->save();
|
||||||
|
} else {
|
||||||
|
$product->label = 'tersedia';
|
||||||
|
$product->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -33,6 +33,7 @@ public function index()
|
||||||
'products.price',
|
'products.price',
|
||||||
'products.stock'
|
'products.stock'
|
||||||
)
|
)
|
||||||
|
->having('total_sold', '>=', 100) // ← tambah baris ini
|
||||||
->orderByDesc('total_sold')
|
->orderByDesc('total_sold')
|
||||||
->take(10)
|
->take(10)
|
||||||
->get();
|
->get();
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
use App\Notifications\OrderStatusChanged;
|
use App\Notifications\OrderStatusChanged;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class TransaksiController extends Controller
|
class TransaksiController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -501,5 +503,89 @@ public function rejectCancellation(Request $request, $id)
|
||||||
return back()->with('success', 'Pengajuan pembatalan berhasil ditolak.');
|
return back()->with('success', 'Pengajuan pembatalan berhasil ditolak.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pembeli upload bukti pembayaran
|
||||||
|
*/
|
||||||
|
public function konfirmasiPembayaran(Request $request, $id)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'payment_proof' => 'required|image|mimes:jpg,jpeg,png|max:2048',
|
||||||
|
], [
|
||||||
|
'payment_proof.required' => 'Bukti pembayaran wajib diupload.',
|
||||||
|
'payment_proof.image' => 'File harus berupa gambar.',
|
||||||
|
'payment_proof.mimes' => 'Format gambar harus JPG atau PNG.',
|
||||||
|
'payment_proof.max' => 'Ukuran gambar maksimal 2MB.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$transaksi = Transaksi::findOrFail($id);
|
||||||
|
|
||||||
|
$path = $request->file('payment_proof')->store('payment_proofs', 'public');
|
||||||
|
|
||||||
|
$transaksi->update([
|
||||||
|
'payment_proof' => $path,
|
||||||
|
'payment_status' => 'waiting_confirmation',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Kirim notif WA ke admin via Fonnte
|
||||||
|
try {
|
||||||
|
$adminPhone = env('ADMIN_PHONE', '628131830561');
|
||||||
|
$fonnteToken = env('FONNTE_TOKEN');
|
||||||
|
|
||||||
|
$pesan = "🔔 *Konfirmasi Pembayaran Masuk!*\n\n"
|
||||||
|
. "📋 Invoice: *{$transaksi->invoice_number}*\n"
|
||||||
|
. "👤 Pembeli: {$transaksi->customer_name}\n"
|
||||||
|
. "📞 Telepon: {$transaksi->customer_phone}\n"
|
||||||
|
. "💳 Metode: " . strtoupper($transaksi->payment_method) . "\n"
|
||||||
|
. "💰 Total: Rp " . number_format($transaksi->total_amount, 0, ',', '.') . "\n\n"
|
||||||
|
. "Silakan approve di panel admin:\n"
|
||||||
|
. url("/admin/transaksis/{$transaksi->id}");
|
||||||
|
|
||||||
|
Http::withHeaders(['Authorization' => $fonnteToken])
|
||||||
|
->post('https://api.fonnte.com/send', [
|
||||||
|
'target' => $adminPhone,
|
||||||
|
'message' => $pesan,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Gagal kirim notif WA admin: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('success', 'Bukti pembayaran berhasil dikirim! Menunggu konfirmasi admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin approve pembayaran → tandai lunas + notif WA ke pembeli
|
||||||
|
*/
|
||||||
|
public function approvePembayaran($id)
|
||||||
|
{
|
||||||
|
$transaksi = Transaksi::findOrFail($id);
|
||||||
|
|
||||||
|
$transaksi->update([
|
||||||
|
'payment_status' => 'paid',
|
||||||
|
'paid_at' => now(),
|
||||||
|
'confirmed_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Kirim notif WA ke pembeli via Fonnte
|
||||||
|
try {
|
||||||
|
$fonnteToken = env('FONNTE_TOKEN');
|
||||||
|
|
||||||
|
$pesan = "✅ *Pembayaran Dikonfirmasi!*\n\n"
|
||||||
|
. "Halo *{$transaksi->customer_name}*,\n"
|
||||||
|
. "Pembayaran Anda untuk invoice *{$transaksi->invoice_number}* telah dikonfirmasi.\n\n"
|
||||||
|
. "📦 Pesanan Anda sedang kami proses dan akan segera dikirim.\n"
|
||||||
|
. "Terima kasih telah berbelanja di *Shop Bibit Cabai Bondowoso*! 🌶️";
|
||||||
|
|
||||||
|
Http::withHeaders(['Authorization' => $fonnteToken])
|
||||||
|
->post('https://api.fonnte.com/send', [
|
||||||
|
'target' => $transaksi->customer_phone,
|
||||||
|
'message' => $pesan,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::warning('Gagal kirim notif WA pembeli: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->with('success', 'Pembayaran berhasil dikonfirmasi dan pembeli telah dinotifikasi!');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -57,9 +57,8 @@ public function store(Request $request)
|
||||||
'regex:/^(?=.*[a-zA-Z])(?=.*[0-9])\S+$/' // huruf+angka, no spasi
|
'regex:/^(?=.*[a-zA-Z])(?=.*[0-9])\S+$/' // huruf+angka, no spasi
|
||||||
],
|
],
|
||||||
'phone' => [
|
'phone' => [
|
||||||
'required', 'string', 'min:12',
|
'required', 'string', 'min:10',
|
||||||
'regex:/^(\+62)[0-9]+$/' // harus diawali +62, hanya angka
|
],
|
||||||
],
|
|
||||||
'address' => ['required', 'string', 'min:10'],
|
'address' => ['required', 'string', 'min:10'],
|
||||||
], [
|
], [
|
||||||
'name.required' => 'Nama lengkap wajib diisi.',
|
'name.required' => 'Nama lengkap wajib diisi.',
|
||||||
|
|
@ -77,8 +76,7 @@ public function store(Request $request)
|
||||||
'password.regex' => 'Password tidak boleh mengandung spasi dan harus terdiri dari huruf dan angka.',
|
'password.regex' => 'Password tidak boleh mengandung spasi dan harus terdiri dari huruf dan angka.',
|
||||||
|
|
||||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
'phone.min' => 'Nomor telepon minimal 12 karakter.',
|
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||||
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya boleh berisi angka.',
|
|
||||||
|
|
||||||
'address.required' => 'Alamat wajib diisi.',
|
'address.required' => 'Alamat wajib diisi.',
|
||||||
'address.min' => 'Alamat minimal 10 karakter.',
|
'address.min' => 'Alamat minimal 10 karakter.',
|
||||||
|
|
@ -102,7 +100,7 @@ public function store(Request $request)
|
||||||
*/
|
*/
|
||||||
public function show(User $user)
|
public function show(User $user)
|
||||||
{
|
{
|
||||||
return view('admin.users.show', [
|
return view('admin.users.Show', [
|
||||||
'user' => $user
|
'user' => $user
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +116,7 @@ public function edit(User $user)
|
||||||
->with('error', 'Anda tidak dapat mengedit akun pengguna lain.');
|
->with('error', 'Anda tidak dapat mengedit akun pengguna lain.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('admin.users.edit', compact('user'));
|
return view('admin.users.Edit', compact('user'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -144,8 +142,7 @@ public function update(Request $request, User $user)
|
||||||
'regex:/^[^\s]+@gmail\.com$/'
|
'regex:/^[^\s]+@gmail\.com$/'
|
||||||
],
|
],
|
||||||
'phone' => [
|
'phone' => [
|
||||||
'required', 'string', 'min:12',
|
'required', 'string', 'min:10',
|
||||||
'regex:/^(\+62)[0-9]+$/'
|
|
||||||
],
|
],
|
||||||
'address' => ['required', 'string', 'min:10'],
|
'address' => ['required', 'string', 'min:10'],
|
||||||
], [
|
], [
|
||||||
|
|
@ -159,8 +156,7 @@ public function update(Request $request, User $user)
|
||||||
'email.regex' => 'Email tidak boleh mengandung spasi dan harus menggunakan @gmail.com.',
|
'email.regex' => 'Email tidak boleh mengandung spasi dan harus menggunakan @gmail.com.',
|
||||||
|
|
||||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||||
'phone.min' => 'Nomor telepon minimal 12 karakter.',
|
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||||
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya boleh berisi angka.',
|
|
||||||
|
|
||||||
'address.required' => 'Alamat wajib diisi.',
|
'address.required' => 'Alamat wajib diisi.',
|
||||||
'address.min' => 'Alamat minimal 10 karakter.',
|
'address.min' => 'Alamat minimal 10 karakter.',
|
||||||
|
|
@ -171,8 +167,10 @@ public function update(Request $request, User $user)
|
||||||
'email' => $request->email,
|
'email' => $request->email,
|
||||||
'phone' => $request->phone,
|
'phone' => $request->phone,
|
||||||
'address' => $request->address,
|
'address' => $request->address,
|
||||||
'is_admin' => $request->has('is_admin') ? 1 : 0,
|
'is_admin' => ($user->id === auth()->id())
|
||||||
];
|
? $user->is_admin
|
||||||
|
: ($request->has('is_admin') ? 1 : 0),
|
||||||
|
];
|
||||||
|
|
||||||
if ($request->filled('password')) {
|
if ($request->filled('password')) {
|
||||||
$request->validate([
|
$request->validate([
|
||||||
|
|
@ -199,11 +197,6 @@ public function update(Request $request, User $user)
|
||||||
*/
|
*/
|
||||||
public function destroy(User $user)
|
public function destroy(User $user)
|
||||||
{
|
{
|
||||||
// Tidak bisa hapus akun siapapun selain diri sendiri
|
|
||||||
if (auth()->id() !== $user->id) {
|
|
||||||
return back()->with('error', 'Anda tidak dapat menghapus akun pengguna lain.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tidak bisa hapus akun sendiri
|
// Tidak bisa hapus akun sendiri
|
||||||
if (auth()->id() === $user->id) {
|
if (auth()->id() === $user->id) {
|
||||||
return back()->with('error', 'Anda tidak dapat menghapus akun sendiri.');
|
return back()->with('error', 'Anda tidak dapat menghapus akun sendiri.');
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ class Transaksi extends Model
|
||||||
'total_amount',
|
'total_amount',
|
||||||
'payment_method',
|
'payment_method',
|
||||||
'payment_status',
|
'payment_status',
|
||||||
|
'payment_proof', // ← pastikan ada ini
|
||||||
|
'confirmed_at', // ← pastikan ada ini
|
||||||
'order_status',
|
'order_status',
|
||||||
'notes',
|
'notes',
|
||||||
'paid_at'
|
'paid_at'
|
||||||
|
|
@ -33,6 +35,7 @@ class Transaksi extends Model
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'paid_at' => 'datetime',
|
'paid_at' => 'datetime',
|
||||||
|
'confirmed_at' => 'datetime', // ← tambah ini
|
||||||
'subtotal' => 'decimal:2',
|
'subtotal' => 'decimal:2',
|
||||||
'shipping_cost'=> 'decimal:2',
|
'shipping_cost'=> 'decimal:2',
|
||||||
'total_amount' => 'decimal:2',
|
'total_amount' => 'decimal:2',
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,26 @@ public function __construct()
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function sendMessage(string $phone, string $message): bool
|
public function sendMessage(string $phone, string $message): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
// Bersihkan format nomor — hapus semua non-digit
|
||||||
|
$phoneCleaned = preg_replace('/\D/', '', $phone);
|
||||||
|
|
||||||
|
// Pastikan diawali 62
|
||||||
|
if (str_starts_with($phoneCleaned, '0')) {
|
||||||
|
$phoneCleaned = '62' . substr($phoneCleaned, 1);
|
||||||
|
} elseif (!str_starts_with($phoneCleaned, '62')) {
|
||||||
|
$phoneCleaned = '62' . $phoneCleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('Fonnte kirim ke: ' . $phoneCleaned);
|
||||||
|
|
||||||
$response = Http::withHeaders([
|
$response = Http::withHeaders([
|
||||||
'Authorization' => $this->token,
|
'Authorization' => $this->token,
|
||||||
])->post($this->apiUrl, [
|
])->post($this->apiUrl, [
|
||||||
'target' => $phone,
|
'target' => $phoneCleaned,
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
'countryCode' => '62', // Indonesia
|
'countryCode' => '62',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$body = $response->json();
|
$body = $response->json();
|
||||||
|
|
@ -46,7 +58,7 @@ public function sendMessage(string $phone, string $message): bool
|
||||||
Log::error('Fonnte exception: ' . $e->getMessage());
|
Log::error('Fonnte exception: ' . $e->getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format nomor WA ke format internasional
|
* Format nomor WA ke format internasional
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'Laravel'),
|
'name' => env('APP_NAME', 'Laravel'),
|
||||||
|
'admin_phone' => env('ADMIN_PHONE'),
|
||||||
|
'fonnte_token' => env('FONNTE_TOKEN'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('phone')->nullable()->after('email');
|
||||||
|
$table->text('address')->nullable()->after('phone');
|
||||||
|
$table->string('city')->nullable()->after('address');
|
||||||
|
$table->string('postal_code')->nullable()->after('city');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
// database/migrations/xxxx_add_payment_proof_to_transaksis_table.php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('transaksis', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('transaksis', 'payment_proof')) {
|
||||||
|
$table->string('payment_proof')->nullable()->after('payment_method');
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('transaksis', 'confirmed_at')) {
|
||||||
|
$table->timestamp('confirmed_at')->nullable()->after('paid_at');
|
||||||
|
}
|
||||||
|
// Tambah status waiting_confirmation
|
||||||
|
// (tidak perlu ubah kolom, cukup allow value baru di payment_status)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('transaksis', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['payment_proof', 'confirmed_at']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -548,7 +548,7 @@
|
||||||
<th>Alasan</th>
|
<th>Alasan</th>
|
||||||
<th>Keterangan</th>
|
<th>Keterangan</th>
|
||||||
<th>Tgl Pengajuan</th>
|
<th>Tgl Pengajuan</th>
|
||||||
<th>Status</th>
|
<th>Tgl Review</th>
|
||||||
<th class="text-center">Aksi</th>
|
<th class="text-center">Aksi</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -576,11 +576,11 @@
|
||||||
<small style="color:#9ca3af;">{{ $item->created_at->format('H:i') }}</small>
|
<small style="color:#9ca3af;">{{ $item->created_at->format('H:i') }}</small>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge-status badge-{{ $item->status }}">
|
|
||||||
{{ $item->status === 'pending' ? 'Menunggu' : ($item->status === 'approved' ? 'Disetujui' : 'Ditolak') }}
|
|
||||||
</span>
|
|
||||||
@if($item->reviewed_at)
|
@if($item->reviewed_at)
|
||||||
<br><small style="color:#9ca3af;">{{ $item->reviewed_at->format('d M Y') }}</small>
|
<small style="color:#374151;">{{ $item->reviewed_at->format('d M Y') }}</small><br>
|
||||||
|
<small style="color:#9ca3af;">{{ $item->reviewed_at->format('H:i') }}</small>
|
||||||
|
@else
|
||||||
|
<small style="color:#9ca3af;">-</small>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
|
|
|
||||||
|
|
@ -186,23 +186,22 @@ class="form-control @error('email') is-invalid @enderror"
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="phone" class="form-label fw-semibold">Nomor WhatsApp Admin</label>
|
<label for="phone" class="form-label fw-semibold">Nomor WhatsApp Admin</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text fw-semibold" style="color:#11998e;">+62</span>
|
|
||||||
<input type="tel"
|
<input type="tel"
|
||||||
class="form-control @error('phone') is-invalid @enderror"
|
class="form-control @error('phone') is-invalid @enderror"
|
||||||
id="phone"
|
id="phone"
|
||||||
name="phone"
|
name="phone"
|
||||||
value="{{ old('phone') }}"
|
value="{{ old('phone') }}"
|
||||||
placeholder="8123456789"
|
placeholder="081234567890"
|
||||||
inputmode="numeric">
|
inputmode="numeric">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text text-muted small">
|
<div class="form-text text-muted small">
|
||||||
ℹ️ Masukkan nomor tanpa angka 0 di depan
|
ℹ️ Masukkan nomor WhatsApp minimal 10 digit
|
||||||
</div>
|
</div>
|
||||||
@error('phone')
|
@error('phone')
|
||||||
<div class="text-danger mt-1 small">{{ $message }}</div>
|
<div class="text-danger mt-1 small">{{ $message }}</div>
|
||||||
@enderror
|
@enderror
|
||||||
<div id="phoneError" class="text-danger mt-1" style="display:none;">
|
<div id="phoneError" class="text-danger mt-1" style="display:none;">
|
||||||
<small>⚠️ Nomor WhatsApp tidak valid (min 9 digit)</small>
|
<small>⚠️ Nomor WhatsApp minimal 10 digit</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -274,11 +273,11 @@ function validateInput() {
|
||||||
submitBtn.disabled = !valid;
|
submitBtn.disabled = !valid;
|
||||||
} else {
|
} else {
|
||||||
const val = document.getElementById('phone').value.replace(/\D/g, '');
|
const val = document.getElementById('phone').value.replace(/\D/g, '');
|
||||||
const valid = val.length >= 9 && val.length <= 13;
|
const valid = val.length >= 10 && val.length <= 15;
|
||||||
document.getElementById('phoneError').style.display =
|
document.getElementById('phoneError').style.display =
|
||||||
(val.length > 0 && !valid) ? 'block' : 'none';
|
(val.length > 0 && !valid) ? 'block' : 'none';
|
||||||
submitBtn.disabled = !valid;
|
submitBtn.disabled = !valid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
|
||||||
|
|
@ -262,8 +262,8 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
@ -276,10 +276,11 @@
|
||||||
|
|
||||||
.table-controls {
|
.table-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
flex-wrap: wrap;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table */
|
/* Table */
|
||||||
.data-table {
|
.data-table {
|
||||||
|
|
@ -569,14 +570,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Table Card -->
|
<!-- Table Card -->
|
||||||
<div class="table-card">
|
|
||||||
<div class="table-card-header">
|
<div class="table-card-header">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<i class="fas fa-shopping-cart text-success"></i>
|
<i class="fas fa-shopping-cart text-success"></i>
|
||||||
Daftar Pesanan ({{ $orders->total() }})
|
Daftar Pesanan ({{ $orders->total() }})
|
||||||
</div>
|
</div>
|
||||||
<div class="table-controls">
|
<div class="table-controls">
|
||||||
<select class="form-select form-select-sm" id="filterStatus" onchange="filterOrders()" style="min-width:140px;">
|
<select class="form-select form-select-sm" id="filterStatus" onchange="filterOrders()" style="min-width:160px;">
|
||||||
<option value="">Semua Status</option>
|
<option value="">Semua Status</option>
|
||||||
<option value="pending">Pending</option>
|
<option value="pending">Pending</option>
|
||||||
<option value="processing">Processing</option>
|
<option value="processing">Processing</option>
|
||||||
|
|
@ -584,11 +584,17 @@
|
||||||
<option value="delivered">Delivered</option>
|
<option value="delivered">Delivered</option>
|
||||||
<option value="cancelled">Cancelled</option>
|
<option value="cancelled">Cancelled</option>
|
||||||
</select>
|
</select>
|
||||||
|
<select class="form-select form-select-sm" id="filterPayment" onchange="filterOrders()" style="min-width:160px;">
|
||||||
|
<option value="">Semua Payment Status</option>
|
||||||
|
<option value="pending">Pending</option>
|
||||||
|
<option value="paid">Paid</option>
|
||||||
|
<option value="failed">Failed</option>
|
||||||
|
</select>
|
||||||
<a href="{{ route('admin.transaksis.export') }}" class="btn btn-success btn-sm">
|
<a href="{{ route('admin.transaksis.export') }}" class="btn btn-success btn-sm">
|
||||||
<i class="fas fa-file-excel me-1"></i>Export Excel
|
<i class="fas fa-file-excel me-1"></i>Export Excel
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="data-table" id="ordersTable">
|
<table class="data-table" id="ordersTable">
|
||||||
|
|
@ -608,6 +614,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse($orders as $order)
|
@forelse($orders as $order)
|
||||||
<tr data-status="{{ $order->order_status }}"
|
<tr data-status="{{ $order->order_status }}"
|
||||||
|
data-payment="{{ $order->payment_status }}"
|
||||||
class="{{ $order->order_status === 'cancelled' ? 'row-cancelled' : '' }}">
|
class="{{ $order->order_status === 'cancelled' ? 'row-cancelled' : '' }}">
|
||||||
<td><strong>{{ $order->invoice_number }}</strong></td>
|
<td><strong>{{ $order->invoice_number }}</strong></td>
|
||||||
<td>{{ $order->customer_name }}</td>
|
<td>{{ $order->customer_name }}</td>
|
||||||
|
|
@ -704,13 +711,17 @@ function closeSidebar() {
|
||||||
|
|
||||||
// Filter orders
|
// Filter orders
|
||||||
function filterOrders() {
|
function filterOrders() {
|
||||||
const filterValue = document.getElementById('filterStatus').value.toLowerCase();
|
const filterStatus = document.getElementById('filterStatus').value.toLowerCase();
|
||||||
|
const filterPayment = document.getElementById('filterPayment').value.toLowerCase();
|
||||||
const rows = document.querySelectorAll('#ordersTable tbody tr[data-status]');
|
const rows = document.querySelectorAll('#ordersTable tbody tr[data-status]');
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
const status = row.getAttribute('data-status');
|
const status = row.getAttribute('data-status');
|
||||||
row.style.display = (filterValue === '' || status === filterValue) ? '' : 'none';
|
const payment = row.getAttribute('data-payment');
|
||||||
|
const matchStatus = filterStatus === '' || status === filterStatus;
|
||||||
|
const matchPayment = filterPayment === '' || payment === filterPayment;
|
||||||
|
row.style.display = (matchStatus && matchPayment) ? '' : 'none';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update status via AJAX
|
// Update status via AJAX
|
||||||
function updateStatus(orderId, field, value, selectEl) {
|
function updateStatus(orderId, field, value, selectEl) {
|
||||||
|
|
|
||||||
|
|
@ -600,7 +600,7 @@ class="form-control @error('name') is-invalid @enderror"
|
||||||
class="form-control @error('price') is-invalid @enderror"
|
class="form-control @error('price') is-invalid @enderror"
|
||||||
name="price" id="price"
|
name="price" id="price"
|
||||||
value="{{ old('price', $product->price) }}"
|
value="{{ old('price', $product->price) }}"
|
||||||
min="0" step="1000" placeholder="0" required>
|
min="0" step="1" placeholder="0" required>
|
||||||
</div>
|
</div>
|
||||||
@error('price')
|
@error('price')
|
||||||
<div class="invalid-feedback d-block">{{ $message }}</div>
|
<div class="invalid-feedback d-block">{{ $message }}</div>
|
||||||
|
|
|
||||||
|
|
@ -486,11 +486,11 @@
|
||||||
<i class="fas fa-print"></i>
|
<i class="fas fa-print"></i>
|
||||||
<span>Print</span>
|
<span>Print</span>
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn-topbar btn-topbar-delete"
|
<!-- <button type="button" class="btn-topbar btn-topbar-delete"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
<span>Hapus</span>
|
<span>Hapus</span>
|
||||||
</button>
|
</button> -->
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
@ -747,7 +747,67 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
{{-- Bukti Pembayaran & Tombol Approve --}}
|
||||||
|
@if($transaksi->payment_method !== 'cod')
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="info-card-header">
|
||||||
|
<div class="info-card-header-left">
|
||||||
|
<div class="info-card-icon icon-green">
|
||||||
|
<i class="fas fa-receipt"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Bukti Pembayaran</h3>
|
||||||
|
<p>Konfirmasi pembayaran pembeli</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-card-body">
|
||||||
|
@if($transaksi->payment_proof)
|
||||||
|
<img src="{{ Storage::url($transaksi->payment_proof) }}"
|
||||||
|
alt="Bukti Bayar"
|
||||||
|
class="img-fluid rounded mb-3 w-100"
|
||||||
|
style="max-height:350px;object-fit:contain;border:1px solid #e5e7eb;">
|
||||||
|
|
||||||
|
@if($transaksi->payment_status === 'paid')
|
||||||
|
<div class="alert alert-success mb-0" style="font-size:0.84rem;">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>
|
||||||
|
Dikonfirmasi lunas pada
|
||||||
|
<strong>{{ $transaksi->confirmed_at ? \Carbon\Carbon::parse($transaksi->confirmed_at)->format('d M Y H:i') : '-' }}</strong>
|
||||||
|
</div>
|
||||||
|
@elseif($transaksi->payment_status === 'waiting_confirmation')
|
||||||
|
<div class="alert alert-warning mb-3" style="font-size:0.84rem;">
|
||||||
|
<i class="fas fa-clock me-2"></i>
|
||||||
|
Menunggu konfirmasi admin
|
||||||
|
</div>
|
||||||
|
<form action="{{ route('admin.transaksis.approve', $transaksi->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<button type="submit"
|
||||||
|
class="btn btn-success w-100 fw-bold"
|
||||||
|
onclick="return confirm('Konfirmasi pembayaran ini sebagai LUNAS?')"
|
||||||
|
style="border-radius:8px;">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>Approve — Tandai Lunas
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
<form action="{{ route('admin.transaksis.approve', $transaksi->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<button type="submit"
|
||||||
|
class="btn btn-success w-100 fw-bold"
|
||||||
|
onclick="return confirm('Konfirmasi pembayaran ini sebagai LUNAS?')"
|
||||||
|
style="border-radius:8px;">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>Approve — Tandai Lunas
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<div class="text-center py-3" style="color:#9ca3af;">
|
||||||
|
<i class="fas fa-image fa-2x mb-2 d-block"></i>
|
||||||
|
<span style="font-size:0.84rem;">Pembeli belum upload bukti pembayaran</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<!-- Akhir col-lg-4 -->
|
<!-- Akhir col-lg-4 -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -631,10 +631,10 @@
|
||||||
class="btn-action btn-view" title="Detail">
|
class="btn-action btn-view" title="Detail">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.users.edit', $user) }}"
|
<!-- <a href="{{ route('admin.users.edit', $user) }}"
|
||||||
class="btn-action btn-edit" title="Edit">
|
class="btn-action btn-edit" title="Edit">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a> -->
|
||||||
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
||||||
style="display:inline;"
|
style="display:inline;"
|
||||||
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
|
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
|
||||||
|
|
@ -704,9 +704,9 @@ class="btn-action btn-edit" title="Edit">
|
||||||
<a href="{{ route('admin.users.show', $user) }}" class="btn-action btn-view" title="Detail">
|
<a href="{{ route('admin.users.show', $user) }}" class="btn-action btn-view" title="Detail">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.users.edit', $user) }}" class="btn-action btn-edit" title="Edit">
|
<!-- <a href="{{ route('admin.users.edit', $user) }}" class="btn-action btn-edit" title="Edit">
|
||||||
<i class="fas fa-edit"></i> Edit
|
<i class="fas fa-edit"></i> Edit
|
||||||
</a>
|
</a> -->
|
||||||
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
||||||
style="flex:0;"
|
style="flex:0;"
|
||||||
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
|
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
|
||||||
|
|
|
||||||
|
|
@ -154,9 +154,9 @@ class="form-control"
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Nomor Telepon</label>
|
<label class="form-label">Nomor Telepon</label>
|
||||||
<input type="text" name="phone"
|
<input type="text" name="phone" class="form-control @error('phone') is-invalid @enderror"
|
||||||
value="{{ old('phone', '+62') }}"
|
value="{{ old('phone') }}"
|
||||||
placeholder="+62812xxxxxxxx">
|
placeholder="081234567890">
|
||||||
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -620,8 +620,8 @@ class="form-control-custom"
|
||||||
<label class="form-label-custom">Nomor Telepon</label>
|
<label class="form-label-custom">Nomor Telepon</label>
|
||||||
<input type="text" name="phone"
|
<input type="text" name="phone"
|
||||||
class="form-control-custom @error('phone') is-invalid @enderror"
|
class="form-control-custom @error('phone') is-invalid @enderror"
|
||||||
value="{{ old('phone', $user->phone ?? '+62') }}"
|
value="{{ old('phone', $user->phone ?? '') }}"
|
||||||
placeholder="+62812xxxxxxxx">
|
placeholder="081234567890">
|
||||||
@error('phone')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
|
@error('phone')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
|
||||||
|
|
@ -396,10 +396,10 @@
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i class="fas fa-arrow-left"></i>
|
||||||
<span>Kembali</span>
|
<span>Kembali</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('admin.users.edit', $user) }}" class="btn-topbar btn-topbar-edit">
|
<!-- <a href="{{ route('admin.users.edit', $user) }}" class="btn-topbar btn-topbar-edit">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
<span>Edit</span>
|
<span>Edit</span>
|
||||||
</a>
|
</a> -->
|
||||||
@if($user->id !== auth()->id())
|
@if($user->id !== auth()->id())
|
||||||
<button type="button" class="btn-topbar btn-topbar-delete"
|
<button type="button" class="btn-topbar btn-topbar-delete"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||||
|
|
@ -548,14 +548,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-card-body" style="display:flex;flex-direction:column;gap:10px;">
|
<div class="info-card-body" style="display:flex;flex-direction:column;gap:10px;">
|
||||||
<a href="{{ route('admin.users.edit', $user) }}"
|
<!-- <a href="{{ route('admin.users.edit', $user) }}"
|
||||||
style="width:100%;padding:10px 16px;border-radius:9px;border:none;
|
style="width:100%;padding:10px 16px;border-radius:9px;border:none;
|
||||||
background:linear-gradient(135deg,#ca8a04,#a16207);
|
background:linear-gradient(135deg,#ca8a04,#a16207);
|
||||||
color:white;font-weight:700;font-size:0.875rem;
|
color:white;font-weight:700;font-size:0.875rem;
|
||||||
display:flex;align-items:center;justify-content:center;gap:8px;
|
display:flex;align-items:center;justify-content:center;gap:8px;
|
||||||
text-decoration:none;transition:all 0.2s;">
|
text-decoration:none;transition:all 0.2s;">
|
||||||
<i class="fas fa-edit"></i> Edit Pengguna
|
<i class="fas fa-edit"></i> Edit Pengguna
|
||||||
</a>
|
</a> -->
|
||||||
<a href="{{ route('admin.users') }}"
|
<a href="{{ route('admin.users') }}"
|
||||||
style="width:100%;padding:10px 16px;border-radius:9px;
|
style="width:100%;padding:10px 16px;border-radius:9px;
|
||||||
border:1.5px solid #e5e7eb;background:#f9fafb;
|
border:1.5px solid #e5e7eb;background:#f9fafb;
|
||||||
|
|
|
||||||
|
|
@ -98,17 +98,19 @@ class="form-control @error('email') is-invalid @enderror"
|
||||||
Nomor WhatsApp <span class="text-danger">*</span>
|
Nomor WhatsApp <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text bg-light fw-semibold text-success">+62</span>
|
|
||||||
<input type="tel"
|
<input type="tel"
|
||||||
class="form-control @error('phone') is-invalid @enderror"
|
class="form-control @error('phone') is-invalid @enderror"
|
||||||
id="phone" name="phone"
|
id="phone" name="phone"
|
||||||
value="{{ old('phone') }}"
|
value="{{ old('phone') }}"
|
||||||
placeholder="8123456789"
|
placeholder="081234567890"
|
||||||
inputmode="numeric">
|
inputmode="numeric">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text text-muted">
|
<div class="form-text text-muted">
|
||||||
|
<small><i class="fas fa-info-circle"></i> Masukkan nomor WhatsApp minimal 10 digit</small>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="form-text text-muted">
|
||||||
<small><i class="fas fa-info-circle"></i> Masukkan nomor tanpa angka 0 di depan</small>
|
<small><i class="fas fa-info-circle"></i> Masukkan nomor tanpa angka 0 di depan</small>
|
||||||
</div>
|
</div> -->
|
||||||
@error('phone')
|
@error('phone')
|
||||||
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||||
@enderror
|
@enderror
|
||||||
|
|
@ -200,10 +202,10 @@ function validateInput() {
|
||||||
submitBtn.disabled = !valid;
|
submitBtn.disabled = !valid;
|
||||||
} else {
|
} else {
|
||||||
const val = document.getElementById('phone').value.replace(/\D/g, '');
|
const val = document.getElementById('phone').value.replace(/\D/g, '');
|
||||||
const valid = val.length >= 9 && val.length <= 13;
|
const valid = val.length >= 10 && val.length <= 15;
|
||||||
document.getElementById('phoneError').style.display = (val.length > 0 && !valid) ? 'block' : 'none';
|
document.getElementById('phoneError').style.display = (val.length > 0 && !valid) ? 'block' : 'none';
|
||||||
submitBtn.disabled = !valid;
|
submitBtn.disabled = !valid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|
|
||||||
|
|
@ -76,16 +76,16 @@ class="form-control @error('phone') is-invalid @enderror"
|
||||||
name="phone"
|
name="phone"
|
||||||
value="{{ old('phone') }}"
|
value="{{ old('phone') }}"
|
||||||
required
|
required
|
||||||
placeholder="+6281234567890">
|
placeholder="081234567890">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text text-muted">
|
<div class="form-text text-muted">
|
||||||
<small><i class="fas fa-info-circle"></i> Harus diawali +62, contoh: +6281234567890</small>
|
<small><i class="fas fa-info-circle"></i> Nomor telepon minimal 10 karakter</small>
|
||||||
</div>
|
</div>
|
||||||
@error('phone')
|
@error('phone')
|
||||||
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||||
@enderror
|
@enderror
|
||||||
<div id="phoneError" class="text-danger mt-1" style="display: none;">
|
<div id="phoneError" class="text-danger mt-1" style="display: none;">
|
||||||
<small>Nomor telepon harus diawali +62 dan hanya boleh berisi angka</small>
|
<small>Nomor telepon minimal 10 karakter</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -99,16 +99,16 @@ class="form-control @error('address') is-invalid @enderror"
|
||||||
name="address"
|
name="address"
|
||||||
value="{{ old('address') }}"
|
value="{{ old('address') }}"
|
||||||
required
|
required
|
||||||
placeholder="Jl. Contoh No. 123">
|
placeholder="Jl. Contoh No. 12, RT 01/RW 02, Desa Kademangan">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text text-muted">
|
<div class="form-text text-muted">
|
||||||
<small><i class="fas fa-info-circle"></i> Alamat minimal 12 karakter</small>
|
<small><i class="fas fa-info-circle"></i> Wajib lengkap: Nama Jalan · No. Rumah · RT/RW · Desa/Kelurahan</small>
|
||||||
</div>
|
</div>
|
||||||
@error('address')
|
@error('address')
|
||||||
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||||
@enderror
|
@enderror
|
||||||
<div id="addressError" class="text-danger mt-1" style="display: none;">
|
<div id="addressError" class="text-danger mt-1" style="display: none;">
|
||||||
<small>Alamat minimal harus 12 karakter</small>
|
<small>Alamat terlalu pendek, harap isi lengkap (minimal 20 karakter)</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -255,23 +255,18 @@ class="form-control"
|
||||||
|
|
||||||
phoneInput.addEventListener('input', function() {
|
phoneInput.addEventListener('input', function() {
|
||||||
const phoneError = document.getElementById('phoneError');
|
const phoneError = document.getElementById('phoneError');
|
||||||
const numberPattern = /^[0-9]+$/;
|
if (this.value.length < 10 && this.value.length > 0) {
|
||||||
|
|
||||||
// Remove non-numeric characters
|
|
||||||
|
|
||||||
|
|
||||||
if (!/^\+62[0-9]{9,15}$/.test(this.value) && this.value.length > 0) {
|
|
||||||
phoneError.style.display = 'block';
|
phoneError.style.display = 'block';
|
||||||
this.classList.add('is-invalid');
|
this.classList.add('is-invalid');
|
||||||
} else {
|
} else {
|
||||||
phoneError.style.display = 'none';
|
phoneError.style.display = 'none';
|
||||||
this.classList.remove('is-invalid');
|
this.classList.remove('is-invalid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addressInput.addEventListener('input', function() {
|
addressInput.addEventListener('input', function() {
|
||||||
const addressError = document.getElementById('addressError');
|
const addressError = document.getElementById('addressError');
|
||||||
if (this.value.length < 12) {
|
if (this.value.length < 20) {
|
||||||
addressError.style.display = 'block';
|
addressError.style.display = 'block';
|
||||||
this.classList.add('is-invalid');
|
this.classList.add('is-invalid');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -327,7 +322,7 @@ function checkPasswordMatch() {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
|
|
||||||
// Check all validations
|
// Check all validations
|
||||||
if (nameInput.value.length < 12) {
|
if (nameInput.value.length < 8) {
|
||||||
document.getElementById('nameError').style.display = 'block';
|
document.getElementById('nameError').style.display = 'block';
|
||||||
nameInput.classList.add('is-invalid');
|
nameInput.classList.add('is-invalid');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
|
|
@ -340,13 +335,13 @@ function checkPasswordMatch() {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/^\+62[0-9]{9,15}$/.test(phoneInput.value)) {
|
if (phoneInput.value.length < 10) {
|
||||||
document.getElementById('phoneError').style.display = 'block';
|
document.getElementById('phoneError').style.display = 'block';
|
||||||
phoneInput.classList.add('is-invalid');
|
phoneInput.classList.add('is-invalid');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addressInput.value.length < 12) {
|
if (addressInput.value.length < 20) {
|
||||||
document.getElementById('addressError').style.display = 'block';
|
document.getElementById('addressError').style.display = 'block';
|
||||||
addressInput.classList.add('is-invalid');
|
addressInput.classList.add('is-invalid');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
<label class="form-label">Nama Lengkap *</label>
|
<label class="form-label">Nama Lengkap *</label>
|
||||||
<input type="text" name="name"
|
<input type="text" name="name"
|
||||||
class="form-control @error('name') is-invalid @enderror"
|
class="form-control @error('name') is-invalid @enderror"
|
||||||
value="{{ old('name') }}"
|
value="{{ old('name', $prefill['name'] ?? '') }}"
|
||||||
placeholder="Minimal 8 karakter"
|
placeholder="Minimal 8 karakter"
|
||||||
minlength="8" required>
|
minlength="8" required>
|
||||||
<small class="text-muted">Minimal 8 karakter</small>
|
<small class="text-muted">Minimal 8 karakter</small>
|
||||||
|
|
@ -69,10 +69,10 @@ class="form-control @error('name') is-invalid @enderror"
|
||||||
<label class="form-label">Nomor Telepon *</label>
|
<label class="form-label">Nomor Telepon *</label>
|
||||||
<input type="text" name="phone"
|
<input type="text" name="phone"
|
||||||
class="form-control @error('phone') is-invalid @enderror"
|
class="form-control @error('phone') is-invalid @enderror"
|
||||||
value="{{ old('phone') }}"
|
value="{{ old('phone', $prefill['phone'] ?? '') }}"
|
||||||
placeholder="+6281234567890"
|
placeholder="081234567890"
|
||||||
minlength="11" required>
|
minlength="10" required>
|
||||||
<small class="text-muted">Harus diawali +62, contoh: +6281234567890</small>
|
<small class="text-muted">Nomor telepon minimal 10 karakter</small>
|
||||||
@error('phone')
|
@error('phone')
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
@enderror
|
@enderror
|
||||||
|
|
@ -81,7 +81,7 @@ class="form-control @error('phone') is-invalid @enderror"
|
||||||
<label class="form-label">Email *</label>
|
<label class="form-label">Email *</label>
|
||||||
<input type="email" name="email"
|
<input type="email" name="email"
|
||||||
class="form-control @error('email') is-invalid @enderror"
|
class="form-control @error('email') is-invalid @enderror"
|
||||||
value="{{ old('email') }}"
|
value="{{ old('email', $prefill['email'] ?? '') }}"
|
||||||
placeholder="contoh@email.com"
|
placeholder="contoh@email.com"
|
||||||
minlength="8" required>
|
minlength="8" required>
|
||||||
<small class="text-muted">Minimal 8 karakter</small>
|
<small class="text-muted">Minimal 8 karakter</small>
|
||||||
|
|
@ -101,29 +101,29 @@ class="form-control @error('email') is-invalid @enderror"
|
||||||
<label class="form-label">Kecamatan *</label>
|
<label class="form-label">Kecamatan *</label>
|
||||||
<select name="city" id="kecamatan" class="form-control @error('city') is-invalid @enderror" required>
|
<select name="city" id="kecamatan" class="form-control @error('city') is-invalid @enderror" required>
|
||||||
<option value="">-- Pilih Kecamatan --</option>
|
<option value="">-- Pilih Kecamatan --</option>
|
||||||
<option value="Bondowoso" {{ old('city') == 'Bondowoso' ? 'selected' : '' }}>Bondowoso (Pusat Kota)</option>
|
<option value="Bondowoso" {{ old('city', $prefill['city'] ?? '') == 'Bondowoso' ? 'selected' : '' }}>Bondowoso (Pusat Kota)</option>
|
||||||
<option value="Grujugan" {{ old('city') == 'Grujugan' ? 'selected' : '' }}>Grujugan</option>
|
<option value="Grujugan" {{ old('city', $prefill['city'] ?? '') == 'Grujugan' ? 'selected' : '' }}>Grujugan</option>
|
||||||
<option value="Jambesari Darus Sholah" {{ old('city') == 'Jambesari Darus Sholah' ? 'selected' : '' }}>Jambesari Darus Sholah</option>
|
<option value="Jambesari Darus Sholah" {{ old('city', $prefill['city'] ?? '') == 'Jambesari Darus Sholah' ? 'selected' : '' }}>Jambesari Darus Sholah</option>
|
||||||
<option value="Klabang" {{ old('city') == 'Klabang' ? 'selected' : '' }}>Klabang</option>
|
<option value="Klabang" {{ old('city', $prefill['city'] ?? '') == 'Klabang' ? 'selected' : '' }}>Klabang</option>
|
||||||
<option value="Tenggarang" {{ old('city') == 'Tenggarang' ? 'selected' : '' }}>Tenggarang</option>
|
<option value="Tenggarang" {{ old('city', $prefill['city'] ?? '') == 'Tenggarang' ? 'selected' : '' }}>Tenggarang</option>
|
||||||
<option value="Binakal" {{ old('city') == 'Binakal' ? 'selected' : '' }}>Binakal</option>
|
<option value="Binakal" {{ old('city', $prefill['city'] ?? '') == 'Binakal' ? 'selected' : '' }}>Binakal</option>
|
||||||
<option value="Prajekan" {{ old('city') == 'Prajekan' ? 'selected' : '' }}>Prajekan</option>
|
<option value="Prajekan" {{ old('city', $prefill['city'] ?? '') == 'Prajekan' ? 'selected' : '' }}>Prajekan</option>
|
||||||
<option value="Botolinggo" {{ old('city') == 'Botolinggo' ? 'selected' : '' }}>Botolinggo</option>
|
<option value="Botolinggo" {{ old('city', $prefill['city'] ?? '') == 'Botolinggo' ? 'selected' : '' }}>Botolinggo</option>
|
||||||
<option value="Maesan" {{ old('city') == 'Maesan' ? 'selected' : '' }}>Maesan</option>
|
<option value="Maesan" {{ old('city', $prefill['city'] ?? '') == 'Maesan' ? 'selected' : '' }}>Maesan</option>
|
||||||
<option value="Tamanan" {{ old('city') == 'Tamanan' ? 'selected' : '' }}>Tamanan</option>
|
<option value="Tamanan" {{ old('city', $prefill['city'] ?? '') == 'Tamanan' ? 'selected' : '' }}>Tamanan</option>
|
||||||
<option value="Wonosari" {{ old('city') == 'Wonosari' ? 'selected' : '' }}>Wonosari</option>
|
<option value="Wonosari" {{ old('city', $prefill['city'] ?? '') == 'Wonosari' ? 'selected' : '' }}>Wonosari</option>
|
||||||
<option value="Pujer" {{ old('city') == 'Pujer' ? 'selected' : '' }}>Pujer</option>
|
<option value="Pujer" {{ old('city', $prefill['city'] ?? '') == 'Pujer' ? 'selected' : '' }}>Pujer</option>
|
||||||
<option value="Tlogosari" {{ old('city') == 'Tlogosari' ? 'selected' : '' }}>Tlogosari</option>
|
<option value="Tlogosari" {{ old('city', $prefill['city'] ?? '') == 'Tlogosari' ? 'selected' : '' }}>Tlogosari</option>
|
||||||
<option value="Sukosari" {{ old('city') == 'Sukosari' ? 'selected' : '' }}>Sukosari</option>
|
<option value="Sukosari" {{ old('city', $prefill['city'] ?? '') == 'Sukosari' ? 'selected' : '' }}>Sukosari</option>
|
||||||
<option value="Sumberwringin" {{ old('city') == 'Sumberwringin' ? 'selected' : '' }}>Sumberwringin</option>
|
<option value="Sumberwringin" {{ old('city', $prefill['city'] ?? '') == 'Sumberwringin' ? 'selected' : '' }}>Sumberwringin</option>
|
||||||
<option value="Tegalampel" {{ old('city') == 'Tegalampel' ? 'selected' : '' }}>Tegalampel</option>
|
<option value="Tegalampel" {{ old('city', $prefill['city'] ?? '') == 'Tegalampel' ? 'selected' : '' }}>Tegalampel</option>
|
||||||
<option value="Sempol" {{ old('city') == 'Sempol' ? 'selected' : '' }}>Sempol</option>
|
<option value="Sempol" {{ old('city', $prefill['city'] ?? '') == 'Sempol' ? 'selected' : '' }}>Sempol</option>
|
||||||
<option value="Pakem" {{ old('city') == 'Pakem' ? 'selected' : '' }}>Pakem</option>
|
<option value="Pakem" {{ old('city', $prefill['city'] ?? '') == 'Pakem' ? 'selected' : '' }}>Pakem</option>
|
||||||
<option value="Curahdami" {{ old('city') == 'Curahdami' ? 'selected' : '' }}>Curahdami</option>
|
<option value="Curahdami" {{ old('city', $prefill['city'] ?? '') == 'Curahdami' ? 'selected' : '' }}>Curahdami</option>
|
||||||
<option value="Ijen" {{ old('city') == 'Ijen' ? 'selected' : '' }}>Ijen</option>
|
<option value="Ijen" {{ old('city', $prefill['city'] ?? '') == 'Ijen' ? 'selected' : '' }}>Ijen</option>
|
||||||
<option value="Tapen" {{ old('city') == 'Tapen' ? 'selected' : '' }}>Tapen</option>
|
<option value="Tapen" {{ old('city', $prefill['city'] ?? '') == 'Tapen' ? 'selected' : '' }}>Tapen</option>
|
||||||
<option value="Wringin" {{ old('city') == 'Wringin' ? 'selected' : '' }}>Wringin</option>
|
<option value="Wringin" {{ old('city', $prefill['city'] ?? '') == 'Wringin' ? 'selected' : '' }}>Wringin</option>
|
||||||
<option value="Taman Krocok" {{ old('city') == 'Taman Krocok' ? 'selected' : '' }}>Taman Krocok</option>
|
<option value="Taman Krocok" {{ old('city', $prefill['city'] ?? '') == 'Taman Krocok' ? 'selected' : '' }}>Taman Krocok</option>
|
||||||
</select>
|
</select>
|
||||||
@error('city')
|
@error('city')
|
||||||
<div class="invalid-feedback">{{ $message }}</div>
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
|
@ -148,7 +148,7 @@ class="form-control @error('email') is-invalid @enderror"
|
||||||
class="form-control @error('address') is-invalid @enderror"
|
class="form-control @error('address') is-invalid @enderror"
|
||||||
rows="4"
|
rows="4"
|
||||||
placeholder="Contoh: Jl. Merdeka No. 12, RT 02/RW 05, Desa Kademangan, Arah dari pasar ke utara 200m"
|
placeholder="Contoh: Jl. Merdeka No. 12, RT 02/RW 05, Desa Kademangan, Arah dari pasar ke utara 200m"
|
||||||
minlength="20" required>{{ old('address') }}</textarea>
|
minlength="20" required>{{ old('address', $prefill['address'] ?? '') }}</textarea>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
Wajib lengkap: Nama Jalan · No. Rumah · RT/RW · Desa/Kelurahan · Ancer-ancer
|
Wajib lengkap: Nama Jalan · No. Rumah · RT/RW · Desa/Kelurahan · Ancer-ancer
|
||||||
</small>
|
</small>
|
||||||
|
|
@ -163,7 +163,7 @@ class="form-control @error('address') is-invalid @enderror"
|
||||||
<label class="form-label">Kode Pos *</label>
|
<label class="form-label">Kode Pos *</label>
|
||||||
<input type="text" name="postal_code"
|
<input type="text" name="postal_code"
|
||||||
class="form-control @error('postal_code') is-invalid @enderror"
|
class="form-control @error('postal_code') is-invalid @enderror"
|
||||||
value="{{ old('postal_code', '68200') }}"
|
value="{{ old('postal_code', $prefill['postal_code'] ?? '68200') }}"
|
||||||
placeholder="68200"
|
placeholder="68200"
|
||||||
maxlength="5" required>
|
maxlength="5" required>
|
||||||
@error('postal_code')
|
@error('postal_code')
|
||||||
|
|
|
||||||
|
|
@ -359,15 +359,80 @@ class="border p-2 bg-white">
|
||||||
@endif
|
@endif
|
||||||
<div class="alert alert-info mt-3 mb-0">
|
<div class="alert alert-info mt-3 mb-0">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
Setelah pembayaran berhasil, mohon konfirmasi melalui WhatsApp ke <strong>0813-3183-0561</strong> dengan menyertakan <strong>bukti pembayaran</strong> dan <strong>nomor invoice: {{ $transaksi->invoice_number }}</strong>
|
Setelah pembayaran, silakan <strong>upload bukti pembayaran</strong> di form di bawah ini. Admin akan mengkonfirmasi pembayaran Anda.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- Card Tombol Aksi (tidak ikut print) --}}
|
{{-- Card Tombol Aksi (tidak ikut print) --}}
|
||||||
<div class="card success-card mt-4">
|
<div class="card success-card mt-4">
|
||||||
<div class="card-body p-4 text-center">
|
<div class="card-body p-4">
|
||||||
|
|
||||||
|
{{-- Flash message --}}
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{{ session('success') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="fas fa-exclamation-circle me-2"></i>{{ $errors->first() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Form Upload Bukti Bayar (hanya untuk non-COD & belum lunas) --}}
|
||||||
|
@if($transaksi->payment_method !== 'cod' && $transaksi->payment_status !== 'paid')
|
||||||
|
@if(!$transaksi->fresh()->payment_proof)
|
||||||
|
<div class="mb-4">
|
||||||
|
<h6 class="fw-bold mb-3">
|
||||||
|
<i class="fas fa-upload me-2 text-success"></i>Upload Bukti Pembayaran
|
||||||
|
</h6>
|
||||||
|
<form action="{{ route('checkout.konfirmasi', $transaksi->id) }}"
|
||||||
|
method="POST"
|
||||||
|
enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted small">
|
||||||
|
Upload foto/screenshot bukti transfer (JPG/PNG, maks 2MB)
|
||||||
|
</label>
|
||||||
|
<input type="file"
|
||||||
|
name="payment_proof"
|
||||||
|
class="form-control @error('payment_proof') is-invalid @enderror"
|
||||||
|
accept="image/jpg,image/jpeg,image/png"
|
||||||
|
required>
|
||||||
|
@error('payment_proof')
|
||||||
|
<div class="invalid-feedback">{{ $message }}</div>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success w-100">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i>Kirim Konfirmasi Pembayaran ke Admin
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
@else
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<i class="fas fa-clock me-2"></i>
|
||||||
|
Bukti pembayaran sudah dikirim. <strong>Menunggu konfirmasi admin.</strong>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($transaksi->payment_method === 'cod')
|
||||||
|
<div class="alert alert-secondary mb-4">
|
||||||
|
<i class="fas fa-truck me-2"></i>
|
||||||
|
Pembayaran COD dilakukan saat pesanan tiba. Status akan diperbarui oleh admin.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($transaksi->payment_status === 'paid')
|
||||||
|
<div class="alert alert-success mb-4">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>
|
||||||
|
Pembayaran Anda telah <strong>dikonfirmasi lunas</strong> oleh admin!
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a href="{{ route('home') }}" class="btn btn-outline-success w-100">
|
<a href="{{ route('home') }}" class="btn btn-outline-success w-100">
|
||||||
|
|
@ -380,20 +445,9 @@ class="border p-2 bg-white">
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
|
||||||
<a href="https://wa.me/6282313155053?text=Halo, saya ingin konfirmasi pembayaran dengan invoice {{ $transaksi->invoice_number }}"
|
|
||||||
target="_blank"
|
|
||||||
class="btn btn-success w-100">
|
|
||||||
<i class="fab fa-whatsapp me-2"></i>Konfirmasi via WhatsApp
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const transaksiId = {{ $transaksi->id }};
|
const transaksiId = {{ $transaksi->id }};
|
||||||
const statusBadge = document.getElementById('statusBadge');
|
const statusBadge = document.getElementById('statusBadge');
|
||||||
|
|
|
||||||
|
|
@ -362,43 +362,134 @@ class="card-img-top product-image"
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Konfirmasi Alamat -->
|
||||||
|
<div class="modal fade" id="addressModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-success text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="fas fa-map-marker-alt me-2"></i>Pilih Alamat Pengiriman
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="savedAddressSection">
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<i class="fas fa-user-check me-2"></i>
|
||||||
|
<strong>Gunakan data yang tersimpan?</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card border-success mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="mb-1"><i class="fas fa-user me-2 text-success"></i><span id="savedName">-</span></p>
|
||||||
|
<p class="mb-1"><i class="fas fa-phone me-2 text-success"></i><span id="savedPhone">-</span></p>
|
||||||
|
<p class="mb-1"><i class="fas fa-envelope me-2 text-success"></i><span id="savedEmail">-</span></p>
|
||||||
|
<p class="mb-0"><i class="fas fa-map-marker-alt me-2 text-success"></i><span id="savedAddress">-</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<button class="btn btn-success" id="useSavedAddressBtn">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>Ya, Gunakan Data Ini
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-secondary" id="useNewAddressBtn">
|
||||||
|
<i class="fas fa-plus-circle me-2"></i>Gunakan Alamat Baru
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="noSavedAddressSection" style="display:none;">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Belum ada data tersimpan. Anda akan mengisi form checkout secara manual.
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button class="btn btn-success" id="continueNewBtn">
|
||||||
|
<i class="fas fa-arrow-right me-2"></i>Lanjut ke Checkout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Cek status login dari Laravel (true/false)
|
|
||||||
const isLoggedIn = @json(auth()->check());
|
const isLoggedIn = @json(auth()->check());
|
||||||
const loginUrl = "{{ route('login') }}";
|
const loginUrl = "{{ route('login') }}";
|
||||||
|
|
||||||
|
let pendingProductId = null;
|
||||||
|
let pendingQtyKey = null;
|
||||||
|
let savedUserData = null;
|
||||||
|
|
||||||
function increaseQty(productId, maxStock) {
|
function increaseQty(productId, maxStock) {
|
||||||
const input = document.getElementById('qty-' + productId);
|
const input = document.getElementById('qty-' + productId);
|
||||||
let value = parseInt(input.value);
|
let value = parseInt(input.value);
|
||||||
if (value < maxStock) {
|
if (value < maxStock) input.value = value + 1;
|
||||||
input.value = value + 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function decreaseQty(productId) {
|
function decreaseQty(productId) {
|
||||||
const input = document.getElementById('qty-' + productId);
|
const input = document.getElementById('qty-' + productId);
|
||||||
let value = parseInt(input.value);
|
let value = parseInt(input.value);
|
||||||
if (value > 1) {
|
if (value > 1) input.value = value - 1;
|
||||||
input.value = value - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkout(productId) {
|
function openAddressModal(productId, qtyKey) {
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
window.location.href = loginUrl;
|
window.location.href = loginUrl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const quantity = document.getElementById('qty-' + productId).value;
|
pendingProductId = productId;
|
||||||
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
pendingQtyKey = qtyKey;
|
||||||
|
|
||||||
|
fetch("{{ route('checkout.last-address') }}")
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
savedUserData = data;
|
||||||
|
const hasData = data.name || data.address;
|
||||||
|
|
||||||
|
if (hasData) {
|
||||||
|
document.getElementById('savedName').textContent = data.name || '-';
|
||||||
|
document.getElementById('savedPhone').textContent = data.phone || '-';
|
||||||
|
document.getElementById('savedEmail').textContent = data.email || '-';
|
||||||
|
document.getElementById('savedAddress').textContent =
|
||||||
|
[data.address, data.city, data.postal_code].filter(Boolean).join(', ') || '-';
|
||||||
|
|
||||||
|
document.getElementById('savedAddressSection').style.display = 'block';
|
||||||
|
document.getElementById('noSavedAddressSection').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
document.getElementById('savedAddressSection').style.display = 'none';
|
||||||
|
document.getElementById('noSavedAddressSection').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
new bootstrap.Modal(document.getElementById('addressModal')).show();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
goToCheckout(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToCheckout(useSaved) {
|
||||||
|
const qty = document.getElementById('qty-' + pendingQtyKey).value;
|
||||||
|
let url = `/checkout?product_id=${pendingProductId}&quantity=${qty}`;
|
||||||
|
if (useSaved) url += '&use_saved=1';
|
||||||
|
|
||||||
|
const modalEl = document.getElementById('addressModal');
|
||||||
|
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||||
|
if (modal) modal.hide();
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
document.getElementById('useSavedAddressBtn')?.addEventListener('click', () => goToCheckout(true));
|
||||||
|
document.getElementById('useNewAddressBtn')?.addEventListener('click', () => goToCheckout(false));
|
||||||
|
document.getElementById('continueNewBtn')?.addEventListener('click', () => goToCheckout(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkout(productId) {
|
||||||
|
openAddressModal(productId, productId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkoutAll(productId, inputKey) {
|
function checkoutAll(productId, inputKey) {
|
||||||
if (!isLoggedIn) {
|
openAddressModal(productId, inputKey);
|
||||||
window.location.href = loginUrl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const quantity = document.getElementById('qty-' + inputKey).value;
|
|
||||||
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
@ -13,8 +13,7 @@
|
||||||
use App\Http\Controllers\UserController;
|
use App\Http\Controllers\UserController;
|
||||||
use App\Http\Controllers\ProfileController;
|
use App\Http\Controllers\ProfileController;
|
||||||
use App\Http\Controllers\Auth\ForgotPasswordController;
|
use App\Http\Controllers\Auth\ForgotPasswordController;
|
||||||
use App\Http\Controllers\AdminForgotPasswordController; // ← tambahkan ini
|
use App\Http\Controllers\AdminForgotPasswordController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Public Routes
|
| Public Routes
|
||||||
|
|
@ -45,9 +44,16 @@
|
||||||
Route::get('/', [CheckoutController::class, 'index'])->name('index');
|
Route::get('/', [CheckoutController::class, 'index'])->name('index');
|
||||||
Route::post('/process', [CheckoutController::class, 'process'])->name('process');
|
Route::post('/process', [CheckoutController::class, 'process'])->name('process');
|
||||||
Route::get('/success/{id}', [CheckoutController::class, 'success'])->name('success');
|
Route::get('/success/{id}', [CheckoutController::class, 'success'])->name('success');
|
||||||
Route::get('/status/{id}', [CheckoutController::class, 'getStatus'])->name('status'); // ✅ hapus /checkout/
|
Route::get('/status/{id}', [CheckoutController::class, 'getStatus'])->name('status');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ✅ Di LUAR group, sejajar dengan group di atas
|
||||||
|
Route::get('/checkout/last-address', [CheckoutController::class, 'getLastAddress'])
|
||||||
|
->middleware('auth')
|
||||||
|
->name('checkout.last-address');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Authentication Routes
|
| Authentication Routes
|
||||||
|
|
@ -206,3 +212,9 @@
|
||||||
Route::post('/reset-password', [ForgotPasswordController::class, 'resetPassword'])
|
Route::post('/reset-password', [ForgotPasswordController::class, 'resetPassword'])
|
||||||
->name('password.update');
|
->name('password.update');
|
||||||
|
|
||||||
|
// Konfirmasi pembayaran oleh pembeli (upload bukti)
|
||||||
|
// Route pembeli upload bukti bayar
|
||||||
|
Route::post('/checkout/konfirmasi/{id}', [CheckoutController::class, 'konfirmasiPembayaran'])->name('checkout.konfirmasi');
|
||||||
|
|
||||||
|
// Route admin approve pembayaran (taruh di group admin/auth)
|
||||||
|
Route::post('/admin/transaksis/{id}/approve-pembayaran', [TransaksiController::class, 'approvePembayaran'])->name('admin.transaksis.approve');
|
||||||
Loading…
Reference in New Issue