The gov-fee email now lists exactly what the amount covers (full breakdown) so the customer can check it for accuracy, with two clear actions: a ✅ pay link and a ❓ 'something looks wrong' link to /order/dispute. New /order/dispute page shows the fee breakdown and lets the customer describe what's wrong; it opens an 'issue' support ticket pre-tagged with the order (amount + label + their note) via /api/v1/tickets, so ops corrects the fee before any payment is taken. The /order/pay page also shows the itemized breakdown and a dispute link.
149 lines
8.3 KiB
HTML
149 lines
8.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<meta name="robots" content="noindex, nofollow" />
|
|
<title>Review your fee | Performance West</title>
|
|
<script>
|
|
window.__PW_API = (function () {
|
|
var h = window.location.hostname;
|
|
if (h === "localhost" || h === "127.0.0.1") return "http://" + h + ":3001";
|
|
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
|
|
return "https://api.performancewest.net";
|
|
})();
|
|
</script>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
<style>
|
|
:root { --pw600:#3a6192; --pw700:#2d4e78; --pw800:#213b5c; --pw900:#182c45;
|
|
--g100:#f3f4f6; --g200:#e5e7eb; --g300:#d1d5db; --g500:#6b7280; --g700:#374151; --g900:#111827; }
|
|
* { box-sizing: border-box; }
|
|
body { margin:0; font-family:'Inter',system-ui,sans-serif; background:#f7f8fa; color:var(--g700); }
|
|
.wrap { max-width:560px; margin:0 auto; padding:32px 16px; }
|
|
.card { background:#fff; border:1px solid var(--g200); border-radius:14px; overflow:hidden; box-shadow:0 1px 3px rgba(0,0,0,.06); }
|
|
.head { background:var(--pw900); color:#fff; padding:20px 24px; }
|
|
.head h1 { margin:6px 0 0; font-size:18px; font-weight:700; }
|
|
.head p { margin:4px 0 0; font-size:13px; color:#b8cde5; }
|
|
.logo { font-weight:700; color:#fff; font-size:15px; }
|
|
.body { padding:24px; }
|
|
.row { display:flex; justify-content:space-between; padding:7px 0; font-size:14px; border-bottom:1px solid var(--g100); }
|
|
.row .lbl { color:var(--g500); } .row .val { color:var(--g900); font-weight:500; text-align:right; }
|
|
.muted { color:var(--g500); font-size:12px; }
|
|
h2 { font-size:13px; text-transform:uppercase; letter-spacing:.05em; color:var(--g500); margin:22px 0 8px; }
|
|
label { display:block; font-size:13px; font-weight:500; color:var(--g700); margin:12px 0 4px; }
|
|
textarea, input { width:100%; padding:10px 12px; border:1px solid var(--g300); border-radius:8px; font-family:inherit; font-size:14px; }
|
|
textarea:focus, input:focus { outline:2px solid var(--pw600); border-color:var(--pw600); }
|
|
.btn { width:100%; margin-top:18px; padding:13px; border:none; border-radius:10px; background:var(--pw700); color:#fff; font-size:15px; font-weight:600; cursor:pointer; }
|
|
.btn:hover { background:var(--pw800); } .btn:disabled { opacity:.55; cursor:not-allowed; }
|
|
.btn-secondary { display:block; text-align:center; margin-top:10px; padding:11px; border-radius:10px; background:#fff; border:1px solid var(--g300); color:var(--g700); text-decoration:none; font-size:14px; font-weight:500; }
|
|
.err { margin-top:12px; font-size:13px; color:#b91c1c; background:#fef2f2; border:1px solid #fecaca; border-radius:8px; padding:10px 12px; }
|
|
.ok { margin-top:12px; font-size:14px; color:#166534; background:#dcfce7; border:1px solid #bbf7d0; border-radius:8px; padding:12px; }
|
|
pre { background:var(--g100); border-radius:8px; padding:10px 12px; font-size:12px; white-space:pre-wrap; margin:0; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrap">
|
|
<div class="card">
|
|
<div class="head">
|
|
<div class="logo">Performance West</div>
|
|
<h1>Review your government fee</h1>
|
|
<p id="subtitle">Loading…</p>
|
|
</div>
|
|
<div class="body">
|
|
<div id="loading" class="muted">Loading…</div>
|
|
<div id="content" style="display:none;">
|
|
<p class="muted">Here is exactly what your fee covers. If anything looks wrong
|
|
(fleet size, operating states, weight, or the amount), tell us below and we'll
|
|
correct it before you pay.</p>
|
|
<h2>Fee breakdown</h2>
|
|
<div id="breakdown"></div>
|
|
<div class="row" style="border-bottom:none;margin-top:6px;">
|
|
<span class="lbl" style="font-weight:700;color:var(--pw900);">Total</span>
|
|
<span class="val" id="total" style="font-weight:700;color:var(--pw900);"></span>
|
|
</div>
|
|
|
|
<h2>What looks wrong?</h2>
|
|
<form id="form">
|
|
<label for="name">Your name</label>
|
|
<input id="name" type="text" autocomplete="name" />
|
|
<label for="email">Email</label>
|
|
<input id="email" type="email" autocomplete="email" required />
|
|
<label for="msg">Tell us what to fix</label>
|
|
<textarea id="msg" rows="5" required placeholder="e.g. We only run in SC, not NC — please recalculate."></textarea>
|
|
<div id="err" class="err" style="display:none;"></div>
|
|
<button id="submit" class="btn" type="submit">Submit correction request</button>
|
|
</form>
|
|
<a id="payok" class="btn-secondary" href="#">Actually, the fee looks right — pay now →</a>
|
|
<div id="done" class="ok" style="display:none;"></div>
|
|
</div>
|
|
<div id="notfound" class="muted" style="display:none;">This link is invalid or the order was already handled.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API = window.__PW_API;
|
|
const $ = (id) => document.getElementById(id);
|
|
const money = (c) => "$" + (Number(c || 0) / 100).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
const orderId = new URLSearchParams(location.search).get("order") || "";
|
|
let order = null;
|
|
|
|
async function load() {
|
|
if (!orderId) { $("loading").style.display = "none"; $("notfound").style.display = "block"; return; }
|
|
try {
|
|
const r = await fetch(API + "/api/v1/compliance-orders/" + encodeURIComponent(orderId));
|
|
if (!r.ok) throw new Error("nf");
|
|
order = await r.json();
|
|
const gov = Number(order.gov_fee_cents || 0);
|
|
const intake = (order.intake_data && typeof order.intake_data === "object") ? order.intake_data : {};
|
|
const breakdown = Array.isArray(intake.breakdown) ? intake.breakdown : [];
|
|
$("subtitle").textContent = `${order.customer_name || ""} · ${order.order_number}`;
|
|
$("breakdown").innerHTML =
|
|
`<div class="row"><span class="lbl">${order.gov_fee_label || "Government fee"}</span><span class="val"></span></div>`
|
|
+ (breakdown.length
|
|
? breakdown.map((b) => `<div class="row"><span class="lbl" style="font-size:13px;">${b.replace(/</g,"<")}</span><span class="val"></span></div>`).join("")
|
|
: "");
|
|
$("total").textContent = money(gov);
|
|
$("payok").href = "/order/pay?order=" + encodeURIComponent(orderId);
|
|
$("loading").style.display = "none";
|
|
$("content").style.display = "block";
|
|
} catch (e) {
|
|
$("loading").style.display = "none"; $("notfound").style.display = "block";
|
|
}
|
|
}
|
|
|
|
$("form").addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
$("err").style.display = "none";
|
|
$("submit").disabled = true; $("submit").textContent = "Submitting…";
|
|
try {
|
|
const r = await fetch(API + "/api/v1/tickets", {
|
|
method: "POST", headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
category: "issue",
|
|
subject: `Government fee correction — ${order.order_number} (${order.gov_fee_label || "gov fee"})`,
|
|
message: `Customer requests a correction to the government fee on order ${order.order_number}.\n\n`
|
|
+ `Current amount: ${money(order.gov_fee_cents)}\n`
|
|
+ `Current label: ${order.gov_fee_label || ""}\n\n`
|
|
+ `Customer says:\n${$("msg").value.trim()}`,
|
|
email: $("email").value.trim(),
|
|
name: $("name").value.trim(),
|
|
page: location.href,
|
|
}),
|
|
});
|
|
const d = await r.json();
|
|
if (!r.ok) throw new Error(d.error || "Could not submit");
|
|
$("form").style.display = "none"; $("payok").style.display = "none";
|
|
$("done").style.display = "block";
|
|
$("done").textContent = "Thanks — we got your correction request and will review it before any payment is taken. We'll email you a corrected fee shortly.";
|
|
} catch (e) {
|
|
$("err").textContent = e.message; $("err").style.display = "block";
|
|
$("submit").disabled = false; $("submit").textContent = "Submit correction request";
|
|
}
|
|
});
|
|
|
|
load();
|
|
</script>
|
|
</body>
|
|
</html>
|