<?php

namespace App\Controllers;

/**
 * Port SEBAGIAN dari Assync.php (CI3) -- controller backend yang dipanggil
 * lewat AJAX dari halaman-halaman Manage/keranjang/checkout yang sudah
 * diport. Slice 1: profil/password/PIN. Slice 2: alamat (CRUD + cascading
 * wilayah). Slice 3: rekening bank (CRUD). Slice 4: riwayat saldo + top-up
 * Midtrans. Slice 5: keranjang produk fisik/jasa (prosesBeli/hapusKeranjang/
 * updateKeranjang) + dukungan checkout (cekVoucher/cekPin/cekOngkir).
 * Slice 6: submit ulasan produk (tambahUlasan). Slice 7: chat ke admin
 * (kirimPesan/cekPesanBaru) -- port dari Apih2h.php (API mobile CI3),
 * dipakai ulang utk UI chat baru di web (lihat Manage::pesan()). Slice 8:
 * status pesanan fisik/jasa lengkap (pesanan/terimaPesanan/batalkanPesanan/
 * requestbatalkanPesanan/perpanjangPesanan/bayarulangpesanan/cekmidtrans)
 * -- CI3 punya 3 versi terpisah (pesanan/pesananjasa/pesananppob), di sini
 * fisik+jasa DIGABUNG jadi satu (tab "Pesanan" tidak filter `jasa`); PPOB
 * (pesananppob) & Pre Order checkout (invoicepreorder/bayarpreorder) BELUM
 * diport -- lihat catatan di pesanan-preorder.php. Method lain di
 * Assync.php asli (PPOB H2H, dkk) BELUM diport.
 *
 * midtransWebhook() ditulis generik (cek tipe record by order_id) --
 * sekarang menangani topup (saldotarik) dan pembayaran fisik/jasa
 * (pembayaran), persis seperti midtrans_webhook() asli CI3 yang mengecek
 * beberapa tabel berurutan dalam satu method.
 */
class Assync extends BaseController
{
    public function updateProfil()
    {
        $usrid = $this->func->cekLogin();
        if ($usrid === 0 || $this->request->getPost('nama') === null) {
            return $this->response->setJSON(['success' => false, 'msg' => 'Forbidden!']);
        }

        $nama    = (string) $this->request->getPost('nama');
        $kelamin = $this->request->getPost('kelamin');
        $markup  = $this->request->getPost('markup');

        $this->db->table('profil')->where('usrid', $usrid)->update(['nama' => $nama, 'kelamin' => $kelamin]);
        $this->db->table('userdata')->where('id', $usrid)->update(['nama' => $nama, 'markup' => $markup]);

        $this->func->addlog([
            'usrid'   => $usrid,
            'ip'      => $this->request->getIPAddress(),
            'type'    => 'web',
            'ket'     => 'update Profil',
            'created' => date('Y-m-d H:i:s'),
        ]);

        return $this->response->setJSON(['success' => true, 'msg' => 'Berhasil mengupdate profil']);
    }

    /**
     * Beda dari CI3: password baru di-hash (func->hashPassword(), bcrypt)
     * sebelum disimpan -- CI3 asli menyimpan plaintext langsung di kolom
     * ini, tapi konvensi yang sudah dipakai Auth.php di sisi CI4 selalu
     * hash password baru, jadi disamakan supaya tidak menambah password
     * plaintext baru ke database.
     */
    public function updatePass()
    {
        $usrid    = $this->func->cekLogin();
        $password = $this->request->getPost('password');
        if ($usrid === 0 || $password === null) {
            return $this->response->setJSON(['success' => false, 'msg' => 'Forbidden!']);
        }

        $this->db->table('userdata')->where('id', $usrid)->update([
            'password' => $this->func->hashPassword((string) $password),
        ]);

        return $this->response->setJSON(['success' => true, 'msg' => 'Berhasil mengupdate profil']);
    }

    public function updatePin()
    {
        $usrid = $this->func->cekLogin();
        $pin   = $this->request->getPost('pin');
        if ($usrid === 0 || $pin === null) {
            return $this->response->setJSON(['success' => false, 'message' => 'Gagal Ubah PIN']);
        }

        $row = $this->db->table('userdata')
            ->where('id', $usrid)
            ->where('pin', $this->request->getPost('pinlama'))
            ->where('status', '1')
            ->get(1)
            ->getRow();

        if ($row === null) {
            return $this->response->setJSON(['success' => false, 'message' => 'Gagal, PIN lama Salah.']);
        }

        $this->db->table('userdata')->where('id', $row->id)->update(['pin' => $pin]);

        $this->func->addlog([
            'usrid'   => $usrid,
            'ip'      => $this->request->getIPAddress(),
            'type'    => 'web',
            'ket'     => 'update PIN',
            'created' => date('Y-m-d H:i:s'),
        ]);

        return $this->response->setJSON([
            'success' => true,
            'message' => 'PIN berhasil diganti',
            'pinlama' => $row->pin,
            'level'   => $row->level,
            'usrid'   => $row->id,
        ]);
    }

    public function tambahAlamat()
    {
        $usrid = $this->func->cekLogin();
        if ($usrid === 0 || $this->request->getPost('idkec') === null) {
            return $this->response->setJSON(['success' => false]);
        }

        if ((int) $this->request->getPost('status') === 1) {
            $this->db->table('alamat')->where('usrid', $usrid)->update(['status' => 0]);
        }

        $data = [
            'usrid'   => $usrid,
            'idkec'   => $this->request->getPost('idkec'),
            'idkel'   => $this->request->getPost('idkel'),
            'nama'    => $this->request->getPost('nama'),
            'judul'   => $this->request->getPost('judul'),
            'alamat'  => $this->request->getPost('alamat'),
            'kodepos' => $this->request->getPost('kodepos'),
            'nohp'    => $this->request->getPost('nohp'),
            'status'  => $this->request->getPost('status'),
        ];

        $id = (int) $this->request->getPost('id');
        if ($id > 0) {
            $this->db->table('alamat')->where('id', $id)->where('usrid', $usrid)->update($data);
        } else {
            $this->db->table('alamat')->insert($data);
            $id = $this->db->insertID();
        }

        $this->func->addlog([
            'usrid'   => $usrid,
            'ip'      => $this->request->getIPAddress(),
            'type'    => 'web',
            'ket'     => 'tambah alamat',
            'created' => date('Y-m-d H:i:s'),
        ]);

        return $this->response->setJSON(['success' => true, 'id' => $id]);
    }

    public function getAlamat()
    {
        $usrid = $this->func->cekLogin();
        $rek   = $this->request->getPost('rek');
        if ($usrid === 0 || $rek === null) {
            return $this->response->setJSON(['success' => false]);
        }

        $alamat = $this->func->getAlamat($rek, 'semua');
        if ($alamat === null || (int) $alamat->usrid !== $usrid) {
            return $this->response->setJSON(['success' => false]);
        }

        $kec  = $this->func->getKel($alamat->idkel, 'idkec');
        $kab  = $this->func->getKec($alamat->idkec, 'idkab');
        $prov = $this->func->getKab($kab, 'idprov');

        return $this->response->setJSON([
            'success' => true,
            'kec'     => $kec,
            'kab'     => $kab,
            'prov'    => $prov,
            'idkec'   => $alamat->idkec,
            'idkel'   => $alamat->idkel,
            'nama'    => $alamat->nama,
            'judul'   => $alamat->judul,
            'alamat'  => $alamat->alamat,
            'kodepos' => $alamat->kodepos,
            'nohp'    => $alamat->nohp,
            'status'  => $alamat->status,
        ]);
    }

