Server-driven Stripe Terminal integration for taking in-person card payments
against an invoice, running on the same Stripe Connect connected account used
for online payments. No native app or Terminal SDK — the WisePOS E is driven
from the web backend via Stripe's REST API.
- Domain: TerminalReader entity + status enum, PaymentMethod.CardReader,
Company.StripeTerminalLocationId / TerminalSurchargeEnabled, DbSet + tenant
filter + indexes, IUnitOfWork repo, migration AddTerminalReaders (additive).
- StripeConnectService: location/reader registration, list, delete, process
payment on reader, status poll, cancel, and a test-mode simulated tap. All
routed to the connected account like the existing online-payment methods.
- TerminalController: admin reader management + per-invoice ProcessPayment,
PaymentStatus (poll), CancelPayment, SimulateTap (test mode only). Stores the
PaymentIntent id on the invoice; the webhook remains the authoritative writer.
- PaymentController webhook: HandlePaymentSucceededAsync records source=terminal
payments as CardReader (online path unchanged — no source key means no change);
new terminal.reader.action_failed handler for declines/timeouts (notification
only, no ledger mutation). Refund path reused unchanged.
- UI: Card Readers settings tab (register/list/deactivate + in-person surcharge
toggle, default off with a compliance warning) and an invoice "Take Card
Payment" modal with live status polling. External JS per project convention.
- Feature bundled with the existing online-payments entitlement (no new plan
flag); additionally requires StripeConnectStatus == Active.
- Help: HelpKnowledgeBase + Invoices help article updated.
- Tests: TerminalController validation + surcharge-routing tests (241 pass).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- New CustomerContact entity + migration (AddCustomerContactsAndCrmFields)
- Customer.LeadSource + ShipToAddress/City/State/ZipCode/Country fields
- Additional Contacts card on Customer Details with AJAX add/edit/delete
- Lead Source dropdown on Create/Edit; Ship-To section on Create/Edit
- Customer Details: side-by-side billing/ship-to when ship-to is set
- Help docs: Customers (contacts, ship-to, lead source, preferred powders, outstanding pickups)
- Help docs: Jobs (clone job, project name), Quotes (project name), Invoices (project name), Inventory (low stock clickable filter)
- HelpKnowledgeBase.cs updated for all features above
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PatchItem: add case-insensitive JSON deserialization; add legacy fallback
that computes a live breakdown from job items when PricingBreakdownJson is null
- PatchItem: return itemsSubtotal, subtotalBeforeDiscount, subtotalAfterDiscount,
taxAmount in JSON response for immediate DOM updates
- GetCostingBreakdown: use job.FinalPrice as revenue (not invoice total) so
costing figures reflect inline edits before an invoice exists
- Details.cshtml: add data-pb attributes to visible pricing rows; add
job-final-price-display class to visible Total element
- Details.cshtml: wire afterSave callback to call costing.load() after each edit
- inline-item-edit.js: add afterSave hook in commit(); clean up debug logging
- Help docs: add Inline Price Editing sections to Jobs, Quotes, and Invoices
help articles; add inline editing + job costing revenue notes to AI knowledge base
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all corruption variants with HTML entities across 226 view files:
- 3-char UTF-8-as-Win1252 sequences (ae-corruption)
- Standalone smart/curly quotes that break C# Razor expressions
- Partially re-corrupted variants where the 3rd byte was normalised to ASCII
tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the
script itself never contains a literal non-ASCII character; supports -DryRun
.githooks/pre-commit: blocks commits containing the ae-corruption byte
signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the
hook is repo-committed and active for all future work on this machine.
Build clean; 225 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- HelpKnowledgeBase: invoice-from-job now mentions discount carried over,
Discount Applied display row, and negative line items; new entry for
PC-based Log Material modal on job details
- Help/Invoices.cshtml: from-job steps updated with discount/terms/due date
pre-fill detail; sending section corrects due date source (quote/customer)
- Help/Jobs.cshtml: new "Logging Material Usage from a PC" section documenting
the Log Material modal alongside the existing QR scan instructions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The HTML entity sweep script had a bug where it wrote empty files for any
view that contained no target Unicode characters, zeroing out 215 view files.
All views restored from the pre-sweep commit (cefdf3e).
Bulk gift certificate feature:
- BulkCreateGiftCertificateDto with Quantity (1-500), Amount, Reason, Expiry, Notes
- GenerateBulkGiftCertificatePdfAsync on IPdfService / PdfService: one Letter page
per cert, reusing the same purple/gold branded ComposeGiftCertificateContent helper
- GiftCertificatesController: BulkCreate GET/POST, BulkResult GET, BulkDownloadPdf POST
- Views: BulkCreate.cshtml (form with live total preview), BulkResult.cshtml (table +
Download All PDF button that POSTs cert IDs to avoid URL length limits)
- gift-certificate-bulk.js: live preview + spinner/disable on submit
- Index.cshtml: Bulk Create button added alongside New Certificate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sweeps em dashes, en dashes, multiplication signs, ellipses, and curly quotes
to their HTML entity equivalents (— – × … ‘ ’)
in all .cshtml files, skipping <script> blocks. Prevents encoding corruption
from AI tools and Windows encoding mismatches that caused recurring symbol bugs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>