Skip to content

Merchant Integration - Engineering Implementation Guide

Related Documents:


Architecture Overview

Phase 1: Role-Based Access (Current)

  • Single domain: ourlantern.app
  • Role-based routing using Firebase Auth Custom Claims
  • Shared authentication and database
  • Merchant routes: #/merchant/signup, #/merchant, #/merchant/new

Phase 2: Separate Merchant Portal (Future)

  • Separate subdomain: merchant.ourlantern.app
  • Independent deployment
  • SSO with main app via Firebase Auth
  • Trigger: Merchant count > 25 OR building merchant mobile apps

Data Models

User Profile Enhancement

Add role and merchant-specific fields to existing user profile schema:

javascript
// Firestore collection: users/{userId}
{
  uid: "user123",
  email: "merchant@example.com",
  lanternName: "Sunny Cafe Owner",
  role: "merchant", // NEW: "user" | "merchant" | "admin"
  merchantProfile: { // NEW: Optional merchant-specific data
    businessName: "Sunny Cafe",
    venueId: "venue_xyz", // Link to venue in venues collection
    businessType: "cafe", // cafe, bar, restaurant, retail, etc.
    approved: true, // Manual approval flag
    onboardedAt: "2026-01-11T06:00:00Z"
  },
  // ... existing profile fields (lanternName, interests, mood, etc.)
  createdAt: "2026-01-01T00:00:00Z",
  updatedAt: "2026-01-11T06:00:00Z"
}

Merchant Applications Collection

New Firestore collection for tracking merchant applications:

javascript
// Firestore collection: merchant_applications/{applicationId}
{
  id: "app_123",
  applicantEmail: "merchant@example.com",
  applicantName: "John Doe",
  businessName: "Sunny Cafe",
  venueAddress: "123 Main St, San Diego, CA",
  businessType: "cafe", // cafe | bar | restaurant | retail | other
  phoneNumber: "+1234567890",
  website: "https://sunnycafe.com", // optional
  message: "I'd love to bring more customers in...", // optional
  status: "pending", // pending | approved | rejected
  appliedAt: "2026-01-11T06:00:00Z",
  reviewedAt: null,
  reviewedBy: null, // userId of admin who reviewed
  notes: "" // Admin notes (internal only)
}

Frontend Implementation

1. Merchant Signup Component

File: src/screens/merchant/MerchantSignup.jsx

Features:

  • Multi-step form (currently single page)
  • Form validation (required fields, email format)
  • Business type dropdown
  • Optional message field
  • Terms agreement checkbox
  • Success screen with confirmation

TODO: Wire to Firebase:

javascript
// Replace this in MerchantSignup.jsx handleSubmit()
const { addDoc, collection, serverTimestamp } = await import('firebase/firestore')
const { db } = await import('../../firebase')

const applicationData = {
  ...formData,
  status: 'pending',
  appliedAt: serverTimestamp(),
  reviewedAt: null,
  reviewedBy: null,
  notes: ''
}

const docRef = await addDoc(collection(db, 'merchant_applications'), applicationData)
console.log('Application submitted with ID:', docRef.id)

2. Merchant Discovery (CTA)

Location: Multiple entry points per user request

A. Info Panel (Primary - New Requirement)

File: src/components/InfoPanel.jsx

  • FAQ section includes merchant signup question
  • Direct link to #/merchant/signup
  • Closes InfoPanel and navigates on click

File: src/components/dashboard/HomeView.jsx (lines 308-326)

  • Subtle banner at bottom of Places screen
  • Non-intrusive placement
  • Clear value proposition

3. Access Control (To Be Implemented)

File: src/App.jsx

Add role checks to protect merchant routes:

javascript
// TODO: Implement role-based protection
const isMerchant = currentUser && userProfile?.role === 'merchant'

{route === '#/merchant' && (
  isMerchant ? (
    <MerchantDashboard />
  ) : (
    <AccessDeniedScreen 
      title="Merchant Access Required"
      message="You need merchant access to view this page."
      ctaText="Apply for Merchant Access"
      ctaLink="#/merchant/signup"
    />
  )
)}

4. Navigation Changes

File: src/components/dashboard/DashboardNavigation.jsx

Changes Made:

  • Replaced "Activity" button with "Info" button
  • Removed notificationCount prop (no longer displayed)
  • Added onInfoClick callback prop
  • Changed icon from Bell to Info

File: src/components/LanternHub.jsx

Existing: "Recent Activity" already present in LanternHub

  • Lines 182-188: When lantern is lit
  • Lines 229-235: When no active lantern
  • Accessible via fire icon click

Backend Integration (Pending)

Firestore Security Rules

Add to firestore.rules:

javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    // Merchant Applications
    match /merchant_applications/{applicationId} {
      // Anyone can create (submit application)
      allow create: if request.auth != null;
      
      // Applicants can read their own applications
      allow read: if request.auth.token.role == 'admin'
                  || resource.data.applicantEmail == request.auth.token.email;
      
      // Only admins can update/delete
      allow update, delete: if request.auth.token.role == 'admin';
    }
    
    // User Profiles - Enhanced role protection
    match /users/{userId} {
      allow read: if request.auth != null;
      
      // Users can update their own profile but cannot change role/merchantProfile
      allow write: if request.auth.uid == userId
                      && (!request.resource.data.diff(resource.data).affectedKeys().hasAny(['role', 'merchantProfile']))
                      || request.auth.token.role == 'admin'; // Admins can change anything
    }
  }
}

Cloud Functions

1. Application Notification Email

File: functions/src/merchantApplicationNotification.js (to be created)

javascript
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const nodemailer = require('nodemailer')

exports.notifyOnMerchantApplication = functions.firestore
  .document('merchant_applications/{applicationId}')
  .onCreate(async (snap, context) => {
    const application = snap.data()
    
    // Send email to admin
    const adminEmail = 'merchants@ourlantern.app'
    const subject = `New Merchant Application: ${application.businessName}`
    const body = `
      New merchant application received:
      
      Business: ${application.businessName}
      Contact: ${application.applicantName} (${application.applicantEmail})
      Type: ${application.businessType}
      Address: ${application.venueAddress}
      
      Review at: https://ourlantern.app/admin/merchant-applications/${context.params.applicationId}
    `
    
    // TODO: Configure email transport and send
    // await transporter.sendMail({ to: adminEmail, subject, text: body })
    
    console.log('Merchant application notification sent for:', application.businessName)
  })

2. Welcome Email on Approval

File: functions/src/merchantWelcomeEmail.js (to be created)

javascript
exports.sendMerchantWelcomeEmail = functions.firestore
  .document('merchant_applications/{applicationId}')
  .onUpdate(async (change, context) => {
    const before = change.before.data()
    const after = change.after.data()
    
    // Check if status changed from pending to approved
    if (before.status !== 'approved' && after.status === 'approved') {
      const email = after.applicantEmail
      const subject = 'Welcome to Lantern Merchant Program!'
      const body = `
        Hi ${after.applicantName},
        
        Great news! Your merchant application has been approved.
        
        You now have access to the Lantern merchant dashboard where you can:
        - Create and manage offers
        - Track performance metrics (impressions, clicks, redemptions)
        - View analytics for your venue
        
        Get started: https://ourlantern.app/#/merchant
        
        Questions? Reply to this email or contact merchants@ourlantern.app
        
        Welcome aboard!
        The Lantern Team
      `
      
      // TODO: Send email
      console.log('Welcome email sent to:', email)
    }
  })

Firebase Custom Claims

Set merchant role using Admin SDK:

javascript
// Run this when approving a merchant application
const admin = require('firebase-admin')

async function approveMerchantApplication(applicationId) {
  const applicationRef = admin.firestore().collection('merchant_applications').doc(applicationId)
  const application = (await applicationRef.get()).data()
  
  // 1. Find or create user by email
  let user
  try {
    user = await admin.auth().getUserByEmail(application.applicantEmail)
  } catch (error) {
    console.error('User not found, they must sign up first')
    return
  }
  
  // 2. Set custom claim for role
  await admin.auth().setCustomUserClaims(user.uid, { role: 'merchant' })
  
  // 3. Update user profile in Firestore
  await admin.firestore().collection('users').doc(user.uid).update({
    role: 'merchant',
    merchantProfile: {
      businessName: application.businessName,
      venueId: null, // TODO: Link to venue
      businessType: application.businessType,
      approved: true,
      onboardedAt: admin.firestore.FieldValue.serverTimestamp()
    }
  })
  
  // 4. Update application status
  await applicationRef.update({
    status: 'approved',
    reviewedAt: admin.firestore.FieldValue.serverTimestamp(),
    reviewedBy: 'admin' // TODO: Track actual admin user
  })
  
  console.log('Merchant application approved:', application.businessName)
}

Testing Checklist

Frontend Testing

  • [x] Merchant signup form displays correctly
  • [x] Form validation works (required fields)
  • [x] Success screen shows after submission
  • [x] Navigation to merchant signup from Info panel works
  • [x] Info button appears in navigation
  • [x] Activity section accessible from fire icon (LanternHub)
  • [ ] Form submission writes to Firestore
  • [ ] Error handling for failed submissions
  • [ ] Loading states during submission

Backend Testing (Pending)

  • [ ] Firestore security rules prevent unauthorized access
  • [ ] Applications can be created by any authenticated user
  • [ ] Only admins can read all applications
  • [ ] Applicants can read their own applications
  • [ ] Users cannot change their own role
  • [ ] Notification email sent on new application
  • [ ] Welcome email sent on approval
  • [ ] Custom claims set correctly
  • [ ] Role check works in frontend