    public function hapusAlamat()
    {
        $usrid = $this->func->cekLogin();
        $rek   = $this->request->getPost('rek');
        if ($usrid === 0 || $rek === null) {
            return $this->response->setJSON(['success' => false]);
        }

        $this->db->table('alamat')->where('id', $rek)->where('usrid', $usrid)->delete();

        $this->func->addlog([
            'usrid'   => $usrid,
            'ip'      => $this->request->getIPAddress(),
            'type'    => 'web',
            'ket'     => 'hapus alamat',
            'created' => date('Y-m-d H:i:s'),
        ]);

        return $this->response->setJSON(['success' => true]);
    }

    /**
     * Bentuk respons {rajaongkir, html} dipertahankan sama seperti CI3
     * (bukan JSON array murni) supaya JS cascading dropdown di view bisa
     * dipakai ulang persis (changeKab/changeKec/changeKel langsung
     * menyuntik data.html ke elemen <select>).
     */
    public function getKab()
    {
        $id = $this->request->getPost('id') ?? 0;

        $result = "<option value=''>Pilih Kabupaten/Kota</option>";
        foreach ($this->func->getKabByProv($id) as $res) {
            $result .= "<option value='{$res->id}' data-rajaongkir='{$res->rajaongkir}'>{$res->tipe} {$res->nama}</option>";
        }

        return $this->response->setJSON([
            'rajaongkir' => $this->func->getProv($id, 'rajaongkir'),
            'html'       => $result,
        ]);
    }

    public function getKec()
    {
        $id = $this->request->getPost('id') ?? 0;

        $result = "<option value=''>Pilih Kecamatan</option>";
        foreach ($this->func->getKecByKab($id) as $res) {
            $result .= "<option value='{$res->id}' data-rajaongkir='{$res->rajaongkir}'>{$res->nama}</option>";
        }

        return $this->response->setJSON([
            'rajaongkir' => $this->func->getKab($id, 'rajaongkir'),
            'html'       => $result,
        ]);
    }

    public function getKel()
    {
        $id = $this->request->getPost('id') ?? 0;

        $result = "<option value=''>Pilih Kelurahan</option>";
        foreach ($this->func->getKelByKec($id) as $res) {
            $result .= "<option value='{$res->id}' data-rajaongkir='{$res->rajaongkir}'>{$res->nama}</option>";
        }

        return $this->response->setJSON([
            'rajaongkir' => $this->func->getKec($id, 'rajaongkir'),
            'html'       => $result,
        ]);
    }

    public function tambahRekening()
    {
        $usrid = $this->func->cekLogin();
        if ($usrid === 0 || $this->request->getPost('idbank') === null) {
            return $this->response->setJSON(['success' => false]);
        }

        $data = [
            'usrid'    => $usrid,
            'idbank'   => $this->request->getPost('idbank'),
            'atasnama' => $this->request->getPost('atasnama'),
            'norek'    => $this->request->getPost('norek'),
            'kcp'      => $this->request->getPost('kcp'),
            'tgl'      => date('Y-m-d H:i:s'),
        ];

        $id = (int) $this->request->getPost('id');
        if ($id > 0) {
            $this->db->table('rekening')->where('id', $id)->where('usrid', $usrid)->update($data);
        } else {
            $this->db->table('rekening')->insert($data);
            $id = $this->db->insertID();
        }

        $this->func->addlog([
            'usrid'   => $usrid,
            'ip'      => $this->request->getIPAddress(),
            'type'    => 'web',
            'ket'     => 'tambah rekening',
            'created' => date('Y-m-d H:i:s'),
        ]);

        return $this->response->setJSON(['success' => true, 'id' => $id]);
    }

    public function getRekening()
    {
        $usrid = $this->func->cekLogin();
        $rek   = $this->request->getPost('rek');
        if ($usrid === 0 || $rek === null) {
            return $this->response->setJSON(['success' => false]);
        }

        $rekening = $this->func->getRekening($rek, 'semua');
        if ($rekening === null || (int) $rekening->usrid !== $usrid) {
            return $this->response->setJSON(['success' => false]);
        }

        return $this->response->setJSON([
            'success'  => true,
            'id'       => $rekening->id,
            'idbank'   => $rekening->idbank,
            'norek'    => $rekening->norek,
            'atasnama' => $rekening->atasnama,
            'kcp'      => $rekening->kcp,
        ]);
    }

    public function hapusRekening()
    {
        $usrid = $this->func->cekLogin();
        $rek   = $this->request->getPost('rek');
        if ($usrid === 0 || $rek === null) {
            return $this->response->setJSON(['success' => false]);
        }

        $this->db->table('rekening')->where('id', $rek)->where('usrid', $usrid)->delete();

        $this->func->addlog([
            'usrid'   => $usrid,
            'ip'      => $this->request->getIPAddress(),
            'type'    => 'web',
            'ket'     => 'hapus rekening',
            'created' => date('Y-m-d H:i:s'),
        ]);

        return $this->response->setJSON(['success' => true]);
    }

    public function getHistoryTarik()
    {
        $usrid = session()->get('usrid');
        if ($usrid === null) {
            return redirect()->to(site_url('home/signin'));
        }

        $page    = max(1, (int) $this->request->getGet('page'));
        $perpage = 10;

        $rows = $this->db->table('saldohistory')->where('usrid', $usrid)->countAllResults();
        $saldo = $this->db->table('saldohistory')
            ->where('usrid', $usrid)
            ->orderBy('id', 'DESC')
            ->limit($perpage, ($page - 1) * $perpage)
            ->get()
            ->getResult();

        return view('customer/admin/historysaldo', [
            'func'    => $this->func,
            'db'      => $this->db,
            'saldo'   => $saldo,
            'rows'    => $rows,
            'perpage' => $perpage,
            'page'    => $page,
        ]);
    }

    public function getHistoryTopup()
    {
        $usrid = session()->get('usrid');
        if ($usrid === null) {
            return redirect()->to(site_url('home/signin'));
        }

        $page    = max(1, (int) $this->request->getGet('page'));
        $perpage = 10;

        $rows = $this->db->table('saldotarik')->where('usrid', $usrid)->where('jenis', 2)->countAllResults();
        $saldo = $this->db->table('saldotarik')
            ->where('usrid', $usrid)
            ->where('jenis', 2)
            ->orderBy('id', 'DESC')
            ->limit($perpage, ($page - 1) * $perpage)
            ->get()
            ->getResult();

        return view('customer/admin/topupsaldo', [
            'func'    => $this->func,
            'db'      => $this->db,
            'saldo'   => $saldo,
            'rows'    => $rows,
            'perpage' => $perpage,
            'page'    => $page,
        ]);
    }

