Initial commit
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
@using PowderCoating.Web.Controllers
|
||||
@model DepositPaymentPageViewModel
|
||||
@{
|
||||
Layout = null;
|
||||
var hasSurcharge = Model.SurchargeType != PowderCoating.Core.Enums.OnlinePaymentSurchargeType.None && Model.SurchargeValue > 0;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Pay Deposit — Quote @Model.QuoteNumber — @Model.CompanyName</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
|
||||
<style>
|
||||
body { background: #f0f2f5; font-family: 'Segoe UI', sans-serif; }
|
||||
.pay-card { max-width: 520px; margin: 60px auto; background: #fff; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,.10); padding: 36px 40px; }
|
||||
.company-name { font-size: 1.1rem; color: #6c757d; margin-bottom: 4px; }
|
||||
.quote-number { font-size: 1.5rem; font-weight: 700; color: #1a1a2e; }
|
||||
.deposit-badge { display: inline-block; background: #fff3cd; color: #856404; border-radius: 6px; font-size: .8rem; font-weight: 600; padding: 3px 10px; margin-bottom: 12px; }
|
||||
.amount-display { background: #f8f9fa; border-radius: 8px; padding: 16px 20px; margin: 20px 0; }
|
||||
.amount-display .label { font-size: .85rem; color: #6c757d; }
|
||||
.amount-display .value { font-size: 1.1rem; font-weight: 600; }
|
||||
.total-line { border-top: 2px solid #dee2e6; margin-top: 8px; padding-top: 10px; }
|
||||
.total-line .value { font-size: 1.4rem; color: #f59e0b; font-weight: 700; }
|
||||
#payment-element { margin: 20px 0; }
|
||||
#pay-btn { font-size: 1.05rem; padding: 12px; }
|
||||
#error-msg { font-size: .9rem; }
|
||||
.surcharge-notice { font-size: .78rem; color: #6c757d; margin-top: 6px; }
|
||||
.expires-note { font-size: .8rem; color: #6c757d; text-align: center; margin-top: 16px; }
|
||||
#processing-overlay { display: none; position: fixed; inset: 0; background: rgba(255,255,255,.7); z-index: 9999; align-items: center; justify-content: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="processing-overlay" aria-live="polite">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-warning" role="status"></div>
|
||||
<p class="mt-2 fw-semibold">Processing deposit…</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pay-card">
|
||||
<p class="company-name">@Model.CompanyName</p>
|
||||
<p class="quote-number">Quote #@Model.QuoteNumber</p>
|
||||
<span class="deposit-badge">Deposit Required — @Model.DepositPercent.ToString("0.##")% of quote total</span>
|
||||
<p class="text-muted mb-0">Hello, @Model.CustomerName</p>
|
||||
|
||||
<div class="amount-display">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="label">Quote Total</span>
|
||||
<span class="value text-muted">@Model.QuoteTotal.ToString("C")</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-1 total-line">
|
||||
<span class="label fw-semibold">Deposit Due</span>
|
||||
<span class="value">@Model.DepositAmount.ToString("C")</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="payment-form">
|
||||
@if (hasSurcharge)
|
||||
{
|
||||
<div id="surchargeRow" class="amount-display py-2 d-none">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="label">Deposit</span>
|
||||
<span id="subtotalDisplay" class="value"></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="label">
|
||||
@if (Model.SurchargeType == PowderCoating.Core.Enums.OnlinePaymentSurchargeType.Percent)
|
||||
{
|
||||
@($"Online payment fee ({Model.SurchargeValue:0.##}%)")
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>Online payment fee</text>
|
||||
}
|
||||
</span>
|
||||
<span id="surchargeDisplay" class="value"></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between total-line">
|
||||
<span class="label fw-semibold">Total Charged</span>
|
||||
<span id="totalDisplay" class="value"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="surcharge-notice">
|
||||
A convenience fee applies to online payments. You may pay in person to avoid this fee.
|
||||
Surcharges do not apply to debit card transactions in states where prohibited.
|
||||
</p>
|
||||
}
|
||||
|
||||
<div id="payment-element"></div>
|
||||
|
||||
<div id="error-msg" class="alert alert-danger d-none mt-3" role="alert"></div>
|
||||
|
||||
<button id="pay-btn" type="submit" class="btn btn-warning w-100 mt-3" disabled>
|
||||
<span id="btn-text">Loading payment form…</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="expires-note">
|
||||
This payment link expires on @Model.ExpiresAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MMMM d, yyyy").
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script>
|
||||
const STRIPE_KEY = '@Model.StripePublishableKey';
|
||||
const ACCOUNT_ID = '@Model.StripeAccountId';
|
||||
const TOKEN = '@Model.Token';
|
||||
const DEPOSIT_AMOUNT = @Model.DepositAmount.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
||||
const HAS_SURCHARGE = @(hasSurcharge ? "true" : "false");
|
||||
|
||||
const stripe = Stripe(STRIPE_KEY, { stripeAccount: ACCOUNT_ID });
|
||||
let elements = null;
|
||||
let paymentElement = null;
|
||||
|
||||
const form = document.getElementById('payment-form');
|
||||
const payBtn = document.getElementById('pay-btn');
|
||||
const btnText = document.getElementById('btn-text');
|
||||
const errorMsg = document.getElementById('error-msg');
|
||||
const surchargeRow = document.getElementById('surchargeRow');
|
||||
const subtotalDisplay = document.getElementById('subtotalDisplay');
|
||||
const surchargeDisplay = document.getElementById('surchargeDisplay');
|
||||
const totalDisplay = document.getElementById('totalDisplay');
|
||||
const overlay = document.getElementById('processing-overlay');
|
||||
|
||||
function formatCurrency(val) {
|
||||
return '$' + parseFloat(val).toFixed(2);
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
errorMsg.textContent = msg;
|
||||
errorMsg.classList.remove('d-none');
|
||||
overlay.style.display = 'none';
|
||||
payBtn.disabled = false;
|
||||
btnText.textContent = 'Try Again';
|
||||
}
|
||||
|
||||
async function initPaymentElement() {
|
||||
errorMsg.classList.add('d-none');
|
||||
payBtn.disabled = true;
|
||||
btnText.textContent = 'Preparing…';
|
||||
|
||||
if (paymentElement) { paymentElement.destroy(); paymentElement = null; }
|
||||
document.getElementById('payment-element').innerHTML = '';
|
||||
|
||||
const resp = await fetch('/pay/deposit/' + TOKEN + '/intent', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount: DEPOSIT_AMOUNT })
|
||||
});
|
||||
|
||||
const data = await resp.json();
|
||||
if (!resp.ok) { showError(data.error || 'Could not initialize payment.'); return; }
|
||||
|
||||
const surcharge = data.surchargeAmount || 0;
|
||||
|
||||
if (HAS_SURCHARGE && surcharge > 0) {
|
||||
subtotalDisplay.textContent = formatCurrency(DEPOSIT_AMOUNT);
|
||||
surchargeDisplay.textContent = formatCurrency(surcharge);
|
||||
totalDisplay.textContent = formatCurrency(DEPOSIT_AMOUNT + surcharge);
|
||||
surchargeRow.classList.remove('d-none');
|
||||
}
|
||||
|
||||
elements = stripe.elements({ clientSecret: data.clientSecret });
|
||||
paymentElement = elements.create('payment');
|
||||
paymentElement.mount('#payment-element');
|
||||
paymentElement.on('ready', () => {
|
||||
payBtn.disabled = false;
|
||||
btnText.textContent = 'Pay Deposit ' + formatCurrency(DEPOSIT_AMOUNT + surcharge);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
errorMsg.classList.add('d-none');
|
||||
payBtn.disabled = true;
|
||||
overlay.style.display = 'flex';
|
||||
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: window.location.origin + '/pay/deposit/' + TOKEN + '/success'
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
showError(error.message || 'Payment failed. Please try again.');
|
||||
}
|
||||
});
|
||||
|
||||
initPaymentElement();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Deposit Received</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
|
||||
<style>
|
||||
body { background: #f0f2f5; font-family: 'Segoe UI', sans-serif; }
|
||||
.success-card { max-width: 460px; margin: 80px auto; background: #fff; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,.10); padding: 40px; text-align: center; }
|
||||
.check-icon { font-size: 3.5rem; margin-bottom: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="success-card">
|
||||
<div class="check-icon">✅</div>
|
||||
<h1 class="h4 fw-bold mb-2">Deposit Received</h1>
|
||||
<p class="text-muted">Thank you! Your deposit was successfully processed. The shop has been notified and will be in touch to schedule your job.</p>
|
||||
<hr />
|
||||
<p class="text-muted small mb-0">You may close this window.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,273 @@
|
||||
@using PowderCoating.Web.Controllers
|
||||
@model PaymentPageViewModel
|
||||
@{
|
||||
Layout = null; // Standalone page — no app chrome
|
||||
var hasSurcharge = Model.SurchargeType != PowderCoating.Core.Enums.OnlinePaymentSurchargeType.None && Model.SurchargeValue > 0;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Pay Invoice @Model.InvoiceNumber — @Model.CompanyName</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
|
||||
<style>
|
||||
body { background: #f0f2f5; font-family: 'Segoe UI', sans-serif; }
|
||||
.pay-card { max-width: 520px; margin: 60px auto; background: #fff; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,.10); padding: 36px 40px; }
|
||||
.company-name { font-size: 1.1rem; color: #6c757d; margin-bottom: 4px; }
|
||||
.invoice-number { font-size: 1.5rem; font-weight: 700; color: #1a1a2e; }
|
||||
.amount-display { background: #f8f9fa; border-radius: 8px; padding: 16px 20px; margin: 20px 0; }
|
||||
.amount-display .label { font-size: .85rem; color: #6c757d; }
|
||||
.amount-display .value { font-size: 1.1rem; font-weight: 600; }
|
||||
.total-line { border-top: 2px solid #dee2e6; margin-top: 8px; padding-top: 10px; }
|
||||
.total-line .value { font-size: 1.4rem; color: #198754; font-weight: 700; }
|
||||
#payment-element { margin: 20px 0; }
|
||||
#pay-btn { font-size: 1.05rem; padding: 12px; }
|
||||
#error-msg { font-size: .9rem; }
|
||||
.surcharge-notice { font-size: .78rem; color: #6c757d; margin-top: 6px; }
|
||||
.partial-section { margin-bottom: 16px; }
|
||||
.expires-note { font-size: .8rem; color: #6c757d; text-align: center; margin-top: 16px; }
|
||||
#processing-overlay { display: none; position: fixed; inset: 0; background: rgba(255,255,255,.7); z-index: 9999; align-items: center; justify-content: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="processing-overlay" aria-live="polite">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-success" role="status"></div>
|
||||
<p class="mt-2 fw-semibold">Processing payment…</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pay-card">
|
||||
<p class="company-name">@Model.CompanyName</p>
|
||||
<p class="invoice-number">Invoice #@Model.InvoiceNumber</p>
|
||||
<p class="text-muted mb-0">Hello, @Model.CustomerName</p>
|
||||
|
||||
<div class="amount-display">
|
||||
@if (Model.AmountPaid > 0)
|
||||
{
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="label">Invoice Total</span>
|
||||
<span class="value">@Model.InvoiceTotal.ToString("C")</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="label">Previously Paid</span>
|
||||
<span class="value text-muted">-@Model.AmountPaid.ToString("C")</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-1 total-line">
|
||||
<span class="label fw-semibold">Balance Due</span>
|
||||
<span class="value">@Model.BalanceDue.ToString("C")</span>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-between total-line">
|
||||
<span class="label fw-semibold">Amount Due</span>
|
||||
<span class="value">@Model.BalanceDue.ToString("C")</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<form id="payment-form">
|
||||
@* Partial payment option *@
|
||||
<div class="partial-section">
|
||||
<label class="form-label fw-semibold mb-1">Payment amount</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="payOption" id="optFull" value="full" checked />
|
||||
<label class="form-check-label" for="optFull">
|
||||
Pay full balance — <strong>@Model.BalanceDue.ToString("C")</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check mt-1">
|
||||
<input class="form-check-input" type="radio" name="payOption" id="optPartial" value="partial" />
|
||||
<label class="form-check-label" for="optPartial">Pay a different amount</label>
|
||||
</div>
|
||||
<div id="partialAmountRow" class="mt-2 d-none">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">$</span>
|
||||
<input type="number" id="partialAmount" class="form-control" min="1"
|
||||
step="0.01" max="@Model.BalanceDue.ToString("F2")"
|
||||
placeholder="Enter amount" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (hasSurcharge)
|
||||
{
|
||||
<div id="surchargeRow" class="amount-display py-2 d-none">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="label">Subtotal</span>
|
||||
<span id="subtotalDisplay" class="value"></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="label">
|
||||
@if (Model.SurchargeType == PowderCoating.Core.Enums.OnlinePaymentSurchargeType.Percent)
|
||||
{
|
||||
@($"Online payment fee ({Model.SurchargeValue:0.##}%)")
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>Online payment fee</text>
|
||||
}
|
||||
</span>
|
||||
<span id="surchargeDisplay" class="value"></span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between total-line">
|
||||
<span class="label fw-semibold">Total Charged</span>
|
||||
<span id="totalDisplay" class="value"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="surcharge-notice">
|
||||
A convenience fee applies to online payments. You may pay in person to avoid this fee.
|
||||
Surcharges do not apply to debit card transactions in states where prohibited.
|
||||
</p>
|
||||
}
|
||||
|
||||
<div id="payment-element"></div>
|
||||
|
||||
<div id="error-msg" class="alert alert-danger d-none mt-3" role="alert"></div>
|
||||
|
||||
<button id="pay-btn" type="submit" class="btn btn-success w-100 mt-3" disabled>
|
||||
<span id="btn-text">Loading payment form…</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="expires-note">
|
||||
This payment link expires on @Model.ExpiresAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MMMM d, yyyy").
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script>
|
||||
const STRIPE_KEY = '@Model.StripePublishableKey';
|
||||
const ACCOUNT_ID = '@Model.StripeAccountId';
|
||||
const TOKEN = '@Model.Token';
|
||||
const BALANCE_DUE = @Model.BalanceDue.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
|
||||
const HAS_SURCHARGE = @(hasSurcharge ? "true" : "false");
|
||||
|
||||
const stripe = Stripe(STRIPE_KEY, { stripeAccount: ACCOUNT_ID });
|
||||
let elements = null;
|
||||
let paymentElement = null;
|
||||
let currentClientSecret = null;
|
||||
|
||||
const form = document.getElementById('payment-form');
|
||||
const payBtn = document.getElementById('pay-btn');
|
||||
const btnText = document.getElementById('btn-text');
|
||||
const errorMsg = document.getElementById('error-msg');
|
||||
const surchargeRow = document.getElementById('surchargeRow');
|
||||
const subtotalDisplay = document.getElementById('subtotalDisplay');
|
||||
const surchargeDisplay = document.getElementById('surchargeDisplay');
|
||||
const totalDisplay = document.getElementById('totalDisplay');
|
||||
const overlay = document.getElementById('processing-overlay');
|
||||
const partialAmountRow = document.getElementById('partialAmountRow');
|
||||
const partialAmountInput = document.getElementById('partialAmount');
|
||||
|
||||
let currentAmount = BALANCE_DUE;
|
||||
|
||||
function formatCurrency(val) {
|
||||
return '$' + parseFloat(val).toFixed(2);
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
errorMsg.textContent = msg;
|
||||
errorMsg.classList.remove('d-none');
|
||||
overlay.style.display = 'none';
|
||||
payBtn.disabled = false;
|
||||
btnText.textContent = 'Try Again';
|
||||
}
|
||||
|
||||
function clearError() {
|
||||
errorMsg.classList.add('d-none');
|
||||
}
|
||||
|
||||
// Tear down and rebuild the Stripe Elements for the given amount
|
||||
async function initPaymentElement(amountDollars) {
|
||||
clearError();
|
||||
payBtn.disabled = true;
|
||||
btnText.textContent = 'Preparing…';
|
||||
|
||||
if (paymentElement) { paymentElement.destroy(); paymentElement = null; }
|
||||
document.getElementById('payment-element').innerHTML = '';
|
||||
|
||||
const resp = await fetch('/pay/' + TOKEN + '/intent', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount: parseFloat(amountDollars) })
|
||||
});
|
||||
|
||||
const data = await resp.json();
|
||||
if (!resp.ok) { showError(data.error || 'Could not initialize payment.'); return; }
|
||||
|
||||
currentClientSecret = data.clientSecret;
|
||||
const surcharge = data.surchargeAmount || 0;
|
||||
|
||||
if (HAS_SURCHARGE && surcharge > 0) {
|
||||
subtotalDisplay.textContent = formatCurrency(amountDollars);
|
||||
surchargeDisplay.textContent = formatCurrency(surcharge);
|
||||
totalDisplay.textContent = formatCurrency(parseFloat(amountDollars) + surcharge);
|
||||
surchargeRow.classList.remove('d-none');
|
||||
} else if (surchargeRow) {
|
||||
surchargeRow.classList.add('d-none');
|
||||
}
|
||||
|
||||
elements = stripe.elements({ clientSecret: data.clientSecret });
|
||||
paymentElement = elements.create('payment');
|
||||
paymentElement.mount('#payment-element');
|
||||
paymentElement.on('ready', () => {
|
||||
payBtn.disabled = false;
|
||||
btnText.textContent = 'Pay ' + formatCurrency(parseFloat(amountDollars) + surcharge);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle radio toggle
|
||||
document.querySelectorAll('input[name="payOption"]').forEach(r => {
|
||||
r.addEventListener('change', () => {
|
||||
if (r.value === 'partial') {
|
||||
partialAmountRow.classList.remove('d-none');
|
||||
partialAmountInput.focus();
|
||||
} else {
|
||||
partialAmountRow.classList.add('d-none');
|
||||
currentAmount = BALANCE_DUE;
|
||||
initPaymentElement(currentAmount);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Debounce partial amount changes
|
||||
let debounceTimer;
|
||||
partialAmountInput.addEventListener('input', () => {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
const val = parseFloat(partialAmountInput.value);
|
||||
if (val > 0 && val <= BALANCE_DUE) {
|
||||
currentAmount = val;
|
||||
initPaymentElement(currentAmount);
|
||||
}
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Handle form submit
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
clearError();
|
||||
payBtn.disabled = true;
|
||||
overlay.style.display = 'flex';
|
||||
|
||||
const { error } = await stripe.confirmPayment({
|
||||
elements,
|
||||
confirmParams: {
|
||||
return_url: window.location.origin + '/pay/' + TOKEN + '/success'
|
||||
}
|
||||
});
|
||||
|
||||
// If we get here, confirmPayment redirected or there was an error
|
||||
if (error) {
|
||||
showError(error.message || 'Payment failed. Please try again.');
|
||||
}
|
||||
});
|
||||
|
||||
// Boot
|
||||
initPaymentElement(currentAmount);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
@model string
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Payment Unavailable</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
|
||||
<style>
|
||||
body { background: #f0f2f5; font-family: 'Segoe UI', sans-serif; }
|
||||
.error-card { max-width: 480px; margin: 80px auto; background: #fff; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,.10); padding: 40px; text-align: center; }
|
||||
.icon { font-size: 3rem; margin-bottom: 16px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-card">
|
||||
<div class="icon">⚠️</div>
|
||||
<h1 class="h4 fw-bold mb-3">Payment Unavailable</h1>
|
||||
<p class="text-muted">@Model</p>
|
||||
<hr />
|
||||
<p class="text-muted small mb-0">If you believe this is an error, please contact the shop directly.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Payment Successful</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
|
||||
<style>
|
||||
body { background: #f0f2f5; font-family: 'Segoe UI', sans-serif; }
|
||||
.success-card { max-width: 460px; margin: 80px auto; background: #fff; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,.10); padding: 40px; text-align: center; }
|
||||
.check-icon { font-size: 3.5rem; margin-bottom: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="success-card">
|
||||
<div class="check-icon">✅</div>
|
||||
<h1 class="h4 fw-bold mb-2">Payment Received</h1>
|
||||
<p class="text-muted">Thank you! Your payment was successfully processed. You'll receive a confirmation email shortly.</p>
|
||||
<hr />
|
||||
<p class="text-muted small mb-0">You may close this window.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user