Fix kiosk SMS consent routing loop and stuck tablet

- Route param renamed customerId→id so /Kiosk/SmsConsent/15307 binds correctly
  (default MVC route uses {id}; mismatched name caused GetByIdAsync(0)→404→loop)
- Cache entry cleared in GET (not just POST) so returning to Welcome after seeing
  the form never redirects again
- Added POST /Kiosk/CancelSmsConsent for staff to free the kiosk if they pushed
  consent accidentally — Customer Details shows a Cancel button after pushing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 23:25:37 -04:00
parent e1256503be
commit 0af31c39b3
3 changed files with 55 additions and 13 deletions
@@ -158,17 +158,34 @@ public class KioskController : Controller
return Json(new { success = true });
}
/// <summary>
/// Cancels a pending kiosk SMS consent request, freeing the kiosk to return to the Welcome
/// screen. Called by staff if they pushed consent accidentally or the customer isn't coming.
/// </summary>
[HttpPost, ValidateAntiForgeryToken]
public IActionResult CancelSmsConsent()
{
var companyId = HttpContext.User.FindFirst("CompanyId")?.Value;
if (int.TryParse(companyId, out var cid))
_cache.Remove(SmsConsentCacheKey(cid));
return Json(new { success = true });
}
/// <summary>
/// Displays the full-screen SMS consent form on the kiosk tablet (anonymous, kiosk layout).
/// Loads the customer by ID with ignoreQueryFilters because the kiosk has no tenant context.
/// </summary>
[AllowAnonymous]
public async Task<IActionResult> SmsConsent(int customerId)
public async Task<IActionResult> SmsConsent(int id)
{
var cookie = ReadKioskCookie();
if (cookie == null) return Forbid();
var customer = await _unitOfWork.Customers.GetByIdAsync(customerId, ignoreQueryFilters: true);
// Clear the pending entry immediately — the kiosk is now showing the form,
// so Welcome must not redirect again if the customer cancels or navigates back.
_cache.Remove(SmsConsentCacheKey(cookie.Value.companyId));
var customer = await _unitOfWork.Customers.GetByIdAsync(id, ignoreQueryFilters: true);
if (customer == null) return NotFound();
var company = await _unitOfWork.Companies.GetByIdAsync(cookie.Value.companyId, ignoreQueryFilters: true);
@@ -179,25 +196,23 @@ public class KioskController : Controller
? $"{customer.ContactFirstName} {customer.ContactLastName}".Trim()
: customer.CompanyName ?? "Customer";
return View(customerId);
return View(id);
}
/// <summary>
/// Records the customer's SMS consent from the kiosk tablet and clears the pending cache entry.
/// Records the customer's SMS consent from the kiosk tablet.
/// Sets NotifyBySms, SmsConsentedAt, SmsConsentMethod = "KioskInPerson" on the customer record.
/// Cache is already cleared by the GET; this handles the agree/decline outcome.
/// </summary>
[AllowAnonymous, HttpPost]
public async Task<IActionResult> SmsConsent(int customerId, bool agreed)
public async Task<IActionResult> SmsConsent(int id, bool agreed)
{
var cookie = ReadKioskCookie();
if (cookie == null) return Forbid();
// Always clear the pending consent so the kiosk stops showing the form
_cache.Remove(SmsConsentCacheKey(cookie.Value.companyId));
if (agreed)
{
var customer = await _unitOfWork.Customers.GetByIdAsync(customerId, ignoreQueryFilters: true);
var customer = await _unitOfWork.Customers.GetByIdAsync(id, ignoreQueryFilters: true);
if (customer != null)
{
customer.NotifyBySms = true;
@@ -206,15 +221,15 @@ public class KioskController : Controller
customer.SmsOptedOutAt = null;
await _unitOfWork.Customers.UpdateAsync(customer);
await _unitOfWork.CompleteAsync();
_logger.LogInformation("SMS consent recorded via kiosk for customer {CustomerId}", customerId);
_logger.LogInformation("SMS consent recorded via kiosk for customer {CustomerId}", id);
await _inApp.CreateAsync(
customer.CompanyId,
"SMS Consent Recorded",
$"{customer.ContactFirstName} {customer.ContactLastName} agreed to SMS notifications on the kiosk.",
"KioskConsent",
link: $"/Customers/Details/{customerId}",
customerId: customerId);
link: $"/Customers/Details/{id}",
customerId: id);
}
}