juspay/hyperswitch · error · Error
Failed to fetch QR code image: ${response.statusText}
Error message
Failed to fetch QR code image: ${response.statusText} What it means
Thrown by fetchAndParseQRCode in cypress-tests-v2 redirection support. It fetches the QR image URL (from a UPI intent response's qrInfoData.qrLink/qr field) with the browser fetch API and throws when response.ok is false, embedding response.statusText. This is a test-side network assertion, not app code.
Source
Thrown at cypress-tests-v2/cypress/support/redirectionHandler.js:413
// No CORS workaround needed
cy.window().its("location.origin").should("eq", expected_url.origin);
} else {
// Workaround for CORS to allow cross-origin iframe
cy.origin(
expected_url.origin,
{ args: { expected_url: expected_url.origin } },
({ expected_url }) => {
cy.window().its("location.origin").should("eq", expected_url);
}
);
}
}
}
async function fetchAndParseQRCode(url) {
const response = await fetch(url, { encoding: "binary" });
if (!response.ok) {
throw new Error(`Failed to fetch QR code image: ${response.statusText}`);
}
const blob = await response.blob();
const reader = new FileReader();
return await new Promise((resolve, reject) => {
reader.onload = () => {
const base64Image = reader.result.split(",")[1]; // Remove data URI prefix
const image = new Image();
image.src = base64Image;
image.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const qrCodeData = jsQR(View on GitHub (pinned to 9b8b89dc37)
Solutions
- Fetch the QR immediately after receiving the payment response, before any cy.wait, so the signed link has not expired
- Manually curl/GET the failing URL from the same CI environment to see the real status code and body
- Re-run the payment initiation to obtain a fresh QR link before parsing
- Verify the URL used comes from response.body.qrInfoData.qrLink (or qr) exactly, with no truncation or encoding issues
Example fix
// before
const response = await fetch(url, { encoding: 'binary' });
if (!response.ok) {
throw new Error(`Failed to fetch QR code image: ${response.statusText}`);
}
// after (retry once with a fresh short-lived link in mind)
async function fetchQr(url, attempts = 2) {
for (let i = 0; i < attempts; i++) {
const response = await fetch(url, { encoding: 'binary' });
if (response.ok) return response;
if (i === attempts - 1) {
throw new Error(`Failed to fetch QR code image: ${response.status} ${response.statusText}`);
}
await new Promise((r) => setTimeout(r, 2000));
}
} Defensive patterns
Strategy: retry
Validate before calling
// Before parsing, confirm the QR link exists and looks fetchable
const qrLink = response.body.qrInfoData?.qrLink || response.body.qrInfoData?.qr;
if (!qrLink || !/^https?:\/\//.test(qrLink)) {
throw new Error(`No QR link to parse (qrInfoData: ${JSON.stringify(response.body.qrInfoData)})`);
} Try / catch
// Short-lived signed QR links: retry briefly, then fail with the status
async function fetchQrWithRetry(url, attempts = 3) {
let lastErr;
for (let i = 0; i < attempts; i++) {
const response = await fetch(url, { encoding: 'binary' });
if (response.ok) return response;
lastErr = new Error(`Failed to fetch QR code image: ${response.status} ${response.statusText}`);
await new Promise((r) => setTimeout(r, 2000));
}
throw lastErr;
} Prevention
- Fetch the QR immediately after receiving the payment response, before any cy.wait
- Initiate a fresh payment (new QR link) rather than retrying an old URL after long waits
- Verify CI can reach the QR host (DNS/proxy) — 404 here usually means expiry, 5xx usually means reachability
When it happens
Trigger: cy flows that parse a UPI intent QR: the fetch of the QR image URL returns 404 (session/link expired), 401/403 (signed URL or auth issue), or 5xx from the QR service — response.ok is false and the error carries the status text.
Common situations: The QR link is short-lived and the test reached it after expiry (slow CI, retries, cy.wait delays); the environment's QR host is unreachable from the CI runner (DNS/proxy/firewall); baseUrl routing differences between sandbox and integ so the QR URL points at the wrong host.
Related errors
- Unsupported connector: ${connectorId}
- Unsupported payment method type: ${payment_method_type}
- computeHmac: 'key' and 'message' are required (got key=${!!k
- Missing merchantId, adminApiKey, or baseUrl in globalState
- Health Check failed with status: `${response.status}` and bo
AI-assisted analysis of juspay/hyperswitch@9b8b89dc37 (2026-08-16).
Data as JSON: /api/errors/1e2b5cd27c66d24a.
Report an issue: GitHub.