reg query "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /s
reg query "HKCU\SOFTWARE\Policies\Micro ...
--NIPDIPROSES 810450539,954500220 --proxy http://10.12.11.69:8080 --multiplier 4
120363425343637418@g
instruksi awal: nanti jika untuk element pada web saya ngasihnya berupa xpath atau hal lain, jika memungkinkan pakai saj ...
php -r "echo password_hash('PasswordAndaYangKuat', PASSWORD_DEFAULT), PHP_EOL;"
#!/usr/bin/env python3
"""Coretax Self-Service Billing automation.
Workflow:
1. Fetch list of billing requests ...
https://t.kemenkeu.go.id/SosialisasiNewSIKKA
=== Memproses data ke-45 (row 46) ===
SUB_CASE : C0020476335
SUB_CASE = C0020476335
NOMOR_ST = ST ...
=== Memproses data ke-8 (row 9) ===
SUB_CASE : C0020471420
SUB_CASE = C0020471420
NOMOR_ST = ST-0 ...
120363427904873165
✨ *KINERJA PENERIMAAN*
🏢 *KPP Pratama Banyuwangi*
🗓 Update Data: 05 Mei 2026 07:30:15 WIB (Sumber DRM)
📢 *Rencana P ...
PS C:\Users\DAC\Documents\spt.batu.kemenkeu.one> & C:/Users/DAC/Documents/spt.batu.kemenkeu.one/venv/Scripts/python.exe ...
@echo off
echo [%date% %time%] Stopping iphlpsvc...
net stop iphlpsvc
echo [%date% %time%] Waiting 5 seconds...
ti ...
pengaruh factor internl dan factor eksternal terhadap kepatuhan wajib pajak dalam melaporkan dan membayar pajak
factor ...
Rapat Microsoft Teams
Bergabung: https://teams.microsoft.com/meet/44660973024892?p=V9dSb9qgt7XGAxuqFu
ID Rapat: 446 60 ...
1. buka url https://coretax.intranet.pajak.go.id/ tunggu sampai page load selesai dibuka dengan sempurna
2. pada xpath ...
https://kemenkeu-my.sharepoint.com/:x:/g/personal/dymas_sedhayu_kemenkeu_go_id/IQDxKb87K95tSq0J5byxmYDnAQvQw4XUSDvRprJEc ...
http://localhost:1455/auth/callback?code=ac_zXsNPU8mUkW2RHk14A29c1spfwrfG_UiFa7wQLedXCs.ggOP19-NDnX_1jUw4PZXX5B6zmHghDRt ...
POST /WhatsApp/xml-data-post.php
Authorization: Bearer bwi627
Content-Type: multipart/form-data
ID=...&STATUS=sukse ...
https://portal.kppbwi.id/WhatsApp/xml-data-req.php?token=a3f8c2e1d74b905f6e3a1c8d2b047e9f
ID: 30aff1a1 •
2026-05-25 01:40:29
#!/usr/bin/env python3
"""Coretax Self-Service Billing automation.
Workflow:
1. Fetch list of billing requests from portal XML feed.
2. Ensure Coretax SSO session (reuse persistent profile or fresh login).
3. Loop each XML item, fill the self-service billing form, submit and
download the resulting BILLING_CODE_*.pdf into Coretax/billing/.
4. Extract Kode Billing from the PDF.
5. POST status (and PDF when available) back to portal.
Usage:
python coretax-billing.py
"""
import json
import logging
import os
import re
import time
import xml.etree.ElementTree as ET
from pathlib import Path
import requests
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, WebDriverException
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
try:
from PyPDF2 import PdfReader
except Exception:
PdfReader = None
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
log = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Paths & constants
# ---------------------------------------------------------------------------
BASE = Path('/Users/dymas/.openclaw/workspace/Project/Python')
DRIVER_PATH = BASE / 'driver' / 'chromedriver'
CHROME_APP_PATH = Path('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome')
PROFILE_DIR = BASE / 'session' / 'coretax-intranet-profile'
COOKIE_DEBUG_FILE = BASE / 'session' / 'coretax-intranet.json'
BILLING_DIR = BASE / 'Coretax' / 'billing'
TARGET_URL = 'https://coretax.intranet.pajak.go.id'
BILLING_URL = 'https://coretax.intranet.pajak.go.id/payment/id-ID/self-billing'
PROXY = 'http://10.12.11.69:8080'
USERNAME = 'dymas.sedhayu'
PASSWORD = 'K3lu@rg4Cemenkau'
CORETAX_HOST = 'coretax.intranet.pajak.go.id'
SSO_HOST = 'satu.intranet.pajak.go.id'
XML_REQ_URL = 'https://portal.kppbwi.id/WhatsApp/xml-data-req.php?token=a3f8c2e1d74b905f6e3a1c8d2b047e9f'
XML_POST_URL = 'https://portal.kppbwi.id/WhatsApp/xml-data-post.php?token=a3f8c2e1d74b905f6e3a1c8d2b047e9f'
KODE_MAP_NEED_NOP = {'411128-402', '411128-432', '411211-103'}
LOGIN_USERNAME_SELECTORS = [
(By.NAME, 'username'),
(By.XPATH, "//input[@placeholder='Email ID']"),
]
LOGIN_PASSWORD_SELECTORS = [
(By.NAME, 'password'),
(By.ID, 'passwordInput'),
(By.XPATH, "//input[@placeholder='Password']"),
]
LOGIN_SUBMIT_SELECTORS = [
(By.ID, 'kt_sign_in_submit'),
(By.XPATH, "//button[@type='submit' and contains(normalize-space(.), 'Masuk')]"),
]
# ---------------------------------------------------------------------------
# URL state helpers
# ---------------------------------------------------------------------------
def classify_url(url: str) -> str:
u = url.lower()
if CORETAX_HOST in u and 'auth-callback' not in u:
return 'coretax-home'
if SSO_HOST in u and 'auth-callback' in u:
return 'auth-callback'
if SSO_HOST in u and re.search(r'/(login|signin)', u):
return 'sso-login'
if SSO_HOST in u:
return 'sso-other'
if CORETAX_HOST in u:
return 'coretax-callback'
return 'unknown'
def visible_first(driver, selectors):
for by, selector in selectors:
for element in driver.find_elements(by, selector):
if element.is_displayed() and element.is_enabled():
return element
return None
def click_first(driver, selectors, label: str):
element = visible_first(driver, selectors)
if element is None:
raise RuntimeError(f'Elemen untuk {label} tidak ditemukan')
driver.execute_script('arguments[0].scrollIntoView({block:"center"});', element)
time.sleep(0.5)
driver.execute_script('arguments[0].click();', element)
log.info('Klik %s', label)
return element
def is_sso_login_page(driver) -> bool:
state = classify_url(driver.current_url)
if state == 'sso-login':
return True
if state in ('coretax-home', 'auth-callback', 'coretax-callback'):
return False
has_user = visible_first(driver, LOGIN_USERNAME_SELECTORS) is not None
has_pass = visible_first(driver, LOGIN_PASSWORD_SELECTORS) is not None
return has_user and has_pass
def is_coretax_home(driver) -> bool:
return classify_url(driver.current_url) == 'coretax-home'
def wait_for_page_load(driver, timeout: int = 30):
WebDriverWait(driver, timeout).until(
lambda d: d.execute_script('return document.readyState') == 'complete'
)
# ---------------------------------------------------------------------------
# Driver
# ---------------------------------------------------------------------------
def build_driver() -> webdriver.Chrome:
if not DRIVER_PATH.exists():
raise FileNotFoundError(f'Chromedriver tidak ditemukan di: {DRIVER_PATH}')
if not CHROME_APP_PATH.exists():
raise FileNotFoundError(f'Google Chrome tidak ditemukan di: {CHROME_APP_PATH}')
PROFILE_DIR.mkdir(parents=True, exist_ok=True)
BILLING_DIR.mkdir(parents=True, exist_ok=True)
log.info('[DRIVER] Profile dir: %s', PROFILE_DIR)
log.info('[DRIVER] Download dir: %s', BILLING_DIR)
options = Options()
options.binary_location = str(CHROME_APP_PATH)
options.add_argument(f'--user-data-dir={PROFILE_DIR}')
options.add_argument('--start-maximized')
options.add_argument('--no-default-browser-check')
options.add_argument('--disable-notifications')
options.add_argument('--disable-popup-blocking')
options.add_argument('--ignore-certificate-errors')
options.add_argument('--allow-insecure-localhost')
options.add_argument(f'--proxy-server={PROXY}')
prefs = {
'download.default_directory': str(BILLING_DIR),
'download.prompt_for_download': False,
'download.directory_upgrade': True,
'plugins.always_open_pdf_externally': True,
'profile.default_content_settings.popups': 0,
'safebrowsing.enabled': True,
'safebrowsing.disable_download_protection': True,
}
options.add_experimental_option('prefs', prefs)
return webdriver.Chrome(service=Service(str(DRIVER_PATH)), options=options)
def _save_cookies_debug(driver):
try:
COOKIE_DEBUG_FILE.parent.mkdir(parents=True, exist_ok=True)
cookies = driver.get_cookies()
with open(COOKIE_DEBUG_FILE, 'w', encoding='utf-8') as fh:
json.dump(cookies, fh, indent=2, ensure_ascii=False)
except Exception as exc:
log.debug('[DEBUG] Cookie export gagal: %s', exc)
# ---------------------------------------------------------------------------
# Auth flow (mirrors coretax-aktivasi-kose.py)
# ---------------------------------------------------------------------------
def wait_for_authenticated_landing(driver, timeout: int = 90) -> bool:
end_time = time.time() + timeout
last_url = None
seen_callback = False
while time.time() < end_time:
try:
current = driver.current_url
except WebDriverException:
time.sleep(1)
continue
state = classify_url(current)
if current != last_url:
log.info('[TRANSITION] %-16s %s', state.upper(), current)
last_url = current
if state == 'auth-callback':
seen_callback = True
if state == 'coretax-home':
try:
wait_for_page_load(driver, 15)
except TimeoutException:
pass
log.info('[HOME] Authenticated landing dikonfirmasi: %s', current)
return True
if state == 'sso-login' and seen_callback:
log.warning('[LOGIN-FAILED] Redirect kembali ke SSO login: %s', current)
return False
time.sleep(1)
try:
wait_for_page_load(driver, 8)
except TimeoutException:
pass
log.warning('[TIMEOUT] Tidak mencapai Coretax home dalam %ds. URL: %s', timeout, last_url)
return False
def check_profile_session(driver) -> bool:
log.info('[PROFILE] Memeriksa sesi dari persistent profile...')
try:
driver.get(TARGET_URL)
wait_for_page_load(driver, 30)
except TimeoutException:
log.warning('[PROFILE] Timeout navigasi ke Coretax home')
time.sleep(3)
if is_coretax_home(driver):
log.info('[PROFILE] Sesi profile valid')
return True
if is_sso_login_page(driver):
log.info('[PROFILE] Sesi profile tidak valid - perlu fresh login')
return False
return wait_for_authenticated_landing(driver, timeout=30)
def do_login(driver) -> bool:
log.info('[LOGIN] Memulai login flow')
try:
driver.get(TARGET_URL)
wait_for_page_load(driver, 30)
except TimeoutException:
pass
time.sleep(2)
if is_coretax_home(driver):
return True
if not is_sso_login_page(driver):
deadline = time.time() + 20
while time.time() < deadline:
time.sleep(2)
if is_sso_login_page(driver):
break
if is_coretax_home(driver):
return True
if not is_sso_login_page(driver):
log.error('[LOGIN] Halaman SSO tidak ditemukan: %s', driver.current_url)
return False
username_el = visible_first(driver, LOGIN_USERNAME_SELECTORS)
if username_el is None:
log.error('[LOGIN] Field username tidak ditemukan')
return False
username_el.clear()
username_el.send_keys(USERNAME)
password_el = visible_first(driver, LOGIN_PASSWORD_SELECTORS)
if password_el is None:
log.error('[LOGIN] Field password tidak ditemukan')
return False
password_el.clear()
password_el.send_keys(PASSWORD)
click_first(driver, LOGIN_SUBMIT_SELECTORS, 'tombol Masuk')
return wait_for_authenticated_landing(driver, timeout=90)
def ensure_logged_in(driver):
if check_profile_session(driver):
log.info('[AUTH] Sesi profile valid')
_save_cookies_debug(driver)
return
if not do_login(driver):
raise RuntimeError('Login gagal')
_save_cookies_debug(driver)
log.info('[AUTH] Fresh login berhasil')
# ---------------------------------------------------------------------------
# XML helpers
# ---------------------------------------------------------------------------
def fetch_xml_items():
log.info('[XML] Fetching %s', XML_REQ_URL)
r = requests.get(XML_REQ_URL, timeout=30)
r.raise_for_status()
text = r.text.strip()
if not text:
return []
try:
root = ET.fromstring(text)
except ET.ParseError as e:
log.error('[XML] Gagal parse XML: %s', e)
return []
items = []
for it in root.findall('.//item'):
rec = {}
for child in it:
rec[child.tag] = (child.text or '').strip()
items.append(rec)
return items
# ---------------------------------------------------------------------------
# PDF / download helpers
# ---------------------------------------------------------------------------
def wait_for_billing_pdf(dirpath: Path, existing: set, timeout: int = 60):
start = time.time()
while time.time() - start < timeout:
try:
files = [f for f in os.listdir(dirpath)
if f.lower().endswith('.pdf') or f.lower().endswith('.crdownload')]
except Exception:
files = []
new = [f for f in files if f not in existing]
completed = [f for f in new
if f.lower().endswith('.pdf') and not f.lower().endswith('.crdownload')]
billing_match = [f for f in completed if f.upper().startswith('BILLING_CODE_')]
target = billing_match or completed
if target:
target.sort(key=lambda p: os.path.getmtime(dirpath / p), reverse=True)
return dirpath / target[0]
time.sleep(0.5)
return None
def extract_kode_billing(path: Path) -> str:
if PdfReader is None:
log.warning('PyPDF2 tidak tersedia')
return ''
try:
reader = PdfReader(str(path))
full = '\n'.join((p.extract_text() or '') for p in reader.pages)
except Exception as e:
log.warning('Gagal baca PDF: %s', e)
return ''
# Kode Billing biasanya 15 digit
m = re.search(r'Kode\s*Billing[^\d]{0,40}(\d[\d\s]{13,20}\d)', full, re.IGNORECASE)
if m:
return re.sub(r'\D', '', m.group(1))
m = re.search(r'\b\d{15}\b', full)
if m:
return m.group(0)
return ''
# ---------------------------------------------------------------------------
# Billing flow
# ---------------------------------------------------------------------------
def _wait_clickable(driver, xpath, timeout):
return WebDriverWait(driver, timeout).until(EC.element_to_be_clickable((By.XPATH, xpath)))
def _wait_present(driver, xpath, timeout):
return WebDriverWait(driver, timeout).until(EC.presence_of_element_located((By.XPATH, xpath)))
def _fill_dropdown_global(driver, dropdown_xpath, value, item_xpath_options=None, timeout=10):
"""Click a p-dropdown that opens an overlay attached to body, type value, pick first."""
_wait_clickable(driver, dropdown_xpath, timeout).click()
time.sleep(0.5)
inp_xpath = '/html/body/div/div/div/div[1]/div/input'
inp = _wait_present(driver, inp_xpath, timeout)
WebDriverWait(driver, timeout).until(lambda d: inp.is_enabled())
time.sleep(1)
inp.clear()
inp.send_keys(value)
time.sleep(1)
candidates = item_xpath_options or [
'/html/body/div/div/div/div[2]/ul/p-dropdownitem/li/span',
'/html/body/div/div/div/div[2]/p-scroller/div/ul/p-dropdownitem/li/span',
]
for ix in candidates:
try:
_wait_clickable(driver, ix, timeout).click()
return
except TimeoutException:
continue
raise TimeoutException(f'Dropdown item tidak muncul untuk value: {value}')
def process_billing(driver, item: dict) -> dict:
"""Run the billing creation for a single XML item.
Returns dict with keys: status, kode_billing, pdf_path
"""
npwp16 = item.get('NPWP16') or ''
nik = item.get('NIK') or ''
kode_map = item.get('KODE_MAP_BAYAR') or ''
masa = item.get('MASA_TAHUN_PAJAK') or ''
nop = item.get('NOP') or ''
alamat_nop = item.get('ALAMAT_NOP') or ''
prov_nop = item.get('PROV_NOP') or ''
kab_nop = item.get('KAB_NOP') or ''
kec_nop = item.get('KEC_NOP') or ''
kel_nop = item.get('KEL_NOP') or ''
jumlah = item.get('JUMLAH') or ''
uraian = item.get('URAIAN') or ''
search_value = npwp16 or nik
log.info('[BILLING] search=%s kode_map=%s masa=%s jumlah=%s',
search_value, kode_map, masa, jumlah)
result = {'status': 'gagal', 'kode_billing': '', 'pdf_path': None}
driver.get(BILLING_URL)
wait_for_page_load(driver, 60)
time.sleep(2)
# Step 1: Input search NPWP/NIK
search_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-taxpayer-search-grid/pmnt-taxpayer-search-grid/'
'ui-taxpayer-search-grid-2/div[1]/div/input')
inp = _wait_present(driver, search_xpath, 60)
inp.clear()
inp.send_keys(search_value)
# Step 2: Wait for result row & click select button
select_btn_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-taxpayer-search-grid/pmnt-taxpayer-search-grid/'
'ui-taxpayer-search-grid-2/p-table/div/div/table/tbody/tr/td[1]/div/div/p-button/button')
_wait_present(driver, select_btn_xpath, 60)
btn = _wait_clickable(driver, select_btn_xpath, 60)
time.sleep(1)
btn.click()
# Step 3: Click "Tambah Detail Pembayaran" button
add_detail_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[4]/button')
_wait_clickable(driver, add_detail_xpath, 30).click()
# Step 4: KODE_MAP_BAYAR dropdown
kode_dropdown_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[3]/div[2]/p-dropdown')
_wait_clickable(driver, kode_dropdown_xpath, 30).click()
kode_input_xpath = kode_dropdown_xpath + '/div/p-overlay/div/div/div/div[1]/div/input'
kode_input = _wait_present(driver, kode_input_xpath, 10)
time.sleep(1)
kode_input.clear()
kode_input.send_keys(kode_map)
time.sleep(1)
kode_item_xpath = kode_dropdown_xpath + '/div/p-overlay/div/div/div/div[2]/ul/p-dropdownitem/li/span'
_wait_clickable(driver, kode_item_xpath, 10).click()
time.sleep(2)
# Step 5: MASA_TAHUN_PAJAK dropdown
masa_dropdown_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[4]/div[2]/p-dropdown')
_wait_clickable(driver, masa_dropdown_xpath, 10).click()
time.sleep(1)
masa_input_xpath = masa_dropdown_xpath + '/div/p-overlay/div/div/div/div[1]/div/input'
masa_input = _wait_present(driver, masa_input_xpath, 10)
time.sleep(1)
masa_input.clear()
masa_input.send_keys(masa)
time.sleep(1)
masa_item_xpath = masa_dropdown_xpath + '/div/p-overlay/div/div/div/div[2]/ul/p-dropdownitem/li/span'
_wait_clickable(driver, masa_item_xpath, 10).click()
time.sleep(1)
# Step 6: NOP block (only when KODE_MAP_BAYAR matches or radio appears)
radio_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div/div/div[1]/div[1]/p-radiobutton/div/div[2]')
needs_nop = kode_map in KODE_MAP_NEED_NOP
if not needs_nop:
try:
WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH, radio_xpath)))
needs_nop = True
except TimeoutException:
needs_nop = False
if needs_nop:
log.info('[BILLING] Mengisi blok NOP')
_wait_clickable(driver, radio_xpath, 10).click()
nop_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div/div/div[4]/div[2]/input')
nop_inp = _wait_clickable(driver, nop_xpath, 10)
nop_inp.clear()
nop_inp.send_keys(nop)
alamat_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div/div/div[5]/div[2]/input')
alamat_inp = _wait_clickable(driver, alamat_xpath, 10)
alamat_inp.clear()
alamat_inp.send_keys(alamat_nop)
prov_dd = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div/div/div[6]/div[2]/ui-dropdown/p-dropdown')
_fill_dropdown_global(driver, prov_dd, prov_nop)
kab_dd = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div/div/div[7]/div[2]/ui-dropdown/p-dropdown')
_fill_dropdown_global(driver, kab_dd, kab_nop)
kec_dd = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div/div/div[8]/div[2]/ui-dropdown/p-dropdown')
_fill_dropdown_global(driver, kec_dd, kec_nop)
kel_dd = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div/div/div[9]/div[2]/ui-dropdown/p-dropdown')
_fill_dropdown_global(
driver, kel_dd, kel_nop,
item_xpath_options=[
'/html/body/div/div/div/div[2]/p-scroller/div/ul/p-dropdownitem/li/span',
'/html/body/div/div/div/div[2]/ul/p-dropdownitem/li/span',
],
)
save_nop_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[6]/div[2]/button')
_wait_clickable(driver, save_nop_xpath, 10).click()
else:
save_alt_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[5]/div[2]/button')
_wait_clickable(driver, save_alt_xpath, 30).click()
time.sleep(1)
# Step 7: JUMLAH
jumlah_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[7]/div[2]/p-inputnumber/span/input')
jumlah_inp = _wait_present(driver, jumlah_xpath, 30)
jumlah_inp.click()
jumlah_inp.send_keys(Keys.CONTROL, 'a')
jumlah_inp.send_keys(Keys.DELETE)
jumlah_inp.send_keys(jumlah)
time.sleep(0.5)
# Step 8: URAIAN (optional)
if uraian:
uraian_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[9]/div[2]/input')
try:
uraian_inp = _wait_present(driver, uraian_xpath, 30)
uraian_inp.clear()
uraian_inp.send_keys(uraian)
except TimeoutException:
log.warning('[BILLING] Field URAIAN tidak ditemukan')
# Step 9: Submit "Untuk Kode Billing"
existing = set(os.listdir(BILLING_DIR))
submit_xpath = ('/html/body/pmnt-root/div/ui-coretax-one-column-layout/div/div/div/'
'pmnt-self-service-billing-code/pmntshr-self-service-billing-code-form/'
'form/div/div/div/div[4]/div/div[2]/div[10]/div[2]/button')
_wait_clickable(driver, submit_xpath, 30).click()
log.info('[BILLING] Submit kode billing')
# Step 10: Wait for PDF download
pdf_path = wait_for_billing_pdf(BILLING_DIR, existing, timeout=90)
if not pdf_path:
log.error('[BILLING] PDF tidak terdownload')
return result
log.info('[BILLING] PDF didownload: %s', pdf_path)
result['pdf_path'] = str(pdf_path)
# Step 11: Extract Kode Billing
kode_billing = extract_kode_billing(pdf_path)
result['kode_billing'] = kode_billing
log.info('[BILLING] KODE_BILLING=%s', kode_billing)
if pdf_path and kode_billing:
result['status'] = 'sukses'
return result
# ---------------------------------------------------------------------------
# Posting back
# ---------------------------------------------------------------------------
def post_result(item_id: str, status: str, kode_billing: str, pdf_path):
data = {'ID': item_id, 'STATUS': status, 'IDBILLING': kode_billing}
files = None
fh = None
try:
if pdf_path:
if os.path.exists(pdf_path):
file_size = os.path.getsize(pdf_path)
log.info('[POST] PDF ditemukan: %s (size=%d bytes)', pdf_path, file_size)
if file_size == 0:
log.warning('[POST] File PDF kosong (0 bytes), tidak diupload')
else:
fh = open(pdf_path, 'rb')
files = {'FILEBILLING': (os.path.basename(pdf_path), fh, 'application/pdf')}
else:
log.warning('[POST] pdf_path diset tapi file tidak ditemukan: %s', pdf_path)
else:
log.info('[POST] Tidak ada PDF untuk diupload')
log.info('[POST] Kirim ke %s | data=%s | file=%s',
XML_POST_URL, data,
os.path.basename(pdf_path) if files else 'NONE')
r = requests.post(XML_POST_URL, data=data, files=files, timeout=60)
log.info('[POST] HTTP %s | response: %s', r.status_code, r.text)
# Cek apakah server berhasil menyimpan file
try:
resp_json = r.json()
saved_file = resp_json.get('file', None)
if files and saved_file == '':
log.warning('[POST] File dikirim tapi server tidak menyimpan (file=""). '
'Cek field name $_FILES pada server PHP.')
elif files and saved_file:
log.info('[POST] File berhasil disimpan di server: %s', saved_file)
except Exception:
pass # Response bukan JSON, sudah terlog di atas
except Exception as e:
log.error('[POST] Gagal: %s', e)
finally:
if fh:
fh.close()
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
log.info('=== Coretax Self-Billing Automation ===')
items = fetch_xml_items()
if not items:
log.info('[XML] Tidak ada data, script dihentikan')
return
log.info('[XML] %d item ditemukan', len(items))
driver = build_driver()
try:
ensure_logged_in(driver)
log.info('=== Login dikonfirmasi - mulai loop billing ===')
for idx, item in enumerate(items, start=1):
item_id = item.get('ID', '')
log.info('--- [%d/%d] ID=%s ---', idx, len(items), item_id)
status = 'gagal'
kode_billing = ''
pdf_path = None
try:
res = process_billing(driver, item)
status = res['status']
kode_billing = res['kode_billing']
pdf_path = res['pdf_path']
except Exception as e:
log.exception('[BILLING] Error pada ID=%s: %s', item_id, e)
finally:
post_result(item_id, status, kode_billing, pdf_path)
finally:
try:
driver.quit()
except Exception:
pass
log.info('Browser ditutup')
if __name__ == '__main__':
main()