Thai Visa Submissions
What it does
Section titled “What it does”The submission system handles the workflow for Nepali clients applying for Thai visas through Shanvi Travels. A client submits passport documents and a payment receipt; the office processes the application and sends back a bank transfer confirmation.
User flow
Section titled “User flow”1. Client fills ThaiSubmission form (public, no login required) └── Provides: name, passport number, phone, email └── Uploads: passport photo(s), receipt PDF(s) └── Can add multiple participants (family members, group)
2. Submission created → status: null/Pending └── System generates a displayId: "08212001", "08212002", etc. └── Confirmation email sent to client
3. Client submits payment proof (public, no login) └── POST /api/submissions/:id/submit-payment └── Uploads payment proof image or transaction ID └── System generates a companyId: "SHV-2025-A3F2" └── Status → "Processing"
4. Admin/Agent reviews and verifies └── PUT /api/submissions/:id/verify └── Sets status (Verified/Rejected) + paymentStatus (Success/Failed) └── Optionally uploads bank proof image
5. Client tracks status └── Public search by displayId, companyId, or passport number └── GET /api/submissions/public/search/:idAPI endpoints
Section titled “API endpoints”| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/submissions | None | Create submission with files |
GET | /api/submissions | Token | List submissions (scoped by role) |
GET | /api/submissions/:id | Token | Get single submission |
PUT | /api/submissions/:id | Token | Update submission details |
DELETE | /api/submissions/:id | ADMIN | Delete submission |
POST | /api/submissions/:id/submit-payment | None | Submit payment proof |
PUT | /api/submissions/:id/verify | ADMIN/AGENT | Verify/reject submission |
POST | /api/submissions/:id/bank-proof | Token | Upload bank proof |
POST | /api/submissions/:id/bank-status | Token | Update bank status + message |
GET | /api/submissions/public/search/:id | None | Public status lookup |
Access control
Section titled “Access control”- ADMIN + HEAD_OFFICE agents see all submissions
- Other agents see only submissions assigned to them (
agentId = req.user.id) - Role is re-fetched from the DB on every list/update call (not trusted from JWT)
- Submission reassignment (changing
agentId) is only allowed for ADMIN/HEAD_OFFICE
Data model
Section titled “Data model”submissions id INT (auto) name TEXT passportNumber TEXT phoneNumber TEXT email TEXT passportPhoto TEXT (file key) receiptPdf TEXT (file key) paymentProof TEXT (file key) bankProof TEXT (file key) bankMessage TEXT transactionId TEXT companyId TEXT ← "SHV-2025-A3F2" status TEXT ← null | "Processing" | "Verified" | "Rejected" paymentStatus TEXT ← null | "Success" | "Failed" participantCount INT totalAmount DECIMAL agentId INT (FK → users) createdAt DATETIME
submission_participants id INT submissionId INT (FK → submissions) name TEXT passportNumber TEXT passportPhoto TEXT (file key) receiptPdf TEXT (file key)Pricing calculation
Section titled “Pricing calculation”The total amount is calculated at submission time using settings from the settings table:
totalAmount = (visa_fee + service_fee + bank_fee) × participantCountDefault values if settings are missing: visa_fee = 3500, service_fee = 0, bank_fee = 0.
Display ID vs Company ID
Section titled “Display ID vs Company ID”| ID Type | Format | When assigned | Purpose |
|---|---|---|---|
displayId | 08212001 | At creation (derived from id) | Public-facing reference |
companyId | SHV-2025-A3F2 | At payment submission | Tracks payment verification |
The displayId is not stored in the database — it’s computed as "08212" + id.padStart(3, '0') every time. This means it’s fragile: if records are deleted and IDs are reused, display IDs could collide. Recommendation: Store displayId as a proper unique column.
Known issues
Section titled “Known issues”-
/submit-paymentis unauthenticated — any caller who knows a sequential submission ID can submit payment on someone else’s submission. The status guard (AND status = 'Pending') limits the damage, but rate limiting should be added and optionally the endpoint should require a token. -
Participant file order is assumed — if the client sends files out of order, participant photos/receipts will be mismatched. The API should accept named fields per participant rather than relying on array index.