Appearance
Offers System ​
Overview ​
The offers system enables buyers to submit purchase proposals and sellers to manage negotiations. It includes offer submission, response management, counter-offers, and automatic deal creation upon acceptance.
Offer Lifecycle ​
mermaid
graph LR
A[Offer Submitted] --> B[Pending Review]
B --> C{Seller Decision}
C -->|Accept| D[Deal Created]
C -->|Reject| E[Rejected]
C -->|Counter| F[Counter Offer]
F --> G[Buyer Decision]
G -->|Accept| D
G -->|Reject| E
G -->|Counter| H[New Counter]Core Components ​
Offer Submission ​
File: src/components/offers/OfferForm.tsxFeatures:
- Price input with validation
- Terms and conditions specification
- Message to seller
- Financing information (if applicable)
- Due diligence period selection
Offer Management Dashboard ​
File: src/pages/MyOffers.tsx (Buyer View) File: src/pages/seller/ManageOffers.tsx (Seller View) Features:
- Offer status tracking
- Response management
- Deal progression monitoring
- Communication history
Data Model ​
Core Table: offers ​
sql
id uuid PRIMARY KEY
listing_id uuid REFERENCES listings(id)
buyer_id uuid REFERENCES profiles(id)
seller_id uuid REFERENCES profiles(id)
amount numeric NOT NULL
status offer_status DEFAULT 'pending'
message text -- Buyer's initial message
response_message text -- Seller's response
created_at timestamptz DEFAULT now()
updated_at timestamptz DEFAULT now()Offer Status Types ​
sql
CREATE TYPE offer_status AS ENUM (
'pending', -- Awaiting seller response
'accepted', -- Seller accepted, deal created
'rejected', -- Seller declined
'countered', -- Seller made counter-offer
'withdrawn', -- Buyer withdrew offer
'expired' -- Offer expired (time-based)
);Offer Submission Process ​
Buyer Workflow ​
- Browse Listing: Find business of interest
- Submit Offer: Complete offer form with terms
- Wait for Response: Seller has 7 days to respond
- Negotiate: Exchange counter-offers if needed
- Deal Creation: Automatic upon acceptance
Validation Rules ​
typescript
const offerValidation = z.object({
amount: z.number().min(1000).max(10000000), // $1K - $10M range
message: z.string().min(50).max(1000), // Meaningful communication
buyerRole: z.enum(['individual', 'company', 'investor']),
experience: z.string().optional(),
timeline: z.enum(['immediate', '30days', '60days']),
financing: z.enum(['cash', 'sba_loan', 'seller_financing'])
})Offer Form Implementation ​
typescript
const OfferForm = ({ listing }: { listing: Listing }) => {
const { mutate: submitOffer } = useMutation({
mutationFn: async (offerData: OfferSubmission) => {
const { data, error } = await supabase
.from('offers')
.insert({
listing_id: listing.id,
buyer_id: user.id,
seller_id: listing.seller_id,
amount: offerData.amount,
message: offerData.message
})
if (error) throw error
return data
},
onSuccess: () => {
toast.success('Offer submitted successfully!')
navigate('/my-offers')
}
})
// Form implementation...
}Seller Response System ​
Response Options ​
- Accept Offer: Creates deal automatically
- Reject Offer: Declines with optional message
- Counter Offer: Proposes different terms
- Request Information: Ask for more buyer details
Seller Response Functions ​
RPC Functions:
seller_accept_offer(offer_id, response_message?): Accept and create dealseller_reject_offer(offer_id, response_message?): Decline offerseller_send_message(offer_id, message): Send message without status change
typescript
// Accept offer implementation
const acceptOffer = async (offerId: string, responseMessage?: string) => {
const { data, error } = await supabase.rpc('seller_accept_offer', {
p_offer_id: offerId,
p_response_message: responseMessage
})
if (error) throw error
// Returns transaction ID for new deal
navigate(`/deal/${data}`)
}Notification System ​
Automatic Notifications ​
Trigger: create_offer_notification()Events:
- Offer Submitted: Notify seller of new offer
- Offer Accepted: Notify buyer of acceptance + deal creation
- Offer Rejected: Notify buyer of rejection
- Offer Message: Notify about new communications
sql
-- Example notification creation
INSERT INTO notifications (user_id, type, message, link)
VALUES (
seller_id,
'offer',
'New offer of €' || amount || ' for "' || listing_title || '"',
'/manage-offers'
);Email Integration ​
Service: Resend API for transactional emails Templates:
- New offer received (to seller)
- Offer status update (to buyer)
- Counter-offer notification (to both parties)
- Deal creation confirmation (to both parties)
Counter-Offer System ​
Counter-Offer Flow ​
- Seller Proposes: New price and terms
- Buyer Reviews: Consider counter-proposal
- Decision Making: Accept, reject, or counter again
- Negotiation Limit: Maximum 3 rounds to prevent endless loops
Implementation ​
typescript
const createCounterOffer = async (originalOfferId: string, newAmount: number, message: string) => {
// Update original offer status to 'countered'
await supabase
.from('offers')
.update({
status: 'countered',
response_message: message
})
.eq('id', originalOfferId)
// Create new offer with counter terms
const { data } = await supabase
.from('offers')
.insert({
listing_id: originalOffer.listing_id,
buyer_id: originalOffer.buyer_id,
seller_id: originalOffer.seller_id,
amount: newAmount,
message: `Counter-offer: ${message}`,
parent_offer_id: originalOfferId // Track negotiation chain
})
}Offer Analytics ​
Key Metrics ​
- Offer Conversion Rate: Percentage of offers that become deals
- Average Response Time: How quickly sellers respond
- Price Negotiation: Difference between asking price and final price
- Offer Volume: Number of offers per listing
Analytics Implementation ​
typescript
const offerAnalytics = {
conversionRate: async (sellerId: string) => {
const offers = await supabase
.from('offers')
.select('status')
.eq('seller_id', sellerId)
const accepted = offers.filter(o => o.status === 'accepted').length
return (accepted / offers.length) * 100
},
averageResponseTime: async (sellerId: string) => {
// Calculate time between offer creation and first response
},
priceNegotiation: async (listingId: string) => {
// Analyze final accepted price vs. listing price
}
}Security and Validation ​
Access Control ​
RLS Policies:
sql
-- Buyers can insert offers for others' listings
CREATE POLICY "Offers buyer can insert"
ON offers FOR INSERT
TO authenticated
WITH CHECK (
buyer_id = auth.uid()
AND buyer_id <> seller_id -- Prevent self-offers
);
-- Users can view their own offers (as buyer or seller)
CREATE POLICY "Offers select own or admin"
ON offers FOR SELECT
TO authenticated
USING (
buyer_id = auth.uid()
OR seller_id = auth.uid()
OR is_admin()
);Anti-Spam Measures ​
- Rate Limiting: Maximum 3 offers per user per day
- Duplicate Prevention: Cannot submit multiple offers on same listing
- Minimum Message Length: Encourage meaningful communication
- Account Age Requirement: New accounts have limited offer capabilities
Fraud Prevention ​
typescript
// Offer validation checks
const validateOffer = async (offerData: OfferSubmission) => {
// Check if buyer has sufficient verification
if (offerData.amount > 50000 && !buyer.kyc_verified) {
throw new Error('KYC verification required for offers over $50K')
}
// Prevent unrealistic offers
if (offerData.amount < listing.price * 0.1) {
throw new Error('Offer too low - minimum 10% of asking price')
}
// Check buyer subscription level for high-value offers
if (offerData.amount > 100000 && buyer.subscription_level === 'free') {
throw new Error('Paid subscription required for offers over $100K')
}
}Integration Points ​
With Listings System ​
- Offer Count: Display number of offers on listing
- Offer Buttons: Contextual call-to-action based on user state
- Price Validation: Ensure offers are reasonable relative to asking price
With Messaging System ​
- Automatic Conversations: Create message thread upon offer submission
- Structured Communication: Offer-specific message templates
- Notification Integration: Link offer updates to messaging notifications
With Deals System ​
- Automatic Deal Creation: Upon offer acceptance
- Data Transfer: Offer terms become deal foundation
- Timeline Integration: Offer acceptance triggers deal timeline
Performance Optimization ​
Database Optimization ​
sql
-- Indexes for offer queries
CREATE INDEX idx_offers_buyer ON offers(buyer_id, created_at DESC);
CREATE INDEX idx_offers_seller ON offers(seller_id, status, created_at DESC);
CREATE INDEX idx_offers_listing ON offers(listing_id, status);Caching Strategy ​
- Offer Lists: 30-second cache for dashboard views
- Offer Details: Fresh data to ensure accuracy
- Offer Counts: 5-minute cache for listing displays
- Analytics: 1-hour cache for metrics calculations
Testing Strategy ​
Unit Tests ​
- Offer submission validation
- Status transition logic
- Price calculation accuracy
- Notification trigger correctness
Integration Tests ​
- Complete offer-to-deal workflow
- Multi-party counter-offer negotiations
- Notification delivery verification
- RLS policy enforcement
E2E Tests ​
- Buyer offer submission flow
- Seller response management
- Deal creation automation
- Email notification delivery
Common Issues and Troubleshooting ​
Offer Submission Failures ​
Problem: Form submission errors Solutions:
- Verify user authentication status
- Check subscription level requirements
- Validate offer amount against business rules
- Confirm listing availability
Missing Notifications ​
Problem: Users not receiving offer notifications Solutions:
- Verify notification trigger functions
- Check email service configuration
- Confirm user notification preferences
- Review RLS policies for notifications table
Deal Creation Issues ​
Problem: Accepted offers not creating deals Solutions:
- Check
seller_accept_offerRPC function - Verify transaction table permissions
- Confirm deal creation trigger logic
- Review commission calculation functions
Best Practices ​
For Buyers ​
- Research Thoroughly: Review all available business information
- Meaningful Communication: Provide detailed offer rationale
- Realistic Pricing: Base offers on business metrics and market analysis
- Professional Approach: Maintain courteous and business-like communication
For Sellers ​
- Timely Responses: Respond to offers within 48 hours
- Clear Communication: Provide specific feedback on offers
- Fair Evaluation: Consider all aspects of buyer's proposal
- Professional Documentation: Maintain records of all negotiations
For Platform ​
- Clear Guidelines: Provide offer submission best practices
- Dispute Resolution: Establish clear escalation procedures
- Performance Monitoring: Track conversion rates and user satisfaction
- Continuous Improvement: Regular review and optimization of offer flow
Future Enhancements ​
- AI-Powered Valuation: Suggest fair offer prices based on business metrics
- Offer Templates: Pre-filled forms for common offer types
- Bulk Offers: Allow buyers to submit offers on multiple listings
- Advanced Analytics: Detailed offer performance and conversion tracking
- Mobile Optimization: Enhanced mobile experience for offer management
Related Documentation:
- Deals System - What happens after offer acceptance
- Messaging - Communication during negotiations
- Listings - How offers integrate with business listings