    /** Port dari Assync::topupsaldo() (CI3) -- bikin permintaan top-up, BELUM memotong saldo apa pun. */
    public function topupSaldo()
    {
        $usrid  = $this->func->cekLogin();
        $jumlah = (int) $this->request->getPost('jumlah');
        if ($usrid === 0 || $jumlah <= 0) {
            return $this->response->setJSON(['success' => false, 'pesan' => 'Permintaan topup gagal']);
        }

        $usr       = $this->func->getUser($usrid, 'semua');
        $saldoawal = (int) $this->func->getSaldo($usrid, 'saldo', 'usrid');
        $total     = $jumlah + $saldoawal;

        if ($total > $usr->max_saldo) {
            return $this->response->setJSON(['success' => false, 'pesan' => 'Max Saldo Akhir adalah Rp ' . $usr->max_saldo]);
        }
        if ($jumlah > $usr->max_topup) {
            return $this->response->setJSON(['success' => false, 'pesan' => 'Max Topup adalah Rp ' . $usr->max_topup]);
        }

        $digit = random_int(100, 999);
        $trxid = $usrid . date('YmdHis');

        $this->db->table('saldotarik')->insert([
            'status' => 0,
            'jenis'  => 2,
            'usrid'  => $usrid,
            'total'  => $jumlah + $digit,
            'tgl'    => date('Y-m-d H:i:s'),
            'trxid'  => $trxid,
        ]);

        $toko     = $this->func->getSetting('semua');
        $notifset = $this->func->getNotifSetting('semua');
        $waktu    = date('Y-m-d H:i:s');
        $kadaluarsa = date('Y-m-d H:i:s', strtotime('+6 hours'));
        $totalTopup = $this->func->formUang($jumlah + $digit);

        if (($notifset->email_topup ?? 0) == 1) {
            $pesan = "Halo <b>{$usr->nama}</b><br/>Permintaan Topup di {$toko->nama} kami terima<br/>" .
                "Segera lakukan pembayaran agar tidak terjadi pembatalan otomatis.<br/>" .
                "ID Topup: <b>{$trxid}</b><br/>Permintaan: <b>{$waktu}</b><br/>Batas Bayar: <b>{$kadaluarsa}</b><br/>" .
                "Total Topup: <b>Rp {$totalTopup}</b><br/><br/>Terima Kasih<br/><br/>";
            $this->func->sendEmail($usr->username, $toko->nama . ' - Topup', $pesan, 'Topup Saldo');
        }
        if (($notifset->wa_topup ?? 0) == 1) {
            $pesan = "Halo *{$usr->nama}*\nPermintaan Topup di {$toko->nama} kami terima.\n" .
                "Segera lakukan pembayaran agar tidak terjadi pembatalan otomatis.\n\n" .
                "ID Topup: *{$trxid}*\nPermintaan: *{$waktu}*\nBatas Bayar: *{$kadaluarsa}*\nTotal Topup: *Rp {$totalTopup}*\n\nTerima Kasih";
            $this->func->sendWA($this->func->getProfil($usrid, 'nohp', 'usrid'), $pesan);
        }

        $this->func->addlog([
            'usrid'   => $usrid,
            'ip'      => $this->request->getIPAddress(),
            'type'    => 'web',
            'ket'     => 'permintaan topup',
            'created' => date('Y-m-d H:i:s'),
        ]);

        // Rekening tujuan milik toko (CI3: baris `rekening` dgn usrid<=0 = akun penerima toko, bukan rekening customer).
        $bankTujuan = $this->db->table('rekening')
            ->select('rekening.norek, rekening.atasnama, rekening.kcp, rekeningbank.nama as bank')
            ->join('rekeningbank', 'rekeningbank.id = rekening.idbank')
            ->where('rekening.usrid <=', 0)
            ->get()->getResult();

        return $this->response->setJSON([
            'success'         => true,
            'trxid'           => $trxid,
            'total'           => $jumlah + $digit,
            'totalFormatted'  => $totalTopup,
            'kadaluarsa'      => $kadaluarsa,
            'wasap'           => $toko->wasap ?? '',
            'paymentTransfer' => (int) ($toko->payment_transfer ?? 0) === 1,
            'paymentMidtrans' => (int) ($toko->payment_midtrans ?? 0) === 1,
            'bankTujuan'      => $bankTujuan,
        ]);
    }

    public function batalTopup()
    {
        $usrid = $this->func->cekLogin();
        $id    = $this->request->getPost('id');
        if ($usrid === 0 || $id === null) {
            return $this->response->setJSON(['success' => false]);
        }

        $this->db->table('saldotarik')->where('id', $id)->where('usrid', $usrid)->where('status', 0)
            ->update(['selesai' => date('Y-m-d H:i:s'), 'status' => 2]);

        return $this->response->setJSON(['success' => true]);
    }

    /** Port dari Home::midtranstokentopup() (CI3) -- generate Snap token, BELUM memotong saldo. */
    public function midtransTokenTopup()
    {
        $usrid = $this->func->cekLogin();
        $trxid = $this->request->getPost('trxid');
        if ($usrid === 0 || $trxid === null) {
            return $this->response->setStatusCode(404)->setBody('Invoice tidak ditemukan');
        }

        $bayar = $this->func->getSaldotarik($trxid, 'semua', 'trxid');
        if ($bayar === null || (int) $bayar->usrid !== $usrid) {
            return $this->response->setStatusCode(404)->setBody('Invoice tidak ditemukan');
        }

        $this->func->configureMidtrans();
        $set    = $this->func->getSetting('semua');
        $user   = $this->func->getUser($usrid, 'semua');
        $profil = $this->func->getProfil($usrid, 'semua', 'usrid');

        $token = \Midtrans\Snap::getSnapToken([
            'transaction_details' => [
                'order_id'     => $trxid,
                'gross_amount' => (int) $bayar->total + (int) ($set->payment_biaya ?? 0),
            ],
            'customer_details' => [
                'first_name' => $profil->nama,
                'last_name'  => '',
                'email'      => $user->username,
                'phone'      => $profil->nohp,
            ],
        ]);

        return $this->response->setBody($token);
    }

    /**
     * Port dari Assync::midtrans_webhook() (CI3). Dipanggil server Midtrans
     * sendiri (no-auth, no-CSRF -- lihat Routes.php). Menangani topup
     * (`saldotarik`) dan pembayaran fisik/jasa (`pembayaran`), persis pola
     * CI3 yang mengecek beberapa tabel berurutan dalam satu method.
     */
    public function midtransWebhook()
    {
        $this->func->configureMidtrans();
        $notif = new \Midtrans\Notification();

        $transaction   = $notif->transaction_status;
        $transactionId = $notif->transaction_id;
        $orderId       = $notif->order_id;

        $topup = $this->func->getSaldotarik($orderId, 'semua', 'trxid');
        $bayar = $this->func->getBayar($orderId, 'semua', 'invoice');

        if (in_array($transaction, ['capture', 'settlement'], true)) {
            if ($topup !== null && (int) $topup->status === 0) {
                $this->suksesTrxMidtransTopup($topup, $transactionId);
            }
            if ($bayar !== null && (int) $bayar->status === 0) {
                $this->suksesTrxMidtrans($bayar, $transactionId);
            }

            return $this->response->setBody("Transaction order_id: {$orderId} successfully processed");
        }

        return $this->response->setBody("Transaction order_id: {$orderId} status: {$transaction}");
    }

