Property Decision Support
Property advisory · Service platform
From choosing a service to a prepared property visit.
I designed and built Property Decision Support for an independent property advisor working with buyers and sellers in Malta and Gozo.

- Role
- Full-Stack Product Engineer
- Ownership
- End-to-end service platform
- Status
- Production
- Core flow
- Choose → Pay → Book → Prepare
[ Project snapshot ]
Problem
Property advice depends on context. Buyers and sellers need different support, while the advisor needs useful information before the appointment instead of collecting it during the visit.
What I built
A commercial service platform with separate buyer and seller journeys, online payment, protected booking access, live availability, structured intake, private document uploads, and transactional communication.
My responsibility
Product flows and UX/UI, frontend and backend, booking architecture, payments, private data, access security, scheduling, forms, CMS architecture, integrations, SEO, infrastructure, and production.
[ The challenge ]
The website had to sell the service without oversimplifying it.
Property Decision Support is deliberately not an estate agency, brokerage service, formal valuation, or building survey. The advisor works independently for one side of the property decision.
A buyer may need help understanding what is unclear before making an offer. A seller may need to understand what should be prepared before listing or why an existing sale is losing momentum.
The website therefore had to explain the service clearly, help people choose the right kind of support, collect payment, and carry enough context into the actual appointment. I built the booking around that whole journey instead of treating the website, checkout, calendar, and intake form as separate parts.
[ Buyer and seller are not the same flow ]
They use the same platform, but they are trying to make different decisions.
The obvious option would have been one generic property form with a few fields shown or hidden depending on the selected service. I decided against that.
A buyer needs to explain the property they are considering, asking price, financing, whether an offer has already been made, where they are in the transaction, what worries them, and which questions are still unanswered.
A seller comes with a different situation. Relevant context may include ownership, whether the property is already listed, current asking price, known issues, previous buyer objections, work carried out, and what they want to improve before the next stage of the sale.
Those differences continue into the backend as separate validated data models rather than one oversized form full of unrelated optional fields. The public experience follows the same logic: calmer decisions for buyers and better preparation or regained control for sellers.
- Verify
- Book your slot
- Details
Buyer context
- Financing status
- Transaction stage
- Concerns & open questions
- Decision priorities
Seller context
- Listing / sale status
- Property history & work
- Known issues & buyer objections
- Desired outcome
[ Payment had to unlock the right booking ]
A successful Stripe payment is an event, not the whole booking.
When somebody starts checkout, the application first creates its own booking reference together with the selected service and expected amount. That reference follows the transaction into Stripe.
The browser returning from checkout is not enough evidence that the booking should continue. The server still needs to know which booking was paid for and whether the payment matches what the application expected.
The Stripe webhook verifies the completed session server-side, including the booking reference, payment state, amount, and currency, before changing the booking to paid. Webhook events are recorded as well, because Stripe can legitimately deliver the same event more than once. State updates are protected so two requests cannot silently complete the same transition.
Only after payment is confirmed does the client receive access to the next part of the process.
Choose a service
Pay securely
Payment confirmed
Payment not completed
Booking link is not sent
Receive booking link
[ Content and client data needed a clean boundary ]
Private property information should not live beside website content.
A booking can include contact details, property addresses, asking prices, transaction status, financing context, concerns, access information, and other details about the property. Clients can also provide documents for the advisor to review before the appointment.
Public content
Website & CMS
Private operations
Booking & client data
Private files
Client documents
Public content
Website & CMS
Private operations
Booking & client data
Private files
Client documents
Stripe processes payments, Cal handles availability and appointments, and Resend handles transactional email. Each external service stays focused on its own job.
[ The appointment starts before the visit ]
The form exists so the paid appointment starts with context.
Once the booking is paid and access is verified, the client can see available appointment times and choose a slot. From there, the process moves into the buyer or seller intake.
The form is intentionally detailed because the information has a job to do. It tells the advisor what stage the client is at, what is already known, where the uncertainty is, and what should be reviewed before the appointment.
Clients can also attach supporting documents. Uploads are checked on the server, not only in the browser: file size and allowed formats are validated, and the file contents are checked rather than relying only on the filename or MIME type.
Once the final submission succeeds, the appointment is created and connected to the booking, client information, and intake. By the time the advisor opens the appointment, the administrative preparation should already be done.
[ Production reality ]
A clean booking flow gets less clean once real people start using it.
- Stripe can send the same webhook again.
- Checkout can expire before payment.
- A verification code can expire.
- A form can be submitted more than once.
- A file can have the right extension but the wrong contents.
- A client may need to request a refund after payment.
None of those problems belongs to one particular screen. I modelled the booking state centrally so the backend can decide what is allowed based on what has already happened.
That includes payment state, access and session state, selected service, appointment state, and submission status. The client does not need to create a permanent account just to use a one-off service: paid booking access uses time-limited credentials and a short authenticated session, with email verification available when automatic access is no longer valid.
Refund requests are tied back to an existing booking and verified through the booking email rather than accepted as anonymous messages.
[ The outcome ]
The advisor doesn’t have to reconstruct a booking from five different tools.
A client can move from understanding the service to paying, choosing a time, and preparing the property visit without a manual hand-off between every step.
The advisor receives the appointment together with the context needed to prepare for it instead of piecing together a Stripe payment, calendar event, email conversation, form submission, and document exchange.
The public website remains editable through Sanity, while private booking data and documents stay within the operational side of the system.
[ What I owned ]
I worked across the complete product.
I started with how the service should work online and carried that through product design, frontend, backend, data architecture, and production setup.
That included the buyer and seller journeys, payment and booking state, private data model, secure booking access, calendar integration, structured intake, private file handling, transactional emails, Sanity content architecture, legal and consent implementation, SEO, and deployment.
- Product & UX/UI
- Frontend
- Backend & APIs
- Data architecture
- Payments
- Booking & scheduling
- Security & access
- Forms & file handling
- CMS & content modelling
- Transactional email
- SEO & consent
- Infrastructure & production
- Product & UX/UI
- Frontend
- Backend & APIs
- Data architecture
- Payments
- Booking & scheduling
- Security & access
- Forms & file handling
- CMS & content modelling
- Transactional email
- SEO & consent
- Infrastructure & production
[ Built with ]
- Application
- Private data
- Private files
- Content & publishing
- Payments & scheduling
- Communication & protection
[ Let’s work together ]
Building a service where the important work starts before the appointment?
I can help turn the journey behind the service into a product that works end to end.
