parent
aa29baab24
commit
a7c178d541
|
|
@ -18,7 +18,7 @@ class AdminForgotPasswordController extends Controller
|
|||
// STEP 1 — Tampilkan form input email
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
protected FonnteService $fonnte;
|
||||
protected $fonnte; // ✅
|
||||
|
||||
public function __construct(FonnteService $fonnte)
|
||||
{
|
||||
|
|
@ -84,24 +84,27 @@ private function sendOtpViaEmail(Request $request)
|
|||
private function sendOtpViaWhatsapp(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => ['required', 'digits_between:10,15'],
|
||||
], [
|
||||
'phone' => ['required', 'min:10', 'max:15'],
|
||||
], [
|
||||
'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)
|
||||
->where(function ($q) use ($phoneFormatted, $request) {
|
||||
$q->where('phone', $phoneFormatted)
|
||||
->orWhere('phone', '+' . $phoneFormatted)
|
||||
$admin = User::where('is_admin', true)
|
||||
->where(function ($q) use ($phone, $request) {
|
||||
$q->where('phone', $phone)
|
||||
->orWhere('phone', '+' . $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();
|
||||
|
||||
$phoneFormatted = FonnteService::formatPhone($phone);
|
||||
|
||||
if (!$admin) {
|
||||
return back()->withErrors([
|
||||
'phone' => 'Nomor ini tidak terdaftar sebagai admin.',
|
||||
|
|
@ -117,11 +120,21 @@ private function sendOtpViaWhatsapp(Request $request)
|
|||
. "Berlaku selama *3 menit*.\n"
|
||||
. "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.');
|
||||
}
|
||||
}
|
||||
|
||||
$request->session()->put('admin_reset_email', $admin->email);
|
||||
$request->session()->put('admin_reset_method', 'whatsapp');
|
||||
|
|
|
|||
|
|
@ -77,20 +77,24 @@ private function sendOtpViaEmail(Request $request)
|
|||
private function sendOtpViaWhatsapp(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => ['required', 'digits_between:10,15'],
|
||||
], [
|
||||
'phone' => ['required', 'min:10', 'max:15'],
|
||||
], [
|
||||
'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)
|
||||
->orWhere('phone', '+' . $phoneFormatted)
|
||||
$user = User::where('phone', $phone)
|
||||
->orWhere('phone', '+' . $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();
|
||||
|
||||
$phoneFormatted = FonnteService::formatPhone($phone);
|
||||
|
||||
if (!$user) {
|
||||
return back()->withErrors([
|
||||
'phone' => 'Nomor WhatsApp tidak ditemukan di sistem kami.',
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ public function register(Request $request): RedirectResponse
|
|||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|min:8|max:255',
|
||||
'email' => 'required|string|email|max:255|unique:users|regex:/@gmail\.com$/|regex:/^\S+$/',
|
||||
'phone' => 'required|string|regex:/^\+62[0-9]{9,15}$/|max:20',
|
||||
'address' => 'required|string|min:12|max:500',
|
||||
'password' => 'required|string|min:8|confirmed|regex:/^\S+$/',
|
||||
'phone' => 'required|string|min:10|max:20',
|
||||
'address' => 'required|string|min:20|max:500',
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
'agree' => 'required|accepted',
|
||||
], [
|
||||
], [
|
||||
'name.required' => 'Nama wajib diisi.',
|
||||
'name.min' => 'Nama minimal harus 8 karakter.',
|
||||
'email.required' => 'Email wajib diisi.',
|
||||
|
|
@ -36,38 +36,35 @@ public function register(Request $request): RedirectResponse
|
|||
'email.unique' => 'Email sudah digunakan.',
|
||||
'email.regex' => 'Email harus menggunakan domain @gmail.com dan tidak boleh mengandung spasi.',
|
||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||
'phone.regex' => 'Nomor telepon harus diawali dengan +62 dan hanya boleh berisi angka.',
|
||||
'phone.unique' => 'Nomor telepon sudah terdaftar di sistem kami.',
|
||||
// 'phone.min' => 'Nomor telepon minimal harus 12 karakter.',
|
||||
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||
'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.min' => 'Password minimal harus 8 karakter.',
|
||||
'password.confirmed' => 'Konfirmasi password tidak cocok.',
|
||||
'password.regex' => 'Password tidak boleh mengandung spasi.',
|
||||
'password.confirmed'=> 'Konfirmasi password tidak cocok.',
|
||||
'agree.required' => 'Anda harus menyetujui syarat dan ketentuan.',
|
||||
'agree.accepted' => 'Anda harus menyetujui syarat dan ketentuan.',
|
||||
]);
|
||||
]);
|
||||
|
||||
// Validasi password familiar
|
||||
// Validasi password familiar & pernah dipakai user lain
|
||||
$validator->after(function ($validator) use ($request) {
|
||||
if ($request->has('password')) {
|
||||
if ($this->isCommonPassword($request->password)) {
|
||||
$validator->errors()->add('password', 'Password yang Anda gunakan terlalu umum. Silakan pilih password yang lebih unik.');
|
||||
}
|
||||
// $validator->after(function ($validator) use ($request) {
|
||||
// if ($request->has('password')) {
|
||||
// if ($this->isCommonPassword($request->password)) {
|
||||
// $validator->errors()->add('password', 'Password yang Anda gunakan terlalu umum. Silakan pilih password yang lebih unik.');
|
||||
// }
|
||||
|
||||
if ($this->isPasswordAlreadyUsed($request->password)) {
|
||||
$validator->errors()->add('password', 'Password ini sudah pernah digunakan oleh akun lain. Silakan gunakan password yang berbeda.');
|
||||
}
|
||||
}
|
||||
// if ($this->isPasswordAlreadyUsed($request->password)) {
|
||||
// $validator->errors()->add('password', 'Password ini sudah pernah digunakan oleh akun lain. Silakan gunakan password yang berbeda.');
|
||||
// }
|
||||
// }
|
||||
|
||||
if ($request->has('phone') && $request->phone) {
|
||||
if ($this->isPhoneAlreadyUsed($request->phone)) {
|
||||
$validator->errors()->add('phone', 'Nomor telepon sudah terdaftar di sistem kami.');
|
||||
}
|
||||
}
|
||||
});
|
||||
// if ($request->has('phone') && $request->phone) {
|
||||
// if ($this->isPhoneAlreadyUsed($request->phone)) {
|
||||
// $validator->errors()->add('phone', 'Nomor telepon sudah terdaftar di sistem kami.');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
if ($validator->fails()) {
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
|
|
@ -91,84 +88,84 @@ public function register(Request $request): RedirectResponse
|
|||
/**
|
||||
* Cek apakah password termasuk password yang umum/familiar
|
||||
*/
|
||||
private function isCommonPassword(string $password): bool
|
||||
{
|
||||
$commonPasswords = [
|
||||
// Password numerik umum
|
||||
'12345678', '123456789', '1234567890',
|
||||
'87654321', '11111111', '22222222', '33333333', '44444444', '55555555',
|
||||
'66666666', '77777777', '88888888', '99999999', '00000000',
|
||||
// private function isCommonPassword(string $password): bool
|
||||
// {
|
||||
// $commonPasswords = [
|
||||
// // Password numerik umum
|
||||
// '12345678', '123456789', '1234567890',
|
||||
// '87654321', '11111111', '22222222', '33333333', '44444444', '55555555',
|
||||
// '66666666', '77777777', '88888888', '99999999', '00000000',
|
||||
|
||||
// Password alfabetik umum
|
||||
'password', 'Password', 'PASSWORD', 'password123', 'Password123',
|
||||
'qwerty123', 'qwertyui', 'asdfghjk', 'zxcvbnm123',
|
||||
'abcd1234', 'admin123', 'user1234', 'welcome123',
|
||||
// // Password alfabetik umum
|
||||
// 'password', 'Password', 'PASSWORD', 'password123', 'Password123',
|
||||
// 'qwerty123', 'qwertyui', 'asdfghjk', 'zxcvbnm123',
|
||||
// 'abcd1234', 'admin123', 'user1234', 'welcome123',
|
||||
|
||||
// Password Indonesia umum
|
||||
'indonesia', 'Indonesia', 'garuda123', 'nusantara',
|
||||
'jakarta123', 'bali2023', 'surabaya', 'bandung123',
|
||||
'password1', 'rahasia123', 'bismillah',
|
||||
// // Password Indonesia umum
|
||||
// 'indonesia', 'Indonesia', 'garuda123', 'nusantara',
|
||||
// 'jakarta123', 'bali2023', 'surabaya', 'bandung123',
|
||||
// 'password1', 'rahasia123', 'bismillah',
|
||||
|
||||
// Kombinasi tanggal umum
|
||||
'01011990', '01011995', '01012000', '17081945',
|
||||
'12345abc', 'abc12345', '123qwe123', 'qwe12345',
|
||||
// // Kombinasi tanggal umum
|
||||
// '01011990', '01011995', '01012000', '17081945',
|
||||
// '12345abc', 'abc12345', '123qwe123', 'qwe12345',
|
||||
|
||||
// Pattern keyboard umum
|
||||
'qwerty12', 'asdf1234', 'zxcv1234', '1qaz2wsx',
|
||||
'1q2w3e4r', 'qazwsxed', 'plokijuh',
|
||||
// // Pattern keyboard umum
|
||||
// 'qwerty12', 'asdf1234', 'zxcv1234', '1qaz2wsx',
|
||||
// '1q2w3e4r', 'qazwsxed', 'plokijuh',
|
||||
|
||||
// Password dengan nama umum
|
||||
'admin1234', 'guest1234', 'test1234', 'demo1234',
|
||||
'sample123', 'example123', 'default123',
|
||||
// // Password dengan nama umum
|
||||
// 'admin1234', 'guest1234', 'test1234', 'demo1234',
|
||||
// 'sample123', 'example123', 'default123',
|
||||
|
||||
// Password sosial media umum
|
||||
'facebook', 'instagram', 'whatsapp', 'twitter123',
|
||||
'gmail123', 'yahoo123', 'google123',
|
||||
// // Password sosial media umum
|
||||
// 'facebook', 'instagram', 'whatsapp', 'twitter123',
|
||||
// 'gmail123', 'yahoo123', 'google123',
|
||||
|
||||
// Password gaming umum
|
||||
'minecraft', 'roblox123', 'fortnite', 'pubg1234',
|
||||
'mobilelegend', 'freefire', 'valorant',
|
||||
// // Password gaming umum
|
||||
// 'minecraft', 'roblox123', 'fortnite', 'pubg1234',
|
||||
// 'mobilelegend', 'freefire', 'valorant',
|
||||
|
||||
// Password device umum
|
||||
'android123', 'iphone123', 'samsung123', 'nokia1234',
|
||||
// // Password device umum
|
||||
// 'android123', 'iphone123', 'samsung123', 'nokia1234',
|
||||
|
||||
// Tahun umum
|
||||
'20232023', '20242024', '19901990', '20002000',
|
||||
];
|
||||
// // Tahun umum
|
||||
// '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
|
||||
*/
|
||||
private function isPasswordAlreadyUsed(string $password): bool
|
||||
{
|
||||
$users = User::whereNotNull('password')->get(['password']);
|
||||
// private function isPasswordAlreadyUsed(string $password): bool
|
||||
// {
|
||||
// $users = User::whereNotNull('password')->get(['password']);
|
||||
|
||||
foreach ($users as $user) {
|
||||
if (Hash::check($password, $user->password)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// foreach ($users as $user) {
|
||||
// if (Hash::check($password, $user->password)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
return false;
|
||||
// return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cek nomor telepon sudah terdaftar dalam berbagai format
|
||||
*/
|
||||
private function isPhoneAlreadyUsed(string $phone): bool
|
||||
{
|
||||
$digitsOnly = preg_replace('/\D/', '', $phone);
|
||||
// private function isPhoneAlreadyUsed(string $phone): bool
|
||||
// {
|
||||
// $digitsOnly = preg_replace('/\D/', '', $phone);
|
||||
|
||||
$formats = [
|
||||
$phone, // +6281234567890
|
||||
$digitsOnly, // 6281234567890
|
||||
'+' . $digitsOnly, // +6281234567890
|
||||
'0' . substr($digitsOnly, 2), // 081234567890
|
||||
];
|
||||
// $formats = [
|
||||
// $phone, // +6281234567890
|
||||
// $digitsOnly, // 6281234567890
|
||||
// '+' . $digitsOnly, // +6281234567890
|
||||
// '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 App\Mail\AdminOrderNotification;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class CheckoutController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
{
|
||||
try {
|
||||
$productId = $request->get('product_id');
|
||||
$quantity = $request->get('quantity', 1);
|
||||
|
|
@ -38,14 +40,26 @@ public function index(Request $request)
|
|||
$ongkir = 15000;
|
||||
$total = $subtotal + $ongkir;
|
||||
|
||||
// Panggil view checkout.index (form checkout)
|
||||
return view('checkout.index', compact('product', 'quantity', 'subtotal', 'ongkir', 'total'));
|
||||
$prefill = null;
|
||||
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) {
|
||||
Log::error('Checkout Index Error: ' . $e->getMessage());
|
||||
return redirect()->route('home')->with('error', 'Terjadi kesalahan: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function process(Request $request)
|
||||
{
|
||||
|
|
@ -53,7 +67,7 @@ public function process(Request $request)
|
|||
'product_id' => 'required|exists:products,id',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
'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',
|
||||
'address' => ['required', 'string', 'min:20',
|
||||
'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.min' => 'Nama lengkap minimal 8 karakter.',
|
||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||
'phone.min' => 'Nomor telepon minimal 11 karakter.',
|
||||
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya berisi angka. Contoh: +6281234567890',
|
||||
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||
'phone.max' => 'Nomor telepon maksimal 15 karakter.',
|
||||
'email.required' => 'Email wajib diisi.',
|
||||
'email.email' => 'Format email tidak valid.',
|
||||
'email.min' => 'Email minimal 8 karakter.',
|
||||
|
|
@ -230,4 +244,68 @@ public function getStatus($id)
|
|||
'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([
|
||||
'name' => 'required|string|max:255',
|
||||
'category' => 'required|string|max:100',
|
||||
'price' => 'required|numeric|min:0',
|
||||
'price' => 'required|numeric|min:1',
|
||||
'stock' => 'required|integer|min:0',
|
||||
'status' => 'required|in:aktif,nonaktif',
|
||||
'description' => 'nullable|string',
|
||||
|
|
@ -225,21 +225,24 @@ public function debugImage($id)
|
|||
private function syncLabelAfterSave(Product $product)
|
||||
{
|
||||
if ($product->stock <= 0) {
|
||||
return; // Sudah di-handle updateLabelBasedOnStock
|
||||
return;
|
||||
}
|
||||
|
||||
$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')
|
||||
->orderByDesc('total_sold')
|
||||
->take(10)
|
||||
->having('total_sold', '>=', 100)
|
||||
->orderByDesc('total_sold') // ← ini rumus (2): si > sj
|
||||
->take(4)
|
||||
->pluck('product_id')
|
||||
->toArray();
|
||||
|
||||
if (in_array($product->id, $bestSellingIds)) {
|
||||
$product->label = 'terlaris';
|
||||
$product->save();
|
||||
} else {
|
||||
$product->label = 'tersedia';
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ public function index()
|
|||
'products.price',
|
||||
'products.stock'
|
||||
)
|
||||
->having('total_sold', '>=', 100) // ← tambah baris ini
|
||||
->orderByDesc('total_sold')
|
||||
->take(10)
|
||||
->get();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
use App\Notifications\OrderStatusChanged;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class TransaksiController extends Controller
|
||||
{
|
||||
|
|
@ -501,5 +503,89 @@ public function rejectCancellation(Request $request, $id)
|
|||
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
|
||||
],
|
||||
'phone' => [
|
||||
'required', 'string', 'min:12',
|
||||
'regex:/^(\+62)[0-9]+$/' // harus diawali +62, hanya angka
|
||||
],
|
||||
'required', 'string', 'min:10',
|
||||
],
|
||||
'address' => ['required', 'string', 'min:10'],
|
||||
], [
|
||||
'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.',
|
||||
|
||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||
'phone.min' => 'Nomor telepon minimal 12 karakter.',
|
||||
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya boleh berisi angka.',
|
||||
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||
|
||||
'address.required' => 'Alamat wajib diisi.',
|
||||
'address.min' => 'Alamat minimal 10 karakter.',
|
||||
|
|
@ -102,7 +100,7 @@ public function store(Request $request)
|
|||
*/
|
||||
public function show(User $user)
|
||||
{
|
||||
return view('admin.users.show', [
|
||||
return view('admin.users.Show', [
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
|
|
@ -118,7 +116,7 @@ public function edit(User $user)
|
|||
->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$/'
|
||||
],
|
||||
'phone' => [
|
||||
'required', 'string', 'min:12',
|
||||
'regex:/^(\+62)[0-9]+$/'
|
||||
'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.',
|
||||
|
||||
'phone.required' => 'Nomor telepon wajib diisi.',
|
||||
'phone.min' => 'Nomor telepon minimal 12 karakter.',
|
||||
'phone.regex' => 'Nomor telepon harus diawali +62 dan hanya boleh berisi angka.',
|
||||
'phone.min' => 'Nomor telepon minimal 10 karakter.',
|
||||
|
||||
'address.required' => 'Alamat wajib diisi.',
|
||||
'address.min' => 'Alamat minimal 10 karakter.',
|
||||
|
|
@ -171,8 +167,10 @@ public function update(Request $request, User $user)
|
|||
'email' => $request->email,
|
||||
'phone' => $request->phone,
|
||||
'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')) {
|
||||
$request->validate([
|
||||
|
|
@ -199,11 +197,6 @@ public function update(Request $request, 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
|
||||
if (auth()->id() === $user->id) {
|
||||
return back()->with('error', 'Anda tidak dapat menghapus akun sendiri.');
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ class Transaksi extends Model
|
|||
'total_amount',
|
||||
'payment_method',
|
||||
'payment_status',
|
||||
'payment_proof', // ← pastikan ada ini
|
||||
'confirmed_at', // ← pastikan ada ini
|
||||
'order_status',
|
||||
'notes',
|
||||
'paid_at'
|
||||
|
|
@ -33,6 +35,7 @@ class Transaksi extends Model
|
|||
|
||||
protected $casts = [
|
||||
'paid_at' => 'datetime',
|
||||
'confirmed_at' => 'datetime', // ← tambah ini
|
||||
'subtotal' => 'decimal:2',
|
||||
'shipping_cost'=> 'decimal:2',
|
||||
'total_amount' => 'decimal:2',
|
||||
|
|
|
|||
|
|
@ -23,14 +23,26 @@ public function __construct()
|
|||
* @return bool
|
||||
*/
|
||||
public function sendMessage(string $phone, string $message): bool
|
||||
{
|
||||
{
|
||||
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([
|
||||
'Authorization' => $this->token,
|
||||
])->post($this->apiUrl, [
|
||||
'target' => $phone,
|
||||
'target' => $phoneCleaned,
|
||||
'message' => $message,
|
||||
'countryCode' => '62', // Indonesia
|
||||
'countryCode' => '62',
|
||||
]);
|
||||
|
||||
$body = $response->json();
|
||||
|
|
@ -46,7 +58,7 @@ public function sendMessage(string $phone, string $message): bool
|
|||
Log::error('Fonnte exception: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format nomor WA ke format internasional
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
*/
|
||||
|
||||
'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>Keterangan</th>
|
||||
<th>Tgl Pengajuan</th>
|
||||
<th>Status</th>
|
||||
<th>Tgl Review</th>
|
||||
<th class="text-center">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -576,11 +576,11 @@
|
|||
<small style="color:#9ca3af;">{{ $item->created_at->format('H:i') }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge-status badge-{{ $item->status }}">
|
||||
{{ $item->status === 'pending' ? 'Menunggu' : ($item->status === 'approved' ? 'Disetujui' : 'Ditolak') }}
|
||||
</span>
|
||||
@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
|
||||
</td>
|
||||
<td class="text-center">
|
||||
|
|
|
|||
|
|
@ -186,23 +186,22 @@ class="form-control @error('email') is-invalid @enderror"
|
|||
<div class="mb-4">
|
||||
<label for="phone" class="form-label fw-semibold">Nomor WhatsApp Admin</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text fw-semibold" style="color:#11998e;">+62</span>
|
||||
<input type="tel"
|
||||
class="form-control @error('phone') is-invalid @enderror"
|
||||
id="phone"
|
||||
name="phone"
|
||||
value="{{ old('phone') }}"
|
||||
placeholder="8123456789"
|
||||
placeholder="081234567890"
|
||||
inputmode="numeric">
|
||||
</div>
|
||||
<div class="form-text text-muted small">
|
||||
ℹ️ Masukkan nomor tanpa angka 0 di depan
|
||||
ℹ️ Masukkan nomor WhatsApp minimal 10 digit
|
||||
</div>
|
||||
@error('phone')
|
||||
<div class="text-danger mt-1 small">{{ $message }}</div>
|
||||
@enderror
|
||||
<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>
|
||||
|
|
@ -274,11 +273,11 @@ function validateInput() {
|
|||
submitBtn.disabled = !valid;
|
||||
} else {
|
||||
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';
|
||||
submitBtn.disabled = !valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
|
|
|||
|
|
@ -262,8 +262,8 @@
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1rem;
|
||||
|
|
@ -276,10 +276,11 @@
|
|||
|
||||
.table-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
align-items: flex-end;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.data-table {
|
||||
|
|
@ -569,14 +570,13 @@
|
|||
</div>
|
||||
|
||||
<!-- Table Card -->
|
||||
<div class="table-card">
|
||||
<div class="table-card-header">
|
||||
<div class="section-title">
|
||||
<i class="fas fa-shopping-cart text-success"></i>
|
||||
Daftar Pesanan ({{ $orders->total() }})
|
||||
</div>
|
||||
<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="pending">Pending</option>
|
||||
<option value="processing">Processing</option>
|
||||
|
|
@ -584,11 +584,17 @@
|
|||
<option value="delivered">Delivered</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</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">
|
||||
<i class="fas fa-file-excel me-1"></i>Export Excel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="data-table" id="ordersTable">
|
||||
|
|
@ -608,6 +614,7 @@
|
|||
<tbody>
|
||||
@forelse($orders as $order)
|
||||
<tr data-status="{{ $order->order_status }}"
|
||||
data-payment="{{ $order->payment_status }}"
|
||||
class="{{ $order->order_status === 'cancelled' ? 'row-cancelled' : '' }}">
|
||||
<td><strong>{{ $order->invoice_number }}</strong></td>
|
||||
<td>{{ $order->customer_name }}</td>
|
||||
|
|
@ -704,13 +711,17 @@ function closeSidebar() {
|
|||
|
||||
// Filter orders
|
||||
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]');
|
||||
rows.forEach(row => {
|
||||
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
|
||||
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"
|
||||
name="price" id="price"
|
||||
value="{{ old('price', $product->price) }}"
|
||||
min="0" step="1000" placeholder="0" required>
|
||||
min="0" step="1" placeholder="0" required>
|
||||
</div>
|
||||
@error('price')
|
||||
<div class="invalid-feedback d-block">{{ $message }}</div>
|
||||
|
|
|
|||
|
|
@ -486,11 +486,11 @@
|
|||
<i class="fas fa-print"></i>
|
||||
<span>Print</span>
|
||||
</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">
|
||||
<i class="fas fa-trash"></i>
|
||||
<span>Hapus</span>
|
||||
</button>
|
||||
</button> -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -747,7 +747,67 @@
|
|||
</div>
|
||||
</div>
|
||||
@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>
|
||||
<!-- Akhir col-lg-4 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -631,10 +631,10 @@
|
|||
class="btn-action btn-view" title="Detail">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{{ route('admin.users.edit', $user) }}"
|
||||
<!-- <a href="{{ route('admin.users.edit', $user) }}"
|
||||
class="btn-action btn-edit" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
</a> -->
|
||||
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
||||
style="display:inline;"
|
||||
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">
|
||||
<i class="fas fa-eye"></i>
|
||||
</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
|
||||
</a>
|
||||
</a> -->
|
||||
<form method="POST" action="{{ route('admin.users.destroy', $user) }}"
|
||||
style="flex:0;"
|
||||
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?')">
|
||||
|
|
|
|||
|
|
@ -154,9 +154,9 @@ class="form-control"
|
|||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nomor Telepon</label>
|
||||
<input type="text" name="phone"
|
||||
value="{{ old('phone', '+62') }}"
|
||||
placeholder="+62812xxxxxxxx">
|
||||
<input type="text" name="phone" class="form-control @error('phone') is-invalid @enderror"
|
||||
value="{{ old('phone') }}"
|
||||
placeholder="081234567890">
|
||||
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -620,8 +620,8 @@ class="form-control-custom"
|
|||
<label class="form-label-custom">Nomor Telepon</label>
|
||||
<input type="text" name="phone"
|
||||
class="form-control-custom @error('phone') is-invalid @enderror"
|
||||
value="{{ old('phone', $user->phone ?? '+62') }}"
|
||||
placeholder="+62812xxxxxxxx">
|
||||
value="{{ old('phone', $user->phone ?? '') }}"
|
||||
placeholder="081234567890">
|
||||
@error('phone')<div class="invalid-feedback-custom">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
<div class="col-12">
|
||||
|
|
|
|||
|
|
@ -396,10 +396,10 @@
|
|||
<i class="fas fa-arrow-left"></i>
|
||||
<span>Kembali</span>
|
||||
</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>
|
||||
<span>Edit</span>
|
||||
</a>
|
||||
</a> -->
|
||||
@if($user->id !== auth()->id())
|
||||
<button type="button" class="btn-topbar btn-topbar-delete"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
|
|
@ -548,14 +548,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<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;
|
||||
background:linear-gradient(135deg,#ca8a04,#a16207);
|
||||
color:white;font-weight:700;font-size:0.875rem;
|
||||
display:flex;align-items:center;justify-content:center;gap:8px;
|
||||
text-decoration:none;transition:all 0.2s;">
|
||||
<i class="fas fa-edit"></i> Edit Pengguna
|
||||
</a>
|
||||
</a> -->
|
||||
<a href="{{ route('admin.users') }}"
|
||||
style="width:100%;padding:10px 16px;border-radius:9px;
|
||||
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>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light fw-semibold text-success">+62</span>
|
||||
<input type="tel"
|
||||
class="form-control @error('phone') is-invalid @enderror"
|
||||
id="phone" name="phone"
|
||||
value="{{ old('phone') }}"
|
||||
placeholder="8123456789"
|
||||
placeholder="081234567890"
|
||||
inputmode="numeric">
|
||||
</div>
|
||||
<div class="form-text text-muted">
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div> -->
|
||||
@error('phone')
|
||||
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||
@enderror
|
||||
|
|
@ -200,10 +202,10 @@ function validateInput() {
|
|||
submitBtn.disabled = !valid;
|
||||
} else {
|
||||
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';
|
||||
submitBtn.disabled = !valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
|
|
|||
|
|
@ -76,16 +76,16 @@ class="form-control @error('phone') is-invalid @enderror"
|
|||
name="phone"
|
||||
value="{{ old('phone') }}"
|
||||
required
|
||||
placeholder="+6281234567890">
|
||||
placeholder="081234567890">
|
||||
</div>
|
||||
<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>
|
||||
@error('phone')
|
||||
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||
@enderror
|
||||
<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>
|
||||
|
||||
|
|
@ -99,16 +99,16 @@ class="form-control @error('address') is-invalid @enderror"
|
|||
name="address"
|
||||
value="{{ old('address') }}"
|
||||
required
|
||||
placeholder="Jl. Contoh No. 123">
|
||||
placeholder="Jl. Contoh No. 12, RT 01/RW 02, Desa Kademangan">
|
||||
</div>
|
||||
<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>
|
||||
@error('address')
|
||||
<div class="text-danger mt-1"><small>{{ $message }}</small></div>
|
||||
@enderror
|
||||
<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>
|
||||
|
||||
|
|
@ -255,23 +255,18 @@ class="form-control"
|
|||
|
||||
phoneInput.addEventListener('input', function() {
|
||||
const phoneError = document.getElementById('phoneError');
|
||||
const numberPattern = /^[0-9]+$/;
|
||||
|
||||
// Remove non-numeric characters
|
||||
|
||||
|
||||
if (!/^\+62[0-9]{9,15}$/.test(this.value) && this.value.length > 0) {
|
||||
if (this.value.length < 10 && this.value.length > 0) {
|
||||
phoneError.style.display = 'block';
|
||||
this.classList.add('is-invalid');
|
||||
} else {
|
||||
phoneError.style.display = 'none';
|
||||
this.classList.remove('is-invalid');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
addressInput.addEventListener('input', function() {
|
||||
const addressError = document.getElementById('addressError');
|
||||
if (this.value.length < 12) {
|
||||
if (this.value.length < 20) {
|
||||
addressError.style.display = 'block';
|
||||
this.classList.add('is-invalid');
|
||||
} else {
|
||||
|
|
@ -327,7 +322,7 @@ function checkPasswordMatch() {
|
|||
let isValid = true;
|
||||
|
||||
// Check all validations
|
||||
if (nameInput.value.length < 12) {
|
||||
if (nameInput.value.length < 8) {
|
||||
document.getElementById('nameError').style.display = 'block';
|
||||
nameInput.classList.add('is-invalid');
|
||||
isValid = false;
|
||||
|
|
@ -340,13 +335,13 @@ function checkPasswordMatch() {
|
|||
isValid = false;
|
||||
}
|
||||
|
||||
if (!/^\+62[0-9]{9,15}$/.test(phoneInput.value)) {
|
||||
if (phoneInput.value.length < 10) {
|
||||
document.getElementById('phoneError').style.display = 'block';
|
||||
phoneInput.classList.add('is-invalid');
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (addressInput.value.length < 12) {
|
||||
if (addressInput.value.length < 20) {
|
||||
document.getElementById('addressError').style.display = 'block';
|
||||
addressInput.classList.add('is-invalid');
|
||||
isValid = false;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
<label class="form-label">Nama Lengkap *</label>
|
||||
<input type="text" name="name"
|
||||
class="form-control @error('name') is-invalid @enderror"
|
||||
value="{{ old('name') }}"
|
||||
value="{{ old('name', $prefill['name'] ?? '') }}"
|
||||
placeholder="Minimal 8 karakter"
|
||||
minlength="8" required>
|
||||
<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>
|
||||
<input type="text" name="phone"
|
||||
class="form-control @error('phone') is-invalid @enderror"
|
||||
value="{{ old('phone') }}"
|
||||
placeholder="+6281234567890"
|
||||
minlength="11" required>
|
||||
<small class="text-muted">Harus diawali +62, contoh: +6281234567890</small>
|
||||
value="{{ old('phone', $prefill['phone'] ?? '') }}"
|
||||
placeholder="081234567890"
|
||||
minlength="10" required>
|
||||
<small class="text-muted">Nomor telepon minimal 10 karakter</small>
|
||||
@error('phone')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
|
|
@ -81,7 +81,7 @@ class="form-control @error('phone') is-invalid @enderror"
|
|||
<label class="form-label">Email *</label>
|
||||
<input type="email" name="email"
|
||||
class="form-control @error('email') is-invalid @enderror"
|
||||
value="{{ old('email') }}"
|
||||
value="{{ old('email', $prefill['email'] ?? '') }}"
|
||||
placeholder="contoh@email.com"
|
||||
minlength="8" required>
|
||||
<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>
|
||||
<select name="city" id="kecamatan" class="form-control @error('city') is-invalid @enderror" required>
|
||||
<option value="">-- Pilih Kecamatan --</option>
|
||||
<option value="Bondowoso" {{ old('city') == 'Bondowoso' ? 'selected' : '' }}>Bondowoso (Pusat Kota)</option>
|
||||
<option value="Grujugan" {{ old('city') == 'Grujugan' ? 'selected' : '' }}>Grujugan</option>
|
||||
<option value="Jambesari Darus Sholah" {{ old('city') == 'Jambesari Darus Sholah' ? 'selected' : '' }}>Jambesari Darus Sholah</option>
|
||||
<option value="Klabang" {{ old('city') == 'Klabang' ? 'selected' : '' }}>Klabang</option>
|
||||
<option value="Tenggarang" {{ old('city') == 'Tenggarang' ? 'selected' : '' }}>Tenggarang</option>
|
||||
<option value="Binakal" {{ old('city') == 'Binakal' ? 'selected' : '' }}>Binakal</option>
|
||||
<option value="Prajekan" {{ old('city') == 'Prajekan' ? 'selected' : '' }}>Prajekan</option>
|
||||
<option value="Botolinggo" {{ old('city') == 'Botolinggo' ? 'selected' : '' }}>Botolinggo</option>
|
||||
<option value="Maesan" {{ old('city') == 'Maesan' ? 'selected' : '' }}>Maesan</option>
|
||||
<option value="Tamanan" {{ old('city') == 'Tamanan' ? 'selected' : '' }}>Tamanan</option>
|
||||
<option value="Wonosari" {{ old('city') == 'Wonosari' ? 'selected' : '' }}>Wonosari</option>
|
||||
<option value="Pujer" {{ old('city') == 'Pujer' ? 'selected' : '' }}>Pujer</option>
|
||||
<option value="Tlogosari" {{ old('city') == 'Tlogosari' ? 'selected' : '' }}>Tlogosari</option>
|
||||
<option value="Sukosari" {{ old('city') == 'Sukosari' ? 'selected' : '' }}>Sukosari</option>
|
||||
<option value="Sumberwringin" {{ old('city') == 'Sumberwringin' ? 'selected' : '' }}>Sumberwringin</option>
|
||||
<option value="Tegalampel" {{ old('city') == 'Tegalampel' ? 'selected' : '' }}>Tegalampel</option>
|
||||
<option value="Sempol" {{ old('city') == 'Sempol' ? 'selected' : '' }}>Sempol</option>
|
||||
<option value="Pakem" {{ old('city') == 'Pakem' ? 'selected' : '' }}>Pakem</option>
|
||||
<option value="Curahdami" {{ old('city') == 'Curahdami' ? 'selected' : '' }}>Curahdami</option>
|
||||
<option value="Ijen" {{ old('city') == 'Ijen' ? 'selected' : '' }}>Ijen</option>
|
||||
<option value="Tapen" {{ old('city') == 'Tapen' ? 'selected' : '' }}>Tapen</option>
|
||||
<option value="Wringin" {{ old('city') == 'Wringin' ? 'selected' : '' }}>Wringin</option>
|
||||
<option value="Taman Krocok" {{ old('city') == 'Taman Krocok' ? 'selected' : '' }}>Taman Krocok</option>
|
||||
<option value="Bondowoso" {{ old('city', $prefill['city'] ?? '') == 'Bondowoso' ? 'selected' : '' }}>Bondowoso (Pusat Kota)</option>
|
||||
<option value="Grujugan" {{ old('city', $prefill['city'] ?? '') == 'Grujugan' ? 'selected' : '' }}>Grujugan</option>
|
||||
<option value="Jambesari Darus Sholah" {{ old('city', $prefill['city'] ?? '') == 'Jambesari Darus Sholah' ? 'selected' : '' }}>Jambesari Darus Sholah</option>
|
||||
<option value="Klabang" {{ old('city', $prefill['city'] ?? '') == 'Klabang' ? 'selected' : '' }}>Klabang</option>
|
||||
<option value="Tenggarang" {{ old('city', $prefill['city'] ?? '') == 'Tenggarang' ? 'selected' : '' }}>Tenggarang</option>
|
||||
<option value="Binakal" {{ old('city', $prefill['city'] ?? '') == 'Binakal' ? 'selected' : '' }}>Binakal</option>
|
||||
<option value="Prajekan" {{ old('city', $prefill['city'] ?? '') == 'Prajekan' ? 'selected' : '' }}>Prajekan</option>
|
||||
<option value="Botolinggo" {{ old('city', $prefill['city'] ?? '') == 'Botolinggo' ? 'selected' : '' }}>Botolinggo</option>
|
||||
<option value="Maesan" {{ old('city', $prefill['city'] ?? '') == 'Maesan' ? 'selected' : '' }}>Maesan</option>
|
||||
<option value="Tamanan" {{ old('city', $prefill['city'] ?? '') == 'Tamanan' ? 'selected' : '' }}>Tamanan</option>
|
||||
<option value="Wonosari" {{ old('city', $prefill['city'] ?? '') == 'Wonosari' ? 'selected' : '' }}>Wonosari</option>
|
||||
<option value="Pujer" {{ old('city', $prefill['city'] ?? '') == 'Pujer' ? 'selected' : '' }}>Pujer</option>
|
||||
<option value="Tlogosari" {{ old('city', $prefill['city'] ?? '') == 'Tlogosari' ? 'selected' : '' }}>Tlogosari</option>
|
||||
<option value="Sukosari" {{ old('city', $prefill['city'] ?? '') == 'Sukosari' ? 'selected' : '' }}>Sukosari</option>
|
||||
<option value="Sumberwringin" {{ old('city', $prefill['city'] ?? '') == 'Sumberwringin' ? 'selected' : '' }}>Sumberwringin</option>
|
||||
<option value="Tegalampel" {{ old('city', $prefill['city'] ?? '') == 'Tegalampel' ? 'selected' : '' }}>Tegalampel</option>
|
||||
<option value="Sempol" {{ old('city', $prefill['city'] ?? '') == 'Sempol' ? 'selected' : '' }}>Sempol</option>
|
||||
<option value="Pakem" {{ old('city', $prefill['city'] ?? '') == 'Pakem' ? 'selected' : '' }}>Pakem</option>
|
||||
<option value="Curahdami" {{ old('city', $prefill['city'] ?? '') == 'Curahdami' ? 'selected' : '' }}>Curahdami</option>
|
||||
<option value="Ijen" {{ old('city', $prefill['city'] ?? '') == 'Ijen' ? 'selected' : '' }}>Ijen</option>
|
||||
<option value="Tapen" {{ old('city', $prefill['city'] ?? '') == 'Tapen' ? 'selected' : '' }}>Tapen</option>
|
||||
<option value="Wringin" {{ old('city', $prefill['city'] ?? '') == 'Wringin' ? 'selected' : '' }}>Wringin</option>
|
||||
<option value="Taman Krocok" {{ old('city', $prefill['city'] ?? '') == 'Taman Krocok' ? 'selected' : '' }}>Taman Krocok</option>
|
||||
</select>
|
||||
@error('city')
|
||||
<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"
|
||||
rows="4"
|
||||
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">
|
||||
Wajib lengkap: Nama Jalan · No. Rumah · RT/RW · Desa/Kelurahan · Ancer-ancer
|
||||
</small>
|
||||
|
|
@ -163,7 +163,7 @@ class="form-control @error('address') is-invalid @enderror"
|
|||
<label class="form-label">Kode Pos *</label>
|
||||
<input type="text" name="postal_code"
|
||||
class="form-control @error('postal_code') is-invalid @enderror"
|
||||
value="{{ old('postal_code', '68200') }}"
|
||||
value="{{ old('postal_code', $prefill['postal_code'] ?? '68200') }}"
|
||||
placeholder="68200"
|
||||
maxlength="5" required>
|
||||
@error('postal_code')
|
||||
|
|
|
|||
|
|
@ -359,15 +359,80 @@ class="border p-2 bg-white">
|
|||
@endif
|
||||
<div class="alert alert-info mt-3 mb-0">
|
||||
<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>
|
||||
</div>
|
||||
Setelah pembayaran, silakan <strong>upload bukti pembayaran</strong> di form di bawah ini. Admin akan mengkonfirmasi pembayaran Anda.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Card Tombol Aksi (tidak ikut print) --}}
|
||||
<div class="card success-card mt-4">
|
||||
<div class="card-body p-4 text-center">
|
||||
{{-- Card Tombol Aksi (tidak ikut print) --}}
|
||||
<div class="card success-card mt-4">
|
||||
<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="col-md-6">
|
||||
<a href="{{ route('home') }}" class="btn btn-outline-success w-100">
|
||||
|
|
@ -380,20 +445,9 @@ class="border p-2 bg-white">
|
|||
</button>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
const transaksiId = {{ $transaksi->id }};
|
||||
const statusBadge = document.getElementById('statusBadge');
|
||||
|
|
|
|||
|
|
@ -362,43 +362,134 @@ class="card-img-top product-image"
|
|||
</section>
|
||||
</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>
|
||||
// Cek status login dari Laravel (true/false)
|
||||
const isLoggedIn = @json(auth()->check());
|
||||
const loginUrl = "{{ route('login') }}";
|
||||
|
||||
let pendingProductId = null;
|
||||
let pendingQtyKey = null;
|
||||
let savedUserData = null;
|
||||
|
||||
function increaseQty(productId, maxStock) {
|
||||
const input = document.getElementById('qty-' + productId);
|
||||
let value = parseInt(input.value);
|
||||
if (value < maxStock) {
|
||||
input.value = value + 1;
|
||||
}
|
||||
if (value < maxStock) input.value = value + 1;
|
||||
}
|
||||
|
||||
function decreaseQty(productId) {
|
||||
const input = document.getElementById('qty-' + productId);
|
||||
let value = parseInt(input.value);
|
||||
if (value > 1) {
|
||||
input.value = value - 1;
|
||||
}
|
||||
if (value > 1) input.value = value - 1;
|
||||
}
|
||||
|
||||
function checkout(productId) {
|
||||
function openAddressModal(productId, qtyKey) {
|
||||
if (!isLoggedIn) {
|
||||
window.location.href = loginUrl;
|
||||
return;
|
||||
}
|
||||
const quantity = document.getElementById('qty-' + productId).value;
|
||||
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
||||
pendingProductId = productId;
|
||||
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) {
|
||||
if (!isLoggedIn) {
|
||||
window.location.href = loginUrl;
|
||||
return;
|
||||
}
|
||||
const quantity = document.getElementById('qty-' + inputKey).value;
|
||||
window.location.href = `/checkout?product_id=${productId}&quantity=${quantity}`;
|
||||
openAddressModal(productId, inputKey);
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -13,8 +13,7 @@
|
|||
use App\Http\Controllers\UserController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\Auth\ForgotPasswordController;
|
||||
use App\Http\Controllers\AdminForgotPasswordController; // ← tambahkan ini
|
||||
|
||||
use App\Http\Controllers\AdminForgotPasswordController;
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Public Routes
|
||||
|
|
@ -45,9 +44,16 @@
|
|||
Route::get('/', [CheckoutController::class, 'index'])->name('index');
|
||||
Route::post('/process', [CheckoutController::class, 'process'])->name('process');
|
||||
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
|
||||
|
|
@ -206,3 +212,9 @@
|
|||
Route::post('/reset-password', [ForgotPasswordController::class, 'resetPassword'])
|
||||
->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