    private function suksesTrxMidtrans($bayar, string $transactionId): void
    {
        $this->db->table('pembayaran')->where('id', $bayar->id)->update([
            'status'      => 1,
            'tglupdate'   => date('Y-m-d H:i:s'),
            'midtrans_id' => $transactionId,
        ]);
        $this->db->table('transaksi')->where('idbayar', $bayar->id)->update(['status' => 1]);

        $trx    = $this->func->getTransaksi($bayar->id, 'semua', 'idbayar');
        $alamat = $this->func->getAlamat($trx->alamat, 'semua');
        $usr    = $this->func->getUser($bayar->usrid, 'semua');
        $toko   = $this->func->getSetting('semua');
        $notifset = $this->func->getNotifSetting('semua');
        $pembelian = $bayar->total - $trx->ongkir - $bayar->kodebayar;

        if (($notifset->wa_midtrans_sukses ?? 0) == 1) {
            $pesan = "Halo *{$usr->nama}*\nTerimakasih, pembayaran untuk pesananmu sudah kami terima.\n" .
                "Mohon ditunggu, admin kami akan segera memproses pesananmu\n" .
                "*Detail Pesanan*\nNo Invoice: *#{$bayar->invoice}*\nTotal Pesanan: *Rp " . $this->func->formUang($bayar->total) . "*\n" .
                '*Rincian*' . "\nPembelian: *Rp " . $this->func->formUang($pembelian) . "*\n" .
                'Kode Unik: *Rp ' . $this->func->formUang($bayar->kodebayar) . "*\n" .
                'Ongkos Kirim: *Rp ' . $this->func->formUang($trx->ongkir) . "*\n" .
                'Kurir Pengiriman: *' . strtoupper($trx->kurir . ' ' . $trx->paket) . "*\n\n" .
                "Detail Pengiriman\nPenerima: *{$alamat->nama}*\nNo HP: *{$alamat->nohp}*\nAlamat: *{$alamat->alamat}*\n\n" .
                'Cek Status pesananmu langsung di menu: *PESANANKU*';
            $this->func->sendWA($this->func->getProfil($usr->id, 'nohp', 'usrid'), $pesan);
        }
        if (($notifset->email_midtrans_sukses ?? 0) == 1) {
            $pesan = "Halo <b>{$usr->nama}</b><br/>Terimakasih, pembayaran untuk pesananmu sudah kami terima.<br/>" .
                "Mohon ditunggu, admin kami akan segera memproses pesananmu<br/>" .
                "<b>Detail Pesanan</b><br/>No Invoice: <b>#{$bayar->invoice}</b><br/>Total Pesanan: <b>Rp " . $this->func->formUang($bayar->total) . "</b><br/>" .
                '<b>Rincian:</b><br/>Pembelian: <b>Rp ' . $this->func->formUang($pembelian) . "</b><br/>" .
                'Kode Unik: <b>Rp ' . $this->func->formUang($bayar->kodebayar) . "</b><br/>" .
                'Ongkos Kirim: <b>Rp ' . $this->func->formUang($trx->ongkir) . "</b><br/>" .
                'Kurir Pengiriman: <b>' . strtoupper($trx->kurir . ' ' . $trx->paket) . "</b><br/><br/>" .
                "Detail Pengiriman<br/>Penerima: <b>{$alamat->nama}</b><br/>No HP: <b>{$alamat->nohp}</b><br/>Alamat: <b>{$alamat->alamat}</b><br/><br/>" .
                "Cek Status pesananmu langsung di menu: <a href='" . site_url('manage/pesanan') . "'>PESANANKU &raquo;</a>";
            $this->func->sendEmail($usr->username, $toko->nama . ' - Pesanan', $pesan, 'Pesanan');
        }
    }

    private function suksesTrxMidtransTopup($topup, string $transactionId): void
    {
        $this->db->table('saldotarik')->where('trxid', $topup->trxid)->update([
            'status'   => 1,
            'selesai'  => date('Y-m-d H:i:s'),
            'midtrans' => $transactionId,
        ]);

        $saldoawal = (int) $this->func->getSaldo($topup->usrid, 'saldo', 'usrid');
        $saldoakhir = $saldoawal + (int) $topup->total;

        $this->db->table('saldo')->where('usrid', $topup->usrid)->update(['saldo' => $saldoakhir]);

        $this->db->table('saldohistory')->insert([
            'tgl'        => date('Y-m-d H:i:s'),
            'usrid'      => $topup->usrid,
            'jenis'      => 1,
            'jumlah'     => $topup->total,
            'darike'     => 4,
            'saldoawal'  => $saldoawal,
            'saldoakhir' => $saldoakhir,
            'keterangan' => 'topup ID: ' . $topup->trxid,
            'sambung'    => $topup->usrid,
        ]);

        $user = $this->func->getUser($topup->usrid, 'semua');
        $toko = $this->func->getSetting('semua');
        $pesan = "Halo {$user->nama}\nTOPUP SALDO SUKSES.\nTopup Saldo anda di {$toko->nama} berhasil\n" .
            'Jumlah: Rp ' . $this->func->formUang($topup->total) . "\n" .
            'Saldo Awal: Rp ' . $this->func->formUang($saldoawal) . "\n" .
            'Saldo Akhir: Rp ' . $this->func->formUang($saldoakhir) . "\n\n";
        $this->func->sendWA($this->func->getProfil($topup->usrid, 'nohp', 'usrid'), $pesan);
    }

    /** Port dari Assync::prosesbeli() (CI3) -- tambah produk fisik/jasa ke keranjang. */
    public function prosesBeli()
    {
        $usrid = $this->func->cekLogin();
        $idproduk = $this->request->getPost('idproduk');
        if ($usrid === 0 || $idproduk === null) {
            return $this->response->setJSON(['success' => false, 'msg' => 'Silahkan login terlebih dahulu']);
        }

        $prod    = $this->func->getProduk($idproduk, 'semua');
        $variasi = (int) ($this->request->getPost('variasi') ?? 0);
        $jumlah  = (int) $this->request->getPost('jumlah');
        $keterangan = (string) ($this->request->getPost('keterangan') ?? '');

        if ($prod === null || $jumlah <= 0) {
            return $this->response->setJSON(['success' => false, 'msg' => 'Produk tidak ditemukan']);
        }

        if ($variasi > 0) {
            $var = $this->func->getVariasi($variasi, 'semua');
            if ($var === null || $jumlah > $var->stok) {
                return $this->response->setJSON(['success' => false, 'msg' => 'Stok tidak mencukupi, stok tersedia hanya ' . ($var->stok ?? 0) . ' pcs']);
            }
            $harga = $var->harga;
        } else {
            if ($jumlah > $prod->stok) {
                return $this->response->setJSON(['success' => false, 'msg' => 'Stok tidak mencukupi, stok tersedia hanya ' . $prod->stok . ' pcs']);
            }
            $harga = $prod->harga;
        }

        $existing = $this->db->table('transaksiproduk')
            ->where('idtransaksi', 0)
            ->where('usrid', $usrid)
            ->where('idproduk', $idproduk)
            ->where('variasi', $variasi)
            ->get()->getRow();

        if ($existing !== null) {
            $this->db->table('transaksiproduk')->where('id', $existing->id)->update(['jumlah' => $existing->jumlah + $jumlah]);

            return $this->response->setJSON(['success' => true]);
        }

        $inserted = $this->db->table('transaksiproduk')->insert([
            'usrid'       => $usrid,
            'idproduk'    => $idproduk,
            'tgl'         => date('Y-m-d H:i:s'),
            'jumlah'      => $jumlah,
            'harga'       => $harga,
            'keterangan'  => $keterangan,
            'variasi'     => $variasi,
            'jasa'        => $prod->jasa,
            'idtransaksi' => 0,
            'idpo'        => 0,
        ]);

        if (! $inserted) {
            return $this->response->setJSON(['success' => false, 'msg' => 'terjadi kendala teknis, coba ulangi beberapa saat lagi']);
        }

        return $this->response->setJSON(['success' => true, 'total' => $jumlah * $harga, 'jasa' => $prod->jasa]);
    }