Integration Testing

  • [ ] Complete flow: Signup → Application → Approval → Dashboard Access
  • [ ] Access denied screen shows for non-merchants
  • [ ] Merchant dashboard accessible after approval
  • [ ] Offer creation works for approved merchants

Deployment Steps

1. Deploy Firestore Rules

bash
cd /home/runner/work/lantern_app/lantern_app
firebase deploy --only firestore:rules

2. Deploy Cloud Functions

bash
cd functions
npm install nodemailer
firebase deploy --only functions

3. Deploy Frontend

Already deployed via Cloudflare Pages (automatic on push to main/dev branches)

4. Manual Admin Tasks (Pilot Phase)

  1. Monitor merchant_applications collection in Firestore Console
  2. Review applications within 48 hours
  3. Run approval script or manually update:
    • Set user custom claim: role: 'merchant'
    • Update user profile: add merchantProfile
    • Update application: status: 'approved'
  4. Send welcome email (manual or automated)

File Structure

src/
├── screens/
│   └── merchant/
│       ├── MerchantSignup.jsx        ✅ Created
│       ├── MerchantDashboard.jsx     ✅ Existing
│       └── OfferForm.jsx             ✅ Existing
├── components/
│   ├── InfoPanel.jsx                 ✅ Created (with merchant CTA)
│   ├── LanternHub.jsx                ✅ Existing (has Activity section)
│   └── dashboard/
│       ├── DashboardNavigation.jsx   ✅ Updated (Info button)
│       └── HomeView.jsx              ✅ Updated (merchant CTA banner)
└── App.jsx                           ✅ Updated (merchant/signup route)

docs/
├── business/
│   ├── MERCHANT_INTEGRATION_POA.md   ✅ Business plan
│   └── MERCHANT_INTEGRATION_SUMMARY.md ✅ Summary
└── engineering/
    └── MERCHANT_INTEGRATION.md       ✅ This file (engineering guide)

firestore.rules                       ⏳ Pending update
functions/
├── src/
│   ├── merchantApplicationNotification.js ⏳ To be created
│   └── merchantWelcomeEmail.js            ⏳ To be created
└── package.json                      ⏳ Add nodemailer dependency

Next Steps (Priority Order)

  1. Test UI Changes (Today)

    • Verify Info button works
    • Verify merchant CTA in Info panel
    • Verify Activity in LanternHub
    • Take screenshots for PR
  2. Wire Signup Form (Next - 30 min)

    • Add Firebase imports to MerchantSignup.jsx
    • Replace TODO with actual addDoc call
    • Test submission writes to Firestore
    • Add error handling
  3. Firestore Rules (Next - 15 min)

    • Update firestore.rules file
    • Deploy rules: firebase deploy --only firestore:rules
    • Test security in Firestore Console
  4. Access Control (Next - 1 hour)

    • Create AccessDeniedScreen component
    • Add role checks to merchant routes
    • Test with merchant and non-merchant users
  5. Cloud Functions (Next - 2 hours)

    • Set up Functions project if not exists
    • Create notification email function
    • Create welcome email function
    • Test locally with emulator
  6. Admin Approval (Next - 1 hour)

    • Document manual approval process
    • Create approval script (Admin SDK)
    • Test approval flow end-to-end
  7. Pilot Launch (1 week)

    • Recruit 3-5 test merchants
    • Walk through application process
    • Gather feedback and iterate

Performance Considerations

  • Merchant CTA banner adds ~10KB to HomeView bundle
  • InfoPanel adds ~10KB (loaded on demand)
  • No impact on initial page load (components lazy-loaded)
  • Firestore queries minimal (1 read per application submission)

Security Considerations

  • ✅ Merchant role cannot be self-assigned (requires admin)
  • ✅ Applications readable only by applicant or admin
  • ✅ Form sanitization (email validation, XSS prevention)
  • ⏳ Email verification before approval (to be implemented)
  • ⏳ Rate limiting on application submissions (to be implemented)

Support & Troubleshooting

Common Issues

Q: Form submits but no document in Firestore

  • Check Firebase config is loaded
  • Verify user is authenticated
  • Check browser console for errors
  • Verify Firestore security rules allow create

Q: User approved but can't access merchant dashboard

  • Verify custom claim is set: Check Firebase Auth console
  • User must sign out and sign in again for claims to refresh
  • Check userProfile.role === 'merchant' in browser console

Q: Notification emails not sending

  • Verify Cloud Functions deployed
  • Check Functions logs in Firebase Console
  • Verify email transport configured
  • Test with emulator locally first

Additional Resources


Last Updated: 2026-01-11
Status: Phase 1 UI Complete, Backend Integration Pending

Built with VitePress