From 3758a423eb6dd41095e17b52460a9c7fcc4685f7 Mon Sep 17 00:00:00 2001 From: Vladimir Buzalka Date: Wed, 10 Dec 2025 07:41:35 +0100 Subject: [PATCH] notebookVB --- 10 Tests/MBcert.pfx | Bin 0 -> 7026 bytes 10 Tests/test stavpojisteni.py | 13 ++ 10 Tests/test.py | 12 ++ 10 Tests/vzpb2b_client.py | 213 +++++++++++++++++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 10 Tests/MBcert.pfx create mode 100644 10 Tests/test stavpojisteni.py create mode 100644 10 Tests/test.py create mode 100644 10 Tests/vzpb2b_client.py diff --git a/10 Tests/MBcert.pfx b/10 Tests/MBcert.pfx new file mode 100644 index 0000000000000000000000000000000000000000..6fbd2741c20d386c7fd14a989157822f1b4d63bb GIT binary patch literal 7026 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 PjOjJ)3vB%V`2hVd5Y;@9 literal 0 HcmV?d00001 diff --git a/10 Tests/test stavpojisteni.py b/10 Tests/test stavpojisteni.py new file mode 100644 index 0000000..309b7cb --- /dev/null +++ b/10 Tests/test stavpojisteni.py @@ -0,0 +1,13 @@ +from vzpb2b_client import VZPB2BClient + +client = VZPB2BClient( + env="production", + pfx_path="mbcert.pfx", + pfx_password="Vlado7309208104++", + icz="00000000", + dic="00000000" +) + +response = client.stav_pojisteni("0308020152") +# print(response) +print(client.parse_stav_pojisteni(response)) \ No newline at end of file diff --git a/10 Tests/test.py b/10 Tests/test.py new file mode 100644 index 0000000..70f2d92 --- /dev/null +++ b/10 Tests/test.py @@ -0,0 +1,12 @@ +from vzpb2b_client import VZPB2BClient + +client = VZPB2BClient( + env="simu", # or "prod" + pfx_path="mbcert.pfx", + pfx_password="Vlado7309208104++", + icz="00000000", + dic="00000000" +) + +result_xml = client.over_prukaz_pojistence("80203111194350000001") +print(result_xml) diff --git a/10 Tests/vzpb2b_client.py b/10 Tests/vzpb2b_client.py new file mode 100644 index 0000000..a8ea67f --- /dev/null +++ b/10 Tests/vzpb2b_client.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from requests_pkcs12 import Pkcs12Adapter +import requests +import uuid +from datetime import date + + + +class VZPB2BClient: + def __init__(self, env: str, pfx_path: str, pfx_password: str, + icz: str = "00000000", dic: str = "00000000"): + + # Normalize environment name + env = env.lower().strip() + + if env in ("prod", "production", "live", "real"): + self.env = "prod" + elif env in ("simu", "simulace", "test", "testing"): + self.env = "simu" + else: + raise ValueError(f"Unknown environment '{env}'. Use 'simu' or 'prod'.") + + self.pfx_path = pfx_path + self.pfx_password = pfx_password + self.icz = icz + self.dic = dic + + # Prepare mTLS session + session = requests.Session() + session.mount( + "https://", + Pkcs12Adapter(pkcs12_filename=pfx_path, pkcs12_password=pfx_password) + ) + self.session = session + + # -------------------------------------------------------------- + # URL BUILDER + # -------------------------------------------------------------- + def _build_endpoint(self, service_name: str) -> str: + """ + SIMU: + https://simu.b2b.vzp.cz/B2BProxy/HttpProxy/SIMU?sluzba=SIMU + PROD: + https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/ + """ + + if self.env == "simu": + simu_service = f"SIMU{service_name}" + return ( + f"https://simu.b2b.vzp.cz/B2BProxy/HttpProxy/" + f"{simu_service}?sluzba={simu_service}" + ) + + # Production + return ( + f"https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/{service_name}" + ) + + # -------------------------------------------------------------- + # SOAP HEADER BUILDER + # -------------------------------------------------------------- + def _header(self) -> str: + idZpravy = uuid.uuid4().hex[:12] # must be alphanumeric, max 12 chars + return f""" + {idZpravy} + + {self.icz} + {self.dic} + + """ + + # -------------------------------------------------------------- + # OVERPRUKAZ — EHIC CHECK + # -------------------------------------------------------------- + def over_prukaz_pojistence(self, cislo_prukazu: str, k_datu: str = None) -> str: + """ + Calls OverPrukazPojistenceB2B (SIMU or PROD depending on env). + Returns raw XML string. + """ + + service = "OverPrukazPojistenceB2B" + endpoint = self._build_endpoint(service) + + if not k_datu: + k_datu = date.today().isoformat() + + soap = f""" + + + + {self._header()} + + + + + {cislo_prukazu} + {k_datu} + + + + +""" + + headers = {"Content-Type": "text/xml; charset=utf-8"} + + print(f"Calling: {endpoint}") + response = self.session.post( + endpoint, + data=soap.encode("utf-8"), + headers=headers, + timeout=30 + ) + print("HTTP:", response.status_code) + return response.text + + def stav_pojisteni(self, rc: str, k_datu: str = None, prijmeni: str = None): + """ + Calls stavPojisteniB2B (SIMU or PROD). + """ + service = "stavPojisteniB2B" + endpoint = self._build_endpoint(service) + + if not k_datu: + k_datu = date.today().isoformat() + + prijmeni_xml = f"{prijmeni}" if prijmeni else "" + + soap = f""" + + + + {self._header()} + + + + + {rc} + {prijmeni_xml} + {k_datu} + + + + +""" + + headers = { + "Content-Type": "text/xml; charset=utf-8", + "SOAPAction": "process" + } + + print(f"Calling: {endpoint}") + resp = self.session.post(endpoint, data=soap.encode("utf-8"), + headers=headers, timeout=30) + print("HTTP:", resp.status_code) + return resp.text + + def parse_stav_pojisteni(self, xml_text: str): + """ + Parses stavPojisteniB2B SOAP response into a Python dict. + + Returned structure: + { + "stavVyrizeni": int, + "stav": str | None, + "kodPojistovny": str | None, + "nazevPojistovny": str | None, + "pojisteniKod": str | None + } + """ + + import xml.etree.ElementTree as ET + + NS = { + "soap": "http://schemas.xmlsoap.org/soap/envelope/", + "vzp": "http://xmlns.gemsystem.cz/stavPojisteniB2B" + } + + root = ET.fromstring(xml_text) + + # ---- Extract status ---- + stav_vyr = root.find(".//vzp:stavVyrizeniPozadavku", NS) + stav_vyr = int(stav_vyr.text.strip()) if stav_vyr is not None else None + + # ---- If no stavPojisteni element present (e.g. 0 or some errors) ---- + node_stav = root.find(".//vzp:stavPojisteni", NS) + if node_stav is None: + return { + "stavVyrizeni": stav_vyr, + "stav": None, + "kodPojistovny": None, + "nazevPojistovny": None, + "pojisteniKod": None, + } + + def get(tag): + el = node_stav.find(f"vzp:{tag}", NS) + return el.text.strip() if el is not None and el.text else None + + return { + "stavVyrizeni": stav_vyr, + "stav": get("stav"), + "kodPojistovny": get("kodPojistovny"), + "nazevPojistovny": get("nazevPojistovny"), + "pojisteniKod": get("pojisteniKod"), + }