    /** Port dari Assync::hapuskeranjang() (CI3). Validasi ownership ditambahkan sebagai pengamanan. */
    public function hapusKeranjang()
    {
        $usrid = $this->func->cekLogin();
        $hapus = (int) $this->request->getPost('hapus');
        if ($usrid === 0 || $hapus <= 0) {
            return $this->response->setJSON(['success' => false]);
        }

        $this->db->table('transaksiproduk')->where('id', $hapus)->where('usrid', $usrid)->delete();

        return $this->response->setJSON(['success' => true]);
    }

    /** Port dari Assync::updatekeranjang() (CI3). */
    public function updateKeranjang()
    {
        $usrid  = $this->func->cekLogin();
        $update = (int) $this->request->getPost('update');
        $jumlah = (int) $this->request->getPost('jumlah');
        if ($usrid === 0 || $update <= 0) {
            return $this->response->setJSON(['success' => false, 'msg' => 'Produk tidak tersedia']);
        }

        $trx = $this->func->getTransaksiProduk($update, 'semua');
        if ($trx === null || (int) $trx->usrid !== $usrid) {
            return $this->response->setJSON(['success' => false, 'msg' => 'Produk tidak tersedia']);
        }

        $stok = $trx->variasi > 0
            ? $this->func->getVariasi($trx->variasi, 'stok')
            : $this->func->getProduk($trx->idproduk, 'stok');

        if ($stok >= $jumlah) {
            $this->db->table('transaksiproduk')->where('id', $update)->update(['jumlah' => $jumlah]);

            return $this->response->setJSON(['success' => true]);
        }

        $this->db->table('transaksiproduk')->where('id', $update)->update(['jumlah' => $stok]);

        return $this->response->setJSON(['success' => false, 'msg' => 'Stok produk tidak mencukupi, maksimal pemesanan ' . $stok . ' pcs']);
    }

    /** Port dari Assync::cekvoucher() (CI3). */
    public function cekVoucher()
    {
        $kode = $this->request->getPost('kode');
        if ($kode === null) {
            return $this->response->setJSON(['success' => false]);
        }

        $voc    = $this->func->getVoucher($kode, 'semua', 'kode');
        $harga  = (int) $this->request->getPost('harga');
        $ongkir = (int) $this->request->getPost('ongkir');

        if ($voc === null) {
            return $this->response->setJSON(['success' => false, 'pesan' => 'Kode Voucher tidak valid']);
        }

        $mulai   = $this->func->ubahTgl('Ymd', $voc->mulai);
        $selesai = $this->func->ubahTgl('Ymd', $voc->selesai);
        if (! ($mulai <= date('Ymd') && $selesai >= date('Ymd') && $harga >= $voc->potonganmin)) {
            return $this->response->setJSON(['success' => false, 'pesan' => 'Minimal pembelian kurang, Minimal Rp ' . $this->func->formUang((int) $voc->potonganmin)]);
        }

        $diskon   = 0;
        $potongan = '';
        if ((int) $voc->jenis === 1) {
            if ((int) $voc->tipe === 2) {
                $diskon   = $voc->potongan;
                $potongan = 'harga Rp ' . $this->func->formUang((int) $voc->potongan);
            } else {
                $diskon   = $harga * ($voc->potongan / 100);
                $potongan = 'harga ' . $voc->potongan . '%';
            }
        } elseif ((int) $voc->jenis === 2) {
            if ((int) $voc->tipe === 2) {
                $diskon   = $voc->potongan;
                $potongan = 'ongkir Rp ' . $this->func->formUang((int) $voc->potongan);
            } else {
                $diskon   = $ongkir * ($voc->potongan / 100);
                $potongan = 'ongkir ' . $voc->potongan . '%';
            }
        }

        if ((int) $voc->potonganmaks !== 0 && $diskon > $voc->potonganmaks) {
            $diskon = $voc->potonganmaks;
        }

        return $this->response->setJSON(['success' => true, 'diskon' => $diskon, 'potongan' => $potongan]);
    }

    /** Port dari Assync::cekpin() (CI3) -- termasuk penguncian setelah 4x salah, dipertahankan sama. */
    public function cekPin()
    {
        $usrid = $this->func->cekLogin();
        if ($usrid === 0) {
            return $this->response->setJSON(['success' => false, 'message' => 'ID tidak dikenal']);
        }

        $row = $this->db->table('userdata')
            ->where('id', $usrid)
            ->where('pin', $this->request->getPost('pin'))
            ->where('status', '1')
            ->where('pin_input <', 4)
            ->get(1)
            ->getRow();

        if ($row !== null) {
            return $this->response->setJSON(['success' => true, 'salah_pin' => $row->pin_input, 'level' => $row->level, 'usrid' => $row->id]);
        }

        $usr      = $this->func->getUser($usrid, 'semua');
        $salahpin = $usr->pin_input + 1;
        $this->db->table('userdata')->where('id', $usr->id)->update(['pin_input' => $salahpin]);

        return $this->response->setJSON(['success' => false, 'salah_pin' => $salahpin, 'message' => 'Gagal, PIN Salah.']);
    }

