From d8de844f4b2183e11bae266fb036143d41acae67 Mon Sep 17 00:00:00 2001 From: DanielS Date: Tue, 22 Sep 2026 22:20:44 +0200 Subject: [PATCH] =?UTF-8?q?fix(qr):=20ServicePass=20Link=20URL-Handling=20?= =?UTF-8?q?&=20APPKILL=20Token=20Ablaufpr=C3=BCfung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- posClient.js | 62 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/posClient.js b/posClient.js index 98c63bf..d3e805c 100644 --- a/posClient.js +++ b/posClient.js @@ -60,20 +60,58 @@ async function processQrSetup(qrPayload) { const cleanUrl = url.replace(/\/$/, ''); - // 1. GET ServicePass: GET {url}/@LNK{servicepasslink} - const linkUrl = `${cleanUrl}/@LNK${servicepasslink}`; - const resLink = await fetch(linkUrl, { - method: 'GET', - headers: { 'User-Agent': 'Getraenkekarte-POS-Client/1.0' } - }); + // Endpunkt-Varianten für ServicePass Link (Cloud-Instanzen unterstützen @LNK... oder @...) + const candidateUrls = [ + `${cleanUrl}/@${servicepasslink}`, + `${cleanUrl}/@LNK${servicepasslink}`, + `${cleanUrl}/LNK${servicepasslink}`, + `${cleanUrl}/@LNK/${servicepasslink}`, + `${cleanUrl}/WWSVC/@LNK${servicepasslink}` + ]; - if (!resLink.ok) { - throw new Error(`Fehler beim Abruf von ServicePass (@LNK): ${resLink.status} ${resLink.statusText}`); + let resLink = null; + let responseText = ''; + let usedUrl = ''; + + for (const cUrl of candidateUrls) { + try { + const res = await fetch(cUrl, { + method: 'GET', + headers: { 'User-Agent': 'Getraenkekarte-POS-Client/1.0' } + }); + if (res.ok) { + resLink = res; + responseText = await res.text(); + usedUrl = cUrl; + break; + } + } catch (e) {} } - const passData = await resLink.json(); - const passId = passData.passId || passData.PassId || passData.id; - const appId = passData.appId || passData.AppId || passData.key; + if (!resLink) { + throw new Error(`Fehler beim Abruf von ServicePass (@LNK): 404 Resource not found`); + } + + let passId = ''; + let appId = ''; + + try { + const passData = JSON.parse(responseText); + passId = passData.passId || passData.PassId || passData.id; + appId = passData.appId || passData.AppId || passData.key; + } catch (e) { + // Falls Text / Delimiter Format + if (responseText.includes('APPKILL')) { + throw new Error(`QR-Code Token abgelaufen oder bereits verbraucht (Server meldet: APPKILL). Bitte neuen QR-Code im Kassensystem erzeugen.`); + } + const parts = responseText.split('@)').map(s => s.trim()).filter(Boolean); + if (parts.length >= 2) { + passId = parts[0]; + appId = parts[1]; + } else { + throw new Error(`Unerwartete Antwort von ServicePass: ${responseText}`); + } + } if (!passId || !appId) { throw new Error('Antwort von ServicePass enthält keine gültige passId/appId'); @@ -86,7 +124,7 @@ async function processQrSetup(qrPayload) { upsert.run('pos_app_id', appId); if (mdcDeviceId) upsert.run('pos_cash_register', String(mdcDeviceId)); - // 3. Pass aktivieren (PUT /WWSVC/EXECURL/{passId}/CASPOS.PUT/REGISTERID={mdcDeviceId}/ENTITY=SERVICEPASS/SERVICEPASS_ID={passId}) + // 3. Pass aktivieren const devId = mdcDeviceId || '1'; const activatePath = `/WWSVC/EXECURL/${passId}/CASPOS.PUT/REGISTERID=${devId}/ENTITY=SERVICEPASS/SERVICEPASS_ID=${passId}`; const activateUrl = `${cleanUrl}${activatePath}`;