This is some text with a footnote.

Clicks on the footnote: 0

CENTURY 21 eviction

************copywrite date 10/13/2025*********

************buiessess name: CENTURY 21 Real Estate Unlawful Detainer********* Stripe SSA Demo

Stripe Checkout + SSA Acceptance Demo

$10,000
Use Stripe CLI to forward webhooks to local server
Instructions
  1. Start your webhook-enabled server (see server example) at port 4242.
  2. Expose Stripe webhooks locally using Stripe CLI: stripe listen --forward-to localhost:4242/webhook
  3. Serve this file via local static server: npx http-server . -p 5500 then open http://localhost:5500/stripe_ssa_demo.html
  4. Click the Checkout button after accepting SSA; the demo will simulate a call to /create-checkout-session.
Developer notes: Replace the client stub key with your publishable key and implement server endpoint /create-checkout-session to call Stripe using your secret key. Record SSA acceptance server-side with timestamp and user metadata before creating a session.
Demo log
Ready.
Image OCR Upload Demo

Image OCR Upload Demo

Source: Google Lens / Camera
This demo sends a Base64 image to /process-image and shows mapped results and encrypted download options.
No output yet
Image OCR Upload Demo

Image OCR Upload Demo

Source: Google Lens / Camera
This demo sends a Base64 image to /process-image. Replace with HTTPS server and KMS in production.
No output yet
Image OCR & AI Mapping
Server will run OCR → AI mapping → encrypt and return mapped form. Consent and SSA acceptance must be recorded server-side.
:root{ --bg:#f6f7fb; --card:#ffffff; --accent:#b80606; --muted:#556072; --success:#0b9a5a; --warn:#e6a800; --danger:#c62828; --glass: rgba(255,255,255,0.8); --radius:12px; --shadow:0 8px 30px rgba(11,18,30,.06); --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", monospace; } /* Page layout */ body{ margin:0; font-family:Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; background: linear-gradient(180deg,#f8fafc 0%, #f1f5f9 100%); color:#0b1220; -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale; min-height:100vh; } .container{ max-width:1080px; margin:32px auto; padding:20px; } /* Card */ .card{ background:var(--card); border-radius:var(--radius); box-shadow:var(--shadow); padding:18px; overflow:hidden; } /* Heading */ .h3{ font-size:1.25rem; color:var(--accent); margin:0 0 12px 0; font-weight:700; } /* Grid */ .grid{ display:grid; grid-template-columns: 1fr 420px; gap:16px; } @media (max-width:980px){ .grid{grid-template-columns:1fr; padding:0} } /* Form inputs */ .label{display:block;font-weight:600;margin-bottom:6px;color:#1f2937} .input, input[type="file"], select, textarea{ width:100%; padding:10px 12px; border:1px solid #e6eef8; border-radius:10px; background:transparent; box-sizing:border-box; font-size:14px; } .input:focus, textarea:focus, select:focus{ outline:none; border-color:var(--accent); box-shadow:0 4px 18px rgba(184,6,6,0.08); } /* Buttons */ .button{ display:inline-flex; align-items:center; gap:8px; padding:10px 14px; border-radius:10px; border:0; cursor:pointer; font-weight:700; color:#fff; background:linear-gradient(180deg,var(--accent) 0%, #910404 100%); } .button.secondary{ background:#2d3748; } .button.ghost{ background:transparent; color:var(--accent); border:1px dashed #f0dede; } /* Small helpers */ .note{font-size:13px;color:var(--muted);margin-top:8px} .mono{font-family:var(--mono);font-size:13px;background:#0b12201a;padding:8px;border-radius:8px} .row{display:flex;gap:10px;align-items:center} .col{display:flex;flex-direction:column;gap:8px} /* Preview / mapped form card */ .preview{ border:1px dashed #e6eef8; padding:12px;border-radius:10px;background:linear-gradient(180deg,#fff,#fcfcff); min-height:120px; } .preview h4{margin:0 0 8px 0;font-size:13px} .field{display:flex;justify-content:space-between;gap:12px;padding:8px 0;border-bottom:1px solid #f4f7fb} .field:last-child{border-bottom:0} .field .k{color:#334155;font-weight:700} .field .v{color:#0b1220;max-width:70%;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} /* Status badges */ .badge{display:inline-block;padding:6px 8px;border-radius:999px;font-weight:700;font-size:12px;color:#fff} .badge.ok{background:var(--success)} .badge.pending{background:var(--warn);color:#2b2b00} .badge.halted{background:var(--danger)} /* Log area */ .log{ background:#0b12201a;color:#0b1220;padding:10px;border-radius:10px;font-family:var(--mono); max-height:160px;overflow:auto;border:1px solid #eef2f7; } /* File preview thumbnails */ .thumb-grid{display:flex;gap:8px;flex-wrap:wrap} .thumb{ width:86px;height:86px;border-radius:8px;overflow:hidden;border:1px solid #e8eef8;background:#f8fafc;display:flex;align-items:center;justify-content:center; } .thumb img{max-width:100%;max-height:100%;object-fit:cover} /* Footer small actions */ .footer-actions{display:flex;gap:10px;align-items:center;margin-top:12px;justify-content:space-between} .small{font-size:13px;color:var(--muted)} /* Utility classes */ .hidden{display:none} .center{text-align:center} require('dotenv').config(); const express = require('express'); const bodyParser = require('body-parser'); const vision = require('@google-cloud/vision'); const crypto = require('crypto'); const { v4: uuidv4 } = require('uuid'); const client = new vision.ImageAnnotatorClient(); // ensure GOOGLE_APPLICATION_CREDENTIALS env set const app = express(); app.use(bodyParser.json({limit:'10mb'})); /* Simple in-memory store for demo */ const store = {}; /* Helper: basic AES-GCM encryption (use KMS in production) */ function encryptAesGcmJson(plaintextObj, passphrase){ const iv = crypto.randomBytes(12); const salt = crypto.randomBytes(16); const key = crypto.pbkdf2Sync(passphrase, salt, 250000, 32, 'sha256'); const cipher = crypto.createCipheriv('aes-256-gcm', key, iv); const json = Buffer.from(JSON.stringify(plaintextObj), 'utf8'); const encrypted = Buffer.concat([cipher.update(json), cipher.final()]); const tag = cipher.getAuthTag(); return { salt: salt.toString('base64'), iv: iv.toString('base64'), tag: tag.toString('base64'), data: encrypted.toString('base64') }; } /* AI mapping stub: map OCR text to eviction form fields */ function aiMapOcrToForm(ocrText){ // Replace with a real model / API call (structured prompt to LLM or specialized mapper) // Demo heuristics: const form = {}; const lines = ocrText.split('\n').map(l=>l.trim()).filter(Boolean); for(const line of lines){ if(line.match(/^\d{1,5}\s+\w+/)) form.propertyAddress = line; if(line.toLowerCase().includes('tenant') || line.match(/[A-Z][a-z]+\s[A-Z][a-z]+/)) form.tenantName = form.tenantName || line; if(line.match(/\$\s?\d+/)) form.claimAmount = line.match(/\$\s?[\d,]+/)[0]; } // fallback placeholders form.tenantName = form.tenantName || 'Unknown Tenant'; form.propertyAddress = form.propertyAddress || 'Unknown Address'; form.claimAmount = form.claimAmount || '$10,000'; return form; } /* POST /process-image Body: { filename, imageBase64, userMeta } Returns: { id, ocrText, mappedForm, encryptedBlobId (if stored) } */ app.post('/process-image', async (req, res) => { try { const { filename, imageBase64, userMeta } = req.body; if(!imageBase64) return res.status(400).json({ error: 'no image' }); // 1) OCR using Google Vision const imageBuffer = Buffer.from(imageBase64, 'base64'); const [result] = await client.textDetection({ image: { content: imageBuffer } }); const ocrText = (result.fullTextAnnotation && result.fullTextAnnotation.text) || ''; // 2) AI map OCR -> structured court form const mapped = aiMapOcrToForm(ocrText); // 3) Create audit record and optionally encrypt before storage const id = uuidv4(); const audit = { id, filename, userMeta, processedAt: new Date().toISOString(), ocrSnippet: ocrText.slice(0,200) }; // OPTIONAL: server-side encryption with KMS; demo: simple passphrase fallback const serverPass = process.env.SERVER_ENC_PASSPHRASE || 'demo-server-pass'; const enc = encryptAesGcmJson({ mapped, audit }, serverPass); // Persist encrypted blob (demo in-memory) store[id] = { audit, mapped, encrypted: enc }; // 4) Return structured data (minimal) and id reference return res.json({ id, mappedForm: mapped, encryptedBlobId: id, audit: { id: audit.id, processedAt: audit.processedAt } }); } catch (err) { console.error(err); return res.status(500).json({ error: 'processing failed' }); } }); app.get('/encrypted/:id', (req,res)=>{ const rec = store[req.params.id]; if(!rec) return res.status(404).json({ error: 'not found' }); res.json({ id: req.params.id, encrypted: rec.encrypted, audit: rec.audit }); }); app.listen(3000, ()=>console.log('server listening on 3000'));





stripe listen --forward-to localhost:4242/webhook


curl -X POST http://localhost:4242/create-checkout-session \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1000000,
    "currency": "usd",
    "ssAccepted": true,
    "userMeta": { "userId": "tester01", "email": "tester@example.com" }
  }'