    /**
     * Port dari Assync::cekongkir() (CI3). Dua jalur: kurir custom (tarif
     * flat dari tabel customkurir, dipakai COD/Kurir Toko & jasa) dan kurir
     * RajaOngkir Pro (API eksternal berbayar, dicache di historyongkir).
     */
    public function cekOngkir()
    {
        $dari      = (int) $this->request->getPost('dari');
        $tujuan    = (int) $this->request->getPost('tujuan');
        $kelurahan = (int) $this->request->getPost('kelurahan');
        $idalamat  = (int) $this->request->getPost('idalamat');
        $alamat    = $this->func->getAlamat($idalamat, 'semua');
        $tujuan2   = $alamat->idkel ?? $kelurahan;
        $berat     = (int) $this->request->getPost('berat');
        $berat     = $berat === 0 ? 1000 : $berat;
        $kurir     = (string) ($this->request->getPost('kurir') ?? 'jne');

        $srvdefault = in_array($kurir, ['jne', 'tiki'], true) ? 'REG' : '';
        $service    = (string) ($this->request->getPost('service') ?? $srvdefault);
        $paket      = $this->func->getPaket($service, 'semua', 'rajaongkir');
        $namakurir  = $this->func->getKurir($kurir, 'semua', 'rajaongkir');

        if ($namakurir === null) {
            return $this->response->setJSON(['success' => false, 'harga' => 0]);
        }

        if ((int) $namakurir->custom === 1) {
            $beratkg = $berat >= 1000 ? round($berat / 1000) : 1;
            $row = $this->db->table('customkurir')
                ->where('kurir', $namakurir->id)
                ->where('paket', $paket->id ?? 0)
                ->where('idkec', $tujuan)
                ->where('idkel', $tujuan2)
                ->get()->getRow();

            if ($row !== null) {
                $biaya = $row->harga * $beratkg;

                return $this->response->setJSON([
                    'success' => true, 'dari' => $dari, 'tujuan' => $tujuan, 'kurir' => $kurir,
                    'service' => $service, 'harga' => $biaya, 'update' => date('Y-m-d H:i:s'), 'hargaperkg' => $biaya,
                ]);
            }

            return $this->response->setJSON([
                'success' => false, 'dari' => $dari, 'tujuan' => $tujuan, 'kurir' => $kurir,
                'service' => $service, 'harga' => 0, 'update' => date('Y-m-d H:i:s'), 'hargaperkg' => 0,
            ]);
        }

        $dariKode = $this->func->getKab($dari, 'rajaongkir');
        $datakec  = $this->func->getKec($tujuan, 'semua');
        $tujuanKode = $datakec->rajaongkir ?? '';

        if (isset($datakec->idkab) && (int) $datakec->idkab === $dari && $kurir === 'jne') {
            if ($service === 'REG') {
                $service = 'CTC';
            } elseif ($service === 'YES') {
                $service = 'CTCYES';
            }
        }

        $beratkg = $this->bulatkanBeratKg($berat, $kurir);

        $cached = $this->db->table('historyongkir')
            ->where('dari', $dariKode)
            ->where('tujuan', $tujuanKode)
            ->where('kurir', $kurir)
            ->orderBy('id', 'DESC')
            ->get(1)->getRow();

        if ($cached !== null && (int) $cached->harga > 0 && strcasecmp($service, $cached->service) === 0) {
            return $this->response->setJSON([
                'success' => true, 'dari' => $cached->dari, 'tujuan' => $cached->tujuan, 'kurir' => $cached->kurir,
                'service' => $cached->service, 'harga' => $cached->harga * $beratkg, 'update' => $cached->update,
            ]);
        }

        return $this->reqOngkir($dariKode, $berat, $tujuanKode, $kurir, $service);
    }

    private function bulatkanBeratKg(int $berat, string $kurir): int
    {
        $beratkg = (int) round($berat / 1000, 0, PHP_ROUND_HALF_DOWN);
        $selisih = $berat - ($beratkg * 1000);
        $batasPerKurir = ['jne' => 300, 'pos' => 200, 'tiki' => 299];
        $batas   = $batasPerKurir[$kurir] ?? 0;
        if ($selisih > $batas) {
            $beratkg++;
        }

        return max($beratkg, 1);
    }

