Merchant Integration - Engineering Implementation Guide
Related Documents:
- Business Strategy & POA - Overall plan and business context
- Implementation Summary - Executive summary
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:
// 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:
// 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:
// 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
B. Dashboard Footer (Secondary)
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:
// 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
notificationCountprop (no longer displayed) - Added
onInfoClickcallback prop - Changed icon from
BelltoInfo
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:
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)
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)
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:
// 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
cd /home/runner/work/lantern_app/lantern_app
firebase deploy --only firestore:rules2. Deploy Cloud Functions
cd functions
npm install nodemailer
firebase deploy --only functions3. Deploy Frontend
Already deployed via Cloudflare Pages (automatic on push to main/dev branches)
4. Manual Admin Tasks (Pilot Phase)
- Monitor
merchant_applicationscollection in Firestore Console - Review applications within 48 hours
- Run approval script or manually update:
- Set user custom claim:
role: 'merchant' - Update user profile: add
merchantProfile - Update application:
status: 'approved'
- Set user custom claim:
- 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 dependencyNext Steps (Priority Order)
Test UI Changes (Today)
- Verify Info button works
- Verify merchant CTA in Info panel
- Verify Activity in LanternHub
- Take screenshots for PR
Wire Signup Form (Next - 30 min)
- Add Firebase imports to MerchantSignup.jsx
- Replace TODO with actual
addDoccall - Test submission writes to Firestore
- Add error handling
Firestore Rules (Next - 15 min)
- Update
firestore.rulesfile - Deploy rules:
firebase deploy --only firestore:rules - Test security in Firestore Console
- Update
Access Control (Next - 1 hour)
- Create AccessDeniedScreen component
- Add role checks to merchant routes
- Test with merchant and non-merchant users
Cloud Functions (Next - 2 hours)
- Set up Functions project if not exists
- Create notification email function
- Create welcome email function
- Test locally with emulator
Admin Approval (Next - 1 hour)
- Document manual approval process
- Create approval script (Admin SDK)
- Test approval flow end-to-end
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
- Firebase Custom Claims Docs
- Firestore Security Rules Guide
- Cloud Functions Documentation
- Nodemailer Setup Guide
Last Updated: 2026-01-11
Status: Phase 1 UI Complete, Backend Integration Pending