/* ==========================================================================
DeVere Chemical — Invoice Payment Portal
--------------------------------------------------------------------------
ARCHITECTURE NOTE (read before deploying):
This page is static (no server). Stripe requires a secret-key API call to
create/confirm a real charge, which can never run safely in browser JS.
So actual card entry happens on Stripe's own hosted payment page (a
"Payment Link"), not on this page. This page collects the invoice number,
amount, and contact info, shows the "Ready to pay?" confirmation, and then
hands off to Stripe with the invoice number attached as a reference so it
shows up on the payment record in the Stripe Dashboard.
SETUP REQUIRED before going live:
1. In the Stripe Dashboard, create a Payment Link that allows the
customer to enter a custom amount ("Customer chooses price").
2. Paste that Payment Link URL into STRIPE_PAYMENT_LINK below.
3. Paste your Stripe publishable key into STRIPE_PUBLISHABLE_KEY if you
later add Stripe.js elsewhere on the page (not required for the
Payment Link redirect flow itself).
========================================================================== */
const CONFIG = {
STRIPE_PUBLISHABLE_KEY: "pk_live_REPLACE_ME",
STRIPE_PAYMENT_LINK: "https://buy.stripe.com/9B614ffBK2YiecJavK14400",
DRAFT_STORAGE_KEY: "dv_payment_portal_draft",
};
const form = document.getElementById("dv-payment-form");
const confirmSection = document.getElementById("dv-confirm");
const redirectingSection = document.getElementById("dv-redirecting");
const fields = {
invoiceNumber: document.getElementById("invoiceNumber"),
amount: document.getElementById("amount"),
fullName: document.getElementById("fullName"),
email: document.getElementById("email"),
};
const confirmYesBtn = document.getElementById("dv-confirm-yes");
const confirmNoBtn = document.getElementById("dv-confirm-no");
function loadDraft() {
try {
const raw = localStorage.getItem(CONFIG.DRAFT_STORAGE_KEY);
if (!raw) return;
const draft = JSON.parse(raw);
Object.keys(fields).forEach((key) => {
if (draft[key] !== undefined) fields[key].value = draft[key];
});
} catch (e) {
/* ignore corrupt/unavailable storage */
}
}
function saveDraft() {
const draft = {};
Object.keys(fields).forEach((key) => {
draft[key] = fields[key].value;
});
try {
localStorage.setItem(CONFIG.DRAFT_STORAGE_KEY, JSON.stringify(draft));
} catch (e) {
/* storage unavailable (private browsing, quota) — non-fatal */
}
}
function clearDraft() {
try {
localStorage.removeItem(CONFIG.DRAFT_STORAGE_KEY);
} catch (e) {
/* ignore */
}
}
function setError(fieldName, message) {
const errorEl = document.getElementById("err-" + fieldName);
errorEl.textContent = message || "";
fields[fieldName].classList.toggle("dv-invalid", Boolean(message));
}
function validate() {
let valid = true;
if (!fields.invoiceNumber.value.trim()) {
setError("invoiceNumber", "Invoice number is required.");
valid = false;
} else {
setError("invoiceNumber", "");
}
const amountValue = parseFloat(fields.amount.value);
if (!fields.amount.value || isNaN(amountValue) || amountValue <= 0) {
setError("amount", "Enter a valid payment amount greater than $0.");
valid = false;
} else {
setError("amount", "");
}
if (!fields.fullName.value.trim()) {
setError("fullName", "Name is required.");
valid = false;
} else {
setError("fullName", "");
}
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(fields.email.value.trim())) {
setError("email", "Enter a valid email address.");
valid = false;
} else {
setError("email", "");
}
return valid;
}
function formatCurrency(value) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(value);
}
function showConfirmScreen() {
document.getElementById("sum-invoice").textContent = fields.invoiceNumber.value.trim();
document.getElementById("sum-amount").textContent = formatCurrency(parseFloat(fields.amount.value));
document.getElementById("sum-name").textContent = fields.fullName.value.trim();
document.getElementById("sum-email").textContent = fields.email.value.trim();
form.hidden = true;
confirmSection.hidden = false;
confirmSection.scrollIntoView({ behavior: "smooth", block: "start" });
}
function showForm() {
confirmSection.hidden = true;
form.hidden = false;
}
function buildStripeRedirectUrl() {
const url = new URL(CONFIG.STRIPE_PAYMENT_LINK);
url.searchParams.set("client_reference_id", fields.invoiceNumber.value.trim());
url.searchParams.set("prefilled_email", fields.email.value.trim());
return url.toString();
}
function handlePayNow() {
confirmSection.hidden = true;
redirectingSection.hidden = false;
clearDraft();
window.location.href = buildStripeRedirectUrl();
}
form.addEventListener("submit", (event) => {
event.preventDefault();
if (validate()) {
saveDraft();
showConfirmScreen();
}
});
confirmNoBtn.addEventListener("click", () => {
showForm();
});
confirmYesBtn.addEventListener("click", () => {
handlePayNow();
});
Object.values(fields).forEach((input) => {
input.addEventListener("input", saveDraft);
});
loadDraft();