    /**
     * Panggil RajaOngkir Pro API -- CATATAN: endpoint pro.rajaongkir.com
     * sudah dicoba dan TIDAK BISA dihubungi sama sekali (connection
     * timeout) saat porting ini ditulis, kemungkinan layanan sudah
     * dipensiunkan/diganti RajaOngkir. Logika di bawah port PERSIS dari
     * CI3 (termasuk konversi REG->CTC khusus JNE) supaya siap pakai kalau
     * toko pindah ke endpoint RajaOngkir yang masih aktif -- ganti
     * CURLOPT_URL & format request sesuai API baru saat itu terjadi.
     */
    private function reqOngkir(string $dari, int $berat, string $tujuan, string $kurir, string $service)
    {
        $usrid = session()->get('usrid') ?? 0;
        $beratkg = $this->bulatkanBeratKg($berat, $kurir);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => 'https://pro.rajaongkir.com/api/cost',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 15,
            CURLOPT_CUSTOMREQUEST => 'POST',
            CURLOPT_POSTFIELDS => "origin={$dari}&originType=city&destination={$tujuan}&destinationType=subdistrict&weight={$berat}&courier={$kurir}",
            CURLOPT_HTTPHEADER => [
                'content-type: application/x-www-form-urlencoded',
                'key: ' . $this->func->getSetting('rajaongkir'),
            ],
        ]);
        $response = curl_exec($ch);
        $err      = curl_error($ch);
        curl_close($ch);

        if ($err !== '') {
            return $this->response->setJSON(['success' => false, 'harga' => 0, 'message' => 'Layanan cek ongkir sedang tidak tersedia']);
        }

        $arr    = json_decode((string) $response);
        $hasil  = ['success' => false, 'response' => 'daerah tidak terjangkau!', 'harga' => 0];

        if (($arr->rajaongkir->status->code ?? null) == '200') {
            $hasil = ['success' => false, 'response' => 'daerah tidak terjangkau!', 'message' => 'service code tidak ada data', 'harga' => 0];
            foreach ($arr->rajaongkir->results[0]->costs as $cost) {
                $hargaPerKg = $cost->cost[0]->value / $beratkg;
                $entry = ['dari' => $dari, 'tujuan' => $tujuan, 'kurir' => $kurir, 'service' => $cost->service, 'harga' => $hargaPerKg, 'update' => date('Y-m-d H:i:s'), 'usrid' => $usrid];

                $idhistory = $this->func->getHistoryOngkir(['dari' => $dari, 'tujuan' => $tujuan, 'kurir' => $kurir, 'service' => $cost->service], 'id');
                if ($idhistory > 0) {
                    $this->db->table('historyongkir')->where('id', $idhistory)->update($entry);
                } elseif ($hargaPerKg > 0) {
                    $this->db->table('historyongkir')->insert($entry);
                }

                $cocok = strcasecmp($service, $cost->service) === 0
                    || ($kurir === 'jne' && $service === 'REG' && strcasecmp($cost->service, 'CTC') === 0)
                    || ($kurir === 'jne' && $service === 'YES' && strcasecmp($cost->service, 'CTCYES') === 0);

                if ($cocok) {
                    $hasil = [
                        'success' => true, 'dari' => $dari, 'tujuan' => $tujuan, 'kurir' => $kurir,
                        'service' => $cost->service, 'harga' => $cost->cost[0]->value,
                        'update' => date('Y-m-d H:i:s'), 'hargaperkg' => $hargaPerKg,
                    ];
                }
            }
        }

        return $this->response->setJSON($hasil);
    }

    /**
     * Port dari Assync::tambahUlasan() (CI3). Validasi ownership ditambahkan
     * sebagai pengamanan (CI3 asli tidak mengecek pemilik orderid).
     */
    public function tambahUlasan()
    {
        $usrid = $this->func->cekLogin();
        $nilai = $this->request->getPost('nilai');
        if ($usrid === 0 || $nilai === null) {
            return $this->response->setJSON(['success' => false, 'msg' => 'Forbidden!']);
        }

        $orderidList   = $this->request->getPost('orderid') ?? [];
        $produkList    = $this->request->getPost('produk') ?? [];
        $keteranganList = $this->request->getPost('keterangan') ?? [];

        for ($i = 0; $i < count($nilai); $i++) {
            $trx = $this->func->getTransaksi($orderidList[$i] ?? null, 'semua', 'orderid');
            if ($trx === null || (int) $trx->usrid !== $usrid) {
                continue;
            }

            $this->db->table('review')->insert([
                'usrid'       => $usrid,
                'idproduk'    => $produkList[$i] ?? 0,
                'idtransaksi' => $trx->id,
                'nilai'       => $nilai[$i],
                'tgl'         => date('Y-m-d H:i:s'),
                'keterangan'  => $keteranganList[$i] ?? '',
            ]);
        }

        return $this->response->setJSON(['success' => true, 'msg' => 'Berhasil brow!']);
    }

    /** Port dari Apih2h::kirimpesan() (CI3, API mobile) -- kirim pesan ke admin (tujuan=0). */
    public function kirimPesan()
    {
        $usrid = $this->func->cekLogin();
        $pesan = trim((string) $this->request->getPost('pesan'));
        if ($usrid === 0 || $pesan === '') {
            return $this->response->setJSON(['success' => false]);
        }

        $this->db->table('pesan')->insert([
            'tujuan'   => 0,
            'dari'     => $usrid,
            'isipesan' => $pesan,
            'tgl'      => date('Y-m-d H:i:s'),
            'baca'     => 0,
        ]);

        return $this->response->setJSON(['success' => true]);
    }

    /**
     * Polling sederhana utk halaman chat -- port dari Apih2h::chat() (CI3,
     * API mobile), dipakai ulang utk web. Menandai pesan masuk sbg
     * terbaca & mengembalikan seluruh history (bukan cuma pesan baru),
     * sesuai tingkat kompleksitas asli CI3 (tidak ada mekanisme realtime
     * selain polling).
     */
    public function cekPesanBaru()
    {
        $usrid = $this->func->cekLogin();
        if ($usrid === 0) {
            return $this->response->setJSON(['success' => false]);
        }

        $this->db->table('pesan')->where('tujuan', $usrid)->where('baca', 0)->update(['baca' => 1]);

        $rows = $this->db->table('pesan')
            ->groupStart()->where('tujuan', $usrid)->orWhere('dari', $usrid)->groupEnd()
            ->orderBy('tgl', 'ASC')
            ->get()->getResult();

        $result = [];
        foreach ($rows as $r) {
            $result[] = [
                'pesan'   => $r->isipesan,
                'letak'   => (int) $r->tujuan === 0 ? 'kanan' : 'kiri',
                'tanggal' => $this->func->ubahTgl('d M Y', $r->tgl),
                'jam'     => $this->func->ubahTgl('H:i', $r->tgl),
            ];
        }

        return $this->response->setJSON(['success' => true, 'result' => $result]);
    }

    /**
     * Port dari Assync::pesanan() (CI3) -- daftar pesanan per status, dipanggil
     * AJAX dari tab "Status Pesanan" (Manage::pesanan(), sudah ada sejak
     * sebelum slice ini). CI3 punya 3 versi terpisah (pesanan/pesananjasa/
     * pesananppob) krn fisik & jasa ditampilkan di tab terpisah -- di sini
     * DIGABUNG jadi satu (tidak filter `jasa`) krn CI4 cuma punya satu set
     * tab "Pesanan" utk fisik+jasa. PPOB tetap halaman terpisah
     * (Manage::pesananppob(), sudah ada). Versi VA/iPaymu-spesifik di view
     * asli TIDAK diport -- selaras keputusan Transfer Manual + Midtrans saja.
     */
    public function pesanan()
    {
        $usrid = $this->func->cekLogin();
        if ($usrid === 0) {
            return $this->response->setStatusCode(403)->setBody('Forbidden');
        }

        $status  = (string) $this->request->getGet('status');
        $page    = max(1, (int) $this->request->getGet('page'));
        $perpage = 10;

        $statusMap = ['dikemas' => 1, 'dikirim' => 2, 'selesai' => 3, 'batal' => 4];

        if ($status === 'belumbayar') {
            return $this->pesananBelumBayar($usrid, $page, $perpage);
        }
        if ($status === 'po') {
            return $this->pesananPreorder($usrid, $page, $perpage);
        }
        if (isset($statusMap[$status])) {
            return $this->pesananByStatus($usrid, $page, $perpage, $statusMap[$status], $status);
        }

        throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
    }

    private function pesananBelumBayar(int $usrid, int $page, int $perpage)
    {
        $builder = fn () => $this->db->table('pembayaran')->where('status', 0)->where('usrid', $usrid);
        $totalRows = $builder()->countAllResults();
        $rows = $builder()->orderBy('status', 'ASC')->orderBy('id', 'DESC')->limit($perpage, ($page - 1) * $perpage)->get()->getResult();

        $pesanan = [];
        foreach ($rows as $res) {
            $transaksiList = $this->db->table('transaksi')->where('idbayar', $res->id)->get()->getResult();
            foreach ($transaksiList as $trx) {
                $trx->produkList = $this->db->table('transaksiproduk')->where('idtransaksi', $trx->id)->get()->getResult();
            }
            $pesanan[] = [
                'bayar'      => $res,
                'transaksi'  => $transaksiList,
                'konfirmasi' => $this->func->getKonfirmasi($res->id, 'semua'),
                'linkInvoice' => site_url('home/invoice') . '?inv=' . $this->func->arrEnc(['idbayar' => $res->id]),
            ];
        }

        return view('customer/admin/pesanan-belumbayar', [
            'func' => $this->func, 'db' => $this->db, 'pesanan' => $pesanan,
            'totalRows' => $totalRows, 'page' => $page, 'perpage' => $perpage,
        ]);
    }

    private function pesananByStatus(int $usrid, int $page, int $perpage, int $statusCode, string $statusKey)
    {
        $builder = function () use ($usrid, $statusCode, $statusKey) {
            $b = $this->db->table('transaksi')->where('status', $statusCode)->where('usrid', $usrid);
            if ($statusKey === 'dikemas') {
                $b->where('resi', '');
            }

            return $b;
        };

        $totalRows = $builder()->countAllResults();
        $rows = $builder()->orderBy('id', 'DESC')->limit($perpage, ($page - 1) * $perpage)->get()->getResult();

        $pesanan = [];
        foreach ($rows as $trx) {
            $produkList = $this->db->table('transaksiproduk')->where('idtransaksi', $trx->id)->get()->getResult();
            $totalproduk = 0;
            foreach ($produkList as $p) {
                $totalproduk += $p->harga * $p->jumlah;
            }
            $pesanan[] = ['transaksi' => $trx, 'produkList' => $produkList, 'totalproduk' => $totalproduk];
        }

        return view('customer/admin/pesanan-' . $statusKey, [
            'func' => $this->func, 'db' => $this->db, 'pesanan' => $pesanan,
            'totalRows' => $totalRows, 'page' => $page, 'perpage' => $perpage,
        ]);
    }

    private function pesananPreorder(int $usrid, int $page, int $perpage)
    {
        $builder = fn () => $this->db->table('preorder')->where('usrid', $usrid);
        $totalRows = $builder()->countAllResults();
        $rows = $builder()->orderBy('id', 'DESC')->limit($perpage, ($page - 1) * $perpage)->get()->getResult();

        return view('customer/admin/pesanan-preorder', [
            'func' => $this->func, 'db' => $this->db, 'rows' => $rows,
            'totalRows' => $totalRows, 'page' => $page, 'perpage' => $perpage,
        ]);
    }

    /** Port dari Assync::terimaPesanan() (CI3) -- pembeli konfirmasi pesanan diterima. */
    public function terimaPesanan()
    {
        $usrid   = $this->func->cekLogin();
        $pesanan = $this->request->getPost('pesanan');
        if ($usrid === 0 || $pesanan === null) {
            return $this->response->setJSON(['success' => false, 'message' => 'Forbidden Access']);
        }

        $updated = $this->db->table('transaksi')->where('id', $pesanan)->where('usrid', $usrid)
            ->update(['status' => 3, 'selesai' => date('Y-m-d H:i:s')]);

        return $this->response->setJSON($updated
            ? ['success' => true, 'message' => 'Success!']
            : ['success' => false, 'message' => 'Forbidden Access']);
    }

    /** Port dari Assync::requestbatalkanPesanan() (CI3) -- ajukan pembatalan, ditinjau admin/penjual. */
    public function requestbatalkanPesanan()
    {
        $usrid   = $this->func->cekLogin();
        $pesanan = $this->request->getPost('pesanan');
        if ($usrid === 0 || $pesanan === null) {
            return $this->response->setJSON(['success' => false, 'message' => 'Forbidden Access']);
        }

        $updated = $this->db->table('transaksi')->where('id', $pesanan)->where('usrid', $usrid)->update(['ajukanbatal' => 1]);

        return $this->response->setJSON($updated
            ? ['success' => true, 'message' => 'berhasil mengajukan pembatalan pesanan']
            : ['success' => false, 'message' => 'gagal mengajukan pembatalan pesanan, coba ulangi beberapa saat lagi']);
    }

    /**
     * Port dari Assync::batalkanPesanan() (CI3) -- batalkan pesanan yang
     * belum dibayar. CI3 asli TIDAK mengembalikan saldo otomatis di sini
     * (cuma kirim notifikasi) -- diport persis sama, bukan pengurangan fitur.
     */
    public function batalkanPesanan()
    {
        $usrid   = $this->func->cekLogin();
        $pesanan = $this->request->getPost('pesanan');
        if ($usrid === 0 || $pesanan === null) {
            return $this->response->setJSON(['success' => false, 'message' => 'Forbidden Access']);
        }

        $bayar = $this->func->getBayar($pesanan, 'semua');
        if ($bayar === null || (int) $bayar->usrid !== $usrid) {
            return $this->response->setJSON(['success' => false, 'message' => 'Forbidden Access']);
        }

        $this->db->table('pembayaran')->where('id', $pesanan)->update(['status' => 3]);
        $this->db->table('transaksi')->where('idbayar', $pesanan)->update([
            'status' => 4, 'selesai' => date('Y-m-d H:i:s'), 'keterangan' => 'dibatalkan oleh pembeli.',
        ]);

        $this->func->addlog([
            'usrid' => $usrid, 'ip' => $this->request->getIPAddress(), 'type' => 'web',
            'ket' => 'batalkan pesanan ' . $pesanan, 'created' => date('Y-m-d H:i:s'),
        ]);

        $usr  = $this->func->getUser($usrid, 'semua');
        $toko = $this->func->getSetting('semua');
        $pesan = "Halo <b>{$usr->nama}</b><br/>Pesanan Anda dengan No Invoice <b>#{$bayar->invoice}</b> telah dibatalkan oleh pembeli.<br/>" .
            "Cek status pesanan di <a href='" . site_url('manage/pesanan') . "'>PESANANKU &raquo;</a>";
        $this->func->sendEmail($usr->username, $toko->nama . ' - Pesanan Dibatalkan', $pesan, 'Pesanan Dibatalkan');

        return $this->response->setJSON(['success' => true, 'message' => 'berhasil membatalkan pesanan']);
    }

    /** Port dari Assync::perpanjangPesanan() (CI3) -- perpanjang batas waktu 2 hari. */
    public function perpanjangPesanan()
    {
        $usrid   = $this->func->cekLogin();
        $pesanan = $this->request->getPost('pesanan');
        if ($usrid === 0 || $pesanan === null) {
            return $this->response->setJSON(['success' => false, 'message' => 'Forbidden Access']);
        }

        $trx = $this->db->table('transaksi')->where('id', $pesanan)->where('usrid', $usrid)->get()->getRow();
        if ($trx === null) {
            return $this->response->setJSON(['success' => false, 'message' => 'gagal memperpanjang pesanan, coba ulangi beberapa saat lagi']);
        }

        $newDate = date('Y-m-d H:i:s', strtotime($trx->kadaluarsa . ' + 2 days'));
        $this->db->table('transaksi')->where('id', $pesanan)->update(['kadaluarsa' => $newDate]);

        return $this->response->setJSON(['success' => true, 'message' => 'berhasil memperpanjang pesanan']);
    }

    /** Port dari Home::bayarulangpesanan() (CI3) -- batalkan transaksi Midtrans lama supaya bisa pilih metode baru. */
    public function bayarulangpesanan()
    {
        $usrid = $this->func->cekLogin();
        $bayar = $this->request->getPost('bayar');
        if ($usrid === 0 || $bayar === null) {
            return $this->response->setStatusCode(403)->setJSON(['success' => false]);
        }

        $row = $this->func->getBayar($bayar, 'semua');
        if ($row === null || (int) $row->usrid !== $usrid) {
            return $this->response->setStatusCode(403)->setJSON(['success' => false]);
        }
        if ((string) $row->midtrans_id === '') {
            return $this->response->setStatusCode(404)->setJSON(['success' => false]);
        }

        $this->func->configureMidtrans();
        try {
            \Midtrans\Transaction::cancel($row->midtrans_id);
        } catch (\Throwable $e) {
            return $this->response->setStatusCode(500)->setJSON(['success' => false]);
        }

        $this->db->table('pembayaran')->where('id', $bayar)->update(['midtrans_id' => '']);

        return $this->response->setJSON(['success' => true]);
    }

    /** Port dari Assync::cekmidtrans() (CI3) -- tampilkan status transaksi Midtrans terkini. */
    public function cekmidtrans()
    {
        $usrid = $this->func->cekLogin();
        $idbayar = $this->request->getGet('bayar');
        if ($usrid === 0 || $idbayar === null) {
            return $this->response->setBody('Forbidden');
        }

        $bayar = $this->func->getBayar($idbayar, 'semua');
        if ($bayar === null || (int) $bayar->usrid !== $usrid || (string) $bayar->midtrans_id === '') {
            return $this->response->setBody('Status tidak tersedia untuk pesanan ini.');
        }

        $this->func->configureMidtrans();
        try {
            $status = \Midtrans\Transaction::status($bayar->midtrans_id);
        } catch (\Throwable $e) {
            return $this->response->setBody('Gagal menghubungi Midtrans, silahkan coba lagi.');
        }

        $sukses = in_array($status->transaction_status ?? '', ['settlement', 'capture'], true);
        $html  = '<div class="row m-b-10"><div class="col-4">Status</div><div class="col-8 text-uppercase font-weight-bold ' . ($sukses ? 'text-success' : 'text-danger') . '">: ' . esc(strtoupper($status->transaction_status ?? '-')) . '</div></div>';
        $html .= '<div class="row m-b-10"><div class="col-4">Metode</div><div class="col-8 text-uppercase font-weight-bold">: ' . esc($status->payment_type ?? '-') . '</div></div>';
        $html .= '<div class="row m-b-10"><div class="col-4">Jumlah</div><div class="col-8 font-weight-bold">: Rp ' . esc($this->func->formUang($status->gross_amount ?? 0)) . '</div></div>';

        return $this->response->setBody($html);
    }
}