# using a simple static server (optional but recommended)
npx http-server . -p 5500
# then open http://localhost:5500/stripe_ssa_demo.html


npm install
node server.js







Transaction History Demo




  

Transaction History

Date Invoice ID UD ID Tenant / Client Amount Payment Method Status Notes
Showing 0 transactions
Page 1
require('dotenv').config(); const express = require('express'); const bodyParser = require('body-parser'); const Stripe = require('stripe'); const { v4: uuidv4 } = require('uuid'); const stripe = Stripe(process.env.STRIPE_SECRET_KEY); const PORT = process.env.PORT || 4242; const app = express(); /* Note: Stripe requires the raw body to validate webhook signature. We expose two body parsers: JSON for normal endpoints, raw for /webhook. */ app.use('/webhook', bodyParser.raw({ type: 'application/json' })); app.use(bodyParser.json()); /* In-memory store for demo purposes. Replace with a persistent DB (Postgres, MySQL, MongoDB) in production. */ const db = { invoices: {}, // invoiceId => { invoiceId, amount, currency, ssAccepted, ssMeta, sessionId, status, createdAt } udProcesses: {}, // udId => { udId, status, invoiceId, updatedAt } }; /* Helper: create demo UD process and invoice record In production, create these in DB after user sign up/checkout flow. */ function createInvoice({ amount, currency, ssAccepted, ssMeta }) { const invoiceId = uuidv4(); const createdAt = new Date().toISOString(); db.invoices[invoiceId] = { invoiceId, amount, currency, ssAccepted: !!ssAccepted, ssMeta: ssMeta || null, sessionId: null, status: 'pending', createdAt, }; const udId = uuidv4(); db.udProcesses[udId] = { udId, status: 'active', invoiceId, updatedAt: createdAt }; return { invoiceId, udId }; } /* POST /create-checkout-session Body: { invoiceId?, amount?, currency?, ssAccepted: true/false, userMeta: { userId, email } } Behavior: - requires ssAccepted === true - creates invoice if not provided - creates Stripe Checkout session and returns sessionId to client - stores sessionId server-side linked to invoice */ app.post('/create-checkout-session', async (req, res) => { try { const { invoiceId, amount = 1000000, currency = 'usd', ssAccepted, userMeta = {} } = req.body; if (!ssAccepted) { return res.status(400).json({ error: 'Stripe Services Agreement must be accepted.' }); } // Create invoice and UD process if not passed let invId = invoiceId; if (!invId) { const created = createInvoice({ amount, currency, ssAccepted, ssMeta: { acceptedAt: new Date().toISOString(), userMeta } }); invId = created.invoiceId; } else { // Update existing invoice SSA metadata if (!db.invoices[invId]) { return res.status(404).json({ error: 'Invoice not found.' }); } db.invoices[invId].ssAccepted = true; db.invoices[invId].ssMeta = { acceptedAt: new Date().toISOString(), userMeta }; } // Create a Checkout Session const session = await stripe.checkout.sessions.create({ mode: 'payment', payment_method_types: ['card'], line_items: [ { price_data: { currency, product_data: { name: 'Harbutte Unlawful Detainer Flat Rate', description: 'Flat rate legal processing fee' }, unit_amount: amount }, quantity: 1 } ], metadata: { invoiceId: invId }, success_url: `${process.env.BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.BASE_URL}/cancel` }); // Persist session id to invoice db.invoices[invId].sessionId = session.id; db.invoices[invId].status = 'checkout_created'; return res.json({ sessionId: session.id, checkoutUrl: session.url }); } catch (err) { console.error('create-checkout-session error', err); return res.status(500).json({ error: 'Internal server error' }); } }); /* POST /webhook Verifies Stripe signature and handles events. Important: endpoint receives raw body; we used bodyParser.raw for this path. */ app.post('/webhook', async (req, res) => { const sig = req.headers['stripe-signature']; const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret); } catch (err) { console.error('⚠️ Webhook signature verification failed.', err.message); return res.status(400).send(`Webhook Error: ${err.message}`); } // Handle the event types we care about switch (event.type) { case 'checkout.session.completed': { const session = event.data.object; const invoiceId = session.metadata && session.metadata.invoiceId; if (invoiceId && db.invoices[invoiceId]) { db.invoices[invoiceId].status = 'paid'; db.invoices[invoiceId].paidAt = new Date().toISOString(); // Update associated UD process const ud = Object.values(db.udProcesses).find(u => u.invoiceId === invoiceId); if (ud) { ud.status = 'payment_received'; ud.updatedAt = new Date().toISOString(); } console.log(`Invoice ${invoiceId} marked paid via Checkout session ${session.id}`); } else { console.warn('checkout.session.completed: invoiceId not found in metadata'); } break; } case 'payment_intent.succeeded': { const pi = event.data.object; // optional: map to invoice via metadata or lookup from payment_intent -> session -> metadata console.log(`PaymentIntent succeeded: ${pi.id}`); break; } default: console.log(`Unhandled event type ${event.type}`); } // Return 200 to acknowledge receipt of the event res.json({ received: true }); }); /* Minimal routes for demo inspection */ app.get('/invoices/:id', (req, res) => { const id = req.params.id; const inv = db.invoices[id]; if (!inv) return res.status(404).json({ error: 'Not found' }); return res.json(inv); }); app.get('/ud/:id', (req, res) => { const id = req.params.id; const ud = db.udProcesses[id]; if (!ud) return res.status(404).json({ error: 'Not found' }); return res.json(ud); }); app.get('/', (req, res) => res.send('Harbutte UD Demo Server running')); /* Start server */ app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); console.log(`Ensure STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are configured in environment`); }); PORT=4242 STRIPE_SECRET_KEY=sk_test_... STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_WEBHOOK_SECRET=whsec_... BASE_URL=http://localhost:4242 { "name": "harbutte-ud-demo", "version": "1.0.0", "main": "server.js", "scripts": { "start": "node server.js" }, "dependencies": { "body-parser": "^1.20.2", "dotenv": "^16.3.1", "express": "^4.18.2", "stripe": "^12.9.0", "uuid": "^9.0.0" } } Harbutte UD Demo — Stripe SSA Aware

Harbutte Unlawful Detainer Demo — Stripe Checkout added

Files encrypted locally; production must hand off encrypted blobs and keys to server KMS or HSM, not localStorage.
Flat Rate $10,000
Non-refundable; contractor must accept Stripe Services Agreement to proceed with Stripe payments.
Verified $0.01 to OWNER's verified account halts UD processing. Real verification required server-side.
Process status: Active

Admin / Contractor Controls

Record acceptance of SSA server-side with the admin token and user identity for legal traceability.

Logs

This demo references Stripe Services Agreement for your contractual responsibilities when using Stripe services.
Harbutte Unlawful Detainer Demo

Harbutte Unlawful Detainer Front-End Demo

Files are AES-GCM encrypted locally before any upload. Replace client-side storage with your secure back-end in production.
Flat Rate $10,000
Non-refundable, paid prior to 72-hour court turnover. Partial payment rule: see Payment Control below.
If verified payment of exactly $0.01 to OWNER's verified account is detected, the UD process stops immediately in this demo.
Process status: Active

Admin Login

Logs

Process server registration guidance for production teams is available from county resources; Sacramento county process server registration is an example reference.